Browse Source

Merge pull request #265 from icylogic/feature/availability_log

Add availability statistics service and a simple strategy
tags/2.5.3
clowwindy 10 years ago
parent
commit
f5a63ef1cf
8 changed files with 315 additions and 0 deletions
  1. +109
    -0
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  2. +17
    -0
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  3. +176
    -0
      shadowsocks-csharp/Controller/Strategy/SimplyChooseByStatisticsStrategy.cs
  4. +1
    -0
      shadowsocks-csharp/Controller/Strategy/StrategyManager.cs
  5. +1
    -0
      shadowsocks-csharp/Data/cn.txt
  6. +1
    -0
      shadowsocks-csharp/Model/Configuration.cs
  7. +8
    -0
      shadowsocks-csharp/View/MenuViewController.cs
  8. +2
    -0
      shadowsocks-csharp/shadowsocks-csharp.csproj

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

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using Shadowsocks.Model;
using System.Reflection;

namespace Shadowsocks.Controller
{
class AvailabilityStatistics
{
private static readonly string StatisticsFilesName = "shadowsocks.availability.csv";
private static readonly string Delimiter = ",";
private static readonly int Timeout = 500;
private static readonly int Repeat = 4; //repeat times every evaluation
private static readonly int Interval = 10 * 60 * 1000; //evaluate proxies every 15 minutes
private Timer timer = null;
private State state = null;
private List<Server> servers;

public static string AvailabilityStatisticsFile;

//static constructor to initialize every public static fields before refereced
static AvailabilityStatistics()
{
string temppath = Path.GetTempPath();
AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName);
}

public bool Set(bool enabled)
{
try
{
if (enabled)
{
if (timer?.Change(0, Interval) == null)
{
state = new State();
timer = new Timer(Evaluate, state, 0, Interval);
}
}
else
{
timer?.Dispose();
}
return true;
}
catch (Exception e)
{
Logging.LogUsefulException(e);
return false;
}
}

private void Evaluate(object obj)
{
Ping ping = new Ping();
State state = (State) obj;
foreach (var server in servers)
{
Logging.Debug("eveluating " + server.FriendlyName());
foreach (var _ in Enumerable.Range(0, Repeat))
{
//TODO: do simple analyze of data to provide friendly message, like package loss.
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
//ICMP echo. we can also set options and special bytes
//seems no need to use SendPingAsync
PingReply reply = ping.Send(server.server, Timeout);
state.data = new List<KeyValuePair<string, string>>();
state.data.Add(new KeyValuePair<string, string>("Timestamp", timestamp));
state.data.Add(new KeyValuePair<string, string>("Server", server.FriendlyName()));
state.data.Add(new KeyValuePair<string, string>("Status", reply.Status.ToString()));
state.data.Add(new KeyValuePair<string, string>("RoundtripTime", reply.RoundtripTime.ToString()));
//state.data.Add(new KeyValuePair<string, string>("data", reply.Buffer.ToString())); // The data of reply
Append(state.data);
}
}
}

private static void Append(List<KeyValuePair<string, string>> data)
{
string dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray());
string[] lines;
if (!File.Exists(AvailabilityStatisticsFile))
{
string headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray());
lines = new string[] { headerLine, dataLine };
}
else
{
lines = new string[] { dataLine };
}
File.AppendAllLines(AvailabilityStatisticsFile, lines);
}

internal void UpdateConfiguration(Configuration _config)
{
Set(_config.availabilityStatistics);
servers = _config.configs;
}

private class State
{
public List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>();
}
}
}

+ 17
- 0
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -25,6 +25,7 @@ namespace Shadowsocks.Controller
private StrategyManager _strategyManager; private StrategyManager _strategyManager;
private PolipoRunner polipoRunner; private PolipoRunner polipoRunner;
private GFWListUpdater gfwListUpdater; private GFWListUpdater gfwListUpdater;
private AvailabilityStatistics _availabilityStatics;
private bool stopped = false; private bool stopped = false;
private bool _systemProxyIsDirty = false; private bool _systemProxyIsDirty = false;
@@ -246,6 +247,16 @@ namespace Shadowsocks.Controller
} }
} }
public void ToggleAvailabilityStatistics(bool enabled)
{
if (_availabilityStatics != null)
{
_availabilityStatics.Set(enabled);
_config.availabilityStatistics = enabled;
SaveConfig(_config);
}
}
public void SavePACUrl(string pacUrl) public void SavePACUrl(string pacUrl)
{ {
_config.pacUrl = pacUrl; _config.pacUrl = pacUrl;
@@ -295,6 +306,12 @@ namespace Shadowsocks.Controller
_listener.Stop(); _listener.Stop();
} }
if (_availabilityStatics == null)
{
_availabilityStatics = new AvailabilityStatistics();
_availabilityStatics.UpdateConfiguration(_config);
}
// don't put polipoRunner.Start() before pacServer.Stop() // don't put polipoRunner.Start() before pacServer.Stop()
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 // or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
// though UseShellExecute is set to true now // though UseShellExecute is set to true now


