@@ -1,534 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Concurrent; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Net; | |||||
using System.Net.NetworkInformation; | |||||
using System.Net.Sockets; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using Newtonsoft.Json; | |||||
using NLog; | |||||
using Shadowsocks.Model; | |||||
using Shadowsocks.Util; | |||||
namespace Shadowsocks.Controller | |||||
{ | |||||
using Statistics = Dictionary<string, List<StatisticsRecord>>; | |||||
public sealed class AvailabilityStatistics : IDisposable | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss"; | |||||
private const string StatisticsFilesName = "shadowsocks.availability.json"; | |||||
public static string AvailabilityStatisticsFile; | |||||
//static constructor to initialize every public static fields before refereced | |||||
static AvailabilityStatistics() | |||||
{ | |||||
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName); | |||||
} | |||||
//arguments for ICMP tests | |||||
private int Repeat => Config.RepeatTimesNum; | |||||
public const int TimeoutMilliseconds = 500; | |||||
//records cache for current server in {_monitorInterval} minutes | |||||
private readonly ConcurrentDictionary<string, List<int>> _latencyRecords = new ConcurrentDictionary<string, List<int>>(); | |||||
//speed in KiB/s | |||||
private readonly ConcurrentDictionary<string, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>(); | |||||
private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>(); | |||||
private readonly ConcurrentDictionary<string, InOutBoundRecord> _inOutBoundRecords = new ConcurrentDictionary<string, InOutBoundRecord>(); | |||||
private class InOutBoundRecord | |||||
{ | |||||
private long _inbound; | |||||
private long _lastInbound; | |||||
private long _outbound; | |||||
private long _lastOutbound; | |||||
public void UpdateInbound(long delta) | |||||
{ | |||||
Interlocked.Add(ref _inbound, delta); | |||||
} | |||||
public void UpdateOutbound(long delta) | |||||
{ | |||||
Interlocked.Add(ref _outbound, delta); | |||||
} | |||||
public void GetDelta(out long inboundDelta, out long outboundDelta) | |||||
{ | |||||
var i = Interlocked.Read(ref _inbound); | |||||
var il = Interlocked.Exchange(ref _lastInbound, i); | |||||
inboundDelta = i - il; | |||||
var o = Interlocked.Read(ref _outbound); | |||||
var ol = Interlocked.Exchange(ref _lastOutbound, o); | |||||
outboundDelta = o - ol; | |||||
} | |||||
} | |||||
//tasks | |||||
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1); | |||||
private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2); | |||||
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes); | |||||
private Timer _perSecondTimer; //analyze and save cached records to RawStatistics and filter records | |||||
private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1); | |||||
//private Timer _writer; //write RawStatistics to file | |||||
//private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1); | |||||
private ShadowsocksController _controller; | |||||
private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration; | |||||
// Static Singleton Initialization | |||||
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics(); | |||||
public Statistics RawStatistics { get; private set; } | |||||
public Statistics FilteredStatistics { get; private set; } | |||||
private AvailabilityStatistics() | |||||
{ | |||||
RawStatistics = new Statistics(); | |||||
} | |||||
internal void UpdateConfiguration(ShadowsocksController controller) | |||||
{ | |||||
_controller = controller; | |||||
Reset(); | |||||
try | |||||
{ | |||||
if (Config.StatisticsEnabled) | |||||
{ | |||||
LoadRawStatistics(); | |||||
if (_perSecondTimer == null) | |||||
{ | |||||
_perSecondTimer = new Timer(OperationsPerSecond, new Counter(), _delayBeforeStart, TimeSpan.FromSeconds(1)); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
_perSecondTimer?.Dispose(); | |||||
} | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private void OperationsPerSecond(object state) | |||||
{ | |||||
lock(state) | |||||
{ | |||||
var counter = state as Counter; | |||||
if (counter.count % _monitorInterval.TotalSeconds == 0) | |||||
{ | |||||
UpdateSpeed(); | |||||
} | |||||
if (counter.count % RecordingInterval.TotalSeconds == 0) | |||||
{ | |||||
Run(); | |||||
} | |||||
counter.count++; | |||||
} | |||||
} | |||||
private void UpdateSpeed() | |||||
{ | |||||
foreach (var kv in _inOutBoundRecords) | |||||
{ | |||||
var id = kv.Key; | |||||
var record = kv.Value; | |||||
long inboundDelta, outboundDelta; | |||||
record.GetDelta(out inboundDelta, out outboundDelta); | |||||
var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds); | |||||
var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds); | |||||
// not thread safe | |||||
var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List<int>()); | |||||
var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List<int>()); | |||||
inR.Add(inboundSpeed); | |||||
outR.Add(outboundSpeed); | |||||
logger.Debug( | |||||
$"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s"); | |||||
} | |||||
} | |||||
private void Reset() | |||||
{ | |||||
_inboundSpeedRecords.Clear(); | |||||
_outboundSpeedRecords.Clear(); | |||||
_latencyRecords.Clear(); | |||||
} | |||||
private void Run() | |||||
{ | |||||
UpdateRecords(); | |||||
Reset(); | |||||
} | |||||
private void UpdateRecords() | |||||
{ | |||||
var records = new Dictionary<string, StatisticsRecord>(); | |||||
UpdateRecordsState state = new UpdateRecordsState(); | |||||
int serverCount = _controller.GetCurrentConfiguration().configs.Count; | |||||
state.counter = serverCount; | |||||
bool isPing = Config.Ping; | |||||
for (int i = 0; i < serverCount; i++) | |||||
{ | |||||
try | |||||
{ | |||||
var server = _controller.GetCurrentConfiguration().configs[i]; | |||||
var id = server.Identifier(); | |||||
List<int> inboundSpeedRecords = null; | |||||
List<int> outboundSpeedRecords = null; | |||||
List<int> latencyRecords = null; | |||||
_inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords); | |||||
_outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords); | |||||
_latencyRecords.TryGetValue(id, out latencyRecords); | |||||
StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords); | |||||
/* duplicate server identifier */ | |||||
if (records.ContainsKey(id)) | |||||
records[id] = record; | |||||
else | |||||
records.Add(id, record); | |||||
if (isPing) | |||||
{ | |||||
// FIXME: on ping completed, every thing could be asynchrously changed. | |||||
// focus on: Config/ RawStatistics | |||||
MyPing ping = new MyPing(server, Repeat); | |||||
ping.Completed += ping_Completed; | |||||
ping.Start(new PingState { state = state, record = record }); | |||||
} | |||||
else if (!record.IsEmptyData()) | |||||
{ | |||||
AppendRecord(id, record); | |||||
} | |||||
} | |||||
catch | |||||
{ | |||||
logger.Debug("config changed asynchrously, just ignore this server"); | |||||
} | |||||
} | |||||
if (!isPing) | |||||
{ | |||||
Save(); | |||||
FilterRawStatistics(); | |||||
} | |||||
} | |||||
private void ping_Completed(object sender, MyPing.CompletedEventArgs e) | |||||
{ | |||||
PingState pingState = (PingState)e.UserState; | |||||
UpdateRecordsState state = pingState.state; | |||||
Server server = e.Server; | |||||
StatisticsRecord record = pingState.record; | |||||
record.SetResponse(e.RoundtripTime); | |||||
if (!record.IsEmptyData()) | |||||
{ | |||||
AppendRecord(server.Identifier(), record); | |||||
} | |||||
logger.Debug($"Ping {server} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms"); | |||||
if (Interlocked.Decrement(ref state.counter) == 0) | |||||
{ | |||||
Save(); | |||||
FilterRawStatistics(); | |||||
} | |||||
} | |||||
private void AppendRecord(string serverIdentifier, StatisticsRecord record) | |||||
{ | |||||
try | |||||
{ | |||||
List<StatisticsRecord> records; | |||||
lock (RawStatistics) | |||||
{ | |||||
if (!RawStatistics.TryGetValue(serverIdentifier, out records)) | |||||
{ | |||||
records = new List<StatisticsRecord>(); | |||||
RawStatistics[serverIdentifier] = records; | |||||
} | |||||
} | |||||
records.Add(record); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private void Save() | |||||
{ | |||||
logger.Debug($"save statistics to {AvailabilityStatisticsFile}"); | |||||
if (RawStatistics.Count == 0) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
string content; | |||||
#if DEBUG | |||||
content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented); | |||||
#else | |||||
content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); | |||||
#endif | |||||
File.WriteAllText(AvailabilityStatisticsFile, content); | |||||
} | |||||
catch (IOException e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private bool IsValidRecord(StatisticsRecord record) | |||||
{ | |||||
if (Config.ByHourOfDay) | |||||
{ | |||||
if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false; | |||||
} | |||||
return true; | |||||
} | |||||
private void FilterRawStatistics() | |||||
{ | |||||
try | |||||
{ | |||||
logger.Debug("filter raw statistics"); | |||||
if (RawStatistics == null) return; | |||||
var filteredStatistics = new Statistics(); | |||||
foreach (var serverAndRecords in RawStatistics) | |||||
{ | |||||
var server = serverAndRecords.Key; | |||||
var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord); | |||||
filteredStatistics[server] = filteredRecords; | |||||
} | |||||
FilteredStatistics = filteredStatistics; | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private void LoadRawStatistics() | |||||
{ | |||||
try | |||||
{ | |||||
var path = AvailabilityStatisticsFile; | |||||
logger.Debug($"loading statistics from {path}"); | |||||
if (!File.Exists(path)) | |||||
{ | |||||
using (File.Create(path)) | |||||
{ | |||||
//do nothing | |||||
} | |||||
} | |||||
var content = File.ReadAllText(path); | |||||
RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics; | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
Console.WriteLine($"failed to load statistics; use runtime statistics, some data may be lost"); | |||||
} | |||||
} | |||||
private static int GetSpeedInKiBPerSecond(long bytes, double seconds) | |||||
{ | |||||
var result = (int)(bytes / seconds) / 1024; | |||||
return result; | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
_perSecondTimer.Dispose(); | |||||
} | |||||
public void UpdateLatency(Server server, int latency) | |||||
{ | |||||
_latencyRecords.GetOrAdd(server.Identifier(), (k) => | |||||
{ | |||||
List<int> records = new List<int>(); | |||||
records.Add(latency); | |||||
return records; | |||||
}); | |||||
} | |||||
public void UpdateInboundCounter(Server server, long n) | |||||
{ | |||||
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => | |||||
{ | |||||
var r = new InOutBoundRecord(); | |||||
r.UpdateInbound(n); | |||||
return r; | |||||
}, (k, v) => | |||||
{ | |||||
v.UpdateInbound(n); | |||||
return v; | |||||
}); | |||||
} | |||||
public void UpdateOutboundCounter(Server server, long n) | |||||
{ | |||||
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) => | |||||
{ | |||||
var r = new InOutBoundRecord(); | |||||
r.UpdateOutbound(n); | |||||
return r; | |||||
}, (k, v) => | |||||
{ | |||||
v.UpdateOutbound(n); | |||||
return v; | |||||
}); | |||||
} | |||||
private class Counter | |||||
{ | |||||
public int count = 0; | |||||
} | |||||
class UpdateRecordsState | |||||
{ | |||||
public int counter; | |||||
} | |||||
class PingState | |||||
{ | |||||
public UpdateRecordsState state; | |||||
public StatisticsRecord record; | |||||
} | |||||
class MyPing | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
//arguments for ICMP tests | |||||
public const int TimeoutMilliseconds = 500; | |||||
public EventHandler<CompletedEventArgs> Completed; | |||||
private Server server; | |||||
private int repeat; | |||||
private IPAddress ip; | |||||
private Ping ping; | |||||
private List<int?> RoundtripTime; | |||||
public MyPing(Server server, int repeat) | |||||
{ | |||||
this.server = server; | |||||
this.repeat = repeat; | |||||
RoundtripTime = new List<int?>(repeat); | |||||
ping = new Ping(); | |||||
ping.PingCompleted += Ping_PingCompleted; | |||||
} | |||||
public void Start(object userstate) | |||||
{ | |||||
if (server.server == "") | |||||
{ | |||||
FireCompleted(new Exception("Invalid Server"), userstate); | |||||
return; | |||||
} | |||||
new Task(() => ICMPTest(0, userstate)).Start(); | |||||
} | |||||
private void ICMPTest(int delay, object userstate) | |||||
{ | |||||
try | |||||
{ | |||||
logger.Debug($"Ping {server}"); | |||||
if (ip == null) | |||||
{ | |||||
ip = Dns.GetHostAddresses(server.server) | |||||
.First( | |||||
ip => | |||||
ip.AddressFamily == AddressFamily.InterNetwork || | |||||
ip.AddressFamily == AddressFamily.InterNetworkV6); | |||||
} | |||||
repeat--; | |||||
if (delay > 0) | |||||
Thread.Sleep(delay); | |||||
ping.SendAsync(ip, TimeoutMilliseconds, userstate); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.Error($"An exception occured while eveluating {server}"); | |||||
logger.LogUsefulException(e); | |||||
FireCompleted(e, userstate); | |||||
} | |||||
} | |||||
private void Ping_PingCompleted(object sender, PingCompletedEventArgs e) | |||||
{ | |||||
try | |||||
{ | |||||
if (e.Reply.Status == IPStatus.Success) | |||||
{ | |||||
logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms"); | |||||
RoundtripTime.Add((int?)e.Reply.RoundtripTime); | |||||
} | |||||
else | |||||
{ | |||||
logger.Debug($"Ping {server} timeout"); | |||||
RoundtripTime.Add(null); | |||||
} | |||||
TestNext(e.UserState); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
logger.Error($"An exception occured while eveluating {server}"); | |||||
logger.LogUsefulException(ex); | |||||
FireCompleted(ex, e.UserState); | |||||
} | |||||
} | |||||
private void TestNext(object userstate) | |||||
{ | |||||
if (repeat > 0) | |||||
{ | |||||
//Do ICMPTest in a random frequency | |||||
int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds; | |||||
new Task(() => ICMPTest(delay, userstate)).Start(); | |||||
} | |||||
else | |||||
{ | |||||
FireCompleted(null, userstate); | |||||
} | |||||
} | |||||
private void FireCompleted(Exception error, object userstate) | |||||
{ | |||||
Completed?.Invoke(this, new CompletedEventArgs | |||||
{ | |||||
Error = error, | |||||
Server = server, | |||||
RoundtripTime = RoundtripTime, | |||||
UserState = userstate | |||||
}); | |||||
} | |||||
public class CompletedEventArgs : EventArgs | |||||
{ | |||||
public Exception Error; | |||||
public Server Server; | |||||
public List<int?> RoundtripTime; | |||||
public object UserState; | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -40,9 +40,6 @@ namespace Shadowsocks.Controller | |||||
private PrivoxyRunner privoxyRunner; | private PrivoxyRunner privoxyRunner; | ||||
private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer; | private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer; | ||||
public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance; | |||||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } | |||||
private long _inboundCounter = 0; | private long _inboundCounter = 0; | ||||
private long _outboundCounter = 0; | private long _outboundCounter = 0; | ||||
public long InboundCounter => Interlocked.Read(ref _inboundCounter); | public long InboundCounter => Interlocked.Read(ref _inboundCounter); | ||||
@@ -98,7 +95,6 @@ namespace Shadowsocks.Controller | |||||
httpClient = new HttpClient(); | httpClient = new HttpClient(); | ||||
_config = Configuration.Load(); | _config = Configuration.Load(); | ||||
Configuration.Process(ref _config); | Configuration.Process(ref _config); | ||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||||
_strategyManager = new StrategyManager(this); | _strategyManager = new StrategyManager(this); | ||||
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); | _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); | ||||
StartTrafficStatistics(61); | StartTrafficStatistics(61); | ||||
@@ -188,8 +184,6 @@ namespace Shadowsocks.Controller | |||||
httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString); | httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString); | ||||
} | } | ||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||||
privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); | privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); | ||||
_pacDaemon = _pacDaemon ?? new PACDaemon(_config); | _pacDaemon = _pacDaemon ?? new PACDaemon(_config); | ||||
@@ -202,7 +196,6 @@ namespace Shadowsocks.Controller | |||||
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; | GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; | ||||
GeositeUpdater.Error += PacServer_PACUpdateError; | GeositeUpdater.Error += PacServer_PACUpdateError; | ||||
availabilityStatistics.UpdateConfiguration(this); | |||||
_listener?.Stop(); | _listener?.Stop(); | ||||
StopPlugins(); | StopPlugins(); | ||||
@@ -220,7 +213,6 @@ namespace Shadowsocks.Controller | |||||
privoxyRunner.Start(_config); | privoxyRunner.Start(_config); | ||||
TCPRelay tcpRelay = new TCPRelay(this, _config); | TCPRelay tcpRelay = new TCPRelay(this, _config); | ||||
tcpRelay.OnConnected += UpdateLatency; | |||||
tcpRelay.OnInbound += UpdateInboundCounter; | tcpRelay.OnInbound += UpdateInboundCounter; | ||||
tcpRelay.OnOutbound += UpdateOutboundCounter; | tcpRelay.OnOutbound += UpdateOutboundCounter; | ||||
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); | tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); | ||||
@@ -524,7 +516,7 @@ namespace Shadowsocks.Controller | |||||
#endregion | #endregion | ||||
#region Statistics | |||||
#region Strategy | |||||
public void SelectStrategy(string strategyID) | public void SelectStrategy(string strategyID) | ||||
{ | { | ||||
@@ -533,12 +525,6 @@ namespace Shadowsocks.Controller | |||||
SaveConfig(_config); | SaveConfig(_config); | ||||
} | } | ||||
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) | |||||
{ | |||||
StatisticsConfiguration = configuration; | |||||
StatisticsStrategyConfiguration.Save(configuration); | |||||
} | |||||
public IList<IStrategy> GetStrategies() | public IList<IStrategy> GetStrategies() | ||||
{ | { | ||||
return _strategyManager.GetStrategies(); | return _strategyManager.GetStrategies(); | ||||
@@ -556,43 +542,16 @@ namespace Shadowsocks.Controller | |||||
return null; | return null; | ||||
} | } | ||||
public void UpdateStatisticsConfiguration(bool enabled) | |||||
{ | |||||
if (availabilityStatistics != null) | |||||
{ | |||||
availabilityStatistics.UpdateConfiguration(this); | |||||
_config.availabilityStatistics = enabled; | |||||
SaveConfig(_config); | |||||
} | |||||
} | |||||
public void UpdateLatency(object sender, SSTCPConnectedEventArgs args) | |||||
{ | |||||
GetCurrentStrategy()?.UpdateLatency(args.server, args.latency); | |||||
if (_config.availabilityStatistics) | |||||
{ | |||||
availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds); | |||||
} | |||||
} | |||||
public void UpdateInboundCounter(object sender, SSTransmitEventArgs args) | public void UpdateInboundCounter(object sender, SSTransmitEventArgs args) | ||||
{ | { | ||||
GetCurrentStrategy()?.UpdateLastRead(args.server); | GetCurrentStrategy()?.UpdateLastRead(args.server); | ||||
Interlocked.Add(ref _inboundCounter, args.length); | Interlocked.Add(ref _inboundCounter, args.length); | ||||
if (_config.availabilityStatistics) | |||||
{ | |||||
availabilityStatistics.UpdateInboundCounter(args.server, args.length); | |||||
} | |||||
} | } | ||||
public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) | public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) | ||||
{ | { | ||||
GetCurrentStrategy()?.UpdateLastWrite(args.server); | GetCurrentStrategy()?.UpdateLastWrite(args.server); | ||||
Interlocked.Add(ref _outboundCounter, args.length); | Interlocked.Add(ref _outboundCounter, args.length); | ||||
if (_config.availabilityStatistics) | |||||
{ | |||||
availabilityStatistics.UpdateOutboundCounter(args.server, args.length); | |||||
} | |||||
} | } | ||||
#endregion | #endregion | ||||
@@ -1,170 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Net; | |||||
using System.Threading; | |||||
using Newtonsoft.Json; | |||||
using NLog; | |||||
using Shadowsocks.Model; | |||||
namespace Shadowsocks.Controller.Strategy | |||||
{ | |||||
using Statistics = Dictionary<string, List<StatisticsRecord>>; | |||||
internal class StatisticsStrategy : IStrategy, IDisposable | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
private readonly ShadowsocksController _controller; | |||||
private Server _currentServer; | |||||
private readonly Timer _timer; | |||||
private Statistics _filteredStatistics; | |||||
private AvailabilityStatistics Service => _controller.availabilityStatistics; | |||||
private int ChoiceKeptMilliseconds | |||||
=> (int)TimeSpan.FromMinutes(_controller.StatisticsConfiguration.ChoiceKeptMinutes).TotalMilliseconds; | |||||
public StatisticsStrategy(ShadowsocksController controller) | |||||
{ | |||||
_controller = controller; | |||||
var servers = controller.GetCurrentConfiguration().configs; | |||||
var randomIndex = new Random().Next() % servers.Count; | |||||
_currentServer = servers[randomIndex]; //choose a server randomly at first | |||||
// FIXME: consider Statistics and Config changing asynchrously. | |||||
_timer = new Timer(ReloadStatisticsAndChooseAServer); | |||||
} | |||||
private void ReloadStatisticsAndChooseAServer(object obj) | |||||
{ | |||||
logger.Debug("Reloading statistics and choose a new server...."); | |||||
var servers = _controller.GetCurrentConfiguration().configs; | |||||
LoadStatistics(); | |||||
ChooseNewServer(servers); | |||||
} | |||||
private void LoadStatistics() | |||||
{ | |||||
_filteredStatistics = | |||||
Service.FilteredStatistics ?? | |||||
Service.RawStatistics ?? | |||||
_filteredStatistics; | |||||
} | |||||
//return the score by data | |||||
//server with highest score will be choosen | |||||
private float? GetScore(string identifier, List<StatisticsRecord> records) | |||||
{ | |||||
var config = _controller.StatisticsConfiguration; | |||||
float? score = null; | |||||
var averageRecord = new StatisticsRecord(identifier, | |||||
records.Where(record => record.MaxInboundSpeed != null).Select(record => record.MaxInboundSpeed.Value).ToList(), | |||||
records.Where(record => record.MaxOutboundSpeed != null).Select(record => record.MaxOutboundSpeed.Value).ToList(), | |||||
records.Where(record => record.AverageLatency != null).Select(record => record.AverageLatency.Value).ToList()); | |||||
averageRecord.SetResponse(records.Select(record => record.AverageResponse).ToList()); | |||||
foreach (var calculation in config.Calculations) | |||||
{ | |||||
var name = calculation.Key; | |||||
var field = typeof (StatisticsRecord).GetField(name); | |||||
dynamic value = field?.GetValue(averageRecord); | |||||
var factor = calculation.Value; | |||||
if (value == null || factor.Equals(0)) continue; | |||||
score = score ?? 0; | |||||
score += value * factor; | |||||
} | |||||
if (score != null) | |||||
{ | |||||
logger.Debug($"Highest score: {score} {JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}"); | |||||
} | |||||
return score; | |||||
} | |||||
private void ChooseNewServer(List<Server> servers) | |||||
{ | |||||
if (_filteredStatistics == null || servers.Count == 0) | |||||
{ | |||||
return; | |||||
} | |||||
try | |||||
{ | |||||
var serversWithStatistics = (from server in servers | |||||
let id = server.Identifier() | |||||
where _filteredStatistics.ContainsKey(id) | |||||
let score = GetScore(id, _filteredStatistics[id]) | |||||
where score != null | |||||
select new | |||||
{ | |||||
server, | |||||
score | |||||
}).ToArray(); | |||||
if (serversWithStatistics.Length < 2) | |||||
{ | |||||
LogWhenEnabled("no enough statistics data or all factors in calculations are 0"); | |||||
return; | |||||
} | |||||
var bestResult = serversWithStatistics | |||||
.Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2); | |||||
LogWhenEnabled($"Switch to server: {bestResult.server.ToString()} by statistics: score {bestResult.score}"); | |||||
_currentServer = bestResult.server; | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
private void LogWhenEnabled(string log) | |||||
{ | |||||
if (_controller.GetCurrentStrategy()?.ID == ID) //output when enabled | |||||
{ | |||||
Console.WriteLine(log); | |||||
} | |||||
} | |||||
public string ID => "com.shadowsocks.strategy.scbs"; | |||||
public string Name => I18N.GetString("Choose by statistics"); | |||||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint) | |||||
{ | |||||
if (_currentServer == null) | |||||
{ | |||||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||||
} | |||||
return _currentServer; //current server cached for CachedInterval | |||||
} | |||||
public void ReloadServers() | |||||
{ | |||||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||||
_timer?.Change(0, ChoiceKeptMilliseconds); | |||||
} | |||||
public void SetFailure(Server server) | |||||
{ | |||||
logger.Debug($"failure: {server.ToString()}"); | |||||
} | |||||
public void UpdateLastRead(Server server) | |||||
{ | |||||
} | |||||
public void UpdateLastWrite(Server server) | |||||
{ | |||||
} | |||||
public void UpdateLatency(Server server, TimeSpan latency) | |||||
{ | |||||
} | |||||
public void Dispose() | |||||
{ | |||||
_timer.Dispose(); | |||||
} | |||||
} | |||||
} |
@@ -13,7 +13,6 @@ namespace Shadowsocks.Controller.Strategy | |||||
_strategies = new List<IStrategy>(); | _strategies = new List<IStrategy>(); | ||||
_strategies.Add(new BalancingStrategy(controller)); | _strategies.Add(new BalancingStrategy(controller)); | ||||
_strategies.Add(new HighAvailabilityStrategy(controller)); | _strategies.Add(new HighAvailabilityStrategy(controller)); | ||||
_strategies.Add(new StatisticsStrategy(controller)); | |||||
// TODO: load DLL plugins | // TODO: load DLL plugins | ||||
} | } | ||||
public IList<IStrategy> GetStrategies() | public IList<IStrategy> GetStrategies() | ||||
@@ -16,7 +16,6 @@ PAC,Сценарий настройки (PAC),PAC 模式,PAC 模式,PACモード | |||||
Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global | Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global | ||||
Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs | Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs | ||||
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… | Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… | ||||
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정,Configuration des statistiques… | |||||
Online Config...,,在线配置...,線上配置...,,, | Online Config...,,在线配置...,線上配置...,,, | ||||
Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique | Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique | ||||
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결, | Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결, | ||||
@@ -47,7 +46,6 @@ Quit,Выход,退出,結束,終了,종료,Quitter | |||||
Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs | Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs | ||||
Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge | Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge | ||||
High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité | High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité | ||||
Choose by statistics,На основе статистики,根据统计,根據統計,統計で選ぶ,통계 기반,Choisissez par statistiques | |||||
Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin | Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin | ||||
Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction | Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction | ||||
,,,,,, | ,,,,,, | ||||
@@ -81,36 +79,6 @@ Move D&own,Ниже,下移(&O),下移 (&O),下に移動 (&O),아래로 (&O),Desc | |||||
deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète | deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète | ||||
"Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}" | "Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}" | ||||
,,,,,, | ,,,,,, | ||||
#Statistics Config,,,,,, | |||||
,,,,,, | |||||
Enable Statistics,Включить сбор статистики,启用统计,,統計を有効にする,통계 활성화,Activer statistiques | |||||
Ping Test,Проверка связи (Ping),Ping测试,,Ping測定,Ping 테스트,Test ping | |||||
packages everytime,пакета на проверку,个包/次,,パケット/回,시간 당 패킷,Packages à chaque fois | |||||
By hour of day,Ежечасно,按照每天的小时数统计,,毎日の時間数で統計,매 시간,Par heure du jour | |||||
Collect data per,Собирать данные за,收集数据每,,ごとにデータを収集,데이터 수집 기간,Recueillir des données par | |||||
Keep choice for,Хранить отбор данных за,保持选择每,,,설정 유지하기,Gardez le choix pour | |||||
minutes,мин.,分钟,,分,분,Minutes | |||||
Final Score:,Финальная оценка:,总分:,,,최종 점수:,Score final: | |||||
AverageLatency,СредЗадержка,平均延迟,,平均遅延時間,평균 지연시간,LatenceMoyenne | |||||
MinLatency,МинЗадержка,最小延迟,,最小遅延時間,최소 지연시간,MinLatence | |||||
MaxLatency,МаксЗадержка,最大延迟,,最大遅延時間,최대 지연시간,MaxLatence | |||||
AverageInboundSpeed,СредВходСкорость,平均入站速度,,平均インバウンド速度,평균 인바운드 속도,VitesseEntranteMoyenne | |||||
MinInboundSpeed,МинВходСкорость,最小入站速度,,最小インバウンド速度,최소 인바운드 속도,MinVitesseEntrante | |||||
MaxInboundSpeed,СредВходСкорость,最大入站速度,,最大インバウンド速度,최대 인바운드 속도,MaxVitesseEntrante | |||||
AverageOutboundSpeed,СредИсхСкорость,平均出站速度,,平均アウトバウンド速度,평균 아웃바운드 속도,VitesseSortanteMoyenne | |||||
MinOutboundSpeed,МинИсхСкорость,最小出站速度,,最小アウトバウンド速度,최소 아웃바운드 속도,MinVitesseSortante | |||||
MaxOutboundSpeed,МаксИсхСкорость,最大出站速度,,最大アウトバウンド速度,최대 아웃바운드 속도,MaxVitesseSortante | |||||
AverageResponse,СредВремяОтвета,平均响应速度,,平均レスポンス速度,평균 응답,RéponseMoyenne | |||||
MinResponse,МинВремяОтвета,最小响应速度,,最小レスポンス速度,최소 응답,MinRéponse | |||||
MaxResponse,МаксВремяОтвета,最大响应速度,,最大レスポンス速度,최대 응답,MaxRéponse | |||||
PackageLoss,ПотериПакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets | |||||
Speed,Скорость,速度,,速度,속도,Vitesse | |||||
Package Loss,Потери пакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets | |||||
Ping,Ping,网络延迟,,Ping,Ping,Ping | |||||
Chart Mode,График,图表模式,,図表モード,차트 모드,Mode graphique | |||||
24h,24ч,24小时,,24時間,24시간,24h | |||||
all,За все время,全部,,すべて,전체,tout | |||||
,,,,,, | |||||
# Log Form,,,,,, | # Log Form,,,,,, | ||||
,,,,,, | ,,,,,, | ||||
&File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier | &File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier | ||||
@@ -37,7 +37,6 @@ namespace Shadowsocks.Model | |||||
public bool useOnlinePac; | public bool useOnlinePac; | ||||
public bool secureLocalPac; // enable secret for PAC server | public bool secureLocalPac; // enable secret for PAC server | ||||
public bool regeneratePacOnUpdate; // regenerate pac.txt on version update | public bool regeneratePacOnUpdate; // regenerate pac.txt on version update | ||||
public bool availabilityStatistics; | |||||
public bool autoCheckUpdate; | public bool autoCheckUpdate; | ||||
public bool checkPreRelease; | public bool checkPreRelease; | ||||
public string skippedUpdateVersion; // skip the update with this version number | public string skippedUpdateVersion; // skip the update with this version number | ||||
@@ -76,7 +75,6 @@ namespace Shadowsocks.Model | |||||
useOnlinePac = false; | useOnlinePac = false; | ||||
secureLocalPac = true; | secureLocalPac = true; | ||||
regeneratePacOnUpdate = true; | regeneratePacOnUpdate = true; | ||||
availabilityStatistics = false; | |||||
autoCheckUpdate = false; | autoCheckUpdate = false; | ||||
checkPreRelease = false; | checkPreRelease = false; | ||||
skippedUpdateVersion = ""; | skippedUpdateVersion = ""; | ||||
@@ -1,95 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
namespace Shadowsocks.Model | |||||
{ | |||||
// Simple processed records for a short period of time | |||||
public class StatisticsRecord | |||||
{ | |||||
public DateTime Timestamp { get; set; } = DateTime.Now; | |||||
public string ServerIdentifier { get; set; } | |||||
// in ping-only records, these fields would be null | |||||
public int? AverageLatency; | |||||
public int? MinLatency; | |||||
public int? MaxLatency; | |||||
private bool EmptyLatencyData => (AverageLatency == null) && (MinLatency == null) && (MaxLatency == null); | |||||
public int? AverageInboundSpeed; | |||||
public int? MinInboundSpeed; | |||||
public int? MaxInboundSpeed; | |||||
private bool EmptyInboundSpeedData | |||||
=> (AverageInboundSpeed == null) && (MinInboundSpeed == null) && (MaxInboundSpeed == null); | |||||
public int? AverageOutboundSpeed; | |||||
public int? MinOutboundSpeed; | |||||
public int? MaxOutboundSpeed; | |||||
private bool EmptyOutboundSpeedData | |||||
=> (AverageOutboundSpeed == null) && (MinOutboundSpeed == null) && (MaxOutboundSpeed == null); | |||||
// if user disabled ping test, response would be null | |||||
public int? AverageResponse; | |||||
public int? MinResponse; | |||||
public int? MaxResponse; | |||||
public float? PackageLoss; | |||||
private bool EmptyResponseData | |||||
=> (AverageResponse == null) && (MinResponse == null) && (MaxResponse == null) && (PackageLoss == null); | |||||
public bool IsEmptyData() { | |||||
return EmptyInboundSpeedData && EmptyOutboundSpeedData && EmptyResponseData && EmptyLatencyData; | |||||
} | |||||
public StatisticsRecord() | |||||
{ | |||||
} | |||||
public StatisticsRecord(string identifier, ICollection<int> inboundSpeedRecords, ICollection<int> outboundSpeedRecords, ICollection<int> latencyRecords) | |||||
{ | |||||
ServerIdentifier = identifier; | |||||
var inbound = inboundSpeedRecords?.Where(s => s > 0).ToList(); | |||||
if (inbound != null && inbound.Any()) | |||||
{ | |||||
AverageInboundSpeed = (int) inbound.Average(); | |||||
MinInboundSpeed = inbound.Min(); | |||||
MaxInboundSpeed = inbound.Max(); | |||||
} | |||||
var outbound = outboundSpeedRecords?.Where(s => s > 0).ToList(); | |||||
if (outbound!= null && outbound.Any()) | |||||
{ | |||||
AverageOutboundSpeed = (int) outbound.Average(); | |||||
MinOutboundSpeed = outbound.Min(); | |||||
MaxOutboundSpeed = outbound.Max(); | |||||
} | |||||
var latency = latencyRecords?.Where(s => s > 0).ToList(); | |||||
if (latency!= null && latency.Any()) | |||||
{ | |||||
AverageLatency = (int) latency.Average(); | |||||
MinLatency = latency.Min(); | |||||
MaxLatency = latency.Max(); | |||||
} | |||||
} | |||||
public StatisticsRecord(string identifier, ICollection<int?> responseRecords) | |||||
{ | |||||
ServerIdentifier = identifier; | |||||
SetResponse(responseRecords); | |||||
} | |||||
public void SetResponse(ICollection<int?> responseRecords) | |||||
{ | |||||
if (responseRecords == null) return; | |||||
var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList(); | |||||
if (!records.Any()) return; | |||||
AverageResponse = (int?) records.Average(); | |||||
MinResponse = records.Min(); | |||||
MaxResponse = records.Max(); | |||||
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count; | |||||
} | |||||
} | |||||
} |
@@ -1,69 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using Newtonsoft.Json; | |||||
using NLog; | |||||
namespace Shadowsocks.Model | |||||
{ | |||||
[Serializable] | |||||
public class StatisticsStrategyConfiguration | |||||
{ | |||||
private static Logger logger = LogManager.GetCurrentClassLogger(); | |||||
public static readonly string ID = "com.shadowsocks.strategy.statistics"; | |||||
public bool StatisticsEnabled { get; set; } = false; | |||||
public bool ByHourOfDay { get; set; } = true; | |||||
public bool Ping { get; set; } | |||||
public int ChoiceKeptMinutes { get; set; } = 10; | |||||
public int DataCollectionMinutes { get; set; } = 10; | |||||
public int RepeatTimesNum { get; set; } = 4; | |||||
private const string ConfigFile = "statistics-config.json"; | |||||
public static StatisticsStrategyConfiguration Load() | |||||
{ | |||||
try | |||||
{ | |||||
var content = File.ReadAllText(ConfigFile); | |||||
var configuration = JsonConvert.DeserializeObject<StatisticsStrategyConfiguration>(content); | |||||
return configuration; | |||||
} | |||||
catch (FileNotFoundException) | |||||
{ | |||||
var configuration = new StatisticsStrategyConfiguration(); | |||||
Save(configuration); | |||||
return configuration; | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
return new StatisticsStrategyConfiguration(); | |||||
} | |||||
} | |||||
public static void Save(StatisticsStrategyConfiguration configuration) | |||||
{ | |||||
try | |||||
{ | |||||
var content = JsonConvert.SerializeObject(configuration, Formatting.Indented); | |||||
File.WriteAllText(ConfigFile, content); | |||||
} | |||||
catch (Exception e) | |||||
{ | |||||
logger.LogUsefulException(e); | |||||
} | |||||
} | |||||
public Dictionary<string, float> Calculations; | |||||
public StatisticsStrategyConfiguration() | |||||
{ | |||||
var properties = typeof(StatisticsRecord).GetFields(BindingFlags.Instance | BindingFlags.Public); | |||||
Calculations = properties.ToDictionary(p => p.Name, _ => (float)0); | |||||
} | |||||
} | |||||
} |
@@ -1,10 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<!-- | |||||
This file is automatically generated by Visual Studio .Net. It is | |||||
used to store generic object data source configuration information. | |||||
Renaming the file extension or editing the content of this file may | |||||
cause the file to be unrecognizable by the program. | |||||
--> | |||||
<GenericObjectDataSource DisplayName="StatisticsStrategyConfiguration" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource"> | |||||
<TypeInfo>Shadowsocks.Model.StatisticsStrategyConfiguration, Shadowsocks, Version=2.5.2.0, Culture=neutral, PublicKeyToken=null</TypeInfo> | |||||
</GenericObjectDataSource> |
@@ -1,113 +0,0 @@ | |||||
namespace Shadowsocks.View | |||||
{ | |||||
partial class CalculationControl | |||||
{ | |||||
/// <summary> | |||||
/// Required designer variable. | |||||
/// </summary> | |||||
private System.ComponentModel.IContainer components = null; | |||||
/// <summary> | |||||
/// Clean up any resources being used. | |||||
/// </summary> | |||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||||
protected override void Dispose(bool disposing) | |||||
{ | |||||
if (disposing && (components != null)) | |||||
{ | |||||
components.Dispose(); | |||||
} | |||||
base.Dispose(disposing); | |||||
} | |||||
#region Component Designer generated code | |||||
/// <summary> | |||||
/// Required method for Designer support - do not modify | |||||
/// the contents of this method with the code editor. | |||||
/// </summary> | |||||
private void InitializeComponent() | |||||
{ | |||||
this.factorNum = new System.Windows.Forms.NumericUpDown(); | |||||
this.multiply = new System.Windows.Forms.Label(); | |||||
this.plus = new System.Windows.Forms.Label(); | |||||
this.valueLabel = new System.Windows.Forms.Label(); | |||||
((System.ComponentModel.ISupportInitialize)(this.factorNum)).BeginInit(); | |||||
this.SuspendLayout(); | |||||
// | |||||
// factorNum | |||||
// | |||||
this.factorNum.DecimalPlaces = 2; | |||||
this.factorNum.Dock = System.Windows.Forms.DockStyle.Right; | |||||
this.factorNum.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.factorNum.Increment = new decimal(new int[] { | |||||
1, | |||||
0, | |||||
0, | |||||
131072}); | |||||
this.factorNum.Location = new System.Drawing.Point(236, 0); | |||||
this.factorNum.Minimum = new decimal(new int[] { | |||||
1000, | |||||
0, | |||||
0, | |||||
-2147418112}); | |||||
this.factorNum.Name = "factorNum"; | |||||
this.factorNum.Size = new System.Drawing.Size(86, 34); | |||||
this.factorNum.TabIndex = 6; | |||||
// | |||||
// multiply | |||||
// | |||||
this.multiply.AutoSize = true; | |||||
this.multiply.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.multiply.Location = new System.Drawing.Point(202, 2); | |||||
this.multiply.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.multiply.Name = "multiply"; | |||||
this.multiply.Size = new System.Drawing.Size(26, 28); | |||||
this.multiply.TabIndex = 2; | |||||
this.multiply.Text = "×"; | |||||
// | |||||
// plus | |||||
// | |||||
this.plus.AutoSize = true; | |||||
this.plus.Font = new System.Drawing.Font("Segoe UI", 10F); | |||||
this.plus.Location = new System.Drawing.Point(5, 2); | |||||
this.plus.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.plus.Name = "plus"; | |||||
this.plus.Size = new System.Drawing.Size(26, 28); | |||||
this.plus.TabIndex = 3; | |||||
this.plus.Text = "+"; | |||||
// | |||||
// valueLabel | |||||
// | |||||
this.valueLabel.AutoSize = true; | |||||
this.valueLabel.Font = new System.Drawing.Font("Microsoft YaHei", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.valueLabel.Location = new System.Drawing.Point(39, 6); | |||||
this.valueLabel.Name = "valueLabel"; | |||||
this.valueLabel.Size = new System.Drawing.Size(118, 24); | |||||
this.valueLabel.TabIndex = 7; | |||||
this.valueLabel.Text = "PackageLoss"; | |||||
// | |||||
// CalculationControl | |||||
// | |||||
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); | |||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||||
this.Controls.Add(this.valueLabel); | |||||
this.Controls.Add(this.factorNum); | |||||
this.Controls.Add(this.multiply); | |||||
this.Controls.Add(this.plus); | |||||
this.Margin = new System.Windows.Forms.Padding(0); | |||||
this.Name = "CalculationControl"; | |||||
this.Size = new System.Drawing.Size(322, 34); | |||||
((System.ComponentModel.ISupportInitialize)(this.factorNum)).EndInit(); | |||||
this.ResumeLayout(false); | |||||
this.PerformLayout(); | |||||
} | |||||
#endregion | |||||
private System.Windows.Forms.NumericUpDown factorNum; | |||||
private System.Windows.Forms.Label multiply; | |||||
private System.Windows.Forms.Label plus; | |||||
private System.Windows.Forms.Label valueLabel; | |||||
} | |||||
} |
@@ -1,25 +0,0 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.ComponentModel; | |||||
using System.Drawing; | |||||
using System.Data; | |||||
using System.Globalization; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Windows.Forms; | |||||
namespace Shadowsocks.View | |||||
{ | |||||
public partial class CalculationControl : UserControl | |||||
{ | |||||
public CalculationControl(string text, float value) | |||||
{ | |||||
InitializeComponent(); | |||||
valueLabel.Text = text; | |||||
factorNum.Value = (decimal) value; | |||||
} | |||||
public string Value => valueLabel.Text; | |||||
public float Factor => (float) factorNum.Value; | |||||
} | |||||
} |
@@ -1,120 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<root> | |||||
<!-- | |||||
Microsoft ResX Schema | |||||
Version 2.0 | |||||
The primary goals of this format is to allow a simple XML format | |||||
that is mostly human readable. The generation and parsing of the | |||||
various data types are done through the TypeConverter classes | |||||
associated with the data types. | |||||
Example: | |||||
... ado.net/XML headers & schema ... | |||||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||||
<resheader name="version">2.0</resheader> | |||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||||
</data> | |||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||||
<comment>This is a comment</comment> | |||||
</data> | |||||
There are any number of "resheader" rows that contain simple | |||||
name/value pairs. | |||||
Each data row contains a name, and value. The row also contains a | |||||
type or mimetype. Type corresponds to a .NET class that support | |||||
text/value conversion through the TypeConverter architecture. | |||||
Classes that don't support this are serialized and stored with the | |||||
mimetype set. | |||||
The mimetype is used for serialized objects, and tells the | |||||
ResXResourceReader how to depersist the object. This is currently not | |||||
extensible. For a given mimetype the value must be set accordingly: | |||||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||||
that the ResXResourceWriter will generate, however the reader can | |||||
read any of the formats listed below. | |||||
mimetype: application/x-microsoft.net.object.binary.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.soap.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||||
value : The object must be serialized into a byte array | |||||
: using a System.ComponentModel.TypeConverter | |||||
: and then encoded with base64 encoding. | |||||
--> | |||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||||
<xsd:element name="root" msdata:IsDataSet="true"> | |||||
<xsd:complexType> | |||||
<xsd:choice maxOccurs="unbounded"> | |||||
<xsd:element name="metadata"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||||
<xsd:attribute name="type" type="xsd:string" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="assembly"> | |||||
<xsd:complexType> | |||||
<xsd:attribute name="alias" type="xsd:string" /> | |||||
<xsd:attribute name="name" type="xsd:string" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="data"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="resheader"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:choice> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:schema> | |||||
<resheader name="resmimetype"> | |||||
<value>text/microsoft-resx</value> | |||||
</resheader> | |||||
<resheader name="version"> | |||||
<value>2.0</value> | |||||
</resheader> | |||||
<resheader name="reader"> | |||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
<resheader name="writer"> | |||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
</root> |
@@ -265,7 +265,6 @@ namespace Shadowsocks.View | |||||
this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | ||||
this.SeperatorItem = new MenuItem("-"), | this.SeperatorItem = new MenuItem("-"), | ||||
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | ||||
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click), | |||||
new MenuItem("-"), | new MenuItem("-"), | ||||
CreateMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), | CreateMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), | ||||
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)), | CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)), | ||||
@@ -725,12 +724,6 @@ namespace Shadowsocks.View | |||||
Process.Start(_urlToOpen); | Process.Start(_urlToOpen); | ||||
} | } | ||||
private void StatisticsConfigItem_Click(object sender, EventArgs e) | |||||
{ | |||||
StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller); | |||||
form.Show(); | |||||
} | |||||
private void QRCodeItem_Click(object sender, EventArgs e) | private void QRCodeItem_Click(object sender, EventArgs e) | ||||
{ | { | ||||
if (serverSharingWindow == null) | if (serverSharingWindow == null) | ||||
@@ -1,537 +0,0 @@ | |||||
namespace Shadowsocks.View | |||||
{ | |||||
partial class StatisticsStrategyConfigurationForm | |||||
{ | |||||
/// <summary> | |||||
/// Required designer variable. | |||||
/// </summary> | |||||
private System.ComponentModel.IContainer components = null; | |||||
/// <summary> | |||||
/// Clean up any resources being used. | |||||
/// </summary> | |||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||||
protected override void Dispose(bool disposing) | |||||
{ | |||||
if (disposing && (components != null)) | |||||
{ | |||||
components.Dispose(); | |||||
} | |||||
base.Dispose(disposing); | |||||
} | |||||
#region Windows Form Designer generated code | |||||
/// <summary> | |||||
/// Required method for Designer support - do not modify | |||||
/// the contents of this method with the code editor. | |||||
/// </summary> | |||||
private void InitializeComponent() | |||||
{ | |||||
this.components = new System.ComponentModel.Container(); | |||||
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); | |||||
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend(); | |||||
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); | |||||
System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series(); | |||||
System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series(); | |||||
this.StatisticsChart = new System.Windows.Forms.DataVisualization.Charting.Chart(); | |||||
this.PingCheckBox = new System.Windows.Forms.CheckBox(); | |||||
this.KeepChoiceForLabel = new System.Windows.Forms.Label(); | |||||
this.MinutesLabel2 = new System.Windows.Forms.Label(); | |||||
this.chartModeSelector = new System.Windows.Forms.GroupBox(); | |||||
this.allMode = new System.Windows.Forms.RadioButton(); | |||||
this.dayMode = new System.Windows.Forms.RadioButton(); | |||||
this.splitContainer1 = new System.Windows.Forms.SplitContainer(); | |||||
this.splitContainer2 = new System.Windows.Forms.SplitContainer(); | |||||
this.CollectDataPerLabel = new System.Windows.Forms.Label(); | |||||
this.MinutesLabel1 = new System.Windows.Forms.Label(); | |||||
this.dataCollectionMinutesNum = new System.Windows.Forms.NumericUpDown(); | |||||
this.StatisticsEnabledCheckBox = new System.Windows.Forms.CheckBox(); | |||||
this.choiceKeptMinutesNum = new System.Windows.Forms.NumericUpDown(); | |||||
this.byHourOfDayCheckBox = new System.Windows.Forms.CheckBox(); | |||||
this.repeatTimesNum = new System.Windows.Forms.NumericUpDown(); | |||||
this.PackagePerPingLabel = new System.Windows.Forms.Label(); | |||||
this.splitContainer3 = new System.Windows.Forms.SplitContainer(); | |||||
this.FinalScoreLabel = new System.Windows.Forms.Label(); | |||||
this.calculationContainer = new System.Windows.Forms.FlowLayoutPanel(); | |||||
this.serverSelector = new System.Windows.Forms.ComboBox(); | |||||
this.CancelButton = new System.Windows.Forms.Button(); | |||||
this.OKButton = new System.Windows.Forms.Button(); | |||||
this.CalculatinTip = new System.Windows.Forms.ToolTip(this.components); | |||||
this.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components); | |||||
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).BeginInit(); | |||||
this.chartModeSelector.SuspendLayout(); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); | |||||
this.splitContainer1.Panel1.SuspendLayout(); | |||||
this.splitContainer1.Panel2.SuspendLayout(); | |||||
this.splitContainer1.SuspendLayout(); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); | |||||
this.splitContainer2.Panel1.SuspendLayout(); | |||||
this.splitContainer2.Panel2.SuspendLayout(); | |||||
this.splitContainer2.SuspendLayout(); | |||||
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).BeginInit(); | |||||
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).BeginInit(); | |||||
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).BeginInit(); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); | |||||
this.splitContainer3.Panel1.SuspendLayout(); | |||||
this.splitContainer3.Panel2.SuspendLayout(); | |||||
this.splitContainer3.SuspendLayout(); | |||||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit(); | |||||
this.SuspendLayout(); | |||||
// | |||||
// StatisticsChart | |||||
// | |||||
this.StatisticsChart.BackColor = System.Drawing.Color.Transparent; | |||||
chartArea1.AxisX.MajorGrid.Enabled = false; | |||||
chartArea1.AxisY.MajorGrid.Enabled = false; | |||||
chartArea1.AxisY2.Enabled = System.Windows.Forms.DataVisualization.Charting.AxisEnabled.False; | |||||
chartArea1.AxisY2.MajorGrid.Enabled = false; | |||||
chartArea1.BackColor = System.Drawing.Color.Transparent; | |||||
chartArea1.Name = "DataArea"; | |||||
this.StatisticsChart.ChartAreas.Add(chartArea1); | |||||
this.StatisticsChart.Dock = System.Windows.Forms.DockStyle.Fill; | |||||
legend1.BackColor = System.Drawing.Color.Transparent; | |||||
legend1.Name = "ChartLegend"; | |||||
this.StatisticsChart.Legends.Add(legend1); | |||||
this.StatisticsChart.Location = new System.Drawing.Point(0, 0); | |||||
this.StatisticsChart.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.StatisticsChart.Name = "StatisticsChart"; | |||||
this.StatisticsChart.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.Pastel; | |||||
series1.ChartArea = "DataArea"; | |||||
series1.Color = System.Drawing.Color.DarkGray; | |||||
series1.Legend = "ChartLegend"; | |||||
series1.Name = "Speed"; | |||||
series1.ToolTip = "#VALX\\nMax inbound speed\\n#VAL KiB/s"; | |||||
series1.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time; | |||||
series2.ChartArea = "DataArea"; | |||||
series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Bubble; | |||||
series2.Color = System.Drawing.Color.Crimson; | |||||
series2.CustomProperties = "EmptyPointValue=Zero"; | |||||
series2.Legend = "ChartLegend"; | |||||
series2.Name = "Package Loss"; | |||||
series2.ToolTip = "#VALX\\n#VAL%"; | |||||
series2.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time; | |||||
series2.YAxisType = System.Windows.Forms.DataVisualization.Charting.AxisType.Secondary; | |||||
series2.YValuesPerPoint = 2; | |||||
series3.BorderWidth = 5; | |||||
series3.ChartArea = "DataArea"; | |||||
series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line; | |||||
series3.Color = System.Drawing.Color.DodgerBlue; | |||||
series3.Legend = "ChartLegend"; | |||||
series3.MarkerSize = 10; | |||||
series3.MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle; | |||||
series3.Name = "Ping"; | |||||
series3.ToolTip = "#VALX\\n#VAL ms"; | |||||
series3.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time; | |||||
this.StatisticsChart.Series.Add(series1); | |||||
this.StatisticsChart.Series.Add(series2); | |||||
this.StatisticsChart.Series.Add(series3); | |||||
this.StatisticsChart.Size = new System.Drawing.Size(982, 435); | |||||
this.StatisticsChart.TabIndex = 2; | |||||
// | |||||
// PingCheckBox | |||||
// | |||||
this.PingCheckBox.AutoSize = true; | |||||
this.PingCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "Ping", true)); | |||||
this.PingCheckBox.Location = new System.Drawing.Point(13, 54); | |||||
this.PingCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.PingCheckBox.Name = "PingCheckBox"; | |||||
this.PingCheckBox.Size = new System.Drawing.Size(107, 27); | |||||
this.PingCheckBox.TabIndex = 5; | |||||
this.PingCheckBox.Text = "Ping Test"; | |||||
this.PingCheckBox.UseVisualStyleBackColor = true; | |||||
this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged); | |||||
// | |||||
// KeepChoiceForLabel | |||||
// | |||||
this.KeepChoiceForLabel.AutoSize = true; | |||||
this.KeepChoiceForLabel.Location = new System.Drawing.Point(9, 206); | |||||
this.KeepChoiceForLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.KeepChoiceForLabel.Name = "KeepChoiceForLabel"; | |||||
this.KeepChoiceForLabel.Size = new System.Drawing.Size(139, 23); | |||||
this.KeepChoiceForLabel.TabIndex = 8; | |||||
this.KeepChoiceForLabel.Text = "Keep choice for"; | |||||
// | |||||
// MinutesLabel2 | |||||
// | |||||
this.MinutesLabel2.AutoSize = true; | |||||
this.MinutesLabel2.Location = new System.Drawing.Point(286, 206); | |||||
this.MinutesLabel2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.MinutesLabel2.Name = "MinutesLabel2"; | |||||
this.MinutesLabel2.Size = new System.Drawing.Size(75, 23); | |||||
this.MinutesLabel2.TabIndex = 9; | |||||
this.MinutesLabel2.Text = "minutes"; | |||||
// | |||||
// chartModeSelector | |||||
// | |||||
this.chartModeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||||
this.chartModeSelector.Controls.Add(this.allMode); | |||||
this.chartModeSelector.Controls.Add(this.dayMode); | |||||
this.chartModeSelector.Location = new System.Drawing.Point(733, 182); | |||||
this.chartModeSelector.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.chartModeSelector.Name = "chartModeSelector"; | |||||
this.chartModeSelector.Padding = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.chartModeSelector.Size = new System.Drawing.Size(234, 103); | |||||
this.chartModeSelector.TabIndex = 3; | |||||
this.chartModeSelector.TabStop = false; | |||||
this.chartModeSelector.Text = "Chart Mode"; | |||||
// | |||||
// allMode | |||||
// | |||||
this.allMode.AutoSize = true; | |||||
this.allMode.Location = new System.Drawing.Point(11, 61); | |||||
this.allMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.allMode.Name = "allMode"; | |||||
this.allMode.Size = new System.Drawing.Size(50, 27); | |||||
this.allMode.TabIndex = 1; | |||||
this.allMode.Text = "all"; | |||||
this.allMode.UseVisualStyleBackColor = true; | |||||
this.allMode.CheckedChanged += new System.EventHandler(this.allMode_CheckedChanged); | |||||
// | |||||
// dayMode | |||||
// | |||||
this.dayMode.AutoSize = true; | |||||
this.dayMode.Checked = true; | |||||
this.dayMode.Location = new System.Drawing.Point(11, 29); | |||||
this.dayMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.dayMode.Name = "dayMode"; | |||||
this.dayMode.Size = new System.Drawing.Size(61, 27); | |||||
this.dayMode.TabIndex = 0; | |||||
this.dayMode.TabStop = true; | |||||
this.dayMode.Text = "24h"; | |||||
this.dayMode.UseVisualStyleBackColor = true; | |||||
this.dayMode.CheckedChanged += new System.EventHandler(this.dayMode_CheckedChanged); | |||||
// | |||||
// splitContainer1 | |||||
// | |||||
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; | |||||
this.splitContainer1.IsSplitterFixed = true; | |||||
this.splitContainer1.Location = new System.Drawing.Point(0, 0); | |||||
this.splitContainer1.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.splitContainer1.Name = "splitContainer1"; | |||||
this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; | |||||
// | |||||
// splitContainer1.Panel1 | |||||
// | |||||
this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); | |||||
// | |||||
// splitContainer1.Panel2 | |||||
// | |||||
this.splitContainer1.Panel2.Controls.Add(this.serverSelector); | |||||
this.splitContainer1.Panel2.Controls.Add(this.CancelButton); | |||||
this.splitContainer1.Panel2.Controls.Add(this.OKButton); | |||||
this.splitContainer1.Panel2.Controls.Add(this.chartModeSelector); | |||||
this.splitContainer1.Panel2.Controls.Add(this.StatisticsChart); | |||||
this.splitContainer1.Size = new System.Drawing.Size(982, 753); | |||||
this.splitContainer1.SplitterDistance = 308; | |||||
this.splitContainer1.SplitterWidth = 10; | |||||
this.splitContainer1.TabIndex = 12; | |||||
// | |||||
// splitContainer2 | |||||
// | |||||
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; | |||||
this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; | |||||
this.splitContainer2.IsSplitterFixed = true; | |||||
this.splitContainer2.Location = new System.Drawing.Point(0, 0); | |||||
this.splitContainer2.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.splitContainer2.Name = "splitContainer2"; | |||||
// | |||||
// splitContainer2.Panel1 | |||||
// | |||||
this.splitContainer2.Panel1.Controls.Add(this.CollectDataPerLabel); | |||||
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel1); | |||||
this.splitContainer2.Panel1.Controls.Add(this.dataCollectionMinutesNum); | |||||
this.splitContainer2.Panel1.Controls.Add(this.StatisticsEnabledCheckBox); | |||||
this.splitContainer2.Panel1.Controls.Add(this.choiceKeptMinutesNum); | |||||
this.splitContainer2.Panel1.Controls.Add(this.byHourOfDayCheckBox); | |||||
this.splitContainer2.Panel1.Controls.Add(this.repeatTimesNum); | |||||
this.splitContainer2.Panel1.Controls.Add(this.PackagePerPingLabel); | |||||
this.splitContainer2.Panel1.Controls.Add(this.KeepChoiceForLabel); | |||||
this.splitContainer2.Panel1.Controls.Add(this.PingCheckBox); | |||||
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel2); | |||||
// | |||||
// splitContainer2.Panel2 | |||||
// | |||||
this.splitContainer2.Panel2.Controls.Add(this.splitContainer3); | |||||
this.splitContainer2.Size = new System.Drawing.Size(982, 308); | |||||
this.splitContainer2.SplitterDistance = 384; | |||||
this.splitContainer2.SplitterWidth = 5; | |||||
this.splitContainer2.TabIndex = 7; | |||||
// | |||||
// CollectDataPerLabel | |||||
// | |||||
this.CollectDataPerLabel.AutoSize = true; | |||||
this.CollectDataPerLabel.Location = new System.Drawing.Point(9, 164); | |||||
this.CollectDataPerLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.CollectDataPerLabel.Name = "CollectDataPerLabel"; | |||||
this.CollectDataPerLabel.Size = new System.Drawing.Size(139, 23); | |||||
this.CollectDataPerLabel.TabIndex = 20; | |||||
this.CollectDataPerLabel.Text = "Collect data per"; | |||||
// | |||||
// MinutesLabel1 | |||||
// | |||||
this.MinutesLabel1.AutoSize = true; | |||||
this.MinutesLabel1.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.MinutesLabel1.Location = new System.Drawing.Point(286, 165); | |||||
this.MinutesLabel1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.MinutesLabel1.Name = "MinutesLabel1"; | |||||
this.MinutesLabel1.Size = new System.Drawing.Size(75, 23); | |||||
this.MinutesLabel1.TabIndex = 19; | |||||
this.MinutesLabel1.Text = "minutes"; | |||||
// | |||||
// dataCollectionMinutesNum | |||||
// | |||||
this.dataCollectionMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "DataCollectionMinutes", true)); | |||||
this.dataCollectionMinutesNum.Increment = new decimal(new int[] { | |||||
10, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.dataCollectionMinutesNum.Location = new System.Drawing.Point(177, 162); | |||||
this.dataCollectionMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||||
this.dataCollectionMinutesNum.Maximum = new decimal(new int[] { | |||||
120, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.dataCollectionMinutesNum.Minimum = new decimal(new int[] { | |||||
1, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.dataCollectionMinutesNum.Name = "dataCollectionMinutesNum"; | |||||
this.dataCollectionMinutesNum.Size = new System.Drawing.Size(100, 29); | |||||
this.dataCollectionMinutesNum.TabIndex = 18; | |||||
this.dataCollectionMinutesNum.Value = new decimal(new int[] { | |||||
10, | |||||
0, | |||||
0, | |||||
0}); | |||||
// | |||||
// StatisticsEnabledCheckBox | |||||
// | |||||
this.StatisticsEnabledCheckBox.AutoSize = true; | |||||
this.StatisticsEnabledCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "StatisticsEnabled", true)); | |||||
this.StatisticsEnabledCheckBox.Location = new System.Drawing.Point(13, 12); | |||||
this.StatisticsEnabledCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.StatisticsEnabledCheckBox.Name = "StatisticsEnabledCheckBox"; | |||||
this.StatisticsEnabledCheckBox.Size = new System.Drawing.Size(163, 27); | |||||
this.StatisticsEnabledCheckBox.TabIndex = 17; | |||||
this.StatisticsEnabledCheckBox.Text = "Enable Statistics"; | |||||
this.StatisticsEnabledCheckBox.UseVisualStyleBackColor = true; | |||||
// | |||||
// choiceKeptMinutesNum | |||||
// | |||||
this.choiceKeptMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "ChoiceKeptMinutes", true)); | |||||
this.choiceKeptMinutesNum.Increment = new decimal(new int[] { | |||||
10, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.choiceKeptMinutesNum.Location = new System.Drawing.Point(177, 204); | |||||
this.choiceKeptMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||||
this.choiceKeptMinutesNum.Maximum = new decimal(new int[] { | |||||
120, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.choiceKeptMinutesNum.Minimum = new decimal(new int[] { | |||||
1, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.choiceKeptMinutesNum.Name = "choiceKeptMinutesNum"; | |||||
this.choiceKeptMinutesNum.Size = new System.Drawing.Size(100, 29); | |||||
this.choiceKeptMinutesNum.TabIndex = 16; | |||||
this.choiceKeptMinutesNum.Value = new decimal(new int[] { | |||||
10, | |||||
0, | |||||
0, | |||||
0}); | |||||
// | |||||
// byHourOfDayCheckBox | |||||
// | |||||
this.byHourOfDayCheckBox.AutoSize = true; | |||||
this.byHourOfDayCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "ByHourOfDay", true)); | |||||
this.byHourOfDayCheckBox.Location = new System.Drawing.Point(13, 127); | |||||
this.byHourOfDayCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.byHourOfDayCheckBox.Name = "byHourOfDayCheckBox"; | |||||
this.byHourOfDayCheckBox.Size = new System.Drawing.Size(150, 27); | |||||
this.byHourOfDayCheckBox.TabIndex = 15; | |||||
this.byHourOfDayCheckBox.Text = "By hour of day"; | |||||
this.byHourOfDayCheckBox.UseVisualStyleBackColor = true; | |||||
// | |||||
// repeatTimesNum | |||||
// | |||||
this.repeatTimesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "RepeatTimesNum", true)); | |||||
this.repeatTimesNum.Location = new System.Drawing.Point(34, 84); | |||||
this.repeatTimesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||||
this.repeatTimesNum.Maximum = new decimal(new int[] { | |||||
10, | |||||
0, | |||||
0, | |||||
0}); | |||||
this.repeatTimesNum.Name = "repeatTimesNum"; | |||||
this.repeatTimesNum.Size = new System.Drawing.Size(99, 29); | |||||
this.repeatTimesNum.TabIndex = 14; | |||||
this.repeatTimesNum.Value = new decimal(new int[] { | |||||
4, | |||||
0, | |||||
0, | |||||
0}); | |||||
// | |||||
// PackagePerPingLabel | |||||
// | |||||
this.PackagePerPingLabel.AutoSize = true; | |||||
this.PackagePerPingLabel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.PackagePerPingLabel.Location = new System.Drawing.Point(139, 86); | |||||
this.PackagePerPingLabel.Name = "PackagePerPingLabel"; | |||||
this.PackagePerPingLabel.Size = new System.Drawing.Size(172, 23); | |||||
this.PackagePerPingLabel.TabIndex = 13; | |||||
this.PackagePerPingLabel.Text = "packages everytime"; | |||||
// | |||||
// splitContainer3 | |||||
// | |||||
this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; | |||||
this.splitContainer3.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; | |||||
this.splitContainer3.IsSplitterFixed = true; | |||||
this.splitContainer3.Location = new System.Drawing.Point(0, 0); | |||||
this.splitContainer3.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.splitContainer3.Name = "splitContainer3"; | |||||
this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; | |||||
// | |||||
// splitContainer3.Panel1 | |||||
// | |||||
this.splitContainer3.Panel1.Controls.Add(this.FinalScoreLabel); | |||||
// | |||||
// splitContainer3.Panel2 | |||||
// | |||||
this.splitContainer3.Panel2.Controls.Add(this.calculationContainer); | |||||
this.splitContainer3.Size = new System.Drawing.Size(593, 308); | |||||
this.splitContainer3.SplitterDistance = 42; | |||||
this.splitContainer3.SplitterWidth = 1; | |||||
this.splitContainer3.TabIndex = 6; | |||||
// | |||||
// FinalScoreLabel | |||||
// | |||||
this.FinalScoreLabel.AutoSize = true; | |||||
this.FinalScoreLabel.Location = new System.Drawing.Point(5, 9); | |||||
this.FinalScoreLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||||
this.FinalScoreLabel.Name = "FinalScoreLabel"; | |||||
this.FinalScoreLabel.Size = new System.Drawing.Size(103, 23); | |||||
this.FinalScoreLabel.TabIndex = 0; | |||||
this.FinalScoreLabel.Text = "Final Score:"; | |||||
this.CalculatinTip.SetToolTip(this.FinalScoreLabel, "(The server with the highest score would be choosen)"); | |||||
// | |||||
// calculationContainer | |||||
// | |||||
this.calculationContainer.AutoScroll = true; | |||||
this.calculationContainer.Dock = System.Windows.Forms.DockStyle.Fill; | |||||
this.calculationContainer.Location = new System.Drawing.Point(0, 0); | |||||
this.calculationContainer.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.calculationContainer.Name = "calculationContainer"; | |||||
this.calculationContainer.Size = new System.Drawing.Size(593, 265); | |||||
this.calculationContainer.TabIndex = 1; | |||||
// | |||||
// serverSelector | |||||
// | |||||
this.serverSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||||
this.serverSelector.FormattingEnabled = true; | |||||
this.serverSelector.Location = new System.Drawing.Point(733, 145); | |||||
this.serverSelector.Name = "serverSelector"; | |||||
this.serverSelector.Size = new System.Drawing.Size(233, 31); | |||||
this.serverSelector.TabIndex = 6; | |||||
this.serverSelector.SelectionChangeCommitted += new System.EventHandler(this.serverSelector_SelectionChangeCommitted); | |||||
// | |||||
// CancelButton | |||||
// | |||||
this.CancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||||
this.CancelButton.Location = new System.Drawing.Point(865, 364); | |||||
this.CancelButton.Name = "CancelButton"; | |||||
this.CancelButton.Size = new System.Drawing.Size(101, 41); | |||||
this.CancelButton.TabIndex = 5; | |||||
this.CancelButton.Text = "Cancel"; | |||||
this.CancelButton.UseVisualStyleBackColor = true; | |||||
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click); | |||||
// | |||||
// OKButton | |||||
// | |||||
this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||||
this.OKButton.Location = new System.Drawing.Point(758, 364); | |||||
this.OKButton.Name = "OKButton"; | |||||
this.OKButton.Size = new System.Drawing.Size(101, 41); | |||||
this.OKButton.TabIndex = 4; | |||||
this.OKButton.Text = "OK"; | |||||
this.OKButton.UseVisualStyleBackColor = true; | |||||
this.OKButton.Click += new System.EventHandler(this.OKButton_Click); | |||||
// | |||||
// bindingConfiguration | |||||
// | |||||
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration); | |||||
// | |||||
// StatisticsStrategyConfigurationForm | |||||
// | |||||
this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 23F); | |||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||||
this.AutoSize = true; | |||||
this.ClientSize = new System.Drawing.Size(982, 753); | |||||
this.Controls.Add(this.splitContainer1); | |||||
this.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||||
this.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||||
this.MinimumSize = new System.Drawing.Size(1000, 800); | |||||
this.Name = "StatisticsStrategyConfigurationForm"; | |||||
this.Text = "StatisticsStrategyConfigurationForm"; | |||||
this.Text = "Statistics configuration"; | |||||
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit(); | |||||
this.chartModeSelector.ResumeLayout(false); | |||||
this.chartModeSelector.PerformLayout(); | |||||
this.splitContainer1.Panel1.ResumeLayout(false); | |||||
this.splitContainer1.Panel2.ResumeLayout(false); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); | |||||
this.splitContainer1.ResumeLayout(false); | |||||
this.splitContainer2.Panel1.ResumeLayout(false); | |||||
this.splitContainer2.Panel1.PerformLayout(); | |||||
this.splitContainer2.Panel2.ResumeLayout(false); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); | |||||
this.splitContainer2.ResumeLayout(false); | |||||
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).EndInit(); | |||||
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).EndInit(); | |||||
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).EndInit(); | |||||
this.splitContainer3.Panel1.ResumeLayout(false); | |||||
this.splitContainer3.Panel1.PerformLayout(); | |||||
this.splitContainer3.Panel2.ResumeLayout(false); | |||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); | |||||
this.splitContainer3.ResumeLayout(false); | |||||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit(); | |||||
this.ResumeLayout(false); | |||||
} | |||||
#endregion | |||||
private System.Windows.Forms.DataVisualization.Charting.Chart StatisticsChart; | |||||
private System.Windows.Forms.CheckBox PingCheckBox; | |||||
private System.Windows.Forms.Label KeepChoiceForLabel; | |||||
private System.Windows.Forms.Label MinutesLabel2; | |||||
private System.Windows.Forms.GroupBox chartModeSelector; | |||||
private System.Windows.Forms.RadioButton allMode; | |||||
private System.Windows.Forms.RadioButton dayMode; | |||||
private System.Windows.Forms.SplitContainer splitContainer1; | |||||
private System.Windows.Forms.Label FinalScoreLabel; | |||||
private System.Windows.Forms.SplitContainer splitContainer2; | |||||
private System.Windows.Forms.FlowLayoutPanel calculationContainer; | |||||
private System.Windows.Forms.SplitContainer splitContainer3; | |||||
private System.Windows.Forms.NumericUpDown repeatTimesNum; | |||||
private System.Windows.Forms.Label PackagePerPingLabel; | |||||
private System.Windows.Forms.CheckBox byHourOfDayCheckBox; | |||||
private System.Windows.Forms.NumericUpDown choiceKeptMinutesNum; | |||||
private System.Windows.Forms.CheckBox StatisticsEnabledCheckBox; | |||||
private System.Windows.Forms.Label CollectDataPerLabel; | |||||
private System.Windows.Forms.Label MinutesLabel1; | |||||
private System.Windows.Forms.NumericUpDown dataCollectionMinutesNum; | |||||
private System.Windows.Forms.BindingSource bindingConfiguration; | |||||
private new System.Windows.Forms.Button CancelButton; | |||||
private System.Windows.Forms.Button OKButton; | |||||
private System.Windows.Forms.ComboBox serverSelector; | |||||
private System.Windows.Forms.ToolTip CalculatinTip; | |||||
} | |||||
} |
@@ -1,170 +0,0 @@ | |||||
using System; | |||||
using System.Drawing; | |||||
using System.Collections.Generic; | |||||
using System.Data; | |||||
using System.Linq; | |||||
using System.Windows.Forms; | |||||
using System.Windows.Forms.DataVisualization.Charting; | |||||
using Shadowsocks.Controller; | |||||
using Shadowsocks.Model; | |||||
using Shadowsocks.Properties; | |||||
namespace Shadowsocks.View | |||||
{ | |||||
public partial class StatisticsStrategyConfigurationForm : Form | |||||
{ | |||||
private readonly ShadowsocksController _controller; | |||||
private StatisticsStrategyConfiguration _configuration; | |||||
private readonly DataTable _dataTable = new DataTable(); | |||||
private List<string> _servers; | |||||
private readonly Series _speedSeries; | |||||
private readonly Series _packageLossSeries; | |||||
private readonly Series _pingSeries; | |||||
public StatisticsStrategyConfigurationForm(ShadowsocksController controller) | |||||
{ | |||||
if (controller == null) return; | |||||
InitializeComponent(); | |||||
Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); | |||||
_speedSeries = StatisticsChart.Series["Speed"]; | |||||
_packageLossSeries = StatisticsChart.Series["Package Loss"]; | |||||
_pingSeries = StatisticsChart.Series["Ping"]; | |||||
_controller = controller; | |||||
_controller.ConfigChanged += (sender, args) => LoadConfiguration(); | |||||
UpdateTexts(); | |||||
LoadConfiguration(); | |||||
Load += (sender, args) => InitData(); | |||||
} | |||||
private void UpdateTexts() | |||||
{ | |||||
I18N.TranslateForm(this); | |||||
foreach (var item in StatisticsChart.Series) | |||||
{ | |||||
item.Name = I18N.GetString(item.Name); | |||||
} | |||||
} | |||||
private void LoadConfiguration() | |||||
{ | |||||
var configs = _controller.GetCurrentConfiguration().configs; | |||||
_servers = configs.Select(server => server.Identifier()).ToList(); | |||||
_configuration = _controller.StatisticsConfiguration | |||||
?? new StatisticsStrategyConfiguration(); | |||||
if (_configuration.Calculations == null) | |||||
{ | |||||
_configuration = new StatisticsStrategyConfiguration(); | |||||
} | |||||
} | |||||
private void InitData() | |||||
{ | |||||
bindingConfiguration.Add(_configuration); | |||||
foreach (var kv in _configuration.Calculations) | |||||
{ | |||||
var calculation = new CalculationControl(I18N.GetString(kv.Key), kv.Value); | |||||
calculationContainer.Controls.Add(calculation); | |||||
} | |||||
serverSelector.DataSource = _servers; | |||||
_dataTable.Columns.Add("Timestamp", typeof(DateTime)); | |||||
_dataTable.Columns.Add("Speed", typeof(int)); | |||||
_speedSeries.XValueMember = "Timestamp"; | |||||
_speedSeries.YValueMembers = "Speed"; | |||||
// might be empty | |||||
_dataTable.Columns.Add("Package Loss", typeof(int)); | |||||
_dataTable.Columns.Add("Ping", typeof(int)); | |||||
_packageLossSeries.XValueMember = "Timestamp"; | |||||
_packageLossSeries.YValueMembers = "Package Loss"; | |||||
_pingSeries.XValueMember = "Timestamp"; | |||||
_pingSeries.YValueMembers = "Ping"; | |||||
StatisticsChart.DataSource = _dataTable; | |||||
LoadChartData(); | |||||
StatisticsChart.DataBind(); | |||||
} | |||||
private void CancelButton_Click(object sender, EventArgs e) | |||||
{ | |||||
Close(); | |||||
} | |||||
private void OKButton_Click(object sender, EventArgs e) | |||||
{ | |||||
foreach (CalculationControl calculation in calculationContainer.Controls) | |||||
{ | |||||
_configuration.Calculations[calculation.Value] = calculation.Factor; | |||||
} | |||||
_controller?.SaveStrategyConfigurations(_configuration); | |||||
_controller?.UpdateStatisticsConfiguration(StatisticsEnabledCheckBox.Checked); | |||||
Close(); | |||||
} | |||||
private void LoadChartData() | |||||
{ | |||||
var serverName = _servers[serverSelector.SelectedIndex]; | |||||
_dataTable.Rows.Clear(); | |||||
//return directly when no data is usable | |||||
if (_controller.availabilityStatistics?.FilteredStatistics == null) return; | |||||
List<StatisticsRecord> statistics; | |||||
if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return; | |||||
IEnumerable<IGrouping<int, StatisticsRecord>> dataGroups; | |||||
if (allMode.Checked) | |||||
{ | |||||
_pingSeries.XValueType = ChartValueType.DateTime; | |||||
_packageLossSeries.XValueType = ChartValueType.DateTime; | |||||
_speedSeries.XValueType = ChartValueType.DateTime; | |||||
dataGroups = statistics.GroupBy(data => data.Timestamp.DayOfYear); | |||||
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "g"; | |||||
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "g"; | |||||
} | |||||
else | |||||
{ | |||||
_pingSeries.XValueType = ChartValueType.Time; | |||||
_packageLossSeries.XValueType = ChartValueType.Time; | |||||
_speedSeries.XValueType = ChartValueType.Time; | |||||
dataGroups = statistics.GroupBy(data => data.Timestamp.Hour); | |||||
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "HH:00"; | |||||
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "HH:00"; | |||||
} | |||||
var finalData = from dataGroup in dataGroups | |||||
orderby dataGroup.Key | |||||
select new | |||||
{ | |||||
dataGroup.First().Timestamp, | |||||
Speed = dataGroup.Max(data => data.MaxInboundSpeed) ?? 0, | |||||
Ping = (int)(dataGroup.Average(data => data.AverageResponse) ?? 0), | |||||
PackageLossPercentage = (int)(dataGroup.Average(data => data.PackageLoss) ?? 0) * 100 | |||||
}; | |||||
foreach (var data in finalData.Where(data => data.Speed != 0 || data.PackageLossPercentage != 0 || data.Ping != 0)) | |||||
{ | |||||
_dataTable.Rows.Add(data.Timestamp, data.Speed, data.PackageLossPercentage, data.Ping); | |||||
} | |||||
StatisticsChart.DataBind(); | |||||
} | |||||
private void serverSelector_SelectionChangeCommitted(object sender, EventArgs e) | |||||
{ | |||||
LoadChartData(); | |||||
} | |||||
private void dayMode_CheckedChanged(object sender, EventArgs e) | |||||
{ | |||||
LoadChartData(); | |||||
} | |||||
private void allMode_CheckedChanged(object sender, EventArgs e) | |||||
{ | |||||
LoadChartData(); | |||||
} | |||||
private void PingCheckBox_CheckedChanged(object sender, EventArgs e) | |||||
{ | |||||
repeatTimesNum.ReadOnly = !PingCheckBox.Checked; | |||||
} | |||||
} | |||||
} |
@@ -1,129 +0,0 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<root> | |||||
<!-- | |||||
Microsoft ResX Schema | |||||
Version 2.0 | |||||
The primary goals of this format is to allow a simple XML format | |||||
that is mostly human readable. The generation and parsing of the | |||||
various data types are done through the TypeConverter classes | |||||
associated with the data types. | |||||
Example: | |||||
... ado.net/XML headers & schema ... | |||||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||||
<resheader name="version">2.0</resheader> | |||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||||
</data> | |||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||||
<comment>This is a comment</comment> | |||||
</data> | |||||
There are any number of "resheader" rows that contain simple | |||||
name/value pairs. | |||||
Each data row contains a name, and value. The row also contains a | |||||
type or mimetype. Type corresponds to a .NET class that support | |||||
text/value conversion through the TypeConverter architecture. | |||||
Classes that don't support this are serialized and stored with the | |||||
mimetype set. | |||||
The mimetype is used for serialized objects, and tells the | |||||
ResXResourceReader how to depersist the object. This is currently not | |||||
extensible. For a given mimetype the value must be set accordingly: | |||||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||||
that the ResXResourceWriter will generate, however the reader can | |||||
read any of the formats listed below. | |||||
mimetype: application/x-microsoft.net.object.binary.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.soap.base64 | |||||
value : The object must be serialized with | |||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||||
: and then encoded with base64 encoding. | |||||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||||
value : The object must be serialized into a byte array | |||||
: using a System.ComponentModel.TypeConverter | |||||
: and then encoded with base64 encoding. | |||||
--> | |||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||||
<xsd:element name="root" msdata:IsDataSet="true"> | |||||
<xsd:complexType> | |||||
<xsd:choice maxOccurs="unbounded"> | |||||
<xsd:element name="metadata"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||||
<xsd:attribute name="type" type="xsd:string" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="assembly"> | |||||
<xsd:complexType> | |||||
<xsd:attribute name="alias" type="xsd:string" /> | |||||
<xsd:attribute name="name" type="xsd:string" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="data"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||||
<xsd:attribute ref="xml:space" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
<xsd:element name="resheader"> | |||||
<xsd:complexType> | |||||
<xsd:sequence> | |||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||||
</xsd:sequence> | |||||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:choice> | |||||
</xsd:complexType> | |||||
</xsd:element> | |||||
</xsd:schema> | |||||
<resheader name="resmimetype"> | |||||
<value>text/microsoft-resx</value> | |||||
</resheader> | |||||
<resheader name="version"> | |||||
<value>2.0</value> | |||||
</resheader> | |||||
<resheader name="reader"> | |||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
<resheader name="writer"> | |||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||||
</resheader> | |||||
<metadata name="bindingConfiguration.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||||
<value>4, 5</value> | |||||
</metadata> | |||||
<metadata name="CalculatinTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||||
<value>238, 6</value> | |||||
</metadata> | |||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||||
<value>191</value> | |||||
</metadata> | |||||
</root> |
@@ -268,9 +268,7 @@ | |||||
<Compile Include="Proxy\DirectConnect.cs" /> | <Compile Include="Proxy\DirectConnect.cs" /> | ||||
<Compile Include="Proxy\HttpProxy.cs" /> | <Compile Include="Proxy\HttpProxy.cs" /> | ||||
<Compile Include="Proxy\IProxy.cs" /> | <Compile Include="Proxy\IProxy.cs" /> | ||||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | |||||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | <Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | ||||
<Compile Include="Controller\Strategy\StatisticsStrategy.cs" /> | |||||
<Compile Include="Controller\System\AutoStartup.cs" /> | <Compile Include="Controller\System\AutoStartup.cs" /> | ||||
<Compile Include="Controller\FileManager.cs" /> | <Compile Include="Controller\FileManager.cs" /> | ||||
<Compile Include="Controller\I18N.cs" /> | <Compile Include="Controller\I18N.cs" /> | ||||
@@ -282,8 +280,6 @@ | |||||
<Compile Include="Model\LogViewerConfig.cs" /> | <Compile Include="Model\LogViewerConfig.cs" /> | ||||
<Compile Include="Model\Server.cs" /> | <Compile Include="Model\Server.cs" /> | ||||
<Compile Include="Model\Configuration.cs" /> | <Compile Include="Model\Configuration.cs" /> | ||||
<Compile Include="Model\StatisticsRecord.cs" /> | |||||
<Compile Include="Model\StatisticsStrategyConfiguration.cs" /> | |||||
<Compile Include="Controller\Strategy\BalancingStrategy.cs" /> | <Compile Include="Controller\Strategy\BalancingStrategy.cs" /> | ||||
<Compile Include="Controller\Strategy\StrategyManager.cs" /> | <Compile Include="Controller\Strategy\StrategyManager.cs" /> | ||||
<Compile Include="Controller\Strategy\IStrategy.cs" /> | <Compile Include="Controller\Strategy\IStrategy.cs" /> | ||||
@@ -329,12 +325,6 @@ | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
<Compile Include="Controller\ShadowsocksController.cs" /> | <Compile Include="Controller\ShadowsocksController.cs" /> | ||||
<Compile Include="Controller\System\SystemProxy.cs" /> | <Compile Include="Controller\System\SystemProxy.cs" /> | ||||
<Compile Include="View\CalculationControl.cs"> | |||||
<SubType>UserControl</SubType> | |||||
</Compile> | |||||
<Compile Include="View\CalculationControl.Designer.cs"> | |||||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||||
</Compile> | |||||
<Compile Include="View\LogForm.cs"> | <Compile Include="View\LogForm.cs"> | ||||
<SubType>Form</SubType> | <SubType>Form</SubType> | ||||
</Compile> | </Compile> | ||||
@@ -348,12 +338,6 @@ | |||||
<Compile Include="Views\ServerSharingView.xaml.cs"> | <Compile Include="Views\ServerSharingView.xaml.cs"> | ||||
<DependentUpon>ServerSharingView.xaml</DependentUpon> | <DependentUpon>ServerSharingView.xaml</DependentUpon> | ||||
</Compile> | </Compile> | ||||
<Compile Include="View\StatisticsStrategyConfigurationForm.cs"> | |||||
<SubType>Form</SubType> | |||||
</Compile> | |||||
<Compile Include="View\StatisticsStrategyConfigurationForm.Designer.cs"> | |||||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||||
</Compile> | |||||
<EmbeddedResource Include="Localization\Strings.fr.resx" /> | <EmbeddedResource Include="Localization\Strings.fr.resx" /> | ||||
<EmbeddedResource Include="Localization\Strings.ja.resx" /> | <EmbeddedResource Include="Localization\Strings.ja.resx" /> | ||||
<EmbeddedResource Include="Localization\Strings.ko.resx" /> | <EmbeddedResource Include="Localization\Strings.ko.resx" /> | ||||
@@ -373,15 +357,9 @@ | |||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | <LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Include="View\CalculationControl.resx"> | |||||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||||
</EmbeddedResource> | |||||
<EmbeddedResource Include="View\LogForm.resx"> | <EmbeddedResource Include="View\LogForm.resx"> | ||||
<DependentUpon>LogForm.cs</DependentUpon> | <DependentUpon>LogForm.cs</DependentUpon> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx"> | |||||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||||
</EmbeddedResource> | |||||
<None Include="app.config" /> | <None Include="app.config" /> | ||||
<None Include="app.manifest"> | <None Include="app.manifest"> | ||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
@@ -397,7 +375,6 @@ | |||||
<None Include="Data\sysproxy.exe.gz" /> | <None Include="Data\sysproxy.exe.gz" /> | ||||
<None Include="Data\sysproxy64.exe.gz" /> | <None Include="Data\sysproxy64.exe.gz" /> | ||||
<None Include="packages.config" /> | <None Include="packages.config" /> | ||||
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" /> | |||||
<None Include="Properties\Settings.settings"> | <None Include="Properties\Settings.settings"> | ||||
<Generator>SettingsSingleFileGenerator</Generator> | <Generator>SettingsSingleFileGenerator</Generator> | ||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput> | <LastGenOutput>Settings.Designer.cs</LastGenOutput> | ||||