Browse Source

migrate to record data model

tags/3.0
icylogic 8 years ago
parent
commit
ce67551d31
7 changed files with 268 additions and 187 deletions
  1. +158
    -158
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  2. +21
    -18
      shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs
  3. +5
    -0
      shadowsocks-csharp/Model/Server.cs
  4. +76
    -0
      shadowsocks-csharp/Model/StatisticsRecord.cs
  5. +1
    -3
      shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs
  6. +6
    -8
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs
  7. +1
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

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

@@ -8,53 +8,40 @@ using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Shadowsocks.Model;
using Shadowsocks.Util;

namespace Shadowsocks.Controller
{
using DataUnit = KeyValuePair<string, string>;
using DataList = List<KeyValuePair<string, string>>;

using Statistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>;

using Statistics = Dictionary<string, List<StatisticsRecord>>;

public sealed class AvailabilityStatistics
{
// Static Singleton Initialization
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
private AvailabilityStatistics() { }

public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
private const string StatisticsFilesName = "shadowsocks.availability.csv";
private const string Delimiter = ",";
private const string StatisticsFilesName = "shadowsocks.availability.json";
private const int TimeoutMilliseconds = 500;
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
public Statistics RawStatistics { get; private set; }
public Statistics FilteredStatistics { get; private set; }
public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1);
private int Repeat => _config.RepeatTimesNum;
private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2); //retry 2 minutes after failed
private TimeSpan Interval => TimeSpan.FromMinutes(_config.DataCollectionMinutes);
private Timer _timer;
private Timer _speedMonior;
private State _state;
private List<Server> _servers;
private StatisticsStrategyConfiguration _config;

private const string Empty = "";
public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1);

public static string AvailabilityStatisticsFile;
//speed in KiB/s
private int _inboundSpeed = 0;
private int _outboundSpeed = 0;
private int? _latency = 0;
private Server _currentServer;
private Configuration _globalConfig;
private ShadowsocksController _controller;
private long _lastInboundCounter = 0;
private long _lastOutboundCounter = 0;
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2); //retry 2 minutes after failed
private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
private StatisticsStrategyConfiguration _config;
private ShadowsocksController _controller;
private Server _currentServer;
//speed in KiB/s
private List<int> _inboundSpeedRecords;
private long _lastInboundCounter;
private long _lastOutboundCounter;
private List<int> _latencyRecords;
private List<int> _outboundSpeedRecords;
private Timer _recorder;
private List<Server> _servers;
private Timer _speedMonior;
private Timer _writer;

//static constructor to initialize every public static fields before refereced
static AvailabilityStatistics()
@@ -62,6 +49,18 @@ namespace Shadowsocks.Controller
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName);
}

private AvailabilityStatistics()
{
RawStatistics = new Statistics();
}

// Static Singleton Initialization
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
public Statistics RawStatistics { get; private set; }
public Statistics FilteredStatistics { get; private set; }
private int Repeat => _config.RepeatTimesNum;
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(_config.DataCollectionMinutes);

