Browse Source

track multiple servers at the same time

Correct AvailabilityStatistics's behavior under special strategies like "LoadBalance", which may switch the server several times in a short period.
tags/3.0
icylogic 9 years ago
parent
commit
a99fef37d3
10 changed files with 224 additions and 148 deletions
  1. +106
    -52
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  2. +7
    -10
      shadowsocks-csharp/Controller/Service/TCPRelay.cs
  3. +19
    -10
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  4. +44
    -31
      shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs
  5. +1
    -1
      shadowsocks-csharp/Data/cn.txt
  6. +23
    -8
      shadowsocks-csharp/Model/StatisticsRecord.cs
  7. +11
    -13
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs
  8. +1
    -11
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs
  9. +2
    -2
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx
  10. +10
    -10
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 106
- 52
shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -31,12 +32,14 @@ namespace Shadowsocks.Controller
public const int TimeoutMilliseconds = 500; public const int TimeoutMilliseconds = 500;


//records cache for current server in {_monitorInterval} minutes //records cache for current server in {_monitorInterval} minutes
private List<int> _latencyRecords;
private readonly ConcurrentDictionary<string, List<int>> _latencyRecords = new ConcurrentDictionary<string, List<int>>();
//speed in KiB/s //speed in KiB/s
private long _lastInboundCounter;
private List<int> _inboundSpeedRecords;
private long _lastOutboundCounter;
private List<int> _outboundSpeedRecords;
private readonly ConcurrentDictionary<string, long> _inboundCounter = new ConcurrentDictionary<string, long>();
private readonly ConcurrentDictionary<string, long> _lastInboundCounter = new ConcurrentDictionary<string, long>();
private readonly ConcurrentDictionary<string, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
private readonly ConcurrentDictionary<string, long> _outboundCounter = new ConcurrentDictionary<string, long>();
private readonly ConcurrentDictionary<string, long> _lastOutboundCounter = new ConcurrentDictionary<string, long>();
private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();


//tasks //tasks
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1); private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
@@ -45,12 +48,11 @@ namespace Shadowsocks.Controller
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes); private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes);
private Timer _speedMonior; private Timer _speedMonior;
private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1); private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
private Timer _writer; //write RawStatistics to file
private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
//private Timer _writer; //write RawStatistics to file
//private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);


private ShadowsocksController _controller; private ShadowsocksController _controller;
private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration; private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration;
private Server CurrentServer => _controller.GetCurrentServer();


// Static Singleton Initialization // Static Singleton Initialization
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics(); public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
@@ -73,13 +75,11 @@ namespace Shadowsocks.Controller
StartTimerWithoutState(ref _recorder, Run, RecordingInterval); StartTimerWithoutState(ref _recorder, Run, RecordingInterval);
LoadRawStatistics(); LoadRawStatistics();
StartTimerWithoutState(ref _speedMonior, UpdateSpeed, _monitorInterval); StartTimerWithoutState(ref _speedMonior, UpdateSpeed, _monitorInterval);
StartTimerWithoutState(ref _writer, Save, _writingInterval);
} }
else else
{ {
_recorder?.Dispose(); _recorder?.Dispose();
_speedMonior?.Dispose(); _speedMonior?.Dispose();
_writer?.Dispose();
} }
} }
catch (Exception e) catch (Exception e)
@@ -98,18 +98,27 @@ namespace Shadowsocks.Controller


private void UpdateSpeed(object _) private void UpdateSpeed(object _)
{ {
var bytes = _controller.inboundCounter - _lastInboundCounter;
_lastInboundCounter = _controller.inboundCounter;
var inboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
_inboundSpeedRecords.Add(inboundSpeed);

bytes = _controller.outboundCounter - _lastOutboundCounter;
_lastOutboundCounter = _controller.outboundCounter;
var outboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
_outboundSpeedRecords.Add(outboundSpeed);

Logging.Debug(
$"{CurrentServer.FriendlyName()}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords.Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords.Max()} KiB/s");
foreach (var kv in _lastInboundCounter)
{
var id = kv.Key;

var lastInbound = kv.Value;
var inbound = _inboundCounter[id];
var bytes = inbound - lastInbound;
_lastInboundCounter[id] = inbound;
var inboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
_inboundSpeedRecords.GetOrAdd(id, new List<int> {inboundSpeed}).Add(inboundSpeed);

var lastOutbound = _lastOutboundCounter[id];
var outbound = _outboundCounter[id];
bytes = outbound - lastOutbound;
_lastOutboundCounter[id] = outbound;
var outboundSpeed = GetSpeedInKiBPerSecond(bytes, _monitorInterval.TotalSeconds);
_outboundSpeedRecords.GetOrAdd(id, new List<int> {outboundSpeed}).Add(outboundSpeed);

Logging.Debug(
$"{id}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords[id].Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords[id].Max()} KiB/s");
}
} }


