Correct AvailabilityStatistics's behavior under special strategies like "LoadBalance", which may switch the server several times in a short period.tags/3.2
@@ -1,4 +1,5 @@ | |||
using System; | |||
using System.Collections.Concurrent; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
@@ -31,12 +32,14 @@ namespace Shadowsocks.Controller | |||
public const int TimeoutMilliseconds = 500; | |||
//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 | |||
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 | |||
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1); | |||
@@ -45,12 +48,11 @@ namespace Shadowsocks.Controller | |||
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes); | |||
private Timer _speedMonior; | |||
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 StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration; | |||
private Server CurrentServer => _controller.GetCurrentServer(); | |||
// Static Singleton Initialization | |||
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics(); | |||
@@ -73,13 +75,11 @@ namespace Shadowsocks.Controller | |||
StartTimerWithoutState(ref _recorder, Run, RecordingInterval); | |||
LoadRawStatistics(); | |||
StartTimerWithoutState(ref _speedMonior, UpdateSpeed, _monitorInterval); | |||
StartTimerWithoutState(ref _writer, Save, _writingInterval); | |||
} | |||
else | |||
{ | |||
_recorder?.Dispose(); | |||
_speedMonior?.Dispose(); | |||
_writer?.Dispose(); | |||
} | |||
} | |||
catch (Exception e) | |||
@@ -98,18 +98,27 @@ namespace Shadowsocks.Controller | |||
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) | |||
@@ -161,66 +170,75 @@ namespace Shadowsocks.Controller | |||
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 _) | |||
{ | |||
UpdateRecords(); | |||
Save(); | |||
Reset(); | |||
FilterRawStatistics(); | |||
} | |||
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; | |||
if (!RawStatistics.TryGetValue(server.Identifier(), out records)) | |||
if (!RawStatistics.TryGetValue(serverIdentifier, out records)) | |||
{ | |||
records = new List<StatisticsRecord>(); | |||
} | |||
records.Add(record); | |||
RawStatistics[server.Identifier()] = records; | |||
RawStatistics[serverIdentifier] = records; | |||
} | |||
private void Save(object _) | |||
private void Save() | |||
{ | |||
if (RawStatistics.Count == 0) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
File.WriteAllText(AvailabilityStatisticsFile, | |||
JsonConvert.SerializeObject(RawStatistics, Formatting.None)); | |||
var content = JsonConvert.SerializeObject(RawStatistics, Formatting.None); | |||
File.WriteAllText(AvailabilityStatisticsFile, content); | |||
} | |||
catch (IOException 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) | |||
{ | |||
var result = (int) (bytes/seconds)/1024; | |||
@@ -298,8 +311,49 @@ namespace Shadowsocks.Controller | |||
public void Dispose() | |||
{ | |||
_recorder.Dispose(); | |||
_writer.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; | |||
} | |||
} | |||
} |
@@ -69,14 +69,14 @@ namespace Shadowsocks.Controller | |||
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) | |||
@@ -488,10 +488,7 @@ namespace Shadowsocks.Controller | |||
var latency = DateTime.Now - _startConnectTime; | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLatency(server, latency); | |||
} | |||
strategy?.UpdateLatency(server, latency); | |||
tcprelay.UpdateLatency(server, latency); | |||
StartPipe(); | |||
@@ -543,7 +540,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
int bytesRead = remote.EndReceive(ar); | |||
totalRead += bytesRead; | |||
tcprelay.UpdateInboundCounter(bytesRead); | |||
tcprelay.UpdateInboundCounter(server, bytesRead); | |||
if (bytesRead > 0) | |||
{ | |||
@@ -610,7 +607,7 @@ namespace Shadowsocks.Controller | |||
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | |||
} | |||
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); | |||
tcprelay.UpdateOutboundCounter(bytesToSend); | |||
tcprelay.UpdateOutboundCounter(server, bytesToSend); | |||
_startSendingTime = DateTime.Now; | |||
_bytesToSend = bytesToSend; | |||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | |||
@@ -5,7 +5,7 @@ using System.Net; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Newtonsoft.Json; | |||
using Shadowsocks.Controller.Strategy; | |||
@@ -307,14 +307,30 @@ namespace Shadowsocks.Controller | |||
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); | |||
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); | |||
if (_config.availabilityStatistics) | |||
{ | |||
new Task(() => availabilityStatistics.UpdateOutboundCounter(server, n)).Start(); | |||
} | |||
} | |||
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); | |||
} | |||
} | |||
} | |||
} |
@@ -49,29 +49,32 @@ namespace Shadowsocks.Controller.Strategy | |||
//return the score by data | |||
//server with highest score will be choosen | |||
private float GetScore(string serverName) | |||
private float? GetScore(string identifier, List<StatisticsRecord> records) | |||
{ | |||
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; | |||
} | |||
@@ -83,15 +86,25 @@ namespace Shadowsocks.Controller.Strategy | |||
} | |||
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}"); | |||
_currentServer = bestResult.server; | |||
@@ -112,7 +125,7 @@ namespace Shadowsocks.Controller.Strategy | |||
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) | |||
{ | |||
@@ -30,7 +30,7 @@ Quit=退出 | |||
Edit Servers=编辑服务器 | |||
Load Balance=负载均衡 | |||
High Availability=高可用 | |||
Choose By Total Package Loss=累计丢包率 | |||
Choose by statistics=根据统计 | |||
# Config Form | |||
@@ -9,34 +9,49 @@ namespace Shadowsocks.Model | |||
public class StatisticsRecord | |||
{ | |||
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 | |||
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, 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()) | |||
{ | |||
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; | |||
var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList(); | |||
@@ -71,7 +86,7 @@ namespace Shadowsocks.Model | |||
AverageResponse = (int?) records.Average(); | |||
MinResponse = records.Min(); | |||
MaxResponse = records.Max(); | |||
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count(); | |||
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count; | |||
} | |||
} | |||
} |
@@ -36,6 +36,7 @@ | |||
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.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components); | |||
this.label2 = new System.Windows.Forms.Label(); | |||
this.label3 = new System.Windows.Forms.Label(); | |||
this.chartModeSelector = new System.Windows.Forms.GroupBox(); | |||
@@ -58,8 +59,8 @@ | |||
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(); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit(); | |||
this.chartModeSelector.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); | |||
this.splitContainer1.Panel1.SuspendLayout(); | |||
@@ -76,7 +77,6 @@ | |||
this.splitContainer3.Panel1.SuspendLayout(); | |||
this.splitContainer3.Panel2.SuspendLayout(); | |||
this.splitContainer3.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit(); | |||
this.SuspendLayout(); | |||
// | |||
// StatisticsChart | |||
@@ -142,6 +142,10 @@ | |||
this.PingCheckBox.UseVisualStyleBackColor = true; | |||
this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged); | |||
// | |||
// bindingConfiguration | |||
// | |||
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration); | |||
// | |||
// label2 | |||
// | |||
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.Controls.Add(this.allMode); | |||
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.Name = "chartModeSelector"; | |||
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.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.Size = new System.Drawing.Size(233, 35); | |||
this.serverSelector.TabIndex = 6; | |||
@@ -446,7 +450,7 @@ | |||
// 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(861, 376); | |||
this.CancelButton.Location = new System.Drawing.Point(861, 370); | |||
this.CancelButton.Name = "CancelButton"; | |||
this.CancelButton.Size = new System.Drawing.Size(101, 41); | |||
this.CancelButton.TabIndex = 5; | |||
@@ -457,7 +461,7 @@ | |||
// 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(754, 376); | |||
this.OKButton.Location = new System.Drawing.Point(754, 370); | |||
this.OKButton.Name = "OKButton"; | |||
this.OKButton.Size = new System.Drawing.Size(101, 41); | |||
this.OKButton.TabIndex = 4; | |||
@@ -465,12 +469,6 @@ | |||
this.OKButton.UseVisualStyleBackColor = true; | |||
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 | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F); | |||
@@ -484,6 +482,7 @@ | |||
this.Name = "StatisticsStrategyConfigurationForm"; | |||
this.Text = "StatisticsStrategyConfigurationForm"; | |||
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit(); | |||
this.chartModeSelector.ResumeLayout(false); | |||
this.chartModeSelector.PerformLayout(); | |||
this.splitContainer1.Panel1.ResumeLayout(false); | |||
@@ -503,7 +502,6 @@ | |||
this.splitContainer3.Panel2.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); | |||
this.splitContainer3.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit(); | |||
this.ResumeLayout(false); | |||
} | |||
@@ -36,7 +36,7 @@ namespace Shadowsocks.View | |||
private void LoadConfiguration() | |||
{ | |||
var configs = _controller.GetCurrentConfiguration().configs; | |||
_servers = configs.Select(server => server.FriendlyName()).ToList(); | |||
_servers = configs.Select(server => server.Identifier()).ToList(); | |||
_configuration = _controller.StatisticsConfiguration | |||
?? new StatisticsStrategyConfiguration(); | |||
if (_configuration.Calculations == null) | |||
@@ -153,15 +153,5 @@ namespace Shadowsocks.View | |||
{ | |||
repeatTimesNum.ReadOnly = !PingCheckBox.Checked; | |||
} | |||
private void bindingConfiguration_CurrentItemChanged(object sender, EventArgs e) | |||
{ | |||
Logging.Info("?"); | |||
} | |||
private void bindingConfiguration_BindingComplete(object sender, BindingCompleteEventArgs e) | |||
{ | |||
Logging.Info("?"); | |||
} | |||
} | |||
} |
@@ -118,12 +118,12 @@ | |||
<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>1, 30</value> | |||
<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>37</value> | |||
<value>191</value> | |||
</metadata> | |||
</root> |
@@ -53,13 +53,14 @@ | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> | |||
<OutputPath>bin\x86\Release\</OutputPath> | |||
<DefineConstants>TRACE</DefineConstants> | |||
<Optimize>true</Optimize> | |||
<Optimize>false</Optimize> | |||
<DebugType>pdbonly</DebugType> | |||
<PlatformTarget>x86</PlatformTarget> | |||
<ErrorReport>prompt</ErrorReport> | |||
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet> | |||
<Prefer32Bit>false</Prefer32Bit> | |||
<DebugSymbols>true</DebugSymbols> | |||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | |||
</PropertyGroup> | |||
<PropertyGroup> | |||
<ApplicationManifest>app.manifest</ApplicationManifest> | |||
@@ -341,13 +342,12 @@ | |||
<Files Output="false" Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem[]" /> | |||
</ParameterGroup> | |||
<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(); | |||
if (config == null) return true; | |||
@@ -366,8 +366,8 @@ var filesToCleanup = Files.Select(f => f.ItemSpec).Where(f => !excludedAssemblie | |||
foreach (var item in filesToCleanup) | |||
File.Delete(item); | |||
]]> | |||
</Code></Task> | |||
]]></Code> | |||
</Task> | |||
</UsingTask> | |||
<Target Name="CleanReferenceCopyLocalPaths" AfterTargets="AfterBuild;NonWinFodyTarget"> | |||
<CosturaCleanup Config="FodyWeavers.xml" Files="@(ReferenceCopyLocalPaths->'$(OutDir)%(DestinationSubDirectory)%(Filename)%(Extension)')" /> | |||