You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

AvailabilityStatistics.cs 15 kB

9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.NetworkInformation;
  8. using System.Net.Sockets;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. using Newtonsoft.Json;
  12. using Shadowsocks.Model;
  13. using Shadowsocks.Util;
  14. namespace Shadowsocks.Controller
  15. {
  16. using Statistics = Dictionary<string, List<StatisticsRecord>>;
  17. public sealed class AvailabilityStatistics : IDisposable
  18. {
  19. public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
  20. private const string StatisticsFilesName = "shadowsocks.availability.json";
  21. public static string AvailabilityStatisticsFile;
  22. //static constructor to initialize every public static fields before refereced
  23. static AvailabilityStatistics()
  24. {
  25. AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName);
  26. }
  27. //arguments for ICMP tests
  28. private int Repeat => Config.RepeatTimesNum;
  29. public const int TimeoutMilliseconds = 500;
  30. //records cache for current server in {_monitorInterval} minutes
  31. private readonly ConcurrentDictionary<string, List<int>> _latencyRecords = new ConcurrentDictionary<string, List<int>>();
  32. //speed in KiB/s
  33. private readonly ConcurrentDictionary<string, long> _inboundCounter = new ConcurrentDictionary<string, long>();
  34. private readonly ConcurrentDictionary<string, long> _lastInboundCounter = new ConcurrentDictionary<string, long>();
  35. private readonly ConcurrentDictionary<string, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
  36. private readonly ConcurrentDictionary<string, long> _outboundCounter = new ConcurrentDictionary<string, long>();
  37. private readonly ConcurrentDictionary<string, long> _lastOutboundCounter = new ConcurrentDictionary<string, long>();
  38. private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
  39. //tasks
  40. private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
  41. private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2);
  42. private Timer _recorder; //analyze and save cached records to RawStatistics and filter records
  43. private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes);
  44. private Timer _speedMonior;
  45. private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
  46. //private Timer _writer; //write RawStatistics to file
  47. //private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
  48. private ShadowsocksController _controller;
  49. private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration;
  50. // Static Singleton Initialization
  51. public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
  52. public Statistics RawStatistics { get; private set; }
  53. public Statistics FilteredStatistics { get; private set; }
  54. private AvailabilityStatistics()
  55. {
  56. RawStatistics = new Statistics();
  57. }
  58. internal void UpdateConfiguration(ShadowsocksController controller)
  59. {
  60. _controller = controller;
  61. Reset();
  62. try
  63. {
  64. if (Config.StatisticsEnabled)
  65. {
  66. StartTimerWithoutState(ref _recorder, Run, RecordingInterval);
  67. LoadRawStatistics();
  68. StartTimerWithoutState(ref _speedMonior, UpdateSpeed, _monitorInterval);
  69. }
  70. else
  71. {
  72. _recorder?.Dispose();
  73. _speedMonior?.Dispose();
  74. }
  75. }
  76. catch (Exception e)
  77. {
  78. Logging.LogUsefulException(e);
  79. }
  80. }
  81. private void StartTimerWithoutState(ref Timer timer, TimerCallback callback, TimeSpan interval)
  82. {
  83. if (timer?.Change(_delayBeforeStart, interval) == null)
  84. {
  85. timer = new Timer(callback, null, _delayBeforeStart, interval);
  86. }
  87. }
  88. private void UpdateSpeed(object _)
  89. {
  90. foreach (var kv in _lastInboundCounter)
  91. {
  92. var id = kv.Key;
  93. var lastInbound = kv.Value;
  94. var inbound = _inboundCounter[id];
  95. var bytes = inbound - lastInbound;
  96. _lastInboundCounter[id] = inbound;
  97. var inboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
  98. _inboundSpeedRecords.GetOrAdd(id, (k) =>
  99. {
  100. List<int> records = new List<int>();
  101. records.Add(inboundSpeed);
  102. return records;
  103. });
  104. var lastOutbound = _lastOutboundCounter[id];
  105. var outbound = _outboundCounter[id];
  106. bytes = outbound - lastOutbound;
  107. _lastOutboundCounter[id] = outbound;
  108. var outboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
  109. _outboundSpeedRecords.GetOrAdd(id, (k) =>
  110. {
  111. List<int> records = new List<int>();
  112. records.Add(outboundSpeed);
  113. return records;
  114. });
  115. Logging.Debug(
  116. $"{id}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords[id].Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords[id].Max()} KiB/s");
  117. }
  118. }
  119. private void Reset()
  120. {
  121. _inboundSpeedRecords.Clear();
  122. _outboundSpeedRecords.Clear();
  123. _latencyRecords.Clear();
  124. }
  125. private void Run(object _)
  126. {
  127. UpdateRecords();
  128. Save();
  129. Reset();
  130. FilterRawStatistics();
  131. }
  132. private void UpdateRecords()
  133. {
  134. var records = new Dictionary<string, StatisticsRecord>();
  135. foreach (var server in _controller.GetCurrentConfiguration().configs)
  136. {
  137. var id = server.Identifier();
  138. List<int> inboundSpeedRecords = null;
  139. List<int> outboundSpeedRecords = null;
  140. List<int> latencyRecords = null;
  141. _inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords);
  142. _outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords);
  143. _latencyRecords.TryGetValue(id, out latencyRecords);
  144. StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords);
  145. /* duplicate server identifier */
  146. if (records.ContainsKey(id))
  147. records[id] = record;
  148. else
  149. records.Add(id, record);
  150. if (Config.Ping)
  151. {
  152. MyPing ping = new MyPing(server, Repeat, record);
  153. ping.Completed += ping_Completed;
  154. ping.Start();
  155. }
  156. }
  157. foreach (var kv in records.Where(kv => !kv.Value.IsEmptyData()))
  158. {
  159. AppendRecord(kv.Key, kv.Value);
  160. }
  161. }
  162. private void ping_Completed(object sender, MyPing.CompletedEventArgs e)
  163. {
  164. Server server = ((MyPing)sender).server;
  165. StatisticsRecord record = (StatisticsRecord)((MyPing)sender).userstate;
  166. record.SetResponse(e.RoundtripTime);
  167. }
  168. private void AppendRecord(string serverIdentifier, StatisticsRecord record)
  169. {
  170. List<StatisticsRecord> records;
  171. if (!RawStatistics.TryGetValue(serverIdentifier, out records))
  172. {
  173. records = new List<StatisticsRecord>();
  174. RawStatistics[serverIdentifier] = records;
  175. }
  176. records.Add(record);
  177. }
  178. private void Save()
  179. {
  180. if (RawStatistics.Count == 0)
  181. {
  182. return;
  183. }
  184. try
  185. {
  186. var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
  187. File.WriteAllText(AvailabilityStatisticsFile, content);
  188. }
  189. catch (IOException e)
  190. {
  191. Logging.LogUsefulException(e);
  192. }
  193. }
  194. private bool IsValidRecord(StatisticsRecord record)
  195. {
  196. if (Config.ByHourOfDay)
  197. {
  198. if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
  199. }
  200. return true;
  201. }
  202. private void FilterRawStatistics()
  203. {
  204. if (RawStatistics == null) return;
  205. if (FilteredStatistics == null)
  206. {
  207. FilteredStatistics = new Statistics();
  208. }
  209. foreach (var serverAndRecords in RawStatistics)
  210. {
  211. var server = serverAndRecords.Key;
  212. var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
  213. FilteredStatistics[server] = filteredRecords;
  214. }
  215. }
  216. private void LoadRawStatistics()
  217. {
  218. try
  219. {
  220. var path = AvailabilityStatisticsFile;
  221. Logging.Debug($"loading statistics from {path}");
  222. if (!File.Exists(path))
  223. {
  224. using (File.Create(path))
  225. {
  226. //do nothing
  227. }
  228. }
  229. var content = File.ReadAllText(path);
  230. RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
  231. }
  232. catch (Exception e)
  233. {
  234. Logging.LogUsefulException(e);
  235. Console.WriteLine($"failed to load statistics; try to reload {_retryInterval.TotalMinutes} minutes later");
  236. _recorder.Change(_retryInterval, RecordingInterval);
  237. }
  238. }
  239. private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
  240. {
  241. var result = (int)(bytes / seconds) / 1024;
  242. return result;
  243. }
  244. public void Dispose()
  245. {
  246. _recorder.Dispose();
  247. _speedMonior.Dispose();
  248. }
  249. public void UpdateLatency(Server server, int latency)
  250. {
  251. _latencyRecords.GetOrAdd(server.Identifier(), (k) =>
  252. {
  253. List<int> records = new List<int>();
  254. records.Add(latency);
  255. return records;
  256. });
  257. }
  258. public void UpdateInboundCounter(Server server, long n)
  259. {
  260. _inboundCounter.AddOrUpdate(server.Identifier(), (k) =>
  261. {
  262. _lastInboundCounter.GetOrAdd(server.Identifier(), 0);
  263. return n;
  264. }, (k, v) => (v + n));
  265. }
  266. public void UpdateOutboundCounter(Server server, long n)
  267. {
  268. _outboundCounter.AddOrUpdate(server.Identifier(), (k) =>
  269. {
  270. _lastOutboundCounter.GetOrAdd(server.Identifier(), 0);
  271. return n;
  272. }, (k, v) => (v + n));
  273. }
  274. class MyPing
  275. {
  276. //arguments for ICMP tests
  277. public const int TimeoutMilliseconds = 500;
  278. public EventHandler<CompletedEventArgs> Completed;
  279. public Server server;
  280. public object userstate;
  281. private int repeat;
  282. private IPAddress ip;
  283. private Ping ping;
  284. private List<int?> RoundtripTime;
  285. public MyPing(Server server, int repeat, object userstate)
  286. {
  287. this.server = server;
  288. this.repeat = repeat;
  289. this.userstate = userstate;
  290. RoundtripTime = new List<int?>(repeat);
  291. ping = new Ping();
  292. ping.PingCompleted += Ping_PingCompleted;
  293. }
  294. public void Start()
  295. {
  296. Logging.Debug("Ping " + server.FriendlyName());
  297. if (server.server == "")
  298. return;
  299. new Task(() => ICMPTest(0)).Start();
  300. }
  301. private void ICMPTest(int delay)
  302. {
  303. try
  304. {
  305. if (ip == null)
  306. {
  307. ip = Dns.GetHostAddresses(server.server)
  308. .First(
  309. ip =>
  310. ip.AddressFamily == AddressFamily.InterNetwork ||
  311. ip.AddressFamily == AddressFamily.InterNetworkV6);
  312. }
  313. repeat--;
  314. if (delay > 0)
  315. Thread.Sleep(delay);
  316. ping.SendAsync(ip, TimeoutMilliseconds, null);
  317. }
  318. catch (Exception e)
  319. {
  320. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  321. Logging.LogUsefulException(e);
  322. }
  323. }
  324. private void Ping_PingCompleted(object sender, PingCompletedEventArgs e)
  325. {
  326. try
  327. {
  328. if (e.Reply.Status == IPStatus.Success)
  329. {
  330. RoundtripTime.Add((int?)e.Reply.RoundtripTime);
  331. }
  332. else
  333. {
  334. RoundtripTime.Add(null);
  335. }
  336. TestNext();
  337. }
  338. catch (Exception ex)
  339. {
  340. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  341. Logging.LogUsefulException(ex);
  342. }
  343. }
  344. private void TestNext()
  345. {
  346. if (repeat > 0)
  347. {
  348. //Do ICMPTest in a random frequency
  349. int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds;
  350. new Task(() => ICMPTest(delay)).Start();
  351. }
  352. else
  353. {
  354. Completed?.Invoke(this, new CompletedEventArgs
  355. {
  356. RoundtripTime = RoundtripTime
  357. });
  358. }
  359. }
  360. public class CompletedEventArgs : EventArgs
  361. {
  362. public List<int?> RoundtripTime;
  363. }
  364. }
  365. }
  366. }