private async Task<ICMPResult> ICMPTest(Server server) private async Task<ICMPResult> ICMPTest(Server server)
@@ -161,66 +170,75 @@ namespace Shadowsocks.Controller


private void Reset() private void Reset()
{ {
_inboundSpeedRecords = new List<int>();
_outboundSpeedRecords = new List<int>();
_latencyRecords = new List<int>();
_inboundSpeedRecords.Clear();
_outboundSpeedRecords.Clear();
_latencyRecords.Clear();
} }


private void Run(object _) private void Run(object _)
{ {
UpdateRecords(); UpdateRecords();
Save();
Reset(); Reset();
FilterRawStatistics(); FilterRawStatistics();
} }


private async void UpdateRecords() private async void UpdateRecords()
{ {
var currentServerRecord = new StatisticsRecord(CurrentServer.Identifier(), _inboundSpeedRecords, _outboundSpeedRecords, _latencyRecords);
var records = new Dictionary<string, StatisticsRecord>();


if (!Config.Ping)
foreach (var server in _controller.GetCurrentConfiguration().configs)
{ {
AppendRecord(CurrentServer, currentServerRecord);
return;
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);
records.Add(id, new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords));
} }


var icmpResults = TaskEx.WhenAll(_controller.GetCurrentConfiguration().configs.Select(ICMPTest));

foreach (var result in (await icmpResults).Where(result => result != null))
if (Config.Ping)
{ {
if (result.Server.Equals(CurrentServer))
var icmpResults = await TaskEx.WhenAll(_controller.GetCurrentConfiguration().configs.Select(ICMPTest));
foreach (var result in icmpResults.Where(result => result != null))
{ {
currentServerRecord.setResponse(result.RoundtripTime);
AppendRecord(CurrentServer, currentServerRecord);
}
else
{
AppendRecord(result.Server, new StatisticsRecord(result.Server.Identifier(), result.RoundtripTime));
records[result.Server.Identifier()].SetResponse(result.RoundtripTime);
} }
} }

foreach (var kv in records.Where(kv => !kv.Value.IsEmptyData()))
{
AppendRecord(kv.Key, kv.Value);
}
} }


private void AppendRecord(Server server, StatisticsRecord record)
private void AppendRecord(string serverIdentifier, StatisticsRecord record)
{ {
List<StatisticsRecord> records; List<StatisticsRecord> records;
if (!RawStatistics.TryGetValue(server.Identifier(), out records))
if (!RawStatistics.TryGetValue(serverIdentifier, out records))
{ {
records = new List<StatisticsRecord>(); records = new List<StatisticsRecord>();
} }
records.Add(record); records.Add(record);
RawStatistics[server.Identifier()] = records;
RawStatistics[serverIdentifier] = records;
} }


private void Save(object _)
private void Save()
{ {
if (RawStatistics.Count == 0)
{
return;
}
try try
{ {
File.WriteAllText(AvailabilityStatisticsFile,
JsonConvert.SerializeObject(RawStatistics, Formatting.None));
var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
File.WriteAllText(AvailabilityStatisticsFile, content);
} }
catch (IOException e) catch (IOException e)
{ {
Logging.LogUsefulException(e); Logging.LogUsefulException(e);
_writer.Change(_retryInterval, _writingInterval);
} }
} }


@@ -273,11 +291,6 @@ namespace Shadowsocks.Controller
} }
} }


public void UpdateLatency(int latency)
{
_latencyRecords.Add(latency);
}

