Files
TimberWinR/TimberWinR/NStatsD/Client.cs
2015-05-15 09:53:24 -04:00

179 lines
6.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
namespace NStatsD
{
public sealed class Client
{
private static UdpClient _client;
public static string Host { get; set; }
public static int Port { get; set; }
Client()
{
if (Config != null)
{
var host = Config.Server.Host;
var port = Config.Server.Port;
_client = new UdpClient(host, port);
}
else
{
Config = new StatsDConfigurationSection();
Config.Server.Host = Host;;
Config.Server.Port = Port;
_client = new UdpClient(Host, Port);
}
}
public static Client Current
{
get { return CurrentClient.Instance; }
}
class CurrentClient
{
static CurrentClient() { }
internal static readonly Client Instance = new Client();
}
private StatsDConfigurationSection _config;
public StatsDConfigurationSection Config
{
get
{
if (_config == null)
{
_config = (StatsDConfigurationSection)ConfigurationManager.GetSection("statsD");
}
if(_config != null)
_config.Prefix = ValidatePrefix(_config.Prefix);
return _config;
}
set
{
_config = value;
_config.Prefix = ValidatePrefix(_config.Prefix);
}
}
private string ValidatePrefix(string prefix)
{
if (string.IsNullOrWhiteSpace(prefix))
return prefix;
if (prefix.EndsWith("."))
return prefix;
return string.Format("{0}.", prefix);
}
/// <summary>
/// Sends timing statistics.
/// </summary>
/// <param name="stat">Name of statistic being updated.</param>
/// <param name="time">The timing it took to complete.</param>
/// <param name="sampleRate">Tells StatsD how often to sample this value. Defaults to 1 (send all values).</param>
/// <param name="callback">A callback for when the send is complete. Defaults to null.</param>
public void Timing(string stat, long time, double sampleRate = 1, AsyncCallback callback = null)
{
var data = new Dictionary<string, string> { { stat, string.Format("{0}|ms", time) } };
Send(data, sampleRate, callback);
}
/// <summary>
/// Increments a counter
/// </summary>
/// <param name="stat">Name of statistic being updated.</param>
/// <param name="sampleRate">Tells StatsD how often to sample this value. Defaults to 1 (send all values).</param>
/// <param name="callback">A callback for when the send is complete. Defaults to null.</param>
public void Increment(string stat, double sampleRate = 1, AsyncCallback callback = null)
{
UpdateStats(stat, 1, sampleRate, callback);
}
/// <summary>
/// Decrements a counter
/// </summary>
/// <param name="stat">Name of statistic being updated.</param>
/// <param name="sampleRate">Tells StatsD how often to sample this value. Defaults to 1 (send all values).</param>
/// <param name="callback">A callback for when the send is complete. Defaults to null.</param>
public void Decrement(string stat, double sampleRate = 1, AsyncCallback callback = null)
{
UpdateStats(stat, -1, sampleRate, callback);
}
/// <summary>
/// Updates a counter by an arbitrary amount
/// </summary>
/// <param name="stat">Name of statistic being updated.</param>
/// <param name="value">The value of the metric.</param>
/// <param name="sampleRate">Tells StatsD how often to sample this value. Defaults to 1 (send all values).</param>
/// <param name="callback">A callback for when the send is complete. Defaults to null.</param>
public void Gauge(string stat, int value, double sampleRate = 1, AsyncCallback callback = null)
{
var data = new Dictionary<string, string> { { stat, string.Format("{0}|g", value) } };
Send(data, sampleRate, callback);
}
/// <summary>
/// Updates a counter by an arbitrary amount
/// </summary>
/// <param name="stat">Name of statistic(s) being updated.</param>
/// <param name="delta">The amount to adjust the counter</param>
/// <param name="sampleRate">Tells StatsD how often to sample this value. Defaults to 1 (send all values).</param>
/// <param name="callback">A callback for when the send is complete. Defaults to null.</param>
public void UpdateStats(string stat, int delta = 1, double sampleRate = 1, AsyncCallback callback = null)
{
var dictionary = new Dictionary<string, string> { { stat, string.Format("{0}|c", delta) } };
Send(dictionary, sampleRate, callback);
}
private static int _seed = Environment.TickCount;
private static readonly ThreadLocal<Random> random = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref _seed)));
private void Send(Dictionary<string, string> data, double sampleRate, AsyncCallback callback)
{
if (!Config.Enabled)
return;
if (sampleRate < 1)
{
var nextRand = random.Value.NextDouble();
if (nextRand <= sampleRate)
{
var sampledData = data.Keys.ToDictionary(stat => stat,
stat => string.Format("{0}|@{1}", data[stat], sampleRate));
SendToStatsD(sampledData, callback);
}
}
else
{
SendToStatsD(data, callback);
}
}
private void SendToStatsD(Dictionary<string, string> sampledData, AsyncCallback callback)
{
var prefix = Config.Prefix;
var encoding = new System.Text.ASCIIEncoding();
foreach (var stat in sampledData.Keys)
{
var stringToSend = string.Format("{0}{1}:{2}", prefix, stat, sampledData[stat]);
var sendData = encoding.GetBytes(stringToSend);
_client.BeginSend(sendData, sendData.Length, callback, null);
}
}
}
}