add speed to statistics
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -23,25 +24,39 @@ namespace Shadowsocks.Controller
|
||||
|
||||
using Statistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>;
|
||||
|
||||
//TODO: change to singleton
|
||||
public class AvailabilityStatistics
|
||||
{
|
||||
public static readonly string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
|
||||
public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
|
||||
private const string StatisticsFilesName = "shadowsocks.availability.csv";
|
||||
private const string Delimiter = ",";
|
||||
private const int Timeout = 500;
|
||||
private const int DelayBeforeStart = 1000;
|
||||
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 const int RetryInterval = 2 * 60 * 1000; //retry 2 minutes after failed
|
||||
private int Interval => (int)TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds;
|
||||
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 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 readonly ShadowsocksController _controller;
|
||||
private long _lastInboundCounter = 0;
|
||||
private long _lastOutboundCounter = 0;
|
||||
private readonly TimeSpan MonitorInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
//static constructor to initialize every public static fields before refereced
|
||||
static AvailabilityStatistics()
|
||||
@@ -49,9 +64,11 @@ namespace Shadowsocks.Controller
|
||||
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName);
|
||||
}
|
||||
|
||||
public AvailabilityStatistics(Configuration config, StatisticsStrategyConfiguration statisticsConfig)
|
||||
public AvailabilityStatistics(ShadowsocksController controller)
|
||||
{
|
||||
UpdateConfiguration(config, statisticsConfig);
|
||||
_controller = controller;
|
||||
_globalConfig = controller.GetCurrentConfiguration();
|
||||
UpdateConfiguration(_globalConfig, controller.StatisticsConfiguration);
|
||||
}
|
||||
|
||||
public bool Set(StatisticsStrategyConfiguration config)
|
||||
@@ -70,6 +87,7 @@ namespace Shadowsocks.Controller
|
||||
else
|
||||
{
|
||||
_timer?.Dispose();
|
||||
_speedMonior?.Dispose();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -80,6 +98,27 @@ namespace Shadowsocks.Controller
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSpeed(object state)
|
||||
{
|
||||
var bytes = _controller.inboundCounter - _lastInboundCounter;
|
||||
_lastInboundCounter = _controller.inboundCounter;
|
||||
var inboundSpeed = GetSpeedInKiBPerSecond(bytes ,MonitorInterval.TotalSeconds);
|
||||
|
||||
bytes = _controller.outboundCounter - _lastOutboundCounter;
|
||||
_lastOutboundCounter = _controller.outboundCounter;
|
||||
var outboundSpeed = GetSpeedInKiBPerSecond(bytes, MonitorInterval.TotalSeconds);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
//hardcode
|
||||
//TODO: backup reliable isp&geolocation provider or a local database is required
|
||||
public static async Task<DataList> GetGeolocationAndIsp()
|
||||
@@ -98,6 +137,7 @@ namespace Shadowsocks.Controller
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: remove geolocation
|
||||
private static async Task<DataList> GetInfoFromAPI(string API)
|
||||
{
|
||||
string jsonString;
|
||||
@@ -146,6 +186,9 @@ namespace Shadowsocks.Controller
|
||||
{
|
||||
new KeyValuePair<string, string>("Timestamp", timestamp),
|
||||
new KeyValuePair<string, string>("Server", server.FriendlyName()),
|
||||
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>("Status", reply?.Status.ToString()),
|
||||
new KeyValuePair<string, string>("RoundtripTime", reply?.RoundtripTime.ToString())
|
||||
//new KeyValuePair<string, string>("data", reply.Buffer.ToString()); // The data of reply
|
||||
@@ -162,11 +205,43 @@ namespace Shadowsocks.Controller
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private string GetRecentOutboundSpeed(Server server)
|
||||
{
|
||||
if (server != _currentServer) return Empty;
|
||||
return _outboundSpeed.ToString();
|
||||
}
|
||||
|
||||
private string GetRecentInboundSpeed(Server server)
|
||||
{
|
||||
if (server != _currentServer) return Empty;
|
||||
return _inboundSpeed.ToString();
|
||||
}
|
||||
|
||||
private string GetRecentLatency(Server server)
|
||||
{
|
||||
if (server != _currentServer) return Empty;
|
||||
return _latency == null ? Empty : _latency.ToString();
|
||||
}
|
||||
|
||||
private void ResetSpeed()
|
||||
{
|
||||
_currentServer = _globalConfig.GetCurrentServer();
|
||||
_latency = null;
|
||||
_inboundSpeed = 0;
|
||||
_outboundSpeed = 0;
|
||||
}
|
||||
|
||||
private void Run(object obj)
|
||||
{
|
||||
if (_speedMonior?.Change(DelayBeforeStart, MonitorInterval) == null)
|
||||
{
|
||||
_speedMonior = new Timer(UpdateSpeed, null, DelayBeforeStart, MonitorInterval);
|
||||
}
|
||||
LoadRawStatistics();
|
||||
FilterRawStatistics();
|
||||
evaluate();
|
||||
ResetSpeed();
|
||||
}
|
||||
|
||||
private async void evaluate()
|
||||
@@ -177,7 +252,6 @@ namespace Shadowsocks.Controller
|
||||
if (dataLists == null) continue;
|
||||
foreach (var dataList in dataLists.Where(dataList => dataList != null))
|
||||
{
|
||||
await geolocationAndIsp;
|
||||
Append(dataList, geolocationAndIsp.Result);
|
||||
}
|
||||
}
|
||||
@@ -211,6 +285,7 @@ namespace Shadowsocks.Controller
|
||||
{
|
||||
Set(statisticsConfig);
|
||||
_servers = config.configs;
|
||||
ResetSpeed();
|
||||
}
|
||||
|
||||
private async void FilterRawStatistics()
|
||||
@@ -258,7 +333,7 @@ namespace Shadowsocks.Controller
|
||||
Logging.Debug($"loading statistics from {path}");
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval / 60 / 1000} minutes later");
|
||||
Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval.TotalMinutes} minutes later");
|
||||
_timer.Change(RetryInterval, Interval);
|
||||
return;
|
||||
}
|
||||
@@ -302,6 +377,7 @@ namespace Shadowsocks.Controller
|
||||
public const string Unknown = "Unknown";
|
||||
}
|
||||
|
||||
//TODO: redesign model
|
||||
public class RawStatisticsData
|
||||
{
|
||||
public DateTime Timestamp;
|
||||
@@ -319,5 +395,16 @@ namespace Shadowsocks.Controller
|
||||
public int MinResponse;
|
||||
public int MaxResponse;
|
||||
}
|
||||
|
||||
public void UpdateLatency(int latency)
|
||||
{
|
||||
_latency = latency;
|
||||
}
|
||||
|
||||
private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
|
||||
{
|
||||
var result = (int) (bytes / seconds) / 1024;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,11 @@ namespace Shadowsocks.Controller
|
||||
{
|
||||
_controller.UpdateOutboundCounter(n);
|
||||
}
|
||||
|
||||
public void UpdateLatency(Server server, TimeSpan latency)
|
||||
{
|
||||
_controller.UpdateLatency(server, latency);
|
||||
}
|
||||
}
|
||||
|
||||
class TCPHandler
|
||||
@@ -126,6 +131,9 @@ namespace Shadowsocks.Controller
|
||||
private object decryptionLock = new object();
|
||||
|
||||
private DateTime _startConnectTime;
|
||||
private DateTime _startReceivingTime;
|
||||
private DateTime _startSendingTime;
|
||||
private int _bytesToSend;
|
||||
private TCPRelay tcprelay; // TODO: tcprelay ?= relay
|
||||
|
||||
public TCPHandler(TCPRelay tcprelay)
|
||||
@@ -484,6 +492,7 @@ namespace Shadowsocks.Controller
|
||||
{
|
||||
strategy.UpdateLatency(server, latency);
|
||||
}
|
||||
tcprelay.UpdateLatency(server, latency);
|
||||
|
||||
StartPipe();
|
||||
}
|
||||
@@ -513,6 +522,7 @@ namespace Shadowsocks.Controller
|
||||
}
|
||||
try
|
||||
{
|
||||
_startReceivingTime = DateTime.Now;
|
||||
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null);
|
||||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null);
|
||||
}
|
||||
@@ -601,13 +611,12 @@ namespace Shadowsocks.Controller
|
||||
}
|
||||
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)");
|
||||
tcprelay.UpdateOutboundCounter(bytesToSend);
|
||||
_startSendingTime = DateTime.Now;
|
||||
_bytesToSend = bytesToSend;
|
||||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null);
|
||||
|
||||
IStrategy strategy = controller.GetCurrentStrategy();
|
||||
if (strategy != null)
|
||||
{
|
||||
strategy.UpdateLastWrite(server);
|
||||
}
|
||||
strategy?.UpdateLastWrite(server);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -343,7 +343,7 @@ namespace Shadowsocks.Controller
|
||||
|
||||
if (availabilityStatistics == null)
|
||||
{
|
||||
availabilityStatistics = new AvailabilityStatistics(_config, StatisticsConfiguration);
|
||||
availabilityStatistics = new AvailabilityStatistics(this);
|
||||
}
|
||||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration);
|
||||
|
||||
@@ -498,5 +498,13 @@ namespace Shadowsocks.Controller
|
||||
Thread.Sleep(30 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLatency(Server server, TimeSpan latency)
|
||||
{
|
||||
if (_config.availabilityStatistics)
|
||||
{
|
||||
availabilityStatistics.UpdateLatency((int) latency.TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user