public bool Set(StatisticsStrategyConfiguration config)
{
_config = config;
@@ -69,15 +68,23 @@ namespace Shadowsocks.Controller
{
if (config.StatisticsEnabled)
{
if (_timer?.Change(_delayBeforeStart, Interval) == null)
if (_recorder?.Change(_delayBeforeStart, RecordingInterval) == null)
{
_recorder = new Timer(Run, null, _delayBeforeStart, RecordingInterval);
}
LoadRawStatistics();
if (_speedMonior?.Change(_delayBeforeStart, _monitorInterval) == null)
{
_speedMonior = new Timer(UpdateSpeed, null, _delayBeforeStart, _monitorInterval);
}
if (_writer?.Change(_delayBeforeStart, RecordingInterval) == null)
{
_state = new State();
_timer = new Timer(Run, _state, _delayBeforeStart, Interval);
_writer = new Timer(Save, null, _delayBeforeStart, RecordingInterval);
}
}
else
{
_timer?.Dispose();
_recorder?.Dispose();
_speedMonior?.Dispose();
}
return true;
@@ -93,51 +100,49 @@ namespace Shadowsocks.Controller
{
var bytes = _controller.inboundCounter - _lastInboundCounter;
_lastInboundCounter = _controller.inboundCounter;
var inboundSpeed = GetSpeedInKiBPerSecond(bytes ,_monitorInterval.TotalSeconds);
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);

if (inboundSpeed > _inboundSpeed)
{
_inboundSpeed = inboundSpeed;
}
if (outboundSpeed > _outboundSpeed)
{
_outboundSpeed = outboundSpeed;
}
Logging.Debug($"{_currentServer.FriendlyName()}: current/max inbound {inboundSpeed}/{_inboundSpeed} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeed} KiB/s");
Logging.Debug(
$"{_currentServer.FriendlyName()}: current/max inbound {inboundSpeed}/{_inboundSpeedRecords.Max()} KiB/s, current/max outbound {outboundSpeed}/{_outboundSpeedRecords.Max()} KiB/s");
}

private async Task<List<DataList>> ICMPTest(Server server)
private async Task<ICMPResult> ICMPTest(Server server)
{
Logging.Debug("Ping " + server.FriendlyName());
if (server.server == "") return null;
var ret = new List<DataList>();
try {
var IP = Dns.GetHostAddresses(server.server).First(ip => (ip.AddressFamily == AddressFamily.InterNetwork || ip.AddressFamily == AddressFamily.InterNetworkV6));
var result = new ICMPResult(server);
try
{
var IP =
Dns.GetHostAddresses(server.server)
.First(
ip =>
ip.AddressFamily == AddressFamily.InterNetwork ||
ip.AddressFamily == AddressFamily.InterNetworkV6);
var ping = new Ping();

foreach (var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern)))
foreach (var _ in Enumerable.Range(0, Repeat))
{
//ICMP echo. we can also set options and special bytes
try
{
var reply = await ping.SendTaskAsync(IP, TimeoutMilliseconds);
ret.Add(new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Timestamp", timestamp),
new KeyValuePair<string, string>("Server", server.FriendlyName()),
new KeyValuePair<string, string>("Status", reply?.Status.ToString()),
new KeyValuePair<string, string>("RoundtripTime", reply?.RoundtripTime.ToString()),
new KeyValuePair<string, string>("Latency", GetRecentLatency(server)),
new KeyValuePair<string, string>("InboundSpeed", GetRecentInboundSpeed(server)),
new KeyValuePair<string, string>("OutboundSpeed", GetRecentOutboundSpeed(server))
//new KeyValuePair<string, string>("data", reply.Buffer.ToString()); // The data of reply
});
Thread.Sleep(TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds);
if (!reply.Status.Equals(IPStatus.Success))
{
result.RoundtripTime.Add((int?) reply.RoundtripTime);
}
else
{
result.RoundtripTime.Add(null);
}

//Do ICMPTest in a random frequency
Thread.Sleep(TimeoutMilliseconds + new Random().Next()%TimeoutMilliseconds);
}
catch (Exception e)
{
@@ -145,63 +150,73 @@ namespace Shadowsocks.Controller
Logging.LogUsefulException(e);
}
}
}catch(Exception e)
}
catch (Exception e)
{
Logging.Error($"An exception occured while eveluating {server.FriendlyName()}");
Logging.LogUsefulException(e);
}
return ret;
return result;
}


private string GetRecentOutboundSpeed(Server server)
private void Reset()
{
return server != _currentServer ? Empty : _outboundSpeed.ToString();
_inboundSpeedRecords = new List<int>();
_outboundSpeedRecords = new List<int>();
_latencyRecords = new List<int>();
}

private string GetRecentInboundSpeed(Server server)
private void Run(object _)
{
return server != _currentServer ? Empty : _inboundSpeed.ToString();
AppendRecord();
Reset();
FilterRawStatistics();
}

private string GetRecentLatency(Server server)
private async void AppendRecord()
{
if (server != _currentServer) return Empty;
return _latency == null ? Empty : _latency.ToString();
}
//todo: option for icmp test
var icmpResults = TaskEx.WhenAll(_servers.Select(ICMPTest));

private void ResetSpeed()
{
_currentServer = _controller.GetCurrentServer();
_latency = null;
_inboundSpeed = 0;
_outboundSpeed = 0;
}
var currentServerRecord = new StatisticsRecord(_currentServer.Identifier(),
_inboundSpeedRecords, _outboundSpeedRecords, _latencyRecords);