+ 176
- 0
shadowsocks-csharp/Controller/Strategy/SimplyChooseByStatisticsStrategy.cs View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using Shadowsocks.Model;
using System.IO;
using System.Net.NetworkInformation;
using System.Threading;

namespace Shadowsocks.Controller.Strategy
{
class SimplyChooseByStatisticsStrategy : IStrategy
{
private ShadowsocksController _controller;
private Server _currentServer;
private Timer timer;
private Dictionary<string, StatisticsData> statistics;
private static readonly int CachedInterval = 30 * 60 * 1000; //choose a new server every 30 minutes

public SimplyChooseByStatisticsStrategy(ShadowsocksController controller)
{
_controller = controller;
var servers = controller.GetCurrentConfiguration().configs;
int randomIndex = new Random().Next() % servers.Count();
_currentServer = servers[randomIndex]; //choose a server randomly at first
timer = new Timer(ReloadStatisticsAndChooseAServer);
}

private void ReloadStatisticsAndChooseAServer(object obj)
{
Logging.Debug("Reloading statistics and choose a new server....");
List<Server> servers = _controller.GetCurrentConfiguration().configs;
LoadStatistics();
ChooseNewServer(servers);
}

/*
return a dict:
{
'ServerFriendlyName1':StatisticsData,
'ServerFriendlyName2':...
}
*/
private void LoadStatistics()
{
try
{
var path = AvailabilityStatistics.AvailabilityStatisticsFile;
Logging.Debug(string.Format("loading statistics from{0}", path));
statistics = (from l in File.ReadAllLines(path)
.Skip(1)
let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
let rawData = new
{
ServerName = strings[1],
IPStatus = strings[2],
RoundtripTime = int.Parse(strings[3])
}
group rawData by rawData.ServerName into server
select new
{
ServerName = server.Key,
data = new StatisticsData
{
SuccessTimes = server.Count(data => IPStatus.Success.ToString().Equals(data.IPStatus)),
TimedOutTimes = server.Count(data => IPStatus.TimedOut.ToString().Equals(data.IPStatus)),
AverageResponse = Convert.ToInt32(server.Average(data => data.RoundtripTime)),
MinResponse = server.Min(data => data.RoundtripTime),
MaxResponse = server.Max(data => data.RoundtripTime)
}
}).ToDictionary(server => server.ServerName, server => server.data);
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
}

//return the score by data
//server with highest score will be choosen
private static double GetScore(StatisticsData data)
{
return (double)data.SuccessTimes / (data.SuccessTimes + data.TimedOutTimes); //simply choose min package loss
}

private class StatisticsData
{
public int SuccessTimes;
public int TimedOutTimes;
public int AverageResponse;
public int MinResponse;
public int MaxResponse;
}

private void ChooseNewServer(List<Server> servers)
{
if (statistics == null)
{
return;
}
try
{
var bestResult = (from server in servers
let name = server.FriendlyName()
where statistics.ContainsKey(name)
select new
{
server,
score = GetScore(statistics[name])
}
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2);

if (_controller.GetCurrentStrategy().ID == ID && _currentServer != bestResult.server) //output when enabled
{
Console.WriteLine("Switch to server: {0} by package loss:{1}", bestResult.server.FriendlyName(), 1 - bestResult.score);
}
_currentServer = bestResult.server;
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
}

public string ID
{
get { return "com.shadowsocks.strategy.scbs"; }
}

public string Name
{
get { return I18N.GetString("Choose By Total Package Loss"); }
}

public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint)
{
var oldServer = _currentServer;
if (oldServer == null)
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
}
if (oldServer != _currentServer)
{
}
return _currentServer; //current server cached for CachedInterval
}

public void ReloadServers()
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
timer?.Change(0, CachedInterval);
}

public void SetFailure(Server server)
{
Logging.Debug(String.Format("failure: {0}", server.FriendlyName()));
}

public void UpdateLastRead(Server server)
{
//TODO: combine this part of data with ICMP statics
}

public void UpdateLastWrite(Server server)
{
//TODO: combine this part of data with ICMP statics
}

public void UpdateLatency(Server server, TimeSpan latency)
{
//TODO: combine this part of data with ICMP statics
}

}
}

