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 18 kB

9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
  34. private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
  35. private readonly ConcurrentDictionary<string, InOutBoundRecord> _inOutBoundRecords = new ConcurrentDictionary<string, InOutBoundRecord>();
  36. private class InOutBoundRecord
  37. {
  38. private long _inbound;
  39. private long _lastInbound;
  40. private long _outbound;
  41. private long _lastOutbound;
  42. public void UpdateInbound(long delta)
  43. {
  44. Interlocked.Add(ref _inbound, delta);
  45. }
  46. public void UpdateOutbound(long delta)
  47. {
  48. Interlocked.Add(ref _outbound, delta);
  49. }
  50. public void GetDelta(out long inboundDelta, out long outboundDelta)
  51. {
  52. var i = Interlocked.Read(ref _inbound);
  53. var il = Interlocked.Exchange(ref _lastInbound, i);
  54. inboundDelta = i - il;
  55. var o = Interlocked.Read(ref _outbound);
  56. var ol = Interlocked.Exchange(ref _lastOutbound, o);
  57. outboundDelta = o - ol;
  58. }
  59. }
  60. //tasks
  61. private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
  62. private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2);
  63. private Timer _recorder; //analyze and save cached records to RawStatistics and filter records
  64. private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes);
  65. private Timer _speedMonior;
  66. private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
  67. //private Timer _writer; //write RawStatistics to file
  68. //private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
  69. private ShadowsocksController _controller;
  70. private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration;
  71. // Static Singleton Initialization
  72. public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
  73. public Statistics RawStatistics { get; private set; }
  74. public Statistics FilteredStatistics { get; private set; }
  75. private AvailabilityStatistics()
  76. {
  77. RawStatistics = new Statistics();
  78. }
  79. internal void UpdateConfiguration(ShadowsocksController controller)
  80. {
  81. _controller = controller;
  82. Reset();
  83. try
  84. {
  85. if (Config.StatisticsEnabled)
  86. {
  87. StartTimerWithoutState(ref _recorder, Run, RecordingInterval);
  88. LoadRawStatistics();
  89. StartTimerWithoutState(ref _speedMonior, UpdateSpeed, _monitorInterval);
  90. }
  91. else
  92. {
  93. _recorder?.Dispose();
  94. _speedMonior?.Dispose();
  95. }
  96. }
  97. catch (Exception e)
  98. {
  99. Logging.LogUsefulException(e);
  100. }
  101. }
  102. private void StartTimerWithoutState(ref Timer timer, TimerCallback callback, TimeSpan interval)
  103. {
  104. if (timer?.Change(_delayBeforeStart, interval) == null)
  105. {
  106. timer = new Timer(callback, null, _delayBeforeStart, interval);
  107. }
  108. }
  109. private void UpdateSpeed(object _)
  110. {
  111. foreach (var kv in _inOutBoundRecords)
  112. {
  113. var id = kv.Key;
  114. var record = kv.Value;
  115. long inboundDelta, outboundDelta;
  116. record.GetDelta(out inboundDelta, out outboundDelta);
  117. var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds);
  118. var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds);
  119. var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
  120. var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
  121. inR.Add(inboundSpeed);
  122. outR.Add(outboundSpeed);
  123. Logging.Debug(
  124. $"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s");
  125. }
  126. }
  127. private void Reset()
  128. {
  129. _inboundSpeedRecords.Clear();
  130. _outboundSpeedRecords.Clear();
  131. _latencyRecords.Clear();
  132. }
  133. private void Run(object _)
  134. {
  135. UpdateRecords();
  136. Reset();
  137. }
  138. private void UpdateRecords()
  139. {
  140. var records = new Dictionary<string, StatisticsRecord>();
  141. UpdateRecordsState state = new UpdateRecordsState();
  142. state.counter = _controller.GetCurrentConfiguration().configs.Count;
  143. foreach (var server in _controller.GetCurrentConfiguration().configs)
  144. {
  145. var id = server.Identifier();
  146. List<int> inboundSpeedRecords = null;
  147. List<int> outboundSpeedRecords = null;
  148. List<int> latencyRecords = null;
  149. _inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords);
  150. _outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords);
  151. _latencyRecords.TryGetValue(id, out latencyRecords);
  152. StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords);
  153. /* duplicate server identifier */
  154. if (records.ContainsKey(id))
  155. records[id] = record;
  156. else
  157. records.Add(id, record);
  158. if (Config.Ping)
  159. {
  160. MyPing ping = new MyPing(server, Repeat);
  161. ping.Completed += ping_Completed;
  162. ping.Start(new PingState { state = state, record = record });
  163. }
  164. else if (!record.IsEmptyData())
  165. {
  166. AppendRecord(id, record);
  167. }
  168. }
  169. if (!Config.Ping)
  170. {
  171. Save();
  172. FilterRawStatistics();
  173. }
  174. }
  175. private void ping_Completed(object sender, MyPing.CompletedEventArgs e)
  176. {
  177. PingState pingState = (PingState)e.UserState;
  178. UpdateRecordsState state = pingState.state;
  179. Server server = e.Server;
  180. StatisticsRecord record = pingState.record;
  181. record.SetResponse(e.RoundtripTime);
  182. if (!record.IsEmptyData())
  183. {
  184. AppendRecord(server.Identifier(), record);
  185. }
  186. Logging.Debug($"Ping {server.FriendlyName()} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms");
  187. if (Interlocked.Decrement(ref state.counter) == 0)
  188. {
  189. Save();
  190. FilterRawStatistics();
  191. }
  192. }
  193. private void AppendRecord(string serverIdentifier, StatisticsRecord record)
  194. {
  195. try
  196. {
  197. List<StatisticsRecord> records;
  198. lock (RawStatistics)
  199. {
  200. if (!RawStatistics.TryGetValue(serverIdentifier, out records))
  201. {
  202. records = new List<StatisticsRecord>();
  203. RawStatistics[serverIdentifier] = records;
  204. }
  205. }
  206. records.Add(record);
  207. }
  208. catch (Exception e)
  209. {
  210. Logging.LogUsefulException(e);
  211. }
  212. }
  213. private void Save()
  214. {
  215. Logging.Debug($"save statistics to {AvailabilityStatisticsFile}");
  216. if (RawStatistics.Count == 0)
  217. {
  218. return;
  219. }
  220. try
  221. {
  222. string content;
  223. #if DEBUG
  224. content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented);
  225. #else
  226. content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
  227. #endif
  228. File.WriteAllText(AvailabilityStatisticsFile, content);
  229. }
  230. catch (IOException e)
  231. {
  232. Logging.LogUsefulException(e);
  233. }
  234. }
  235. private bool IsValidRecord(StatisticsRecord record)
  236. {
  237. if (Config.ByHourOfDay)
  238. {
  239. if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
  240. }
  241. return true;
  242. }
  243. private void FilterRawStatistics()
  244. {
  245. try
  246. {
  247. Logging.Debug("filter raw statistics");
  248. if (RawStatistics == null) return;
  249. if (FilteredStatistics == null)
  250. {
  251. FilteredStatistics = new Statistics();
  252. }
  253. foreach (var serverAndRecords in RawStatistics)
  254. {
  255. var server = serverAndRecords.Key;
  256. var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
  257. FilteredStatistics[server] = filteredRecords;
  258. }
  259. }
  260. catch (Exception e)
  261. {
  262. Logging.LogUsefulException(e);
  263. }
  264. }
  265. private void LoadRawStatistics()
  266. {
  267. try
  268. {
  269. var path = AvailabilityStatisticsFile;
  270. Logging.Debug($"loading statistics from {path}");
  271. if (!File.Exists(path))
  272. {
  273. using (File.Create(path))
  274. {
  275. //do nothing
  276. }
  277. }
  278. var content = File.ReadAllText(path);
  279. RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
  280. }
  281. catch (Exception e)
  282. {
  283. Logging.LogUsefulException(e);
  284. Console.WriteLine($"failed to load statistics; try to reload {_retryInterval.TotalMinutes} minutes later");
  285. _recorder.Change(_retryInterval, RecordingInterval);
  286. }
  287. }
  288. private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
  289. {
  290. var result = (int)(bytes / seconds) / 1024;
  291. return result;
  292. }
  293. public void Dispose()
  294. {
  295. _recorder.Dispose();
  296. _speedMonior.Dispose();
  297. }
  298. public void UpdateLatency(Server server, int latency)
  299. {
  300. _latencyRecords.GetOrAdd(server.Identifier(), (k) =>
  301. {
  302. List<int> records = new List<int>();
  303. records.Add(latency);
  304. return records;
  305. });
  306. }
  307. public void UpdateInboundCounter(Server server, long n)
  308. {
  309. _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
  310. {
  311. var r = new InOutBoundRecord();
  312. r.UpdateInbound(n);
  313. return r;
  314. }, (k, v) =>
  315. {
  316. v.UpdateInbound(n);
  317. return v;
  318. });
  319. }
  320. public void UpdateOutboundCounter(Server server, long n)
  321. {
  322. _inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
  323. {
  324. var r = new InOutBoundRecord();
  325. r.UpdateOutbound(n);
  326. return r;
  327. }, (k, v) =>
  328. {
  329. v.UpdateOutbound(n);
  330. return v;
  331. });
  332. }
  333. class UpdateRecordsState
  334. {
  335. public int counter;
  336. }
  337. class PingState
  338. {
  339. public UpdateRecordsState state;
  340. public StatisticsRecord record;
  341. }
  342. class MyPing
  343. {
  344. //arguments for ICMP tests
  345. public const int TimeoutMilliseconds = 500;
  346. public EventHandler<CompletedEventArgs> Completed;
  347. private Server server;
  348. private int repeat;
  349. private IPAddress ip;
  350. private Ping ping;
  351. private List<int?> RoundtripTime;
  352. public MyPing(Server server, int repeat)
  353. {
  354. this.server = server;
  355. this.repeat = repeat;
  356. RoundtripTime = new List<int?>(repeat);
  357. ping = new Ping();
  358. ping.PingCompleted += Ping_PingCompleted;
  359. }
  360. public void Start(object userstate)
  361. {
  362. if (server.server == "")
  363. {
  364. FireCompleted(new Exception("Invalid Server"), userstate);
  365. return;
  366. }
  367. new Task(() => ICMPTest(0, userstate)).Start();
  368. }
  369. private void ICMPTest(int delay, object userstate)
  370. {
  371. try
  372. {
  373. Logging.Debug($"Ping {server.FriendlyName()}");
  374. if (ip == null)
  375. {
  376. ip = Dns.GetHostAddresses(server.server)
  377. .First(
  378. ip =>
  379. ip.AddressFamily == AddressFamily.InterNetwork ||
  380. ip.AddressFamily == AddressFamily.InterNetworkV6);
  381. }
  382. repeat--;
  383. if (delay > 0)
  384. Thread.Sleep(delay);
  385. ping.SendAsync(ip, TimeoutMilliseconds, userstate);
  386. }
  387. catch (Exception e)
  388. {
  389. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  390. Logging.LogUsefulException(e);
  391. FireCompleted(e, userstate);
  392. }
  393. }
  394. private void Ping_PingCompleted(object sender, PingCompletedEventArgs e)
  395. {
  396. try
  397. {
  398. if (e.Reply.Status == IPStatus.Success)
  399. {
  400. Logging.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms");
  401. RoundtripTime.Add((int?)e.Reply.RoundtripTime);
  402. }
  403. else
  404. {
  405. Logging.Debug($"Ping {server.FriendlyName()} timeout");
  406. RoundtripTime.Add(null);
  407. }
  408. TestNext(e.UserState);
  409. }
  410. catch (Exception ex)
  411. {
  412. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  413. Logging.LogUsefulException(ex);
  414. FireCompleted(ex, e.UserState);
  415. }
  416. }
  417. private void TestNext(object userstate)
  418. {
  419. if (repeat > 0)
  420. {
  421. //Do ICMPTest in a random frequency
  422. int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds;
  423. new Task(() => ICMPTest(delay, userstate)).Start();
  424. }
  425. else
  426. {
  427. FireCompleted(null, userstate);
  428. }
  429. }
  430. private void FireCompleted(Exception error, object userstate)
  431. {
  432. Completed?.Invoke(this, new CompletedEventArgs
  433. {
  434. Error = error,
  435. Server = server,
  436. RoundtripTime = RoundtripTime,
  437. UserState = userstate
  438. });
  439. }
  440. public class CompletedEventArgs : EventArgs
  441. {
  442. public Exception Error;
  443. public Server Server;
  444. public List<int?> RoundtripTime;
  445. public object UserState;
  446. }
  447. }
  448. }
  449. }