private void Run(object obj)
{
if (_speedMonior?.Change(_delayBeforeStart, _monitorInterval) == null)
foreach (var result in (await icmpResults).Where(result => result != null))
{
_speedMonior = new Timer(UpdateSpeed, null, _delayBeforeStart, _monitorInterval);
List<StatisticsRecord> records;
if (!RawStatistics.TryGetValue(result.Server.Identifier(), out records))
{
records = new List<StatisticsRecord>();
}

if (result.Server.Equals(_currentServer))
{
currentServerRecord.setResponse(result.RoundtripTime);
records.Add(currentServerRecord);
}
else
{
records.Add(new StatisticsRecord(result.Server.Identifier(), result.RoundtripTime));
}
RawStatistics[result.Server.Identifier()] = records;
}
LoadRawStatistics();
FilterRawStatistics();
Evaluate();
ResetSpeed();
}

private async void Evaluate()
private void Save(object _)
{
foreach (var dataLists in await TaskEx.WhenAll(_servers.Select(ICMPTest)))
try
{
if (dataLists == null) continue;
foreach (var dataList in dataLists.Where(dataList => dataList != null))
{
Append(dataList, Enumerable.Empty<DataUnit>());
}
File.WriteAllText(AvailabilityStatisticsFile,
JsonConvert.SerializeObject(RawStatistics, Formatting.None));
}
catch (IOException e)
{
Logging.LogUsefulException(e);
_writer.Change(_retryInterval, _writingInterval);
}
}

/*
private static void Append(DataList dataList, IEnumerable<DataUnit> extra)
{
var data = dataList.Concat(extra);
@@ -225,15 +240,28 @@ namespace Shadowsocks.Controller
Logging.LogUsefulException(e);
}
}
*/

internal void UpdateConfiguration(ShadowsocksController controller)
{
_controller = controller;
ResetSpeed();
_currentServer = _controller.GetCurrentServer();
Reset();
Set(controller.StatisticsConfiguration);
_servers = _controller.GetCurrentConfiguration().configs;
}

private bool IsValidRecord(StatisticsRecord record)
{
if (_config.ByHourOfDay)
{
var currentHour = DateTime.Now.Hour;
if (record.Timestamp == UnknownDateTime) return false;
if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
}
return true;
}

private void FilterRawStatistics()
{
if (RawStatistics == null) return;
@@ -241,20 +269,12 @@ namespace Shadowsocks.Controller
{
FilteredStatistics = new Statistics();
}
foreach (IEnumerable<RawStatisticsData> rawData in RawStatistics.Values)

foreach (var serverAndRecords in RawStatistics)
{
var filteredData = rawData;
if (_config.ByHourOfDay)
{
var currentHour = DateTime.Now.Hour;
filteredData = filteredData.Where(data =>
data.Timestamp != UnknownDateTime && data.Timestamp.Hour.Equals(currentHour)
);
if (filteredData.LongCount() == 0) return;
}
var dataList = filteredData as List<RawStatisticsData> ?? filteredData.ToList();
var serverName = dataList[0].ServerName;
FilteredStatistics[serverName] = dataList;
var server = serverAndRecords.Key;
var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
FilteredStatistics[server] = filteredRecords;
}
}