private static int GetSpeedInKiBPerSecond(long bytes, double seconds) private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
{ {
var result = (int) (bytes/seconds)/1024; var result = (int) (bytes/seconds)/1024;
@@ -298,8 +311,49 @@ namespace Shadowsocks.Controller
public void Dispose() public void Dispose()
{ {
_recorder.Dispose(); _recorder.Dispose();
_writer.Dispose();
_speedMonior.Dispose(); _speedMonior.Dispose();
} }

public void UpdateLatency(Server server, int latency)
{
List<int> records;
_latencyRecords.TryGetValue(server.Identifier(), out records);
if (records == null)
{
records = new List<int>();
}
records.Add(latency);
_latencyRecords[server.Identifier()] = records;
}

public void UpdateInboundCounter(Server server, long n)
{
long count;
if (_inboundCounter.TryGetValue(server.Identifier(), out count))
{
count += n;
}
else
{
count = n;
_lastInboundCounter[server.Identifier()] = 0;
}
_inboundCounter[server.Identifier()] = count;
}

public void UpdateOutboundCounter(Server server, long n)
{
long count;
if (_outboundCounter.TryGetValue(server.Identifier(), out count))
{
count += n;
}
else
{
count = n;
_lastOutboundCounter[server.Identifier()] = 0;
}
_outboundCounter[server.Identifier()] = count;
}
} }
} }

+ 7
- 10
shadowsocks-csharp/Controller/Service/TCPRelay.cs View File

@@ -69,14 +69,14 @@ namespace Shadowsocks.Controller
return true; return true;
} }
public void UpdateInboundCounter(long n)
public void UpdateInboundCounter(Server server, long n)
{ {
_controller.UpdateInboundCounter(n);
_controller.UpdateInboundCounter(server, n);
} }
public void UpdateOutboundCounter(long n)
public void UpdateOutboundCounter(Server server, long n)
{ {
_controller.UpdateOutboundCounter(n);
_controller.UpdateOutboundCounter(server, n);
} }
public void UpdateLatency(Server server, TimeSpan latency) public void UpdateLatency(Server server, TimeSpan latency)
@@ -488,10 +488,7 @@ namespace Shadowsocks.Controller
var latency = DateTime.Now - _startConnectTime; var latency = DateTime.Now - _startConnectTime;
IStrategy strategy = controller.GetCurrentStrategy(); IStrategy strategy = controller.GetCurrentStrategy();
if (strategy != null)
{
strategy.UpdateLatency(server, latency);
}
strategy?.UpdateLatency(server, latency);
tcprelay.UpdateLatency(server, latency); tcprelay.UpdateLatency(server, latency);
StartPipe(); StartPipe();
@@ -543,7 +540,7 @@ namespace Shadowsocks.Controller
{ {
int bytesRead = remote.EndReceive(ar); int bytesRead = remote.EndReceive(ar);
totalRead += bytesRead; totalRead += bytesRead;
tcprelay.UpdateInboundCounter(bytesRead);
tcprelay.UpdateInboundCounter(server, bytesRead);
if (bytesRead > 0) if (bytesRead > 0)
{ {
@@ -610,7 +607,7 @@ namespace Shadowsocks.Controller
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend);
} }
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)");
tcprelay.UpdateOutboundCounter(bytesToSend);
tcprelay.UpdateOutboundCounter(server, bytesToSend);
_startSendingTime = DateTime.Now; _startSendingTime = DateTime.Now;
_bytesToSend = bytesToSend; _bytesToSend = bytesToSend;
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null);


+ 19
- 10
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -5,7 +5,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json; using Newtonsoft.Json;
using Shadowsocks.Controller.Strategy; using Shadowsocks.Controller.Strategy;
@@ -307,14 +307,30 @@ namespace Shadowsocks.Controller
Configuration.Save(_config); Configuration.Save(_config);
} }
public void UpdateInboundCounter(long n)
public void UpdateLatency(Server server, TimeSpan latency)
{
if (_config.availabilityStatistics)
{
new Task(() => availabilityStatistics.UpdateLatency(server, (int) latency.TotalMilliseconds)).Start();
}
}
public void UpdateInboundCounter(Server server, long n)
{ {
Interlocked.Add(ref inboundCounter, n); Interlocked.Add(ref inboundCounter, n);
if (_config.availabilityStatistics)
{
new Task(() => availabilityStatistics.UpdateInboundCounter(server, n)).Start();
}
} }
public void UpdateOutboundCounter(long n)
public void UpdateOutboundCounter(Server server, long n)
{ {
Interlocked.Add(ref outboundCounter, n); Interlocked.Add(ref outboundCounter, n);
if (_config.availabilityStatistics)
{
new Task(() => availabilityStatistics.UpdateOutboundCounter(server, n)).Start();
}
} }
protected void Reload() protected void Reload()
@@ -498,12 +514,5 @@ namespace Shadowsocks.Controller
} }
} }
public void UpdateLatency(Server server, TimeSpan latency)
{
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateLatency((int) latency.TotalMilliseconds);
}
}
} }
} }

