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
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  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);
  153. ping.Completed += ping_Completed;
  154. ping.Start(record);
  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 = e.Server;
  165. StatisticsRecord record = (StatisticsRecord)e.UserState;
  166. record.SetResponse(e.RoundtripTime);
  167. 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");
  168. }
  169. private void AppendRecord(string serverIdentifier, StatisticsRecord record)
  170. {
  171. List<StatisticsRecord> records;
  172. if (!RawStatistics.TryGetValue(serverIdentifier, out records))
  173. {
  174. records = new List<StatisticsRecord>();
  175. RawStatistics[serverIdentifier] = records;
  176. }
  177. records.Add(record);
  178. }
  179. private void Save()
  180. {
  181. if (RawStatistics.Count == 0)
  182. {
  183. return;
  184. }
  185. try
  186. {
  187. var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
  188. File.WriteAllText(AvailabilityStatisticsFile, content);
  189. }
  190. catch (IOException e)
  191. {
  192. Logging.LogUsefulException(e);
  193. }
  194. }
  195. private bool IsValidRecord(StatisticsRecord record)
  196. {
  197. if (Config.ByHourOfDay)
  198. {
  199. if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
  200. }
  201. return true;
  202. }
  203. private void FilterRawStatistics()
  204. {
  205. if (RawStatistics == null) return;
  206. if (FilteredStatistics == null)
  207. {
  208. FilteredStatistics = new Statistics();
  209. }
  210. foreach (var serverAndRecords in RawStatistics)
  211. {
  212. var server = serverAndRecords.Key;
  213. var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
  214. FilteredStatistics[server] = filteredRecords;
  215. }
  216. }
  217. private void LoadRawStatistics()
  218. {
  219. try
  220. {
  221. var path = AvailabilityStatisticsFile;
  222. Logging.Debug($"loading statistics from {path}");
  223. if (!File.Exists(path))
  224. {
  225. using (File.Create(path))
  226. {
  227. //do nothing
  228. }
  229. }
  230. var content = File.ReadAllText(path);
  231. RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
  232. }
  233. catch (Exception e)
  234. {
  235. Logging.LogUsefulException(e);
  236. Console.WriteLine($"failed to load statistics; try to reload {_retryInterval.TotalMinutes} minutes later");
  237. _recorder.Change(_retryInterval, RecordingInterval);
  238. }
  239. }
  240. private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
  241. {
  242. var result = (int)(bytes / seconds) / 1024;
  243. return result;
  244. }
  245. public void Dispose()
  246. {
  247. _recorder.Dispose();
  248. _speedMonior.Dispose();
  249. }
  250. public void UpdateLatency(Server server, int latency)
  251. {
  252. _latencyRecords.GetOrAdd(server.Identifier(), (k) =>
  253. {
  254. List<int> records = new List<int>();
  255. records.Add(latency);
  256. return records;
  257. });
  258. }
  259. public void UpdateInboundCounter(Server server, long n)
  260. {
  261. _inboundCounter.AddOrUpdate(server.Identifier(), (k) =>
  262. {
  263. _lastInboundCounter.GetOrAdd(server.Identifier(), 0);
  264. return n;
  265. }, (k, v) => (v + n));
  266. }
  267. public void UpdateOutboundCounter(Server server, long n)
  268. {
  269. _outboundCounter.AddOrUpdate(server.Identifier(), (k) =>
  270. {
  271. _lastOutboundCounter.GetOrAdd(server.Identifier(), 0);
  272. return n;
  273. }, (k, v) => (v + n));
  274. }
  275. class MyPing
  276. {
  277. //arguments for ICMP tests
  278. public const int TimeoutMilliseconds = 500;
  279. public EventHandler<CompletedEventArgs> Completed;
  280. private Server server;
  281. private int repeat;
  282. private IPAddress ip;
  283. private Ping ping;
  284. private List<int?> RoundtripTime;
  285. public MyPing(Server server, int repeat)
  286. {
  287. this.server = server;
  288. this.repeat = repeat;
  289. RoundtripTime = new List<int?>(repeat);
  290. ping = new Ping();
  291. ping.PingCompleted += Ping_PingCompleted;
  292. }
  293. public void Start(object userstate)
  294. {
  295. if (server.server == "")
  296. return;
  297. new Task(() => ICMPTest(0, userstate)).Start();
  298. }
  299. private void ICMPTest(int delay, object userstate)
  300. {
  301. try
  302. {
  303. Logging.Debug($"Ping {server.FriendlyName()}");
  304. if (ip == null)
  305. {
  306. ip = Dns.GetHostAddresses(server.server)
  307. .First(
  308. ip =>
  309. ip.AddressFamily == AddressFamily.InterNetwork ||
  310. ip.AddressFamily == AddressFamily.InterNetworkV6);
  311. }
  312. repeat--;
  313. if (delay > 0)
  314. Thread.Sleep(delay);
  315. ping.SendAsync(ip, TimeoutMilliseconds, userstate);
  316. }
  317. catch (Exception e)
  318. {
  319. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  320. Logging.LogUsefulException(e);
  321. }
  322. }
  323. private void Ping_PingCompleted(object sender, PingCompletedEventArgs e)
  324. {
  325. try
  326. {
  327. if (e.Reply.Status == IPStatus.Success)
  328. {
  329. Logging.Debug($"Ping {server.FriendlyName()} {e.Reply.RoundtripTime} ms");
  330. RoundtripTime.Add((int?)e.Reply.RoundtripTime);
  331. }
  332. else
  333. {
  334. Logging.Debug($"Ping {server.FriendlyName()} timeout");
  335. RoundtripTime.Add(null);
  336. }
  337. TestNext(e.UserState);
  338. }
  339. catch (Exception ex)
  340. {
  341. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  342. Logging.LogUsefulException(ex);
  343. }
  344. }
  345. private void TestNext(object userstate)
  346. {
  347. if (repeat > 0)
  348. {
  349. //Do ICMPTest in a random frequency
  350. int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds;
  351. new Task(() => ICMPTest(delay, userstate)).Start();
  352. }
  353. else
  354. {
  355. Completed?.Invoke(this, new CompletedEventArgs
  356. {
  357. Server = server,
  358. RoundtripTime = RoundtripTime,
  359. UserState = userstate
  360. });
  361. }
  362. }
  363. public class CompletedEventArgs : EventArgs
  364. {
  365. public Server Server;
  366. public List<int?> RoundtripTime;
  367. public object UserState;
  368. }
  369. }
  370. }
  371. }