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); } /// /// Sends timing statistics. /// /// Name of statistic being updated. /// The timing it took to complete. /// Tells StatsD how often to sample this value. Defaults to 1 (send all values). /// A callback for when the send is complete. Defaults to null. public void Timing(string stat, long time, double sampleRate = 1, AsyncCallback callback = null) { var data = new Dictionary { { stat, string.Format("{0}|ms", time) } }; Send(data, sampleRate, callback); } /// /// Increments a counter /// /// Name of statistic being updated. /// Tells StatsD how often to sample this value. Defaults to 1 (send all values). /// A callback for when the send is complete. Defaults to null. public void Increment(string stat, double sampleRate = 1, AsyncCallback callback = null) { UpdateStats(stat, 1, sampleRate, callback); } /// /// Decrements a counter /// /// Name of statistic being updated. /// Tells StatsD how often to sample this value. Defaults to 1 (send all values). /// A callback for when the send is complete. Defaults to null. public void Decrement(string stat, double sampleRate = 1, AsyncCallback callback = null) { UpdateStats(stat, -1, sampleRate, callback); } /// /// Updates a counter by an arbitrary amount /// /// Name of statistic being updated. /// The value of the metric. /// Tells StatsD how often to sample this value. Defaults to 1 (send all values). /// A callback for when the send is complete. Defaults to null. public void Gauge(string stat, int value, double sampleRate = 1, AsyncCallback callback = null) { var data = new Dictionary { { stat, string.Format("{0}|g", value) } }; Send(data, sampleRate, callback); } /// /// Updates a counter by an arbitrary amount /// /// Name of statistic(s) being updated. /// The amount to adjust the counter /// Tells StatsD how often to sample this value. Defaults to 1 (send all values). /// A callback for when the send is complete. Defaults to null. public void UpdateStats(string stat, int delta = 1, double sampleRate = 1, AsyncCallback callback = null) { var dictionary = new Dictionary { { stat, string.Format("{0}|c", delta) } }; Send(dictionary, sampleRate, callback); } private static int _seed = Environment.TickCount; private static readonly ThreadLocal random = new ThreadLocal(() => new Random(Interlocked.Increment(ref _seed))); private void Send(Dictionary 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 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); } } } }