move loading and filtering jobs to statistics service
This commit is contained in:
@@ -1,32 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using SimpleJson;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Shadowsocks.Model;
|
||||
using Shadowsocks.Util;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Shadowsocks.Controller
|
||||
{
|
||||
using DataUnit = KeyValuePair<string, string>;
|
||||
using DataList = List<KeyValuePair<string, string>>;
|
||||
|
||||
internal class AvailabilityStatistics
|
||||
using RawStatistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>;
|
||||
using Statistics = Dictionary<string, List<AvailabilityStatistics.StatisticsData>>;
|
||||
|
||||
public class AvailabilityStatistics
|
||||
{
|
||||
public static readonly string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
|
||||
private const string StatisticsFilesName = "shadowsocks.availability.csv";
|
||||
private const string Delimiter = ",";
|
||||
private const int Timeout = 500;
|
||||
private const int DelayBeforeStart = 1000;
|
||||
public RawStatistics rawStatistics { get; private set; }
|
||||
public RawStatistics filteredStatistics { get; private set; }
|
||||
private int _repeat => _config.RepeatTimesNum;
|
||||
private const int RetryInterval = 2*60*1000; //retry 2 minutes after failed
|
||||
private int _interval => (int) TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds;
|
||||
private Timer _timer;
|
||||
private State _state;
|
||||
@@ -57,7 +61,7 @@ namespace Shadowsocks.Controller
|
||||
if (_timer?.Change(DelayBeforeStart, _interval) == null)
|
||||
{
|
||||
_state = new State();
|
||||
_timer = new Timer(Evaluate, _state, DelayBeforeStart, _interval);
|
||||
_timer = new Timer(run, _state, DelayBeforeStart, _interval);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -82,7 +86,7 @@ namespace Shadowsocks.Controller
|
||||
var ret = new DataList
|
||||
{
|
||||
new DataUnit(State.Geolocation, State.Unknown),
|
||||
new DataUnit(State.ISP, State.Unknown),
|
||||
new DataUnit(State.ISP, State.Unknown)
|
||||
};
|
||||
string jsonString;
|
||||
try
|
||||
@@ -95,7 +99,7 @@ namespace Shadowsocks.Controller
|
||||
return ret;
|
||||
}
|
||||
dynamic obj;
|
||||
if (!global::SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return ret;
|
||||
if (!SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return ret;
|
||||
string country = obj["country"];
|
||||
string city = obj["city"];
|
||||
string isp = obj["isp"];
|
||||
@@ -140,7 +144,14 @@ namespace Shadowsocks.Controller
|
||||
return ret;
|
||||
}
|
||||
|
||||
private async void Evaluate(object obj)
|
||||
private void run(object obj)
|
||||
{
|
||||
LoadRawStatistics();
|
||||
FilterRawStatistics();
|
||||
evaluate();
|
||||
}
|
||||
|
||||
private async void evaluate()
|
||||
{
|
||||
var geolocationAndIsp = GetGeolocationAndIsp();
|
||||
foreach (var dataLists in await TaskEx.WhenAll(_servers.Select(ICMPTest)))
|
||||
@@ -184,6 +195,86 @@ namespace Shadowsocks.Controller
|
||||
_servers = config.configs;
|
||||
}
|
||||
|
||||
private void FilterRawStatistics()
|
||||
{
|
||||
if (filteredStatistics == null)
|
||||
{
|
||||
filteredStatistics = new RawStatistics();
|
||||
}
|
||||
foreach (IEnumerable<RawStatisticsData> rawData in rawStatistics.Values)
|
||||
{
|
||||
var filteredData = rawData;
|
||||
if (_config.ByIsp)
|
||||
{
|
||||
var current = GetGeolocationAndIsp().Result;
|
||||
filteredData =
|
||||
filteredData.Where(
|
||||
data =>
|
||||
data.Geolocation == current[0].Value ||
|
||||
data.Geolocation == State.Unknown);
|
||||
filteredData =
|
||||
filteredData.Where(
|
||||
data => data.ISP == current[1].Value || data.ISP == State.Unknown);
|
||||
if (filteredData.LongCount() == 0) return;
|
||||
}
|
||||
if (_config.ByHourOfDay)
|
||||
{
|
||||
var currentHour = DateTime.Now.Hour;
|
||||
filteredData = filteredData.Where(data =>
|
||||
{
|
||||
DateTime dateTime;
|
||||
DateTime.TryParseExact(data.Timestamp, DateTimePattern, null,
|
||||
DateTimeStyles.None, out dateTime);
|
||||
var result = dateTime.Hour.Equals(currentHour);
|
||||
return result;
|
||||
});
|
||||
if (filteredData.LongCount() == 0) return;
|
||||
}
|
||||
var dataList = filteredData as List<RawStatisticsData> ?? filteredData.ToList();
|
||||
var serverName = dataList[0].ServerName;
|
||||
filteredStatistics[serverName] = dataList;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadRawStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = AvailabilityStatisticsFile;
|
||||
Logging.Debug($"loading statistics from {path}");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval/60/1000} minutes later");
|
||||
_timer.Change(RetryInterval, _interval);
|
||||
return;
|
||||
}
|
||||
rawStatistics = (from l in File.ReadAllLines(path)
|
||||
.Skip(1)
|
||||
let strings = l.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
|
||||
let rawData = new RawStatisticsData
|
||||
{
|
||||
Timestamp = strings[0],
|
||||
ServerName = strings[1],
|
||||
ICMPStatus = strings[2],
|
||||
RoundtripTime = int.Parse(strings[3]),
|
||||
Geolocation = 5 > strings.Length ?
|
||||
null
|
||||
: strings[4],
|
||||
ISP = 6 > strings.Length ? null : strings[5]
|
||||
}
|
||||
group rawData by rawData.ServerName into server
|
||||
select new
|
||||
{
|
||||
ServerName = server.Key,
|
||||
data = server.ToList()
|
||||
}).ToDictionary(server => server.ServerName, server=> server.data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.LogUsefulException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class State
|
||||
{
|
||||
public DataList dataList = new DataList();
|
||||
@@ -191,5 +282,24 @@ namespace Shadowsocks.Controller
|
||||
public const string ISP = "ISP";
|
||||
public const string Unknown = "Unknown";
|
||||
}
|
||||
|
||||
public class RawStatisticsData
|
||||
{
|
||||
public string Timestamp;
|
||||
public string ServerName;
|
||||
public string ICMPStatus;
|
||||
public int RoundtripTime;
|
||||
public string Geolocation;
|
||||
public string ISP ;
|
||||
}
|
||||
|
||||
public class StatisticsData
|
||||
{
|
||||
public float PackageLoss;
|
||||
public int AverageResponse;
|
||||
public int MinResponse;
|
||||
public int MaxResponse;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Shadowsocks.Controller
|
||||
private StrategyManager _strategyManager;
|
||||
private PolipoRunner polipoRunner;
|
||||
private GFWListUpdater gfwListUpdater;
|
||||
private AvailabilityStatistics _availabilityStatics;
|
||||
public AvailabilityStatistics availabilityStatistics { get; private set; }
|
||||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
|
||||
|
||||
private bool stopped = false;
|
||||
@@ -260,8 +260,8 @@ namespace Shadowsocks.Controller
|
||||
|
||||
public void UpdateStatisticsConfiguration(bool enabled)
|
||||
{
|
||||
if (_availabilityStatics == null) return;
|
||||
_availabilityStatics.UpdateConfiguration(_config, StatisticsConfiguration);
|
||||
if (availabilityStatistics == null) return;
|
||||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration);
|
||||
_config.availabilityStatistics = enabled;
|
||||
SaveConfig(_config);
|
||||
}
|
||||
@@ -311,11 +311,11 @@ namespace Shadowsocks.Controller
|
||||
gfwListUpdater.Error += pacServer_PACUpdateError;
|
||||
}
|
||||
|
||||
if (_availabilityStatics == null)
|
||||
if (availabilityStatistics == null)
|
||||
{
|
||||
_availabilityStatics = new AvailabilityStatistics(_config, StatisticsConfiguration);
|
||||
availabilityStatistics = new AvailabilityStatistics(_config, StatisticsConfiguration);
|
||||
}
|
||||
_availabilityStatics.UpdateConfiguration(_config, StatisticsConfiguration);
|
||||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration);
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
|
||||
@@ -4,29 +4,21 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Shadowsocks.Model;
|
||||
using System.IO;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Windows.Forms;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using Shadowsocks.Model;
|
||||
using Timer = System.Threading.Timer;
|
||||
|
||||
namespace Shadowsocks.Controller.Strategy
|
||||
{
|
||||
using DataUnit = KeyValuePair<string, string>;
|
||||
using DataList = List<KeyValuePair<string, string>>;
|
||||
|
||||
class StatisticsStrategy : IStrategy
|
||||
{
|
||||
private readonly ShadowsocksController _controller;
|
||||
private Server _currentServer;
|
||||
private readonly Timer _timer;
|
||||
private Dictionary<string, List<StatisticsRawData>> _rawStatistics;
|
||||
private Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>> _filteredStatistics;
|
||||
private int ChoiceKeptMilliseconds
|
||||
=> (int) TimeSpan.FromMinutes(_controller.StatisticsConfiguration.ChoiceKeptMinutes).TotalMilliseconds;
|
||||
private const int RetryInterval = 2*60*1000; //retry 2 minutes after failed
|
||||
|
||||
public StatisticsStrategy(ShadowsocksController controller)
|
||||
{
|
||||
@@ -47,75 +39,21 @@ namespace Shadowsocks.Controller.Strategy
|
||||
|
||||
private void LoadStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = AvailabilityStatistics.AvailabilityStatisticsFile;
|
||||
Logging.Debug($"loading statistics from {path}");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
LogWhenEnabled($"statistics file does not exist, try to reload {RetryInterval/60/1000} minutes later");
|
||||
_timer.Change(RetryInterval, ChoiceKeptMilliseconds);
|
||||
return;
|
||||
}
|
||||
_rawStatistics = (from l in File.ReadAllLines(path)
|
||||
.Skip(1)
|
||||
let strings = l.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
|
||||
let rawData = new StatisticsRawData
|
||||
{
|
||||
Timestamp = strings[0],
|
||||
ServerName = strings[1],
|
||||
ICMPStatus = strings[2],
|
||||
RoundtripTime = int.Parse(strings[3]),
|
||||
Geolocation = 5 > strings.Length ?
|
||||
null
|
||||
: strings[4],
|
||||
ISP = 6 > strings.Length ? null : strings[5]
|
||||
}
|
||||
group rawData by rawData.ServerName into server
|
||||
select new
|
||||
{
|
||||
ServerName = server.Key,
|
||||
data = server.ToList()
|
||||
}).ToDictionary(server => server.ServerName, server=> server.data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.LogUsefulException(e);
|
||||
}
|
||||
_filteredStatistics = _controller.availabilityStatistics.rawStatistics ?? _filteredStatistics ?? new Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>();
|
||||
}
|
||||
|
||||
//return the score by data
|
||||
//server with highest score will be choosen
|
||||
private float GetScore(IEnumerable<StatisticsRawData> rawDataList)
|
||||
private float GetScore(string serverName)
|
||||
{
|
||||
var config = _controller.StatisticsConfiguration;
|
||||
if (config.ByIsp)
|
||||
{
|
||||
var current = AvailabilityStatistics.GetGeolocationAndIsp().Result;
|
||||
rawDataList = rawDataList.Where(data => data.Geolocation == current[0].Value || data.Geolocation == AvailabilityStatistics.State.Unknown);
|
||||
rawDataList = rawDataList.Where(data => data.ISP == current[1].Value || data.ISP == AvailabilityStatistics.State.Unknown);
|
||||
if (rawDataList.LongCount() == 0) return 0;
|
||||
}
|
||||
if (config.ByHourOfDay)
|
||||
{
|
||||
var currentHour = DateTime.Now.Hour;
|
||||
rawDataList = rawDataList.Where(data =>
|
||||
{
|
||||
DateTime dateTime;
|
||||
DateTime.TryParseExact(data.Timestamp, AvailabilityStatistics.DateTimePattern, null,
|
||||
DateTimeStyles.None, out dateTime);
|
||||
var result = dateTime.Hour.Equals(currentHour);
|
||||
return result;
|
||||
});
|
||||
if (rawDataList.LongCount() == 0) return 0;
|
||||
}
|
||||
var dataList = rawDataList as IList<StatisticsRawData> ?? rawDataList.ToList();
|
||||
var serverName = dataList[0]?.ServerName;
|
||||
List<AvailabilityStatistics.RawStatisticsData> dataList;
|
||||
if (_filteredStatistics == null || !_filteredStatistics.TryGetValue(serverName, out dataList)) return 0;
|
||||
var SuccessTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.Success.ToString()));
|
||||
var TimedOutTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.TimedOut.ToString()));
|
||||
var statisticsData = new StatisticsData()
|
||||
var statisticsData = new AvailabilityStatistics.StatisticsData()
|
||||
{
|
||||
PackageLoss = TimedOutTimes / (SuccessTimes + TimedOutTimes) * 100,
|
||||
PackageLoss = TimedOutTimes/(SuccessTimes + TimedOutTimes)*100,
|
||||
AverageResponse = Convert.ToInt32(dataList.Average(data => data.RoundtripTime)),
|
||||
MinResponse = dataList.Min(data => data.RoundtripTime),
|
||||
MaxResponse = dataList.Max(data => data.RoundtripTime)
|
||||
@@ -134,27 +72,9 @@ namespace Shadowsocks.Controller.Strategy
|
||||
return score;
|
||||
}
|
||||
|
||||
class StatisticsRawData
|
||||
{
|
||||
public string Timestamp;
|
||||
public string ServerName;
|
||||
public string ICMPStatus;
|
||||
public int RoundtripTime;
|
||||
public string Geolocation;
|
||||
public string ISP ;
|
||||
}
|
||||
|
||||
public class StatisticsData
|
||||
{
|
||||
public float PackageLoss;
|
||||
public int AverageResponse;
|
||||
public int MinResponse;
|
||||
public int MaxResponse;
|
||||
}
|
||||
|
||||
private void ChooseNewServer(List<Server> servers)
|
||||
{
|
||||
if (_rawStatistics == null || servers.Count == 0)
|
||||
if (_filteredStatistics == null || servers.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -162,11 +82,11 @@ namespace Shadowsocks.Controller.Strategy
|
||||
{
|
||||
var bestResult = (from server in servers
|
||||
let name = server.FriendlyName()
|
||||
where _rawStatistics.ContainsKey(name)
|
||||
where _filteredStatistics.ContainsKey(name)
|
||||
select new
|
||||
{
|
||||
server,
|
||||
score = GetScore(_rawStatistics[name])
|
||||
score = GetScore(name)
|
||||
}
|
||||
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2);
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ namespace Shadowsocks.Model
|
||||
|
||||
public StatisticsStrategyConfiguration()
|
||||
{
|
||||
var statisticsStrategy = typeof (StatisticsStrategy);
|
||||
var statisticsData = statisticsStrategy.GetNestedType("StatisticsData");
|
||||
var availabilityStatisticsType = typeof (AvailabilityStatistics);
|
||||
var statisticsData = availabilityStatisticsType.GetNestedType("StatisticsData");
|
||||
var properties = statisticsData.GetFields(BindingFlags.Instance | BindingFlags.Public);
|
||||
Calculations = properties.ToDictionary(p => p.Name, _ => (float) 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user