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

9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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, new List<int> {inboundSpeed}).Add(inboundSpeed);
  99. var lastOutbound = _lastOutboundCounter[id];
  100. var outbound = _outboundCounter[id];
  101. bytes = outbound - lastOutbound;
  102. _lastOutboundCounter[id] = outbound;
  103. var outboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
  104. _outboundSpeedRecords.GetOrAdd(id, new List<int> {outboundSpeed}).Add(outboundSpeed);
  105. Logging.Debug(
  106. $"{id}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords[id].Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords[id].Max()} KiB/s");
  107. }
  108. }
  109. private async Task<ICMPResult> ICMPTest(Server server)
  110. {
  111. Logging.Debug("Ping " + server.FriendlyName());
  112. if (server.server == "") return null;
  113. var result = new ICMPResult(server);
  114. try
  115. {
  116. var IP =
  117. Dns.GetHostAddresses(server.server)
  118. .First(
  119. ip =>
  120. ip.AddressFamily == AddressFamily.InterNetwork ||
  121. ip.AddressFamily == AddressFamily.InterNetworkV6);
  122. var ping = new Ping();
  123. foreach (var _ in Enumerable.Range(0, Repeat))
  124. {
  125. try
  126. {
  127. var reply = await ping.SendTaskAsync(IP, TimeoutMilliseconds);
  128. if (reply.Status.Equals(IPStatus.Success))
  129. {
  130. result.RoundtripTime.Add((int?) reply.RoundtripTime);
  131. }
  132. else
  133. {
  134. result.RoundtripTime.Add(null);
  135. }
  136. //Do ICMPTest in a random frequency
  137. Thread.Sleep(TimeoutMilliseconds + new Random().Next()%TimeoutMilliseconds);
  138. }
  139. catch (Exception e)
  140. {
  141. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  142. Logging.LogUsefulException(e);
  143. }
  144. }
  145. }
  146. catch (Exception e)
  147. {
  148. Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
  149. Logging.LogUsefulException(e);
  150. }
  151. return result;
  152. }
  153. private void Reset()
  154. {
  155. _inboundSpeedRecords.Clear();
  156. _outboundSpeedRecords.Clear();
  157. _latencyRecords.Clear();
  158. }
  159. private void Run(object _)
  160. {
  161. UpdateRecords();
  162. Save();
  163. Reset();
  164. FilterRawStatistics();
  165. }
  166. private async void UpdateRecords()
  167. {
  168. var records = new Dictionary<string, StatisticsRecord>();
  169. foreach (var server in _controller.GetCurrentConfiguration().configs)
  170. {
  171. var id = server.Identifier();
  172. List<int> inboundSpeedRecords = null;
  173. List<int> outboundSpeedRecords = null;
  174. List<int> latencyRecords = null;
  175. _inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords);
  176. _outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords);
  177. _latencyRecords.TryGetValue(id, out latencyRecords);
  178. records.Add(id, new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords));
  179. }
  180. if (Config.Ping)
  181. {
  182. var icmpResults = await TaskEx.WhenAll(_controller.GetCurrentConfiguration().configs.Select(ICMPTest));
  183. foreach (var result in icmpResults.Where(result => result != null))
  184. {
  185. records[result.Server.Identifier()].SetResponse(result.RoundtripTime);
  186. }
  187. }
  188. foreach (var kv in records.Where(kv => !kv.Value.IsEmptyData()))
  189. {
  190. AppendRecord(kv.Key, kv.Value);
  191. }
  192. }
  193. private void AppendRecord(string serverIdentifier, StatisticsRecord record)
  194. {
  195. List<StatisticsRecord> records;
  196. if (!RawStatistics.TryGetValue(serverIdentifier, out records))
  197. {
  198. records = new List<StatisticsRecord>();
  199. }
  200. records.Add(record);
  201. RawStatistics[serverIdentifier] = records;
  202. }
  203. private void Save()
  204. {
  205. if (RawStatistics.Count == 0)
  206. {
  207. return;
  208. }
  209. try
  210. {
  211. var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
  212. File.WriteAllText(AvailabilityStatisticsFile, content);
  213. }
  214. catch (IOException e)
  215. {
  216. Logging.LogUsefulException(e);
  217. }
  218. }
  219. private bool IsValidRecord(StatisticsRecord record)
  220. {
  221. if (Config.ByHourOfDay)
  222. {
  223. if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
  224. }
  225. return true;
  226. }
  227. private void FilterRawStatistics()
  228. {
  229. if (RawStatistics == null) return;
  230. if (FilteredStatistics == null)
  231. {
  232. FilteredStatistics = new Statistics();
  233. }
  234. foreach (var serverAndRecords in RawStatistics)
  235. {
  236. var server = serverAndRecords.Key;
  237. var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
  238. FilteredStatistics[server] = filteredRecords;
  239. }
  240. }
  241. private void LoadRawStatistics()
  242. {
  243. try
  244. {
  245. var path = AvailabilityStatisticsFile;
  246. Logging.Debug($"loading statistics from {path}");
  247. if (!File.Exists(path))
  248. {
  249. using (File.Create(path))
  250. {
  251. //do nothing
  252. }
  253. }
  254. var content = File.ReadAllText(path);
  255. RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
  256. }
  257. catch (Exception e)
  258. {
  259. Logging.LogUsefulException(e);
  260. Console.WriteLine($"failed to load statistics; try to reload {_retryInterval.TotalMinutes} minutes later");
  261. _recorder.Change(_retryInterval, RecordingInterval);
  262. }
  263. }
  264. private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
  265. {
  266. var result = (int) (bytes/seconds)/1024;
  267. return result;
  268. }
  269. private class ICMPResult
  270. {
  271. internal readonly List<int?> RoundtripTime = new List<int?>();
  272. internal readonly Server Server;
  273. internal ICMPResult(Server server)
  274. {
  275. Server = server;
  276. }
  277. }
  278. public void Dispose()
  279. {
  280. _recorder.Dispose();
  281. _speedMonior.Dispose();
  282. }
  283. public void UpdateLatency(Server server, int latency)
  284. {
  285. List<int> records;
  286. _latencyRecords.TryGetValue(server.Identifier(), out records);
  287. if (records == null)
  288. {
  289. records = new List<int>();
  290. }
  291. records.Add(latency);
  292. _latencyRecords[server.Identifier()] = records;
  293. }
  294. public void UpdateInboundCounter(Server server, long n)
  295. {
  296. long count;
  297. if (_inboundCounter.TryGetValue(server.Identifier(), out count))
  298. {
  299. count += n;
  300. }
  301. else
  302. {
  303. count = n;
  304. _lastInboundCounter[server.Identifier()] = 0;
  305. }
  306. _inboundCounter[server.Identifier()] = count;
  307. }
  308. public void UpdateOutboundCounter(Server server, long n)
  309. {
  310. long count;
  311. if (_outboundCounter.TryGetValue(server.Identifier(), out count))
  312. {
  313. count += n;
  314. }
  315. else
  316. {
  317. count = n;
  318. _lastOutboundCounter[server.Identifier()] = 0;
  319. }
  320. _outboundCounter[server.Identifier()] = count;
  321. }
  322. }
  323. }