+ 44
- 31
shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs View File

@@ -49,29 +49,32 @@ namespace Shadowsocks.Controller.Strategy


//return the score by data //return the score by data
//server with highest score will be choosen //server with highest score will be choosen
private float GetScore(string serverName)
private float? GetScore(string identifier, List<StatisticsRecord> records)
{ {
var config = _controller.StatisticsConfiguration; var config = _controller.StatisticsConfiguration;
List<StatisticsRecord> records;
if (_filteredStatistics == null || !_filteredStatistics.TryGetValue(serverName, out records)) return 0;
float factor;
float score = 0;

var averageRecord = new StatisticsRecord(serverName,
records.FindAll(record => record.MaxInboundSpeed != null).Select(record => record.MaxInboundSpeed.Value),
records.FindAll(record => record.MaxOutboundSpeed != null).Select(record => record.MaxOutboundSpeed.Value),
records.FindAll(record => record.AverageLatency != null).Select(record => record.AverageLatency.Value));
averageRecord.setResponse(records.Select(record => record.AverageResponse));

if (!config.Calculations.TryGetValue("PackageLoss", out factor)) factor = 0;
score += averageRecord.PackageLoss * factor ?? 0;
if (!config.Calculations.TryGetValue("AverageResponse", out factor)) factor = 0;
score += averageRecord.AverageResponse * factor ?? 0;
if (!config.Calculations.TryGetValue("MinResponse", out factor)) factor = 0;
score += averageRecord.MinResponse * factor ?? 0;
if (!config.Calculations.TryGetValue("MaxResponse", out factor)) factor = 0;
score += averageRecord.MaxResponse * factor ?? 0;
Logging.Debug($"Highest score: {score} {JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}");
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)
{
Logging.Debug($"Highest score: {score} {JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}");
}
return score; return score;
} }


@@ -83,15 +86,25 @@ namespace Shadowsocks.Controller.Strategy
} }
try try
{ {
var bestResult = (from server in servers
let name = server.FriendlyName()
where _filteredStatistics.ContainsKey(name)
select new
{
server,
score = GetScore(name)
}
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2);
var serversWithStatistics = (from server in servers
let id = server.Identifier()
where _filteredStatistics.ContainsKey(id)
let score = GetScore(server.Identifier(), _filteredStatistics[server.Identifier()])
where score != null
select new
{
server,
score
}).ToArray();

if (serversWithStatistics.Length < 2)
{
LogWhenEnabled("no enough statistics data for evaluation");
return;
}

var bestResult = serversWithStatistics
.Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2);


LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by statistics: score {bestResult.score}"); LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by statistics: score {bestResult.score}");
_currentServer = bestResult.server; _currentServer = bestResult.server;
@@ -112,7 +125,7 @@ namespace Shadowsocks.Controller.Strategy


public string ID => "com.shadowsocks.strategy.scbs"; public string ID => "com.shadowsocks.strategy.scbs";


public string Name => I18N.GetString("Choose By Total Package Loss");
public string Name => I18N.GetString("Choose by statistics");