+ 1
- 0
shadowsocks-csharp/Controller/Strategy/StrategyManager.cs View File

@@ -13,6 +13,7 @@ namespace Shadowsocks.Controller.Strategy
_strategies = new List<IStrategy>(); _strategies = new List<IStrategy>();
_strategies.Add(new BalancingStrategy(controller)); _strategies.Add(new BalancingStrategy(controller));
_strategies.Add(new HighAvailabilityStrategy(controller)); _strategies.Add(new HighAvailabilityStrategy(controller));
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller));
// TODO: load DLL plugins // TODO: load DLL plugins
} }
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()


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

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


# Config Form # Config Form




+ 1
- 0
shadowsocks-csharp/Model/Configuration.cs View File

@@ -22,6 +22,7 @@ namespace Shadowsocks.Model
public int localPort; public int localPort;
public string pacUrl; public string pacUrl;
public bool useOnlinePac; public bool useOnlinePac;
public bool availabilityStatistics;
private static string CONFIG_FILE = "gui-config.json"; private static string CONFIG_FILE = "gui-config.json";


+ 8
- 0
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -29,6 +29,7 @@ namespace Shadowsocks.View
private MenuItem enableItem; private MenuItem enableItem;
private MenuItem modeItem; private MenuItem modeItem;
private MenuItem AutoStartupItem; private MenuItem AutoStartupItem;
private MenuItem AvailabilityStatistics;
private MenuItem ShareOverLANItem; private MenuItem ShareOverLANItem;
private MenuItem SeperatorItem; private MenuItem SeperatorItem;
private MenuItem ConfigItem; private MenuItem ConfigItem;
@@ -177,6 +178,7 @@ namespace Shadowsocks.View
}), }),
new MenuItem("-"), new MenuItem("-"),
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)),
this.AvailabilityStatistics = CreateMenuItem("Availability Statistics", new EventHandler(this.AvailabilityStatisticsItem_Click)),
this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)),
new MenuItem("-"), new MenuItem("-"),
CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)),
@@ -260,6 +262,7 @@ namespace Shadowsocks.View
PACModeItem.Checked = !config.global; PACModeItem.Checked = !config.global;
ShareOverLANItem.Checked = config.shareOverLan; ShareOverLANItem.Checked = config.shareOverLan;
AutoStartupItem.Checked = AutoStartup.Check(); AutoStartupItem.Checked = AutoStartup.Check();
AvailabilityStatistics.Checked = config.availabilityStatistics;
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac;
localPACItem.Checked = !onlinePACItem.Checked; localPACItem.Checked = !onlinePACItem.Checked;
UpdatePACItemsEnabledStatus(); UpdatePACItemsEnabledStatus();
@@ -524,6 +527,11 @@ namespace Shadowsocks.View
} }
} }
private void AvailabilityStatisticsItem_Click(object sender, EventArgs e) {
AvailabilityStatistics.Checked = !AvailabilityStatistics.Checked;
controller.ToggleAvailabilityStatistics(AvailabilityStatistics.Checked);
}
private void LocalPACItem_Click(object sender, EventArgs e) private void LocalPACItem_Click(object sender, EventArgs e)
{ {
if (!localPACItem.Checked) if (!localPACItem.Checked)


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

@@ -123,7 +123,9 @@
<Compile Include="3rd\zxing\ResultPoint.cs" /> <Compile Include="3rd\zxing\ResultPoint.cs" />
<Compile Include="3rd\zxing\ResultPointCallback.cs" /> <Compile Include="3rd\zxing\ResultPointCallback.cs" />
<Compile Include="3rd\zxing\WriterException.cs" /> <Compile Include="3rd\zxing\WriterException.cs" />
<Compile Include="Controller\Service\AvailabilityStatistics.cs" />
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> <Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" />
<Compile Include="Controller\Strategy\SimplyChooseByStatisticsStrategy.cs" />
<Compile Include="Controller\System\AutoStartup.cs" /> <Compile Include="Controller\System\AutoStartup.cs" />
<Compile Include="Controller\FileManager.cs" /> <Compile Include="Controller\FileManager.cs" />
<Compile Include="Controller\Service\GFWListUpdater.cs" /> <Compile Include="Controller\Service\GFWListUpdater.cs" />


Loading…
Cancel
Save