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.

StatisticsStrategy.cs 7.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text;
  7. using Shadowsocks.Model;
  8. using System.IO;
  9. using System.Net.NetworkInformation;
  10. using System.Threading;
  11. using Shadowsocks.Model;
  12. namespace Shadowsocks.Controller.Strategy
  13. {
  14. class StatisticsStrategy : IStrategy
  15. {
  16. private readonly ShadowsocksController _controller;
  17. private Server _currentServer;
  18. private readonly Timer _timer;
  19. private Dictionary<string, StatisticsData> _statistics;
  20. private const int CachedInterval = 30*60*1000; //choose a new server every 30 minutes
  21. private const int RetryInterval = 2*60*1000; //choose a new server every 30 minutes
  22. public StatisticsStrategy(ShadowsocksController controller)
  23. {
  24. _controller = controller;
  25. var servers = controller.GetCurrentConfiguration().configs;
  26. var randomIndex = new Random().Next() % servers.Count();
  27. _currentServer = servers[randomIndex]; //choose a server randomly at first
  28. _timer = new Timer(ReloadStatisticsAndChooseAServer);
  29. }
  30. private void ReloadStatisticsAndChooseAServer(object obj)
  31. {
  32. Logging.Debug("Reloading statistics and choose a new server....");
  33. var servers = _controller.GetCurrentConfiguration().configs;
  34. LoadStatistics();
  35. ChooseNewServer(servers);
  36. }
  37. /*
  38. return a dict:
  39. {
  40. 'ServerFriendlyName1':StatisticsData,
  41. 'ServerFriendlyName2':...
  42. }
  43. */
  44. private void LoadStatistics()
  45. {
  46. try
  47. {
  48. var path = AvailabilityStatistics.AvailabilityStatisticsFile;
  49. Logging.Debug($"loading statistics from {path}");
  50. if (!File.Exists(path))
  51. {
  52. LogWhenEnabled($"statistics file does not exist, try to reload {RetryInterval} minutes later");
  53. _timer.Change(RetryInterval, CachedInterval);
  54. return;
  55. }
  56. _statistics = (from l in File.ReadAllLines(path)
  57. .Skip(1)
  58. let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
  59. let rawData = new
  60. {
  61. ServerName = strings[1],
  62. IPStatus = strings[2],
  63. RoundtripTime = int.Parse(strings[3])
  64. }
  65. group rawData by rawData.ServerName into server
  66. select new
  67. {
  68. ServerName = server.Key,
  69. data = new StatisticsData
  70. {
  71. SuccessTimes = server.Count(data => IPStatus.Success.ToString().Equals(data.IPStatus)),
  72. TimedOutTimes = server.Count(data => IPStatus.TimedOut.ToString().Equals(data.IPStatus)),
  73. AverageResponse = Convert.ToInt32(server.Average(data => data.RoundtripTime)),
  74. MinResponse = server.Min(data => data.RoundtripTime),
  75. MaxResponse = server.Max(data => data.RoundtripTime)
  76. }
  77. }).ToDictionary(server => server.ServerName, server => server.data);
  78. }
  79. catch (Exception e)
  80. {
  81. Logging.LogUsefulException(e);
  82. }
  83. }
  84. //return the score by data
  85. //server with highest score will be choosen
  86. private static double GetScore(StatisticsData data)
  87. {
  88. return (double)data.SuccessTimes / (data.SuccessTimes + data.TimedOutTimes); //simply choose min package loss
  89. }
  90. public class StatisticsData
  91. {
  92. public int SuccessTimes;
  93. public int TimedOutTimes;
  94. public int AverageResponse;
  95. public int MinResponse;
  96. public int MaxResponse;
  97. }
  98. private void ChooseNewServer(List<Server> servers)
  99. {
  100. if (_statistics == null || servers.Count == 0)
  101. {
  102. return;
  103. }
  104. try
  105. {
  106. var bestResult = (from server in servers
  107. let name = server.FriendlyName()
  108. where _statistics.ContainsKey(name)
  109. select new
  110. {
  111. server,
  112. score = GetScore(_statistics[name])
  113. }
  114. ).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2);
  115. if (!_currentServer.Equals(bestResult.server)) //output when enabled
  116. {
  117. LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by package loss:{1 - bestResult.score}");
  118. }
  119. _currentServer = bestResult.server;
  120. }
  121. catch (Exception e)
  122. {
  123. Logging.LogUsefulException(e);
  124. }
  125. }
  126. private void LogWhenEnabled(string log)
  127. {
  128. if (_controller.GetCurrentStrategy()?.ID == ID) //output when enabled
  129. {
  130. Console.WriteLine(log);
  131. }
  132. }
  133. public string ID => "com.shadowsocks.strategy.scbs";
  134. public string Name => I18N.GetString("Choose By Total Package Loss");
  135. public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint)
  136. {
  137. var oldServer = _currentServer;
  138. if (oldServer == null)
  139. {
  140. ChooseNewServer(_controller.GetCurrentConfiguration().configs);
  141. }
  142. if (oldServer != _currentServer)
  143. {
  144. }
  145. return _currentServer; //current server cached for CachedInterval
  146. }
  147. public void ReloadServers()
  148. {
  149. ChooseNewServer(_controller.GetCurrentConfiguration().configs);
  150. _timer?.Change(0, CachedInterval);
  151. }
  152. public void SetFailure(Server server)
  153. {
  154. Logging.Debug($"failure: {server.FriendlyName()}");
  155. }
  156. public void UpdateLastRead(Server server)
  157. {
  158. //TODO: combine this part of data with ICMP statics
  159. }
  160. public void UpdateLastWrite(Server server)
  161. {
  162. //TODO: combine this part of data with ICMP statics
  163. }
  164. public void UpdateLatency(Server server, TimeSpan latency)
  165. {
  166. //TODO: combine this part of data with ICMP statics
  167. }
  168. }
  169. }