public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint)
{ {


+ 1
- 1
shadowsocks-csharp/Data/cn.txt View File

@@ -30,7 +30,7 @@ Quit=退出
Edit Servers=编辑服务器 Edit Servers=编辑服务器
Load Balance=负载均衡 Load Balance=负载均衡
High Availability=高可用 High Availability=高可用
Choose By Total Package Loss=累计丢包率
Choose by statistics=根据统计


# Config Form # Config Form




+ 23
- 8
shadowsocks-csharp/Model/StatisticsRecord.cs View File

@@ -9,34 +9,49 @@ namespace Shadowsocks.Model
public class StatisticsRecord public class StatisticsRecord
{ {
public DateTime Timestamp { get; set; } = DateTime.Now; public DateTime Timestamp { get; set; } = DateTime.Now;
public string ServerName { get; set; }
public string ServerIdentifier { get; set; }


// in ping-only records, these fields would be null // in ping-only records, these fields would be null
public int? AverageLatency; public int? AverageLatency;
public int? MinLatency; public int? MinLatency;
public int? MaxLatency; public int? MaxLatency;


private bool EmptyLatencyData => (AverageLatency == null) && (MinLatency == null) && (MaxLatency == null);

public int? AverageInboundSpeed; public int? AverageInboundSpeed;
public int? MinInboundSpeed; public int? MinInboundSpeed;
public int? MaxInboundSpeed; public int? MaxInboundSpeed;


private bool EmptyInboundSpeedData
=> (AverageInboundSpeed == null) && (MinInboundSpeed == null) && (MaxInboundSpeed == null);

public int? AverageOutboundSpeed; public int? AverageOutboundSpeed;
public int? MinOutboundSpeed; public int? MinOutboundSpeed;
public int? MaxOutboundSpeed; public int? MaxOutboundSpeed;


private bool EmptyOutboundSpeedData
=> (AverageOutboundSpeed == null) && (MinOutboundSpeed == null) && (MaxOutboundSpeed == null);

// if user disabled ping test, response would be null // if user disabled ping test, response would be null
public int? AverageResponse; public int? AverageResponse;
public int? MinResponse; public int? MinResponse;
public int? MaxResponse; public int? MaxResponse;
public float? PackageLoss; 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()
{ {
} }


public StatisticsRecord(string identifier, IEnumerable<int> inboundSpeedRecords, IEnumerable<int> outboundSpeedRecords, IEnumerable<int> latencyRecords)
public StatisticsRecord(string identifier, ICollection<int> inboundSpeedRecords, ICollection<int> outboundSpeedRecords, ICollection<int> latencyRecords)
{ {
ServerName = identifier;
ServerIdentifier = identifier;
if (inboundSpeedRecords != null && inboundSpeedRecords.Any()) if (inboundSpeedRecords != null && inboundSpeedRecords.Any())
{ {
AverageInboundSpeed = (int) inboundSpeedRecords.Average(); AverageInboundSpeed = (int) inboundSpeedRecords.Average();
@@ -57,13 +72,13 @@ namespace Shadowsocks.Model
} }
} }


public StatisticsRecord(string identifier, IEnumerable<int?> responseRecords)
public StatisticsRecord(string identifier, ICollection<int?> responseRecords)
{ {
ServerName = identifier;
setResponse(responseRecords);
ServerIdentifier = identifier;
SetResponse(responseRecords);
} }


public void setResponse(IEnumerable<int?> responseRecords)
public void SetResponse(ICollection<int?> responseRecords)
{ {
if (responseRecords == null) return; if (responseRecords == null) return;
var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList(); var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList();
@@ -71,7 +86,7 @@ namespace Shadowsocks.Model
AverageResponse = (int?) records.Average(); AverageResponse = (int?) records.Average();
MinResponse = records.Min(); MinResponse = records.Min();
MaxResponse = records.Max(); MaxResponse = records.Max();
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count();
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count;
} }
} }
} }

+ 11
- 13
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs View File