@@ -266,36 +286,26 @@ namespace Shadowsocks.Controller
Logging.Debug($"loading statistics from {path}");
if (!File.Exists(path))
{
try {
try
{
using (var fs = File.Create(path))
{
//do nothing
}
}catch(Exception e)
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
if (!File.Exists(path)) {
Console.WriteLine($"statistics file does not exist, try to reload {_retryInterval.TotalMinutes} minutes later");
_timer.Change(_retryInterval, Interval);
if (!File.Exists(path))
{
Console.WriteLine(
$"statistics file does not exist, try to reload {_retryInterval.TotalMinutes} minutes later");
_recorder.Change(_retryInterval, RecordingInterval);
return;
}
}
RawStatistics = (from l in File.ReadAllLines(path).Skip(1)
let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
let rawData = new RawStatisticsData
{
Timestamp = ParseExactOrUnknown(strings[0]),
ServerName = strings[1],
ICMPStatus = strings[2],
RoundtripTime = int.Parse(strings[3])
}
group rawData by rawData.ServerName into server
select new
{
ServerName = server.Key,
data = server.ToList()
}).ToDictionary(server => server.ServerName, server => server.data);
RawStatistics = JsonConvert.DeserializeObject<Statistics>(File.ReadAllText(path)) ?? RawStatistics;
}
catch (Exception e)
{
@@ -306,41 +316,31 @@ namespace Shadowsocks.Controller
private DateTime ParseExactOrUnknown(string str)
{
DateTime dateTime;
return !DateTime.TryParseExact(str, DateTimePattern, null, DateTimeStyles.None, out dateTime) ? UnknownDateTime : dateTime;
return !DateTime.TryParseExact(str, DateTimePattern, null, DateTimeStyles.None, out dateTime)
? UnknownDateTime
: dateTime;
}

public class State
public void UpdateLatency(int latency)
{
public DataList dataList = new DataList();
public const string Unknown = "Unknown";
_latencyRecords.Add(latency);
}

//TODO: redesign model
public class RawStatisticsData
private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
{
public DateTime Timestamp;
public string ServerName;
public string ICMPStatus;
public int RoundtripTime;
var result = (int) (bytes/seconds)/1024;
return result;
}

public class StatisticsData
private class ICMPResult
{
public float PackageLoss;
public int AverageResponse;
public int MinResponse;
public int MaxResponse;
}
internal readonly List<int?> RoundtripTime = new List<int?>();
internal readonly Server Server;

public void UpdateLatency(int latency)
{
_latency = latency;
}

private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
{
var result = (int) (bytes / seconds) / 1024;
return result;
internal ICMPResult(Server server)
{
Server = server;
}
}
}
}
}

+ 21
- 18
shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs View File

@@ -11,12 +11,14 @@ using Shadowsocks.Model;

namespace Shadowsocks.Controller.Strategy
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;
class StatisticsStrategy : IStrategy
{
private readonly ShadowsocksController _controller;
private Server _currentServer;
private readonly Timer _timer;
private Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>> _filteredStatistics;
private Statistics _filteredStatistics;
private AvailabilityStatistics Service => _controller.availabilityStatistics;
private int ChoiceKeptMilliseconds
=> (int) TimeSpan.FromMinutes(_controller.StatisticsConfiguration.ChoiceKeptMinutes).TotalMilliseconds;

@@ -39,7 +41,10 @@ namespace Shadowsocks.Controller.Strategy

private void LoadStatistics()
{
_filteredStatistics = _controller.availabilityStatistics.RawStatistics ?? _filteredStatistics ?? new Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>();
_filteredStatistics =
Service.FilteredStatistics ??
Service.RawStatistics ??
_filteredStatistics;
}

//return the score by data
@@ -47,28 +52,26 @@ namespace Shadowsocks.Controller.Strategy
private float GetScore(string serverName)
{
var config = _controller.StatisticsConfiguration;
List<AvailabilityStatistics.RawStatisticsData> dataList;
if (_filteredStatistics == null || !_filteredStatistics.TryGetValue(serverName, out dataList)) return 0;
var successTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.Success.ToString()));
var timedOutTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.TimedOut.ToString()));
var statisticsData = new AvailabilityStatistics.StatisticsData
{
PackageLoss = timedOutTimes/(successTimes + timedOutTimes)*100,
AverageResponse = Convert.ToInt32(dataList.Average(data => data.RoundtripTime)),
MinResponse = dataList.Min(data => data.RoundtripTime),
MaxResponse = dataList.Max(data => data.RoundtripTime)
};
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 += statisticsData.PackageLoss*factor;
score += averageRecord.PackageLoss*factor ?? 0;
if (!config.Calculations.TryGetValue("AverageResponse", out factor)) factor = 0;
score += statisticsData.AverageResponse*factor;
score += averageRecord.AverageResponse*factor ?? 0;
if (!config.Calculations.TryGetValue("MinResponse", out factor)) factor = 0;
score += statisticsData.MinResponse*factor;
score += averageRecord.MinResponse*factor ?? 0;
if (!config.Calculations.TryGetValue("MaxResponse", out factor)) factor = 0;
score += statisticsData.MaxResponse*factor;
Logging.Debug($"{serverName} {JsonConvert.SerializeObject(statisticsData)}");
score += averageRecord.MaxResponse*factor ?? 0;
Logging.Debug($"{JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}");
return score;
}



