@@ -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 readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer; | |||
public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance; | |||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } | |||
private long _inboundCounter = 0; | |||
private long _outboundCounter = 0; | |||
public long InboundCounter => Interlocked.Read(ref _inboundCounter); | |||
@@ -98,7 +95,6 @@ namespace Shadowsocks.Controller | |||
httpClient = new HttpClient(); | |||
_config = Configuration.Load(); | |||
Configuration.Process(ref _config); | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
_strategyManager = new StrategyManager(this); | |||
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); | |||
StartTrafficStatistics(61); | |||
@@ -188,8 +184,6 @@ namespace Shadowsocks.Controller | |||
httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString); | |||
} | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); | |||
_pacDaemon = _pacDaemon ?? new PACDaemon(_config); | |||
@@ -202,7 +196,6 @@ namespace Shadowsocks.Controller | |||
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; | |||
GeositeUpdater.Error += PacServer_PACUpdateError; | |||
availabilityStatistics.UpdateConfiguration(this); | |||
_listener?.Stop(); | |||
StopPlugins(); | |||
@@ -220,7 +213,6 @@ namespace Shadowsocks.Controller | |||
privoxyRunner.Start(_config); | |||
TCPRelay tcpRelay = new TCPRelay(this, _config); | |||
tcpRelay.OnConnected += UpdateLatency; | |||
tcpRelay.OnInbound += UpdateInboundCounter; | |||
tcpRelay.OnOutbound += UpdateOutboundCounter; | |||
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); | |||
@@ -524,7 +516,7 @@ namespace Shadowsocks.Controller | |||
#endregion | |||
#region Statistics | |||
#region Strategy | |||
public void SelectStrategy(string strategyID) | |||
{ | |||
@@ -533,12 +525,6 @@ namespace Shadowsocks.Controller | |||
SaveConfig(_config); | |||
} | |||
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) | |||
{ | |||
StatisticsConfiguration = configuration; | |||
StatisticsStrategyConfiguration.Save(configuration); | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
{ | |||
return _strategyManager.GetStrategies(); | |||
@@ -556,43 +542,16 @@ namespace Shadowsocks.Controller | |||
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) | |||
{ | |||
GetCurrentStrategy()?.UpdateLastRead(args.server); | |||
Interlocked.Add(ref _inboundCounter, args.length); | |||
if (_config.availabilityStatistics) | |||
{ | |||
availabilityStatistics.UpdateInboundCounter(args.server, args.length); | |||
} | |||
} | |||
public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) | |||
{ | |||
GetCurrentStrategy()?.UpdateLastWrite(args.server); | |||
Interlocked.Add(ref _outboundCounter, args.length); | |||
if (_config.availabilityStatistics) | |||
{ | |||
availabilityStatistics.UpdateOutboundCounter(args.server, args.length); | |||
} | |||
} | |||
#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.Add(new BalancingStrategy(controller)); | |||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||
_strategies.Add(new StatisticsStrategy(controller)); | |||
// TODO: load DLL plugins | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
@@ -16,7 +16,6 @@ PAC,Сценарий настройки (PAC),PAC 模式,PAC 模式,PACモード | |||
Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global | |||
Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs | |||
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… | |||
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정,Configuration des statistiques… | |||
Online Config...,,在线配置...,線上配置...,,, | |||
Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique | |||
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결, | |||
@@ -47,7 +46,6 @@ Quit,Выход,退出,結束,終了,종료,Quitter | |||
Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs | |||
Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge | |||
High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité | |||
Choose by statistics,На основе статистики,根据统计,根據統計,統計で選ぶ,통계 기반,Choisissez par statistiques | |||
Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin | |||
Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction | |||
,,,,,, | |||
@@ -81,36 +79,6 @@ Move D&own,Ниже,下移(&O),下移 (&O),下に移動 (&O),아래로 (&O),Desc | |||
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}" | |||
,,,,,, | |||
#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,,,,,, | |||
,,,,,, | |||
&File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier | |||
@@ -37,7 +37,6 @@ namespace Shadowsocks.Model | |||
public bool useOnlinePac; | |||
public bool secureLocalPac; // enable secret for PAC server | |||
public bool regeneratePacOnUpdate; // regenerate pac.txt on version update | |||
public bool availabilityStatistics; | |||
public bool autoCheckUpdate; | |||
public bool checkPreRelease; | |||
public string skippedUpdateVersion; // skip the update with this version number | |||
@@ -76,7 +75,6 @@ namespace Shadowsocks.Model | |||
useOnlinePac = false; | |||
secureLocalPac = true; | |||
regeneratePacOnUpdate = true; | |||
availabilityStatistics = false; | |||
autoCheckUpdate = false; | |||
checkPreRelease = false; | |||
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.SeperatorItem = new MenuItem("-"), | |||
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | |||
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click), | |||
new MenuItem("-"), | |||
CreateMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), | |||
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)), | |||
@@ -725,12 +724,6 @@ namespace Shadowsocks.View | |||
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) | |||
{ | |||
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\HttpProxy.cs" /> | |||
<Compile Include="Proxy\IProxy.cs" /> | |||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | |||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\StatisticsStrategy.cs" /> | |||
<Compile Include="Controller\System\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\I18N.cs" /> | |||
@@ -282,8 +280,6 @@ | |||
<Compile Include="Model\LogViewerConfig.cs" /> | |||
<Compile Include="Model\Server.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\StrategyManager.cs" /> | |||
<Compile Include="Controller\Strategy\IStrategy.cs" /> | |||
@@ -329,12 +325,6 @@ | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Controller\ShadowsocksController.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"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
@@ -348,12 +338,6 @@ | |||
<Compile Include="Views\ServerSharingView.xaml.cs"> | |||
<DependentUpon>ServerSharingView.xaml</DependentUpon> | |||
</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.ja.resx" /> | |||
<EmbeddedResource Include="Localization\Strings.ko.resx" /> | |||
@@ -373,15 +357,9 @@ | |||
<SubType>Designer</SubType> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\CalculationControl.resx"> | |||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\LogForm.resx"> | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx"> | |||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<None Include="app.config" /> | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
@@ -397,7 +375,6 @@ | |||
<None Include="Data\sysproxy.exe.gz" /> | |||
<None Include="Data\sysproxy64.exe.gz" /> | |||
<None Include="packages.config" /> | |||
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" /> | |||
<None Include="Properties\Settings.settings"> | |||
<Generator>SettingsSingleFileGenerator</Generator> | |||
<LastGenOutput>Settings.Designer.cs</LastGenOutput> | |||