@@ -36,6 +36,7 @@
System.Windows.Forms.DataVisualization.Charting.Series series3 = 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.StatisticsChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.PingCheckBox = new System.Windows.Forms.CheckBox(); this.PingCheckBox = new System.Windows.Forms.CheckBox();
this.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components);
this.label2 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label();
this.chartModeSelector = new System.Windows.Forms.GroupBox(); this.chartModeSelector = new System.Windows.Forms.GroupBox();
@@ -58,8 +59,8 @@
this.CancelButton = new System.Windows.Forms.Button(); this.CancelButton = new System.Windows.Forms.Button();
this.OKButton = new System.Windows.Forms.Button(); this.OKButton = new System.Windows.Forms.Button();
this.CalculatinTip = new System.Windows.Forms.ToolTip(this.components); this.CalculatinTip = new System.Windows.Forms.ToolTip(this.components);
this.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components);
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit();
this.chartModeSelector.SuspendLayout(); this.chartModeSelector.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout(); this.splitContainer1.Panel1.SuspendLayout();
@@ -76,7 +77,6 @@
this.splitContainer3.Panel1.SuspendLayout(); this.splitContainer3.Panel1.SuspendLayout();
this.splitContainer3.Panel2.SuspendLayout(); this.splitContainer3.Panel2.SuspendLayout();
this.splitContainer3.SuspendLayout(); this.splitContainer3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// StatisticsChart // StatisticsChart
@@ -142,6 +142,10 @@
this.PingCheckBox.UseVisualStyleBackColor = true; this.PingCheckBox.UseVisualStyleBackColor = true;
this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged); this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged);
// //
// bindingConfiguration
//
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration);
//
// label2 // label2
// //
this.label2.AutoSize = true; this.label2.AutoSize = true;
@@ -167,7 +171,7 @@
this.chartModeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 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.allMode);
this.chartModeSelector.Controls.Add(this.dayMode); this.chartModeSelector.Controls.Add(this.dayMode);
this.chartModeSelector.Location = new System.Drawing.Point(729, 194);
this.chartModeSelector.Location = new System.Drawing.Point(729, 188);
this.chartModeSelector.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); this.chartModeSelector.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.chartModeSelector.Name = "chartModeSelector"; this.chartModeSelector.Name = "chartModeSelector";
this.chartModeSelector.Padding = new System.Windows.Forms.Padding(5, 10, 5, 10); this.chartModeSelector.Padding = new System.Windows.Forms.Padding(5, 10, 5, 10);
@@ -437,7 +441,7 @@
// //
this.serverSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.serverSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.serverSelector.FormattingEnabled = true; this.serverSelector.FormattingEnabled = true;
this.serverSelector.Location = new System.Drawing.Point(729, 157);
this.serverSelector.Location = new System.Drawing.Point(729, 151);
this.serverSelector.Name = "serverSelector"; this.serverSelector.Name = "serverSelector";
this.serverSelector.Size = new System.Drawing.Size(233, 35); this.serverSelector.Size = new System.Drawing.Size(233, 35);
this.serverSelector.TabIndex = 6; this.serverSelector.TabIndex = 6;
@@ -446,7 +450,7 @@
// CancelButton // CancelButton
// //
this.CancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.CancelButton.Location = new System.Drawing.Point(861, 376);
this.CancelButton.Location = new System.Drawing.Point(861, 370);
this.CancelButton.Name = "CancelButton"; this.CancelButton.Name = "CancelButton";
this.CancelButton.Size = new System.Drawing.Size(101, 41); this.CancelButton.Size = new System.Drawing.Size(101, 41);
this.CancelButton.TabIndex = 5; this.CancelButton.TabIndex = 5;
@@ -457,7 +461,7 @@
// OKButton // OKButton
// //
this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.OKButton.Location = new System.Drawing.Point(754, 376);
this.OKButton.Location = new System.Drawing.Point(754, 370);
this.OKButton.Name = "OKButton"; this.OKButton.Name = "OKButton";
this.OKButton.Size = new System.Drawing.Size(101, 41); this.OKButton.Size = new System.Drawing.Size(101, 41);
this.OKButton.TabIndex = 4; this.OKButton.TabIndex = 4;
@@ -465,12 +469,6 @@
this.OKButton.UseVisualStyleBackColor = true; this.OKButton.UseVisualStyleBackColor = true;
this.OKButton.Click += new System.EventHandler(this.OKButton_Click); this.OKButton.Click += new System.EventHandler(this.OKButton_Click);
// //
// bindingConfiguration
//
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration);
this.bindingConfiguration.BindingComplete += new System.Windows.Forms.BindingCompleteEventHandler(this.bindingConfiguration_BindingComplete);
this.bindingConfiguration.CurrentItemChanged += new System.EventHandler(this.bindingConfiguration_CurrentItemChanged);
//
// StatisticsStrategyConfigurationForm // StatisticsStrategyConfigurationForm
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F); this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F);
@@ -484,6 +482,7 @@
this.Name = "StatisticsStrategyConfigurationForm"; this.Name = "StatisticsStrategyConfigurationForm";
this.Text = "StatisticsStrategyConfigurationForm"; this.Text = "StatisticsStrategyConfigurationForm";
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit();
this.chartModeSelector.ResumeLayout(false); this.chartModeSelector.ResumeLayout(false);
this.chartModeSelector.PerformLayout(); this.chartModeSelector.PerformLayout();
this.splitContainer1.Panel1.ResumeLayout(false); this.splitContainer1.Panel1.ResumeLayout(false);
@@ -503,7 +502,6 @@
this.splitContainer3.Panel2.ResumeLayout(false); this.splitContainer3.Panel2.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit();
this.splitContainer3.ResumeLayout(false); this.splitContainer3.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);


} }