+ 5
- 0
shadowsocks-csharp/Model/Server.cs View File

@@ -95,5 +95,10 @@ namespace Shadowsocks.Model
throw new FormatException();
}
}
public string Identifier()
{
return server + ':' + server_port;
}
}
}

+ 76
- 0
shadowsocks-csharp/Model/StatisticsRecord.cs View File

@@ -0,0 +1,76 @@
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;

public string ServerName;

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

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

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

// if user disabled ping test, response would be null
public int? AverageResponse;
public int? MinResponse;
public int? MaxResponse;
public float? PackageLoss;

public StatisticsRecord(string identifier, IEnumerable<int> inboundSpeedRecords, IEnumerable<int> outboundSpeedRecords, IEnumerable<int> latencyRecords)
{
Timestamp = DateTime.Now;
ServerName = identifier;
if (inboundSpeedRecords != null && inboundSpeedRecords.Any())
{
AverageInboundSpeed = (int) inboundSpeedRecords.Average();
MinInboundSpeed = inboundSpeedRecords.Min();
MaxInboundSpeed = inboundSpeedRecords.Max();
}
if (outboundSpeedRecords != null && outboundSpeedRecords.Any())
{
AverageOutboundSpeed = (int) outboundSpeedRecords.Average();
MinOutboundSpeed = outboundSpeedRecords.Min();
MaxOutboundSpeed = outboundSpeedRecords.Max();
}
if (latencyRecords != null && latencyRecords.Any())
{
AverageLatency = (int) latencyRecords.Average();
MinLatency = latencyRecords.Min();
MaxLatency = latencyRecords.Max();
}
}

public StatisticsRecord(string identifier, IEnumerable<int?> responseRecords)
{
Timestamp = DateTime.Now;
ServerName = identifier;
setResponse(responseRecords);
}

public void setResponse(IEnumerable<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
- 3
shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs View File

@@ -61,9 +61,7 @@ namespace Shadowsocks.Model

public StatisticsStrategyConfiguration()
{
var availabilityStatisticsType = typeof(AvailabilityStatistics);
var statisticsData = availabilityStatisticsType.GetNestedType("StatisticsData");
var properties = statisticsData.GetFields(BindingFlags.Instance | BindingFlags.Public);
var properties = typeof(StatisticsRecord).GetFields(BindingFlags.Instance | BindingFlags.Public);
Calculations = properties.ToDictionary(p => p.Name, _ => (float)0);
}



+ 6
- 8
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs View File

@@ -10,6 +10,7 @@ using Shadowsocks.Model;

namespace Shadowsocks.View
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;
public partial class StatisticsStrategyConfigurationForm : Form
{
private readonly ShadowsocksController _controller;
@@ -86,9 +87,9 @@ namespace Shadowsocks.View

//return directly when no data is usable
if (_controller.availabilityStatistics?.FilteredStatistics == null) return;
List<AvailabilityStatistics.RawStatisticsData> statistics;
List<StatisticsRecord> statistics;
if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return;
IEnumerable<IGrouping<int, AvailabilityStatistics.RawStatisticsData>> dataGroups;
IEnumerable<IGrouping<int, StatisticsRecord>> dataGroups;
if (allMode.Checked)
{
dataGroups = statistics.GroupBy(data => data.Timestamp.DayOfYear);
@@ -105,12 +106,9 @@ namespace Shadowsocks.View
orderby dataGroup.Key
select new
{
Timestamp = dataGroup.First().Timestamp,
Ping = (int)dataGroup.Average(data => data.RoundtripTime),
PackageLoss = (int)
(dataGroup.Count(data => data.ICMPStatus.Equals(IPStatus.TimedOut.ToString()))
/ (float)dataGroup.Count() * 100)
};
dataGroup.First().Timestamp,
Ping = (int)dataGroup.Average(data => data.AverageResponse),
PackageLoss = dataGroup.Average(data => data.PackageLoss)};
foreach (var data in finalData)
{
_dataTable.Rows.Add(data.Timestamp, data.PackageLoss, data.Ping);


+ 1
- 0
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -195,6 +195,7 @@
<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="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>


Loading…
Cancel
Save