+ 1
- 11
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs View File

@@ -36,7 +36,7 @@ namespace Shadowsocks.View
private void LoadConfiguration() private void LoadConfiguration()
{ {
var configs = _controller.GetCurrentConfiguration().configs; var configs = _controller.GetCurrentConfiguration().configs;
_servers = configs.Select(server => server.FriendlyName()).ToList();
_servers = configs.Select(server => server.Identifier()).ToList();
_configuration = _controller.StatisticsConfiguration _configuration = _controller.StatisticsConfiguration
?? new StatisticsStrategyConfiguration(); ?? new StatisticsStrategyConfiguration();
if (_configuration.Calculations == null) if (_configuration.Calculations == null)
@@ -153,15 +153,5 @@ namespace Shadowsocks.View
{ {
repeatTimesNum.ReadOnly = !PingCheckBox.Checked; repeatTimesNum.ReadOnly = !PingCheckBox.Checked;
} }

private void bindingConfiguration_CurrentItemChanged(object sender, EventArgs e)
{
Logging.Info("?");
}

private void bindingConfiguration_BindingComplete(object sender, BindingCompleteEventArgs e)
{
Logging.Info("?");
}
} }
} }

+ 2
- 2
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx View File

@@ -118,12 +118,12 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<metadata name="bindingConfiguration.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="bindingConfiguration.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>1, 30</value>
<value>4, 5</value>
</metadata> </metadata>
<metadata name="CalculatinTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <metadata name="CalculatinTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>238, 6</value> <value>238, 6</value>
</metadata> </metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> <metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>37</value>
<value>191</value>
</metadata> </metadata>
</root> </root>

+ 10
- 10
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -53,13 +53,14 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath> <OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<Optimize>false</Optimize>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit> <Prefer32Bit>false</Prefer32Bit>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
@@ -341,13 +342,12 @@
<Files Output="false" Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]" /> <Files Output="false" Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]" />
</ParameterGroup> </ParameterGroup>
<Task Evaluate="true"> <Task Evaluate="true">
<Reference xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Include="System.Xml" />
<Reference xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Include="System.Xml.Linq" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System.IO" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System.Xml.Linq" />
<Code xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Type="Fragment" Language="cs">
<![CDATA[
<Reference xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Include="System.Xml" />
<Reference xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Include="System.Xml.Linq" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System.IO" />
<Using xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Namespace="System.Xml.Linq" />
<Code xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Type="Fragment" Language="cs"><![CDATA[
var config = XElement.Load(Config.ItemSpec).Elements("Costura").FirstOrDefault(); var config = XElement.Load(Config.ItemSpec).Elements("Costura").FirstOrDefault();


if (config == null) return true; if (config == null) return true;
@@ -366,8 +366,8 @@ var filesToCleanup = Files.Select(f => f.ItemSpec).Where(f => !excludedAssemblie


foreach (var item in filesToCleanup) foreach (var item in filesToCleanup)
File.Delete(item); File.Delete(item);
]]>
</Code></Task>
]]></Code>
</Task>
</UsingTask> </UsingTask>
<Target Name="CleanReferenceCopyLocalPaths" AfterTargets="AfterBuild;NonWinFodyTarget"> <Target Name="CleanReferenceCopyLocalPaths" AfterTargets="AfterBuild;NonWinFodyTarget">
<CosturaCleanup Config="FodyWeavers.xml" Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" /> <CosturaCleanup Config="FodyWeavers.xml" Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" />


Loading…
Cancel
Save