├── global.json
├── src
├── StatsdClient
│ ├── IStatsdClient.cs
│ ├── StopWatchFactory.cs
│ ├── Stopwatch.cs
│ ├── RandomGenerator.cs
│ ├── MetricsTimer.cs
│ ├── AdressResolution.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── StatsdClient.xproj
│ ├── IStatsd.cs
│ ├── project.json
│ ├── MetricsConfig.cs
│ ├── NullStatsd.cs
│ ├── StatsdTCPClient.cs
│ ├── StatsdUDPClient.cs
│ ├── Metrics.cs
│ └── Statsd.cs
└── Tests
│ ├── project.json
│ ├── A_MetricsTests.cs
│ ├── IPV4ParsingTests.cs
│ ├── UDPSmokeTests.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── Tests.xproj
│ ├── MetricsTests.cs
│ ├── TCPSmokeTests.cs
│ ├── RandomGeneratorUnitTests.cs
│ ├── UdpListener.cs
│ ├── StatsdUDPTests.cs
│ ├── MetricIntegrationTests.cs
│ └── StatsdTests.cs
├── update-projectjson.ps1
├── appveyor.yml
├── MIT-LICENCE.md
├── StatsdClient.sln
├── CONTRIBUTING.md
├── CHANGELOG.md
├── README.md
└── .gitignore
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "1.0.0-preview2-003131"
4 | }
5 | }
--------------------------------------------------------------------------------
/src/StatsdClient/IStatsdClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace StatsdClient
4 | {
5 | public interface IStatsdClient : IDisposable
6 | {
7 | void Send(string command);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/StatsdClient/StopWatchFactory.cs:
--------------------------------------------------------------------------------
1 | namespace StatsdClient
2 | {
3 | public interface IStopWatchFactory
4 | {
5 | IStopwatch Get();
6 | }
7 |
8 | public class StopWatchFactory : IStopWatchFactory
9 | {
10 | public IStopwatch Get()
11 | {
12 | return new Stopwatch();
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/Tests/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0-*",
3 | "testRunner": "nunit",
4 |
5 | "buildOptions": {
6 | "warningsAsErrors": true
7 | },
8 |
9 | "dependencies": {
10 | "dotnet-test-nunit": "3.4.0-beta-3",
11 | "StatsdClient": "1.0.*",
12 | "NUnit": "3.6.0",
13 | "NSubstitute": "1.10.0"
14 | },
15 |
16 | "frameworks": {
17 | "net452": {
18 | "imports": "dnxcore50"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Tests/A_MetricsTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using StatsdClient;
3 |
4 | namespace Tests
5 | {
6 | ///
7 | /// Metrics is static (not thread static), so to consistently test this before .Configure() is called just doing a cheeky A_* naming so it's first alphabetically.
8 | ///
9 | public class A_MetricsTests
10 | {
11 | [Test]
12 | public void defaults_to_null_statsd_to_not_blow_up_when_configure_is_not_called()
13 | {
14 | Assert.DoesNotThrow(() => Metrics.Counter("stat"));
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/update-projectjson.ps1:
--------------------------------------------------------------------------------
1 | # This script sets the project.json version to to be the version of the appveyor build. This is so our nupkg's will have the appveyor build number in them
2 |
3 | $projectJsonFileLocation = "src/StatsdClient/project.json"
4 | $newVersion = $env:APPVEYOR_BUILD_VERSION
5 | if($newVersion -eq $null)
6 | {
7 | return
8 | }
9 |
10 | Write-Host "$projectJsonFileLocation will be update with new version '$newVersion'"
11 |
12 | $json = (Get-Content $projectJsonFileLocation -Raw) | ConvertFrom-Json
13 | $json.version = $newVersion
14 | $json | ConvertTo-Json -depth 100 | Out-File $projectJsonFileLocation
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 3.0.{build}
2 | configuration: Release
3 | before_build:
4 | # matches pinned version in global.json
5 | - ps: invoke-webrequest https://go.microsoft.com/fwlink/?LinkID=830694 -OutFile core.exe
6 | - ps: .\core.exe /install /quiet /norestart
7 | build_script:
8 | - ps: dotnet restore
9 | - ps: dotnet build -c Release .\src\StatsdClient
10 | - ps: dotnet build -c Release .\src\Tests
11 | test_script:
12 | - dotnet test .\src\Tests
13 | after_test:
14 | - ps: powershell -File .\update-projectjson.ps1
15 | - ps: dotnet pack -c Release .\src\StatsdClient --output .
16 | artifacts:
17 | - path: StatsdClient.*.nupkg
18 |
--------------------------------------------------------------------------------
/src/StatsdClient/Stopwatch.cs:
--------------------------------------------------------------------------------
1 | namespace StatsdClient
2 | {
3 | public interface IStopwatch
4 | {
5 | void Start();
6 | void Stop();
7 | int ElapsedMilliseconds { get; }
8 | }
9 |
10 | public class Stopwatch : IStopwatch
11 | {
12 | private readonly System.Diagnostics.Stopwatch _stopwatch = new System.Diagnostics.Stopwatch();
13 |
14 | public void Start()
15 | {
16 | _stopwatch.Start();
17 | }
18 |
19 | public void Stop()
20 | {
21 | _stopwatch.Stop();
22 | }
23 |
24 | public int ElapsedMilliseconds
25 | {
26 | get { return (int)_stopwatch.ElapsedMilliseconds; }
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/StatsdClient/RandomGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace StatsdClient
4 | {
5 | public interface IRandomGenerator
6 | {
7 | bool ShouldSend(double sampleRate);
8 | }
9 |
10 | public class RandomGenerator : IRandomGenerator
11 | {
12 | [ThreadStatic]
13 | static Random _random;
14 |
15 | private static Random Random
16 | {
17 | get
18 | {
19 | if (_random != null) return _random;
20 | return _random = new Random(Guid.NewGuid().GetHashCode());
21 | }
22 | }
23 |
24 | public bool ShouldSend(double sampleRate)
25 | {
26 | return Random.NextDouble() < sampleRate;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/Tests/IPV4ParsingTests.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using StatsdClient;
3 |
4 | namespace Tests
5 | {
6 | [TestFixture]
7 | public class IPV4ParsingTests
8 | {
9 | private const int RandomUnusedLocalPort = 23483;
10 |
11 | [Test]
12 | public void ipv4_parsing_works_with_hostname()
13 | {
14 | var statsdUdp = new StatsdUDPClient("localhost", RandomUnusedLocalPort);
15 | Assert.That(statsdUdp.IPEndpoint.Address.ToString(), Does.Contain("127.0.0.1"));
16 | }
17 |
18 | [Test]
19 | public void ipv4_parsing_works_with_ip()
20 | {
21 | var statsdUdp = new StatsdUDPClient("127.0.0.1", RandomUnusedLocalPort);
22 | Assert.That(statsdUdp.IPEndpoint.Address.ToString(), Does.Contain("127.0.0.1"));
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/src/Tests/UDPSmokeTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using NUnit.Framework;
3 | using StatsdClient;
4 |
5 | namespace Tests
6 | {
7 | [TestFixture]
8 | public class UDPSmokeTests
9 | {
10 | // Smoke test should hit the real thing, but for the purpose of passing the appveyor build we are only checking if the client connects.
11 | // If you want to test against an actual system, change the host/port.
12 |
13 | private static readonly IPAddress ServerHostname = IPAddress.Loopback;
14 |
15 | [Test]
16 | public void Sends_counter_text()
17 | {
18 | using (var client = new StatsdUDPClient(ServerHostname.ToString()))
19 | {
20 | client.Send("statsd-client.udp-smoke-test:6|c");
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Tests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("Tests")]
10 | [assembly: AssemblyTrademark("")]
11 |
12 | // Setting ComVisible to false makes the types in this assembly not visible
13 | // to COM components. If you need to access a type in this assembly from
14 | // COM, set the ComVisible attribute to true on that type.
15 | [assembly: ComVisible(false)]
16 |
17 | // The following GUID is for the ID of the typelib if this project is exposed to COM
18 | [assembly: Guid("35c84fbc-41b2-4f48-9f24-6b98aa94001b")]
19 |
--------------------------------------------------------------------------------
/src/StatsdClient/MetricsTimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace StatsdClient
4 | {
5 | public class MetricsTimer : IDisposable
6 | {
7 | private readonly string _name;
8 | private readonly IStopwatch _stopWatch;
9 | private bool _disposed;
10 | private readonly double _sampleRate;
11 |
12 | public MetricsTimer(string name, double sampleRate)
13 | {
14 | _name = name;
15 | _sampleRate = sampleRate;
16 | _stopWatch = new Stopwatch();
17 | _stopWatch.Start();
18 | }
19 |
20 | public void Dispose()
21 | {
22 | if (!_disposed)
23 | {
24 | _disposed = true;
25 | _stopWatch.Stop();
26 | Metrics.Timer(_name, _stopWatch.ElapsedMilliseconds, _sampleRate);
27 | }
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/StatsdClient/AdressResolution.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Net;
3 | using System.Net.Sockets;
4 |
5 | namespace StatsdClient
6 | {
7 | public class AddressResolution
8 | {
9 | public static IPAddress GetIpv4Address(string name)
10 | {
11 | IPAddress ipAddress;
12 | var isValidIpAddress = IPAddress.TryParse(name, out ipAddress);
13 |
14 | if (!isValidIpAddress)
15 | {
16 | ipAddress = GetIpFromHostname(name);
17 | }
18 |
19 | return ipAddress;
20 | }
21 |
22 | private static IPAddress GetIpFromHostname(string name)
23 | {
24 | #if NETFULL
25 | var addressList = Dns.GetHostEntry(name).AddressList;
26 | #else
27 | var addressList = Dns.GetHostEntryAsync(name).GetAwaiter().GetResult().AddressList;
28 | #endif
29 | var ipv4Addresses = addressList.Where(x => x.AddressFamily != AddressFamily.InterNetworkV6);
30 |
31 | return ipv4Addresses.First();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/MIT-LICENCE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Goncalo Pereira (github@goncalopereira.com) and all contributors
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/src/StatsdClient/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("StatsdClient")]
9 | [assembly: AssemblyDescription("Statsd Client")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("StatsdClient")]
13 | [assembly: AssemblyCopyright("Copyright © 2012")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 | [assembly: InternalsVisibleTo("Tests")]
17 |
18 | // Setting ComVisible to false makes the types in this assembly not visible
19 | // to COM components. If you need to access a type in this assembly from
20 | // COM, set the ComVisible attribute to true on that type.
21 | [assembly: ComVisible(false)]
22 |
23 | // The following GUID is for the ID of the typelib if this project is exposed to COM
24 | [assembly: Guid("ecebfa48-5557-4fe6-84a6-c0b1e3ece14c")]
--------------------------------------------------------------------------------
/src/StatsdClient/StatsdClient.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 |
10 | 0000b49c-9d92-4c54-b902-7067e13a09bd
11 | StatsdClient.Core
12 | .\obj
13 | .\bin\
14 | v4.5.2
15 |
16 |
17 |
18 | 2.0
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/StatsdClient/IStatsd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace StatsdClient
4 | {
5 | public interface IStatsd
6 | {
7 | void Send(string name, int value) where TCommandType : IAllowsInteger;
8 | void Add(string name, int value) where TCommandType : IAllowsInteger;
9 |
10 | void Send(string name, double value) where TCommandType : IAllowsDouble;
11 | void Add(string name, double value) where TCommandType : IAllowsDouble;
12 | void Send(string name, double value, bool isDeltaValue) where TCommandType : IAllowsDouble, IAllowsDelta;
13 |
14 | void Send(string name, int value, double sampleRate) where TCommandType : IAllowsInteger, IAllowsSampleRate;
15 | void Add(string name, int value, double sampleRate) where TCommandType : IAllowsInteger, IAllowsSampleRate;
16 |
17 | void Send(string name, string value) where TCommandType : IAllowsString;
18 |
19 | void Send();
20 |
21 | void Add(Action actionToTime, string statName, double sampleRate=1);
22 | void Send(Action actionToTime, string statName, double sampleRate=1);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/StatsdClient/project.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0-*",
3 | "authors": [ "Goncalo Pereira", "Darrell Mozingo" ],
4 | "title": "Statsd C# Client",
5 | "description": "Statsd client for C#, providing a full set of counter/timer/gauge/set functionality in an easy to use static. Helpers are provided for easily timing chunks of code too.",
6 | "name": "StatsdClient",
7 | "packOptions": {
8 | "releaseNotes": "See CHANGELOG.md in the project's GitHub repository.",
9 | "tags": [ "stats", "statsd", "metrics" ],
10 | "licenseUrl": "https://github.com/Pereingo/statsd-csharp-client/blob/master/MIT-LICENSE.md",
11 | "projectUrl": "https://github.com/Pereingo/statsd-csharp-client",
12 | "repository": {
13 | "type:": "git",
14 | "url": "https://github.com/Pereingo/statsd-csharp-client"
15 | }
16 | },
17 |
18 | "buildOptions": {
19 | "warningsAsErrors": true
20 | },
21 |
22 | "frameworks": {
23 | "net45": {
24 | "buildOptions": {
25 | "define": [ "NETFULL" ]
26 | }
27 | },
28 | "netstandard1.3": {
29 | "dependencies": {
30 | "NETStandard.Library": "1.6.0",
31 | "System.Net.NameResolution": "4.0.0"
32 | },
33 | "imports": "dnxcore50"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Tests/Tests.xproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 | 35c84fbc-41b2-4f48-9f24-6b98aa94001b
10 | StatsdClient.UnitTests
11 | .\obj
12 | .\bin\
13 | v4.5.2
14 |
15 |
16 | 2.0
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Tests/MetricsTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using NUnit.Framework;
4 | using StatsdClient;
5 |
6 | namespace Tests
7 | {
8 | public class MetricsTests
9 | {
10 | ///
11 | /// Since Metrics is a static class withs static fields, in order to test functionality
12 | /// of methods this cleanup method will clean up the static state of the Metrics class between each
13 | /// test iteration.
14 | ///
15 | [SetUp]
16 | public void SetUp()
17 | {
18 | foreach (var field in typeof(Metrics).GetFields(BindingFlags.Static | BindingFlags.NonPublic))
19 | {
20 | field.SetValue(null, null);
21 | }
22 | }
23 |
24 | [Test]
25 | public void throws_when_configured_with_a_null_configuration()
26 | {
27 | Assert.That(() => Metrics.Configure(null), Throws.Exception.TypeOf());
28 | }
29 |
30 | [Test]
31 | public void IsConfigured_returns_false_when_configuration_is_null()
32 | {
33 | Assert.That(Metrics.IsConfigured(), Is.False);
34 | }
35 |
36 | [Test]
37 | public void IsConfigured_returns_true_when_configuration_is_not_null()
38 | {
39 | Metrics.Configure(new MetricsConfig {StatsdServerName = "localhost"});
40 |
41 | Assert.That(Metrics.IsConfigured(), Is.True);
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/Tests/TCPSmokeTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Net.Sockets;
3 | using NUnit.Framework;
4 | using StatsdClient;
5 |
6 | namespace Tests
7 | {
8 | [TestFixture]
9 | public class TCPSmokeTests
10 | {
11 | // Smoke test should hit the real thing, but for the purpose of passing the appveyor build we are only checking if the client connects.
12 | // If you want to test against an actual system, change the host/port.
13 |
14 | private TcpListener _tcpListener;
15 | private static readonly IPAddress ServerHostname = IPAddress.Loopback;
16 | private int _serverPort;
17 |
18 | [OneTimeSetUp]
19 | public void UdpListenerThread()
20 | {
21 | const int nextAvailablePort = 0;
22 | _tcpListener = new TcpListener(ServerHostname, nextAvailablePort);
23 | _tcpListener.Start();
24 |
25 | _serverPort = ((IPEndPoint) _tcpListener.LocalEndpoint).Port;
26 | }
27 |
28 | [OneTimeTearDown]
29 | public void TearDownUdpListener()
30 | {
31 | _tcpListener.Stop();
32 | }
33 |
34 | [Test]
35 | public void Sends_counter_text()
36 | {
37 | using (var client = new StatsdTCPClient(ServerHostname.ToString(), _serverPort))
38 | {
39 | client.Send("statsd-client.tcp-smoke-test:6|c");
40 | }
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/StatsdClient.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "StatsdClient", "src\StatsdClient\StatsdClient.xproj", "{0000B49C-9D92-4C54-B902-7067E13A09BD}"
7 | EndProject
8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Tests", "src\Tests\Tests.xproj", "{35C84FBC-41B2-4F48-9F24-6B98AA94001B}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {0000B49C-9D92-4C54-B902-7067E13A09BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {0000B49C-9D92-4C54-B902-7067E13A09BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {0000B49C-9D92-4C54-B902-7067E13A09BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {0000B49C-9D92-4C54-B902-7067E13A09BD}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {35C84FBC-41B2-4F48-9F24-6B98AA94001B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {35C84FBC-41B2-4F48-9F24-6B98AA94001B}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {35C84FBC-41B2-4F48-9F24-6B98AA94001B}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {35C84FBC-41B2-4F48-9F24-6B98AA94001B}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/src/StatsdClient/MetricsConfig.cs:
--------------------------------------------------------------------------------
1 | namespace StatsdClient
2 | {
3 | public class MetricsConfig
4 | {
5 | ///
6 | /// The full host name of your statsd server.
7 | ///
8 | public string StatsdServerName { get; set; }
9 |
10 | ///
11 | /// Uses the statsd default port if not specified (8125).
12 | ///
13 | public int StatsdServerPort { get; set; }
14 |
15 | ///
16 | /// Allows you to override the maximum UDP packet size (in bytes) if your setup requires that. Defaults to 512.
17 | ///
18 | public int StatsdMaxUDPPacketSize { get; set; }
19 |
20 | ///
21 | /// Allows you to use TCP client
22 | ///
23 | public bool UseTcpProtocol { get; set; }
24 |
25 | ///
26 | /// Allows you to optionally specify a stat name prefix for all your stats.
27 | /// Eg setting it to "Production.MyApp", then sending a counter with the name "Value" will result in a final stat name of "Production.MyApp.Value".
28 | ///
29 | public string Prefix { get; set; }
30 |
31 | public const int DefaultStatsdServerPort = 8125;
32 | public const int DefaultStatsdMaxUDPPacketSize = 512;
33 |
34 | public MetricsConfig()
35 | {
36 | StatsdServerPort = DefaultStatsdServerPort;
37 | StatsdMaxUDPPacketSize = DefaultStatsdMaxUDPPacketSize;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thanks for thinking about contributing something to the project! All suggestions, bugs, even typo fixes are most appreciated.
2 |
3 | Please open an issue and have a chat about any features you're thinking of implementing before you start, so we can discuss the best way to go about it.
4 |
5 | ## Purpose
6 |
7 | Our aim when making this library was to keep it small, lightweight, and very easy to configure and use. We'd rather not have more complex features that'd bloat its size or complexity and are usually just for edgecases or much higher scale. We're also in favour of statics where possible for that ease of use with the idea that you should (almost) never need to test the values you're sending for metrics - they should be incidental to the code and not run during tests.
8 |
9 | Others have implemented features like this, such as the [high performance](https://github.com/Kyle2123/statsd-csharp-client) fork which does in-memory batching, or [StatsN](https://github.com/TryStatsN/StatsN) which skips statics in favour of testability.
10 |
11 | ## Running
12 |
13 | Clone out the repo, fire up with Visual Studio 2015+, and ideally use ReSharper to run all the tests. If you don't have ReSharper, no worries, just grab the matching NUnit binary and either run it from the command line or use its GUI.
14 |
15 | ## Deploying
16 |
17 | * Change major/minor versions in `appveyor.yml` if needed (build number is handled by AppVeyor)
18 | * Update `CHANGELOG.md` to note expected build number after AppVeyor runs
19 | * The NuGet package is generated as an artefact on AppVeyor. Grab that `*.nupkg` and upload it to NuGet.org.
20 | * Create a git tag in the `v1.2.3` format
21 |
--------------------------------------------------------------------------------
/src/StatsdClient/NullStatsd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace StatsdClient
5 | {
6 | public class NullStatsd : IStatsd
7 | {
8 | public NullStatsd()
9 | {
10 | Commands = new List();
11 | }
12 |
13 | public List Commands { get; private set; }
14 |
15 | public void Send(string name, int value) where TCommandType : IAllowsInteger
16 | {
17 | }
18 |
19 | public void Add(string name, int value) where TCommandType : IAllowsInteger
20 | {
21 | }
22 |
23 | public void Send(string name, double value) where TCommandType : IAllowsDouble
24 | {
25 | }
26 |
27 | public void Add(string name, double value) where TCommandType : IAllowsDouble
28 | {
29 | }
30 |
31 | public void Send(string name, int value, double sampleRate)
32 | where TCommandType : IAllowsInteger, IAllowsSampleRate
33 | {
34 | }
35 |
36 | public void Add(string name, int value, double sampleRate)
37 | where TCommandType : IAllowsInteger, IAllowsSampleRate
38 | {
39 | }
40 |
41 | public void Send(string name, string value) where TCommandType : IAllowsString
42 | {
43 | }
44 |
45 | public void Send()
46 | {
47 | }
48 |
49 | public void Add(Action actionToTime, string statName, double sampleRate = 1)
50 | {
51 | actionToTime();
52 | }
53 |
54 | public void Send(Action actionToTime, string statName, double sampleRate = 1)
55 | {
56 | actionToTime();
57 | }
58 |
59 | public void Send(string name, double value, bool isDeltaValue) where TCommandType : IAllowsDouble, IAllowsDelta
60 | {
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 3.0.86
2 | - **BREAKING:** Changes to lower level interfaces unlikely to affect most users, including `IStatsd` and `IStopwatch`
3 | - Fix threading bug when calling `Metrics.Send()` from multiple threads (thanks for helping track down @[bronumski](https://github.com/bronumski)!)
4 | - Fix timing reporting bug that significantly under reported multiple-millisecond timings (thanks @[arexsutton](https://github.com/arexsutton)!)
5 | - Fix casing for .NET Core dependency (thanks for pointing out @[mikemitchellrightside](https://github.com/mikemitchellrightside)!)
6 |
7 | ## 2.0.68
8 | - **BREAKING:** Drops support for < .NET 4.5
9 | - Add .NET Standard 1.3 support (thanks @[TerribleDev](https://github.com/TerribleDev)!)
10 | - Fix async support (previously would only measure async creation time)
11 |
12 | ## 1.4.51
13 | - Add a `Metrics.IsConfigured()` method which returns whether the Metrics class has been initialised yet (thanks @[dkhanaferov](https://github.com/dkhanaferov)!)
14 |
15 | ## 1.3.44
16 | - Add support for [TCP](https://github.com/etsy/statsd/blob/master/docs/server.md) sending via the `MetricsConfig.UseTcpProtocol` property (thanks @[pekiZG](https://github.com/pekiZG)!)
17 |
18 | ## 1.2.32
19 | - Fix the `Stopwatch` class to make it more consisten with .NET's and fix an overflow bug (thanks @[knocte](https://github.com/knocte)!)
20 | - Fix bug in how IPv4 addresses are resolved
21 |
22 | ## 1.1.0
23 | - Add support for [gauge delta values](https://github.com/etsy/statsd/blob/master/docs/metric_types.md#gauges) (thanks @[crunchie84](https://github.com/crunchie84)!)
24 | - Mark the `Metrics.Gauge()` method obsolete in favour of the new `Metrics.GaugeAbsoluteValue()`
25 | - Mark the `StatsdClient.Configuration.Naming` class obsolete in favour of setting `MetricsConfig.Prefix` when you call `Metrics.Configure()`, which reduces the code you need when actually sending metrics each time
26 | - Expose a sample rate for the disposable version of the timer (`Metrics.StartTimer()`)
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Statsd Client - DEPRECATED
2 | ==========================
3 |
4 | This project is deprecated as of March 4, 2019. There's no official maintainer, and better alternatives exist. Security only related updates will be considered going forward. Currently active versions will remain on NuGet.
5 |
6 | We suggest migrating to [JustEat.StatsD](https://github.com/justeat/JustEat.StatsD), which has a very similar API to this project, plenty of additional features, and is actively maintained.
7 |
8 | Thanks to all the contributors and your many PRs and reported issues over the years, the numerous developers that have forked or been inspired by this client, and everyone that used it successfully in production!
9 |
10 | # THIS PROJECT IS DEPRECATED
11 |
12 | Original readme...
13 |
14 | ---
15 |
16 | [](https://ci.appveyor.com/project/DarrellMozingo/statsd-csharp-client)
17 | [](https://www.nuget.org/packages/StatsdClient/)
18 |
19 | A .NET Standard compatible C# client to interface with Etsy's excellent [statsd](https://github.com/etsy/statsd) server.
20 |
21 | Install the client via NuGet with the [StatsdClient package](http://nuget.org/packages/StatsdClient).
22 |
23 | ## Usage
24 |
25 | At app startup, configure the `Metrics` class:
26 |
27 | ``` C#
28 | Metrics.Configure(new MetricsConfig
29 | {
30 | StatsdServerName = "hostname",
31 | Prefix = "myApp.prod"
32 | });
33 | ```
34 |
35 | Start measuring all the things!
36 |
37 | ``` C#
38 | Metrics.Counter("stat-name");
39 | Metrics.Time(() => myMethod(), "timer-name");
40 | var result = Metrics.Time(() => GetResult(), "timer-name");
41 | var result = await Metrics.Time(async () => await myAsyncMethod(), "timer-name");
42 | Metrics.GaugeAbsoluteValue("gauge-name", 35);
43 | Metrics.GaugeDelta("gauge-name", -5);
44 | Metrics.Set("something-special", "3");
45 |
46 | using (Metrics.StartTimer("stat-name"))
47 | {
48 | // Lots of code here
49 | }
50 | ```
51 |
52 | ## Advanced Features
53 |
54 | To enable these, see the `MetricsConfig` class discussed above.
55 |
56 | * `UseTcpProtocol`: sends metrics to statsd via TCP. While supported, UDP is recommended in most cases. If you need TCP reliability, a relay service running locally on the server which you'd send UDP to, and it would relay via TCP, is advised.
57 |
58 | ## Contributing
59 |
60 | See the [Contributing](CONTRIBUTING.md) guidelines.
61 |
--------------------------------------------------------------------------------
/src/Tests/RandomGeneratorUnitTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using StatsdClient;
4 |
5 | namespace Tests
6 | {
7 | [TestFixture]
8 | public class RandomGeneratorUnitTests
9 | {
10 | private readonly RandomGenerator _randomGenerator = new RandomGenerator();
11 | private const int NumberOfTestsToRun = 1000;
12 |
13 | [Test]
14 | public void If_sample_rate_is_1_then_always_true()
15 | {
16 | for (var i = 0; i < NumberOfTestsToRun; i++)
17 | {
18 | Assert.True(_randomGenerator.ShouldSend(1));
19 | }
20 | }
21 |
22 | [Test]
23 | public void If_sample_rate_is_0_5_then_have_half_true()
24 | {
25 | var numberOfTrues = 0;
26 | var randomGenerator = new RandomGenerator();
27 |
28 | for (var i = 0; i < NumberOfTestsToRun; i++)
29 | {
30 | if (randomGenerator.ShouldSend(0.5))
31 | {
32 | numberOfTrues++;
33 | }
34 | }
35 |
36 | Assert.That( Math.Round(numberOfTrues/(double)NumberOfTestsToRun,1),Is.EqualTo(0.5));
37 | }
38 |
39 | [Test]
40 | public void If_sample_rate_is_one_quarter_then_have_one_quarter_true()
41 | {
42 | var numberOfTrues = 0;
43 | var randomGenerator = new RandomGenerator();
44 | const int sampleRate = 1/4;
45 |
46 | for (var i = 0; i < NumberOfTestsToRun; i++)
47 | {
48 | if (randomGenerator.ShouldSend(sampleRate))
49 | {
50 | numberOfTrues++;
51 | }
52 | }
53 |
54 | Assert.That(Math.Round(numberOfTrues / (double)NumberOfTestsToRun, 1), Is.EqualTo(sampleRate));
55 | }
56 |
57 | [Test]
58 | public void If_sample_rate_is_one_tenth_of_pct_then_have_one_tenth_of_pct()
59 | {
60 | var numberOfTrues = 0;
61 | var randomGenerator = new RandomGenerator();
62 | const int sampleRate = 1/1000;
63 |
64 | for (var i = 0; i < NumberOfTestsToRun; i++)
65 | {
66 | if (randomGenerator.ShouldSend(sampleRate))
67 | {
68 | numberOfTrues++;
69 | }
70 | }
71 |
72 | Assert.That(Math.Round(numberOfTrues / (double)NumberOfTestsToRun, 1), Is.EqualTo(sampleRate));
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/StatsdClient/StatsdTCPClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Net;
4 | using System.Net.Sockets;
5 | using System.Text;
6 |
7 | namespace StatsdClient
8 | {
9 | public class StatsdTCPClient : IStatsdClient
10 | {
11 | private IPEndPoint IpEndpoint { get; }
12 | private readonly Socket _clientSocket;
13 |
14 | public StatsdTCPClient(string name, int port = 8125)
15 | {
16 | try
17 | {
18 | _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
19 | IpEndpoint = new IPEndPoint(AddressResolution.GetIpv4Address(name), port);
20 | }
21 | catch (Exception ex)
22 | {
23 | Debug.WriteLine(ex.Message);
24 | }
25 | }
26 |
27 | public void Send(string command)
28 | {
29 | Send(Encoding.ASCII.GetBytes(command));
30 | }
31 |
32 | private void Send(byte[] encodedCommand)
33 | {
34 | try
35 | {
36 | _clientSocket.Connect(IpEndpoint);
37 | _clientSocket.SendTo(encodedCommand, encodedCommand.Length, SocketFlags.None, IpEndpoint);
38 | }
39 | catch (Exception ex)
40 | {
41 | Debug.WriteLine(ex.Message);
42 | }
43 | finally
44 | {
45 | _clientSocket.Shutdown(SocketShutdown.Both);
46 | CloseSocket(_clientSocket);
47 | }
48 | }
49 |
50 | private static void CloseSocket(Socket socket)
51 | {
52 | #if NETFULL
53 | socket.Close();
54 | #else
55 | socket.Dispose();
56 | #endif
57 | }
58 |
59 | #region IDisposable Support
60 | private bool _disposed;
61 |
62 | protected virtual void Dispose(bool disposing)
63 | {
64 | if (_disposed) return;
65 |
66 | if (disposing)
67 | {
68 | if (_clientSocket != null)
69 | {
70 | try
71 | {
72 | CloseSocket(_clientSocket);
73 | }
74 | catch (Exception ex)
75 | {
76 | Debug.WriteLine(ex.Message);
77 | }
78 | }
79 | }
80 | _disposed = true;
81 | }
82 |
83 | ~StatsdTCPClient() {
84 | Dispose(false);
85 | }
86 |
87 | public void Dispose()
88 | {
89 | Dispose(true);
90 | GC.SuppressFinalize(this);
91 | }
92 | #endregion
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Tests/UdpListener.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 | using System.Collections.Generic;
6 |
7 | namespace Tests
8 | {
9 | namespace Helpers
10 | {
11 | // A small UDP server that can be used for testing.
12 | // Stores a list of the last messages that were received by the server
13 | // until they are accessed using GetAndClearLastMessages().
14 |
15 | // By design received messages can only be read once. This
16 | // allows one instance of the listener to be used across
17 | // multiple tests without risk of the results of previous tests
18 | // affecting the current one.
19 |
20 | // Intended use:
21 | // udpListener = new UdpListener(serverName, serverPort);
22 | // listenThread = new Thread(new ParameterizedThreadStart(udpListener.Listen));
23 | // listenThread.Start(n);
24 | // { send n messages to the listener }
25 | // while(listenThread.IsAlive); // wait for listen thread to receive message or time out
26 | // List receivedMessage = udpListener.GetAndClearLastMessages()
27 | // { make sure that the received messages are what was expected }
28 | public class UdpListener : IDisposable
29 | {
30 | List lastReceivedMessages;
31 | IPEndPoint localIpEndPoint;
32 | IPEndPoint senderIpEndPoint;
33 | UdpClient socket;
34 |
35 | public UdpListener(string hostname, int port)
36 | {
37 | lastReceivedMessages = new List();
38 | localIpEndPoint = new IPEndPoint(IPAddress.Parse(hostname), port);
39 | socket = new UdpClient(localIpEndPoint);
40 | socket.Client.ReceiveTimeout = 2000;
41 | senderIpEndPoint = new IPEndPoint(IPAddress.Any, 0);
42 | }
43 |
44 | // Receive messages until it receives count of them or times out.
45 | // This call is blocking; you may want to run it in a
46 | // thread while you send the message.
47 | public void Listen(object count = null)
48 | {
49 | try
50 | {
51 | if (count == null)
52 | count = 1;
53 | for (int i = 0; i < (int)count; i++)
54 | {
55 | byte[] lastReceivedBytes = socket.Receive(ref senderIpEndPoint);
56 | lastReceivedMessages.Add(Encoding.ASCII.GetString(lastReceivedBytes, 0,
57 | lastReceivedBytes.Length));
58 | }
59 | }
60 | catch (SocketException ex)
61 | {
62 | // If we timeout, stop listening.
63 | // If we get another error, propagate it upwards.
64 | if (ex.ErrorCode == 10060) // WSAETIMEDOUT; Timeout error
65 | return;
66 | else
67 | throw ex;
68 | }
69 | }
70 |
71 | // Clear and return the message list. Clearing the list allows us to use the
72 | // same UdpListener instance for several tests; we never have to worry about a
73 | // message received from a previous test giving us a false positive.
74 | public List GetAndClearLastMessages()
75 | {
76 | List messagesToReturn = lastReceivedMessages;
77 | lastReceivedMessages = new List();
78 | return messagesToReturn;
79 | }
80 |
81 | public void Dispose()
82 | {
83 | socket.Close();
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/StatsdClient/StatsdUDPClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 |
6 | namespace StatsdClient
7 | {
8 | public class StatsdUDPClient : IStatsdClient
9 | {
10 | public IPEndPoint IPEndpoint { get; private set; }
11 |
12 | private readonly int _maxUdpPacketSizeBytes;
13 | private readonly Socket _clientSocket;
14 |
15 | ///
16 | /// Creates a new StatsdUDP class for lower level access to statsd.
17 | ///
18 | /// Hostname or IP (v4) address of the statsd server.
19 | /// Port of the statsd server. Default is 8125.
20 | /// Max packet size, in bytes. This is useful to tweak if your MTU size is different than normal. Set to 0 for no limit. Default is MetricsConfig.DefaultStatsdMaxUDPPacketSize.
21 | public StatsdUDPClient(string name, int port = 8125, int maxUdpPacketSizeBytes = MetricsConfig.DefaultStatsdMaxUDPPacketSize)
22 | {
23 | _maxUdpPacketSizeBytes = maxUdpPacketSizeBytes;
24 |
25 | _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
26 |
27 | IPEndpoint = new IPEndPoint(AddressResolution.GetIpv4Address(name), port);
28 | }
29 |
30 | public void Send(string command)
31 | {
32 | Send(Encoding.ASCII.GetBytes(command));
33 | }
34 |
35 | private void Send(byte[] encodedCommand)
36 | {
37 | if (_maxUdpPacketSizeBytes > 0 && encodedCommand.Length > _maxUdpPacketSizeBytes)
38 | {
39 | // If the command is too big to send, linear search backwards from the maximum
40 | // packet size to see if we can find a newline delimiting two stats. If we can,
41 | // split the message across the newline and try sending both componenets individually
42 | var newline = Encoding.ASCII.GetBytes("\n")[0];
43 | for (var i = _maxUdpPacketSizeBytes; i > 0; i--)
44 | {
45 | if (encodedCommand[i] != newline)
46 | {
47 | continue;
48 | }
49 |
50 | var encodedCommandFirst = new byte[i];
51 | Array.Copy(encodedCommand, encodedCommandFirst, encodedCommandFirst.Length); // encodedCommand[0..i-1]
52 | Send(encodedCommandFirst);
53 |
54 | var remainingCharacters = encodedCommand.Length - i - 1;
55 | if (remainingCharacters > 0)
56 | {
57 | var encodedCommandSecond = new byte[remainingCharacters];
58 | Array.Copy(encodedCommand, i + 1, encodedCommandSecond, 0, encodedCommandSecond.Length); // encodedCommand[i+1..end]
59 | Send(encodedCommandSecond);
60 | }
61 |
62 | return; // We're done here if we were able to split the message.
63 | // At this point we found an oversized message but we weren't able to find a
64 | // newline to split upon. We'll still send it to the UDP socket, which upon sending an oversized message
65 | // will fail silently if the user is running in release mode or report a SocketException if the user is
66 | // running in debug mode.
67 | // Since we're conservative with our MAX_UDP_PACKET_SIZE, the oversized message might even
68 | // be sent without issue.
69 | }
70 | }
71 |
72 | _clientSocket.SendTo(encodedCommand, encodedCommand.Length, SocketFlags.None, IPEndpoint);
73 | }
74 |
75 | //reference : https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
76 | private bool _disposed;
77 | public void Dispose()
78 | {
79 | Dispose(true);
80 | GC.SuppressFinalize(this);
81 | }
82 | ~StatsdUDPClient()
83 | {
84 | // Finalizer calls Dispose(false)
85 | Dispose(false);
86 | }
87 | protected virtual void Dispose(bool disposing)
88 | {
89 | if (_disposed)
90 | return;
91 |
92 | if (disposing)
93 | {
94 | if (_clientSocket != null)
95 | {
96 | try
97 | {
98 | #if NETFULL
99 | _clientSocket.Close();
100 | #else
101 | _clientSocket.Dispose();
102 | #endif
103 | }
104 | catch (Exception)
105 | {
106 | //Swallow since we are not using a logger, should we add LibLog and start logging??
107 | }
108 |
109 | }
110 | }
111 |
112 | _disposed = true;
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 | *.vcxproj.filters
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | bld/
24 | [Bb]in/
25 | [Oo]bj/
26 | [Ll]og/
27 |
28 | # Visual Studio 2015 cache/options directory
29 | .vs/
30 | # Uncomment if you have tasks that create the project's static files in wwwroot
31 | #wwwroot/
32 |
33 | # MSTest test Results
34 | [Tt]est[Rr]esult*/
35 | [Bb]uild[Ll]og.*
36 |
37 | # NUNIT
38 | *.VisualState.xml
39 | TestResult.xml
40 |
41 | # Build Results of an ATL Project
42 | [Dd]ebugPS/
43 | [Rr]eleasePS/
44 | dlldata.c
45 |
46 | # .NET Core
47 | project.lock.json
48 | project.fragment.lock.json
49 | artifacts/
50 | **/Properties/launchSettings.json
51 |
52 | *_i.c
53 | *_p.c
54 | *_i.h
55 | *.ilk
56 | *.meta
57 | *.obj
58 | *.pch
59 | *.pdb
60 | *.pgc
61 | *.pgd
62 | *.rsp
63 | *.sbr
64 | *.tlb
65 | *.tli
66 | *.tlh
67 | *.tmp
68 | *.tmp_proj
69 | *.log
70 | *.vspscc
71 | *.vssscc
72 | .builds
73 | *.pidb
74 | *.svclog
75 | *.scc
76 |
77 | # Chutzpah Test files
78 | _Chutzpah*
79 |
80 | # Visual C++ cache files
81 | ipch/
82 | *.aps
83 | *.ncb
84 | *.opendb
85 | *.opensdf
86 | *.sdf
87 | *.cachefile
88 | *.VC.db
89 | *.VC.VC.opendb
90 |
91 | # Visual Studio profiler
92 | *.psess
93 | *.vsp
94 | *.vspx
95 | *.sap
96 |
97 | # TFS 2012 Local Workspace
98 | $tf/
99 |
100 | # Guidance Automation Toolkit
101 | *.gpState
102 |
103 | # ReSharper is a .NET coding add-in
104 | _ReSharper*/
105 | *.[Rr]e[Ss]harper
106 | *.DotSettings.user
107 |
108 | # JustCode is a .NET coding add-in
109 | .JustCode
110 |
111 | # TeamCity is a build add-in
112 | _TeamCity*
113 |
114 | # DotCover is a Code Coverage Tool
115 | *.dotCover
116 |
117 | # Visual Studio code coverage results
118 | *.coverage
119 | *.coveragexml
120 |
121 | # NCrunch
122 | _NCrunch_*
123 | .*crunch*.local.xml
124 | nCrunchTemp_*
125 |
126 | # MightyMoose
127 | *.mm.*
128 | AutoTest.Net/
129 |
130 | # Web workbench (sass)
131 | .sass-cache/
132 |
133 | # Installshield output folder
134 | [Ee]xpress/
135 |
136 | # DocProject is a documentation generator add-in
137 | DocProject/buildhelp/
138 | DocProject/Help/*.HxT
139 | DocProject/Help/*.HxC
140 | DocProject/Help/*.hhc
141 | DocProject/Help/*.hhk
142 | DocProject/Help/*.hhp
143 | DocProject/Help/Html2
144 | DocProject/Help/html
145 |
146 | # Click-Once directory
147 | publish/
148 |
149 | # Publish Web Output
150 | *.[Pp]ublish.xml
151 | *.azurePubxml
152 | # TODO: Comment the next line if you want to checkin your web deploy settings
153 | # but database connection strings (with potential passwords) will be unencrypted
154 | *.pubxml
155 | *.publishproj
156 |
157 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
158 | # checkin your Azure Web App publish settings, but sensitive information contained
159 | # in these scripts will be unencrypted
160 | PublishScripts/
161 |
162 | # NuGet Packages
163 | *.nupkg
164 | # The packages folder can be ignored because of Package Restore
165 | **/packages/*
166 | # except build/, which is used as an MSBuild target.
167 | !**/packages/build/
168 | # Uncomment if necessary however generally it will be regenerated when needed
169 | #!**/packages/repositories.config
170 | # NuGet v3's project.json files produces more ignoreable files
171 | *.nuget.props
172 | *.nuget.targets
173 |
174 | # Microsoft Azure Build Output
175 | csx/
176 | *.build.csdef
177 |
178 | # Microsoft Azure Emulator
179 | ecf/
180 | rcf/
181 |
182 | # Windows Store app package directories and files
183 | AppPackages/
184 | BundleArtifacts/
185 | Package.StoreAssociation.xml
186 | _pkginfo.txt
187 |
188 | # Visual Studio cache files
189 | # files ending in .cache can be ignored
190 | *.[Cc]ache
191 | # but keep track of directories ending in .cache
192 | !*.[Cc]ache/
193 |
194 | # Others
195 | ClientBin/
196 | ~$*
197 | *~
198 | *.dbmdl
199 | *.dbproj.schemaview
200 | *.jfm
201 | *.pfx
202 | *.publishsettings
203 | node_modules/
204 | orleans.codegen.cs
205 |
206 | # Since there are multiple workflows, uncomment next line to ignore bower_components
207 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
208 | #bower_components/
209 |
210 | # RIA/Silverlight projects
211 | Generated_Code/
212 |
213 | # Backup & report files from converting an old project file
214 | # to a newer Visual Studio version. Backup files are not needed,
215 | # because we have git ;-)
216 | _UpgradeReport_Files/
217 | Backup*/
218 | UpgradeLog*.XML
219 | UpgradeLog*.htm
220 |
221 | # SQL Server files
222 | *.mdf
223 | *.ldf
224 |
225 | # Business Intelligence projects
226 | *.rdl.data
227 | *.bim.layout
228 | *.bim_*.settings
229 |
230 | # Microsoft Fakes
231 | FakesAssemblies/
232 |
233 | # GhostDoc plugin setting file
234 | *.GhostDoc.xml
235 |
236 | # Node.js Tools for Visual Studio
237 | .ntvs_analysis.dat
238 |
239 | # Visual Studio 6 build log
240 | *.plg
241 |
242 | # Visual Studio 6 workspace options file
243 | *.opt
244 |
245 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
246 | *.vbw
247 |
248 | # Visual Studio LightSwitch build output
249 | **/*.HTMLClient/GeneratedArtifacts
250 | **/*.DesktopClient/GeneratedArtifacts
251 | **/*.DesktopClient/ModelManifest.xml
252 | **/*.Server/GeneratedArtifacts
253 | **/*.Server/ModelManifest.xml
254 | _Pvt_Extensions
255 |
256 | # Paket dependency manager
257 | .paket/paket.exe
258 | paket-files/
259 |
260 | # FAKE - F# Make
261 | .fake/
262 |
263 | # JetBrains Rider
264 | .idea/
265 | *.sln.iml
266 |
267 | # CodeRush
268 | .cr/
269 |
270 | # Python Tools for Visual Studio (PTVS)
271 | __pycache__/
272 | *.pyc
273 |
274 | # Cake - Uncomment if you are using it
275 | # tools/
276 |
--------------------------------------------------------------------------------
/src/Tests/StatsdUDPTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Collections.Generic;
4 | using NUnit.Framework;
5 | using StatsdClient;
6 | using Tests.Helpers;
7 |
8 | namespace Tests
9 | {
10 | // Most of StatsUDP is tested in StatsdUnitTests. This is mainly to test the splitting of oversized UDP packets
11 | [TestFixture]
12 | public class StatsUDPTests
13 | {
14 | private UdpListener udpListener;
15 | private Thread listenThread;
16 | private const int serverPort = 23483;
17 | private const string serverName = "127.0.0.1";
18 | private StatsdUDPClient udp;
19 | private Statsd statsd;
20 | private List lastPulledMessages;
21 |
22 | [OneTimeSetUp]
23 | public void SetUpUdpListenerAndStatsd()
24 | {
25 | udpListener = new UdpListener(serverName, serverPort);
26 | var metricsConfig = new MetricsConfig { StatsdServerName = serverName };
27 | StatsdClient.Metrics.Configure(metricsConfig);
28 | udp = new StatsdUDPClient(serverName, serverPort);
29 | statsd = new Statsd(udp);
30 | }
31 |
32 | [OneTimeTearDown]
33 | public void TearDownUdpListener()
34 | {
35 | udpListener.Dispose();
36 | udp.Dispose();
37 | }
38 |
39 | [SetUp]
40 | public void UdpListenerThread()
41 | {
42 | lastPulledMessages = new List();
43 | listenThread = new Thread(new ParameterizedThreadStart(udpListener.Listen));
44 | }
45 |
46 | [TearDown]
47 | public void ClearUdpListenerMessages()
48 | {
49 | udpListener.GetAndClearLastMessages(); // just to be sure that nothing is left over
50 | }
51 |
52 | // Test helper. Waits until the listener is done receiving a message,
53 | // then asserts that the passed string is equal to the message received.
54 | private void AssertWasReceived(string shouldBe, int index = 0)
55 | {
56 | if (lastPulledMessages.Count == 0)
57 | {
58 | // Stall until the the listener receives a message or times out
59 | while(listenThread.IsAlive);
60 | lastPulledMessages = udpListener.GetAndClearLastMessages();
61 | }
62 |
63 | string actual;
64 |
65 | try
66 | {
67 | actual = lastPulledMessages[index];
68 | }
69 | catch (System.ArgumentOutOfRangeException)
70 | {
71 | actual = null;
72 | }
73 | Assert.AreEqual(shouldBe, actual);
74 | }
75 |
76 | [Test]
77 | public void send()
78 | {
79 | // (Sanity test)
80 | listenThread.Start();
81 | udp.Send("test-metric");
82 | AssertWasReceived("test-metric");
83 | }
84 |
85 | [Test]
86 | public void send_equal_to_udp_packet_limit_is_still_sent()
87 | {
88 | var msg = new String('f', MetricsConfig.DefaultStatsdMaxUDPPacketSize);
89 | listenThread.Start();
90 | udp.Send(msg);
91 | // As long as we're at or below the limit, the packet should still be sent
92 | AssertWasReceived(msg);
93 | }
94 |
95 | [Test]
96 | public void send_unsplittable_oversized_udp_packets_are_not_split_or_sent_and_no_exception_is_raised()
97 | {
98 | // This message will be one byte longer than the theoretical limit of a UDP packet
99 | var msg = new String('f', 65508);
100 | listenThread.Start();
101 | statsd.Add(msg, 1);
102 | statsd.Send();
103 | // It shouldn't be split or sent, and no exceptions should be raised.
104 | AssertWasReceived(null);
105 | }
106 |
107 | [Test]
108 | public void send_oversized_udp_packets_are_split_if_possible()
109 | {
110 | var msg = new String('f', MetricsConfig.DefaultStatsdMaxUDPPacketSize - 15);
111 | listenThread.Start(3); // Listen for 3 messages
112 | statsd.Add(msg, 1);
113 | statsd.Add(msg, 2);
114 | statsd.Send();
115 | // These two metrics should be split as their combined lengths exceed the maximum packet size
116 | AssertWasReceived(String.Format("{0}:1|c", msg), 0);
117 | AssertWasReceived(String.Format("{0}:2|ms", msg), 1);
118 | // No extra metric should be sent at the end
119 | AssertWasReceived(null, 2);
120 | }
121 |
122 | [Test]
123 | public void send_oversized_udp_packets_are_split_if_possible_with_multiple_messages_in_one_packet()
124 | {
125 | var msg = new String('f', MetricsConfig.DefaultStatsdMaxUDPPacketSize / 2);
126 | listenThread.Start(3);
127 | statsd.Add("counter", 1);
128 | statsd.Add(msg, 2);
129 | statsd.Add(msg, 3);
130 | statsd.Send();
131 | // Make sure that a split packet can contain mulitple metrics
132 | AssertWasReceived(String.Format("counter:1|c\n{0}:2|c", msg), 0);
133 | AssertWasReceived(String.Format("{0}:3|c", msg), 1);
134 | AssertWasReceived(null, 2);
135 | }
136 |
137 | [Test]
138 | public void set_max_udp_packet_size()
139 | {
140 | // Make sure that we can set the max UDP packet size
141 | udp = new StatsdUDPClient(serverName, serverPort, 10);
142 | statsd = new Statsd(udp);
143 | var msg = new String('f', 5);
144 | listenThread.Start(2);
145 | statsd.Add(msg, 1);
146 | statsd.Add(msg, 2);
147 | statsd.Send();
148 | // Since our packet size limit is now 10, this (short) message should still be split
149 | AssertWasReceived(String.Format("{0}:1|c", msg), 0);
150 | AssertWasReceived(String.Format("{0}:2|ms", msg), 1);
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/StatsdClient/Metrics.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace StatsdClient
5 | {
6 | public static class Metrics
7 | {
8 | private static IStatsd _statsD = new NullStatsd();
9 | private static IStatsdClient _statsdClient;
10 | private static string _prefix;
11 |
12 | ///
13 | /// Configures the Metric class with a configuration. Call this once at application startup (Main(), Global.asax, etc).
14 | ///
15 | /// Configuration settings.
16 | public static void Configure(MetricsConfig config)
17 | {
18 | if (config == null)
19 | {
20 | throw new ArgumentNullException("config");
21 | }
22 |
23 | _prefix = config.Prefix ?? "";
24 | _prefix = _prefix.TrimEnd('.');
25 | CreateStatsD(config);
26 | }
27 |
28 | private static void CreateStatsD(MetricsConfig config)
29 | {
30 | if (_statsdClient != null)
31 | {
32 | _statsdClient.Dispose();
33 | }
34 |
35 | _statsdClient = null;
36 |
37 | if (!string.IsNullOrEmpty(config.StatsdServerName))
38 | {
39 | if (config.UseTcpProtocol)
40 | {
41 | _statsdClient = new StatsdTCPClient(config.StatsdServerName, config.StatsdServerPort);
42 | }
43 | else
44 | {
45 | _statsdClient = new StatsdUDPClient(config.StatsdServerName, config.StatsdServerPort, config.StatsdMaxUDPPacketSize);
46 | }
47 | _statsD = new Statsd(_statsdClient);
48 | }
49 | }
50 |
51 | ///
52 | /// Send a counter value.
53 | ///
54 | /// Name of the metric.
55 | /// Value of the counter. Defaults to 1.
56 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
57 | public static void Counter(string statName, int value = 1, double sampleRate = 1)
58 | {
59 | _statsD.Send(BuildNamespacedStatName(statName), value, sampleRate);
60 | }
61 |
62 | ///
63 | /// Modify the current value of the gauge with the given value.
64 | ///
65 | /// Name of the metric.
66 | ///
67 | public static void GaugeDelta(string statName, double deltaValue)
68 | {
69 | _statsD.Send(BuildNamespacedStatName(statName), deltaValue, true);
70 | }
71 |
72 | ///
73 | /// Set the gauge to the given absolute value.
74 | ///
75 | /// Name of the metric.
76 | /// Absolute value of the gauge to set.
77 | public static void GaugeAbsoluteValue(string statName, double absoluteValue)
78 | {
79 | _statsD.Send(BuildNamespacedStatName(statName), absoluteValue, false);
80 | }
81 |
82 | ///
83 | /// Send a manually timed value.
84 | ///
85 | /// Name of the metric.
86 | /// Elapsed miliseconds of the event.
87 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
88 | public static void Timer(string statName, int value, double sampleRate = 1)
89 | {
90 | _statsD.Send(BuildNamespacedStatName(statName), value, sampleRate);
91 | }
92 |
93 | ///
94 | /// Time a given piece of code (with a using block) and send the elapsed miliseconds
95 | ///
96 | /// Name of the metric.
97 | /// A disposable object that will record & send the metric.
98 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
99 | public static IDisposable StartTimer(string name, double sampleRate = 1)
100 | {
101 | return new MetricsTimer(name, sampleRate);
102 | }
103 |
104 | ///
105 | /// Time a given piece of code (with a lambda) and send the elapsed miliseconds.
106 | ///
107 | /// The code to time.
108 | /// Name of the metric.
109 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
110 | public static void Time(Action action, string statName, double sampleRate = 1)
111 | {
112 | _statsD.Send(action, BuildNamespacedStatName(statName), sampleRate);
113 | }
114 |
115 | ///
116 | /// Time a given piece of async code and send the elapsed miliseconds.
117 | ///
118 | /// The code to time.
119 | /// Name of the metric.
120 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
121 | public static async Task Time(Func func, string statName, double sampleRate = 1)
122 | {
123 | using (StartTimer(statName, sampleRate))
124 | {
125 | await func();
126 | }
127 | }
128 |
129 | ///
130 | /// Time a given piece of code and send the elapsed miliseconds.
131 | ///
132 | /// The code to time.
133 | /// Name of the metric.
134 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
135 | /// Return value of the function.
136 | public static T Time(Func func, string statName, double sampleRate = 1)
137 | {
138 | using (StartTimer(statName, sampleRate))
139 | {
140 | return func();
141 | }
142 | }
143 |
144 | ///
145 | /// Time a given piece of async code and send the elapsed miliseconds.
146 | ///
147 | /// The code to time.
148 | /// Name of the metric.
149 | /// Sample rate to reduce the load on your metric server. Defaults to 1 (100%).
150 | /// Return value of the function.
151 | public static async Task Time(Func> func, string statName, double sampleRate = 1)
152 | {
153 | using (StartTimer(statName, sampleRate))
154 | {
155 | return await func();
156 | }
157 | }
158 |
159 | ///
160 | /// Store a unique occurence of an event between flushes.
161 | ///
162 | /// Name of the metric.
163 | /// Value to set.
164 | public static void Set(string statName, string value)
165 | {
166 | _statsD.Send(BuildNamespacedStatName(statName), value);
167 | }
168 |
169 | private static string BuildNamespacedStatName(string statName)
170 | {
171 | if (string.IsNullOrEmpty(_prefix))
172 | {
173 | return statName;
174 | }
175 |
176 | return _prefix + "." + statName;
177 | }
178 |
179 | ///
180 | /// Determine if the Metrics instance has been configured previously.
181 | ///
182 | public static bool IsConfigured()
183 | {
184 | return _statsD != null && !(_statsD is NullStatsd);
185 | }
186 | }
187 | }
--------------------------------------------------------------------------------
/src/StatsdClient/Statsd.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Globalization;
6 |
7 | namespace StatsdClient
8 | {
9 | public interface IAllowsSampleRate { }
10 | public interface IAllowsDelta { }
11 |
12 | public interface IAllowsDouble { }
13 | public interface IAllowsInteger { }
14 | public interface IAllowsString { }
15 |
16 | public class Statsd : IStatsd
17 | {
18 | private readonly object _commandCollectionLock = new object();
19 |
20 | private IStopWatchFactory StopwatchFactory { get; set; }
21 | private IStatsdClient StatsdClient { get; set; }
22 | private IRandomGenerator RandomGenerator { get; set; }
23 |
24 | private readonly string _prefix;
25 |
26 | internal ConcurrentQueue Commands { get; private set; }
27 |
28 | public class Counting : IAllowsSampleRate, IAllowsInteger { }
29 | public class Timing : IAllowsSampleRate, IAllowsInteger { }
30 | public class Gauge : IAllowsDouble, IAllowsDelta { }
31 | public class Histogram : IAllowsInteger { }
32 | public class Meter : IAllowsInteger { }
33 | public class Set : IAllowsString { }
34 |
35 | private readonly IDictionary _commandToUnit = new Dictionary
36 | {
37 | {typeof (Counting), "c"},
38 | {typeof (Timing), "ms"},
39 | {typeof (Gauge), "g"},
40 | {typeof (Histogram), "h"},
41 | {typeof (Meter), "m"},
42 | {typeof (Set), "s"}
43 | };
44 |
45 | public Statsd(IStatsdClient statsdClient, IRandomGenerator randomGenerator, IStopWatchFactory stopwatchFactory, string prefix)
46 | {
47 | Commands = new ConcurrentQueue();
48 | StopwatchFactory = stopwatchFactory;
49 | StatsdClient = statsdClient;
50 | RandomGenerator = randomGenerator;
51 | _prefix = prefix;
52 | }
53 |
54 | public Statsd(IStatsdClient statsdClient, IRandomGenerator randomGenerator, IStopWatchFactory stopwatchFactory)
55 | : this(statsdClient, randomGenerator, stopwatchFactory, string.Empty) { }
56 |
57 | public Statsd(IStatsdClient statsdClient)
58 | : this(statsdClient, new RandomGenerator(), new StopWatchFactory(), string.Empty) { }
59 |
60 | public void Send(string name, int value) where TCommandType : IAllowsInteger
61 | {
62 | var command = GetCommand(name, value.ToString(CultureInfo.InvariantCulture), _commandToUnit[typeof(TCommandType)], 1);
63 | SendSingle(command);
64 | }
65 |
66 | public void Send(string name, double value) where TCommandType : IAllowsDouble
67 | {
68 | var formattedValue = string.Format(CultureInfo.InvariantCulture,"{0:F15}", value);
69 | var command = GetCommand(name, formattedValue, _commandToUnit[typeof(TCommandType)], 1);
70 | SendSingle(command);
71 | }
72 |
73 | public void Send(string name, double value, bool isDeltaValue) where TCommandType : IAllowsDouble, IAllowsDelta
74 | {
75 | if (isDeltaValue)
76 | {
77 | // Sending delta values to StatsD requires a value modifier sign (+ or -) which we append
78 | // using this custom format with a different formatting rule for negative/positive and zero values
79 | // https://msdn.microsoft.com/en-us/library/0c899ak8.aspx#SectionSeparator
80 | const string deltaValueStringFormat = "{0:+#.###;-#.###;+0}";
81 | var formattedValue = string.Format(CultureInfo.InvariantCulture, deltaValueStringFormat, value);
82 | var command = GetCommand(name, formattedValue, _commandToUnit[typeof(TCommandType)], 1);
83 | SendSingle(command);
84 | }
85 | else
86 | {
87 | Send(name, value);
88 | }
89 | }
90 |
91 | public void Send(string name, string value) where TCommandType : IAllowsString
92 | {
93 | var command = GetCommand(name, Convert.ToString(value, CultureInfo.InvariantCulture), _commandToUnit[typeof(TCommandType)], 1);
94 | SendSingle(command);
95 | }
96 |
97 | public void Add(string name, int value) where TCommandType : IAllowsInteger
98 | {
99 | Commands.Enqueue(GetCommand(name, value.ToString(CultureInfo.InvariantCulture), _commandToUnit[typeof (TCommandType)], 1));
100 | }
101 |
102 | public void Add(string name, double value) where TCommandType : IAllowsDouble
103 | {
104 | Commands.Enqueue(GetCommand(name, String.Format(CultureInfo.InvariantCulture,"{0:F15}", value), _commandToUnit[typeof(TCommandType)], 1));
105 | }
106 |
107 | public void Send(string name, int value, double sampleRate) where TCommandType : IAllowsInteger, IAllowsSampleRate
108 | {
109 | if (!RandomGenerator.ShouldSend(sampleRate))
110 | {
111 | return;
112 | }
113 |
114 | var command = GetCommand(name, value.ToString(CultureInfo.InvariantCulture), _commandToUnit[typeof(TCommandType)], sampleRate);
115 | SendSingle(command);
116 | }
117 |
118 | public void Add(string name, int value, double sampleRate) where TCommandType : IAllowsInteger, IAllowsSampleRate
119 | {
120 | if (RandomGenerator.ShouldSend(sampleRate))
121 | {
122 | Commands.Enqueue(GetCommand(name, value.ToString(CultureInfo.InvariantCulture), _commandToUnit[typeof(TCommandType)], sampleRate));
123 | }
124 | }
125 |
126 | private void SendSingle(string command)
127 | {
128 | try
129 | {
130 | StatsdClient.Send(command);
131 | }
132 | catch (Exception e)
133 | {
134 | Debug.WriteLine(e.Message);
135 | }
136 | }
137 |
138 | public void Send()
139 | {
140 | try
141 | {
142 | StatsdClient.Send(string.Join("\n", Commands.ToArray()));
143 | AtomicallyClearQueue();
144 | }
145 | catch(Exception e)
146 | {
147 | Debug.WriteLine(e.Message);
148 | }
149 | }
150 |
151 | private void AtomicallyClearQueue()
152 | {
153 | lock (_commandCollectionLock)
154 | {
155 | Commands = new ConcurrentQueue();
156 | }
157 | }
158 |
159 | private string GetCommand(string name, string value, string unit, double sampleRate)
160 | {
161 | var format = sampleRate == 1 ? "{0}:{1}|{2}" : "{0}:{1}|{2}|@{3}";
162 | return string.Format(CultureInfo.InvariantCulture, format, _prefix + name, value, unit, sampleRate);
163 | }
164 |
165 | public void Add(Action actionToTime, string statName, double sampleRate=1)
166 | {
167 | HandleTiming(actionToTime, statName, sampleRate, Add);
168 | }
169 |
170 | public void Send(Action actionToTime, string statName, double sampleRate=1)
171 | {
172 | HandleTiming(actionToTime, statName, sampleRate, Send);
173 | }
174 |
175 | private void HandleTiming(Action actionToTime, string statName, double sampleRate, Action actionToStore)
176 | {
177 | var stopwatch = StopwatchFactory.Get();
178 |
179 | try
180 | {
181 | stopwatch.Start();
182 | actionToTime();
183 | }
184 | finally
185 | {
186 | stopwatch.Stop();
187 | if (RandomGenerator.ShouldSend(sampleRate))
188 | {
189 | actionToStore(statName, stopwatch.ElapsedMilliseconds);
190 | }
191 | }
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/Tests/MetricIntegrationTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text.RegularExpressions;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using NUnit.Framework;
6 | using StatsdClient;
7 | using Tests.Helpers;
8 |
9 | namespace Tests
10 | {
11 | [TestFixture]
12 | public class MetricIntegrationTests
13 | {
14 | private UdpListener _udpListener;
15 | private Thread _listenThread;
16 | private const int _randomUnusedLocalPort = 23483;
17 | private const string _localhostAddress = "127.0.0.1";
18 | private MetricsConfig _defaultMetricsConfig;
19 |
20 | const string _expectedTestPrefixRegex = @"test_prefix\.";
21 | const string _expectedTimeRegEx = @"time:(\d+)\|ms";
22 | const string _expectedMultiSecondTimeRegEx = @"time:1\d{3}\|ms"; // Expect 1xxx milliseconds reported due to the 1+ second delay below
23 | private static readonly TimeSpan SleepDelay = TimeSpan.FromMilliseconds(200);
24 | private static readonly TimeSpan MultiSecondSleepDelay = TimeSpan.FromMilliseconds(1200);
25 |
26 | [OneTimeSetUp]
27 | public void SetUpUdpListener()
28 | {
29 | _udpListener = new UdpListener(_localhostAddress, _randomUnusedLocalPort);
30 | }
31 |
32 | [OneTimeTearDown]
33 | public void TearDownUdpListener()
34 | {
35 | _udpListener.Dispose();
36 | }
37 |
38 | [SetUp]
39 | public void StartUdpListenerThread()
40 | {
41 | _defaultMetricsConfig = new MetricsConfig
42 | {
43 | StatsdServerName = _localhostAddress,
44 | StatsdServerPort = _randomUnusedLocalPort
45 | };
46 |
47 | _listenThread = new Thread(_udpListener.Listen);
48 | _listenThread.Start();
49 | }
50 |
51 | private string LastPacketMessageReceived()
52 | {
53 | // Stall until the the listener receives a message or times out.
54 | while(_listenThread.IsAlive) {}
55 |
56 | var lastMessages = _udpListener.GetAndClearLastMessages();
57 | try
58 | {
59 | return lastMessages[0];
60 | }
61 | catch (ArgumentOutOfRangeException)
62 | {
63 | return null;
64 | }
65 | }
66 |
67 | public class SanityCheck : MetricIntegrationTests
68 | {
69 | [Test]
70 | public void udp_listener_works()
71 | {
72 | var client = new StatsdUDPClient(_localhostAddress, _randomUnusedLocalPort);
73 | client.Send("iamnotinsane!");
74 |
75 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("iamnotinsane!"));
76 | }
77 | }
78 |
79 | public class Counter : MetricIntegrationTests
80 | {
81 | [Test]
82 | public void counter()
83 | {
84 | Metrics.Configure(_defaultMetricsConfig);
85 |
86 | Metrics.Counter("counter");
87 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("counter:1|c"));
88 | }
89 |
90 | [Test]
91 | public void counter_with_value()
92 | {
93 | Metrics.Configure(_defaultMetricsConfig);
94 |
95 | Metrics.Counter("counter", 10);
96 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("counter:10|c"));
97 | }
98 |
99 | [Test]
100 | public void counter_with_prefix()
101 | {
102 | _defaultMetricsConfig.Prefix = "test_prefix";
103 | Metrics.Configure(_defaultMetricsConfig);
104 |
105 | Metrics.Counter("counter");
106 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.counter:1|c"));
107 | }
108 |
109 | [Test]
110 | public void counter_with_prefix_having_a_trailing_dot()
111 | {
112 | _defaultMetricsConfig.Prefix = "test_prefix.";
113 | Metrics.Configure(_defaultMetricsConfig);
114 |
115 | Metrics.Counter("counter");
116 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.counter:1|c"));
117 | }
118 |
119 | [Test]
120 | public void counter_with_value_and_sampleRate()
121 | {
122 | Metrics.Configure(_defaultMetricsConfig);
123 |
124 | Metrics.Counter("counter", 10, 0.9999);
125 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("counter:10|c|@0.9999"));
126 | }
127 |
128 | [Test]
129 | public void counter_with_no_config_setup_should_not_send_metric()
130 | {
131 | Metrics.Configure(new MetricsConfig());
132 |
133 | Metrics.Counter("counter");
134 | Assert.That(LastPacketMessageReceived(), Is.Null);
135 | }
136 | }
137 |
138 | public class Timer : MetricIntegrationTests
139 | {
140 | [Test]
141 | public void timer()
142 | {
143 | Metrics.Configure(_defaultMetricsConfig);
144 |
145 | Metrics.Timer("timer", 6);
146 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("timer:6|ms"));
147 | }
148 |
149 | [Test]
150 | public void timer_with_prefix()
151 | {
152 | _defaultMetricsConfig.Prefix = "test_prefix";
153 | Metrics.Configure(_defaultMetricsConfig);
154 |
155 | Metrics.Timer("timer", 6);
156 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.timer:6|ms"));
157 | }
158 |
159 | [Test]
160 | public void timer_with_prefix_having_a_trailing_dot()
161 | {
162 | _defaultMetricsConfig.Prefix = "test_prefix.";
163 | Metrics.Configure(_defaultMetricsConfig);
164 |
165 | Metrics.Timer("timer", 6);
166 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.timer:6|ms"));
167 | }
168 |
169 | [Test]
170 | public void timer_with_no_config_setup_should_not_send_metric()
171 | {
172 | Metrics.Configure(new MetricsConfig());
173 |
174 | Metrics.Timer("timer", 6);
175 | Assert.That(LastPacketMessageReceived(), Is.Null);
176 | }
177 | }
178 |
179 | public class DisposableTimer : MetricIntegrationTests
180 | {
181 | [Test]
182 | public void disposable_timer()
183 | {
184 | Metrics.Configure(_defaultMetricsConfig);
185 |
186 | using (Metrics.StartTimer("time"))
187 | {
188 | Thread.Sleep(MultiSecondSleepDelay);
189 | }
190 |
191 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedMultiSecondTimeRegEx));
192 | }
193 | }
194 |
195 | public class Time : MetricIntegrationTests
196 | {
197 | [Test]
198 | public void time()
199 | {
200 | Metrics.Configure(_defaultMetricsConfig);
201 |
202 | Metrics.Time(() => Thread.Sleep(MultiSecondSleepDelay), "time");
203 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedMultiSecondTimeRegEx));
204 | }
205 |
206 | [Test]
207 | public void time_add()
208 | {
209 | var statsd = new Statsd(new StatsdUDPClient(_localhostAddress, _randomUnusedLocalPort));
210 |
211 | statsd.Add(() => Thread.Sleep(MultiSecondSleepDelay), "time");
212 | statsd.Send();
213 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedMultiSecondTimeRegEx));
214 | }
215 |
216 | [Test]
217 | public async Task time_async()
218 | {
219 | Metrics.Configure(_defaultMetricsConfig);
220 |
221 | await Metrics.Time(async () => await Task.Delay(SleepDelay), "time");
222 |
223 | AssertTimerLength();
224 | }
225 |
226 | [Test]
227 | public void time_with_prefix()
228 | {
229 | _defaultMetricsConfig.Prefix = "test_prefix";
230 | Metrics.Configure(_defaultMetricsConfig);
231 |
232 | Metrics.Time(() => Thread.Sleep(SleepDelay), "time");
233 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedTestPrefixRegex + _expectedTimeRegEx));
234 | }
235 |
236 | [Test]
237 | public void time_with_prefix_having_trailing_dot()
238 | {
239 | _defaultMetricsConfig.Prefix = "test_prefix.";
240 | Metrics.Configure(_defaultMetricsConfig);
241 |
242 | Metrics.Time(() => Thread.Sleep(SleepDelay), "time");
243 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedTestPrefixRegex + _expectedTimeRegEx));
244 | }
245 |
246 | [Test]
247 | public void time_with_no_config_setup_should_not_send_metric_but_still_run_action()
248 | {
249 | Metrics.Configure(new MetricsConfig());
250 |
251 | var someValue = 5;
252 | Metrics.Time(() => { someValue = 10; }, "timer");
253 |
254 | Assert.That(someValue, Is.EqualTo(10));
255 | Assert.That(LastPacketMessageReceived(), Is.Null);
256 | }
257 |
258 | [Test]
259 | public async Task time_with_async_return_value()
260 | {
261 | Metrics.Configure(_defaultMetricsConfig);
262 |
263 | var returnValue = await Metrics.Time(async () =>
264 | {
265 | await Task.Delay(SleepDelay);
266 | return 20;
267 | }, "time");
268 |
269 | AssertTimerLength();
270 | Assert.That(returnValue, Is.EqualTo(20));
271 | }
272 |
273 | [Test]
274 | public void time_with_return_value()
275 | {
276 | Metrics.Configure(_defaultMetricsConfig);
277 |
278 | var returnValue = Metrics.Time(() =>
279 | {
280 | Thread.Sleep(SleepDelay);
281 | return 5;
282 | }, "time");
283 |
284 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedTimeRegEx));
285 | Assert.That(returnValue, Is.EqualTo(5));
286 | }
287 |
288 | [Test]
289 | public void time_with_return_value_and_prefix()
290 | {
291 | _defaultMetricsConfig.Prefix = "test_prefix";
292 | Metrics.Configure(_defaultMetricsConfig);
293 |
294 | var returnValue = Metrics.Time(() =>
295 | {
296 | Thread.Sleep(SleepDelay);
297 | return 5;
298 | }, "time");
299 |
300 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedTestPrefixRegex + _expectedTimeRegEx));
301 | Assert.That(returnValue, Is.EqualTo(5));
302 | }
303 |
304 | [Test]
305 | public void time_with_return_value_and_prefix_having_a_trailing_dot()
306 | {
307 | _defaultMetricsConfig.Prefix = "test_prefix.";
308 | Metrics.Configure(_defaultMetricsConfig);
309 |
310 | var returnValue = Metrics.Time(() =>
311 | {
312 | Thread.Sleep(SleepDelay);
313 | return 5;
314 | }, "time");
315 |
316 | Assert.That(LastPacketMessageReceived(), Does.Match(_expectedTestPrefixRegex + _expectedTimeRegEx));
317 | Assert.That(returnValue, Is.EqualTo(5));
318 | }
319 |
320 | [Test]
321 | public void time_with_return_value_and_no_config_setup_should_not_send_metric_but_still_return_value()
322 | {
323 | Metrics.Configure(new MetricsConfig());
324 |
325 | var returnValue = Metrics.Time(() => 5, "time");
326 |
327 | Assert.That(LastPacketMessageReceived(), Is.Null);
328 | Assert.That(returnValue, Is.EqualTo(5));
329 | }
330 |
331 | private void AssertTimerLength()
332 | {
333 | var lastPacketMessageReceived = LastPacketMessageReceived();
334 | Assert.That(lastPacketMessageReceived, Does.Match(_expectedTimeRegEx));
335 |
336 | var match = Regex.Match(lastPacketMessageReceived, _expectedTimeRegEx);
337 | var timerValue = Convert.ToInt32(match.Groups[1].Value);
338 | Assert.That(timerValue, Is.EqualTo(SleepDelay.Milliseconds).Within(100));
339 | }
340 | }
341 |
342 | public class GaugeDelta : MetricIntegrationTests
343 | {
344 | [Test]
345 | [TestCase(123d, "gauge:+123|g")]
346 | [TestCase(-123d, "gauge:-123|g")]
347 | [TestCase(0d, "gauge:+0|g")]
348 | public void GaugeDelta_EmitsCorrect_Format(double gaugeDeltaValue, string expectedPacketMessageFormat)
349 | {
350 | Metrics.Configure(_defaultMetricsConfig);
351 |
352 | Metrics.GaugeDelta("gauge", gaugeDeltaValue);
353 | Assert.That(LastPacketMessageReceived(), Is.EqualTo(expectedPacketMessageFormat));
354 | }
355 | }
356 |
357 | public class GaugeAbsolute : MetricIntegrationTests
358 | {
359 | [Test]
360 | public void absolute_gauge_with_double_value()
361 | {
362 | Metrics.Configure(_defaultMetricsConfig);
363 |
364 | const double value = 12345678901234567890;
365 | Metrics.GaugeAbsoluteValue("gauge", value);
366 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("gauge:12345678901234600000.000000000000000|g"));
367 | }
368 |
369 | [Test]
370 | public void absolute_gauge_with_double_value_with_floating_point()
371 | {
372 | Metrics.Configure(_defaultMetricsConfig);
373 |
374 | const double value = 1.234567890123456;
375 | Metrics.GaugeAbsoluteValue("gauge", value);
376 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("gauge:1.234567890123460|g"));
377 | }
378 |
379 | [Test]
380 | public void absolute_gauge_with_prefix()
381 | {
382 | _defaultMetricsConfig.Prefix = "test_prefix";
383 | Metrics.Configure(_defaultMetricsConfig);
384 |
385 | Metrics.GaugeAbsoluteValue("gauge", 3);
386 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.gauge:3.000000000000000|g"));
387 | }
388 |
389 | [Test]
390 | public void absolute_gauge_with_prefix_having_a_trailing_dot()
391 | {
392 | _defaultMetricsConfig.Prefix = "test_prefix.";
393 | Metrics.Configure(_defaultMetricsConfig);
394 |
395 | Metrics.GaugeAbsoluteValue("gauge", 3);
396 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.gauge:3.000000000000000|g"));
397 | }
398 |
399 | [Test]
400 | public void gauge_with_no_config_setup_should_not_send_metric()
401 | {
402 | Metrics.Configure(new MetricsConfig());
403 |
404 | Metrics.GaugeAbsoluteValue("gauge", 3);
405 | Assert.That(LastPacketMessageReceived(), Is.Null);
406 | }
407 | }
408 |
409 | public class Set : MetricIntegrationTests
410 | {
411 | [Test]
412 | public void set()
413 | {
414 | Metrics.Configure(_defaultMetricsConfig);
415 |
416 | Metrics.Set("timer", "value");
417 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("timer:value|s"));
418 | }
419 |
420 | [Test]
421 | public void set_with_prefix()
422 | {
423 | _defaultMetricsConfig.Prefix = "test_prefix";
424 | Metrics.Configure(_defaultMetricsConfig);
425 |
426 | Metrics.Set("timer", "value");
427 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.timer:value|s"));
428 | }
429 |
430 | [Test]
431 | public void set_with_prefix_having_a_trailing_dot()
432 | {
433 | _defaultMetricsConfig.Prefix = "test_prefix.";
434 | Metrics.Configure(_defaultMetricsConfig);
435 |
436 | Metrics.Set("timer", "value");
437 | Assert.That(LastPacketMessageReceived(), Is.EqualTo("test_prefix.timer:value|s"));
438 | }
439 |
440 | [Test]
441 | public void set_with_no_config_setup_should_not_send_metric()
442 | {
443 | Metrics.Configure(new MetricsConfig());
444 |
445 | Metrics.Set("timer", "value");
446 | Assert.That(LastPacketMessageReceived(), Is.Null);
447 | }
448 | }
449 | }
450 | }
--------------------------------------------------------------------------------
/src/Tests/StatsdTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using NSubstitute;
5 | using NUnit.Framework;
6 | using StatsdClient;
7 |
8 | namespace Tests
9 | {
10 | [TestFixture]
11 | public class StatsdTests
12 | {
13 | private IStatsdClient _udp;
14 | private IRandomGenerator _randomGenerator;
15 | private IStopWatchFactory _stopwatch;
16 |
17 | [SetUp]
18 | public void Setup()
19 | {
20 | _udp = Substitute.For();
21 | _stopwatch = Substitute.For();
22 | _randomGenerator = Substitute.For();
23 | _randomGenerator.ShouldSend(0).ReturnsForAnyArgs(true);
24 | }
25 |
26 | public class Counter : StatsdTests
27 | {
28 | [Test]
29 | public void increases_counter_with_value_of_X()
30 | {
31 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
32 | s.Send("counter", 5);
33 | _udp.Received().Send("counter:5|c");
34 | }
35 |
36 | [Test]
37 | public void increases_counter_with_value_of_X_and_sample_rate()
38 | {
39 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
40 | s.Send("counter", 5, 0.1);
41 | _udp.Received().Send("counter:5|c|@0.1");
42 | }
43 |
44 | [Test]
45 | public void counting_exception_fails_silently()
46 | {
47 | GivenUdpSendFails();
48 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
49 | s.Send("counter", 5);
50 | }
51 | }
52 |
53 | public class Timer : StatsdTests
54 | {
55 | [Test]
56 | public void adds_timing()
57 | {
58 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
59 | s.Send("timer", 5);
60 | _udp.Received().Send("timer:5|ms");
61 | }
62 |
63 | [Test]
64 | public void timing_with_value_of_X_and_sample_rate()
65 | {
66 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
67 | s.Send("timer", 5, 0.1);
68 | _udp.Received().Send("timer:5|ms|@0.1");
69 | }
70 |
71 | [Test]
72 | public void timing_exception_fails_silently()
73 | {
74 | GivenUdpSendFails();
75 | var s = new Statsd(_udp);
76 | s.Send("timer", 5);
77 | }
78 |
79 | [Test]
80 | public void add_timer_with_lamba()
81 | {
82 | const string statName = "name";
83 |
84 | var stopwatch = Substitute.For();
85 | stopwatch.ElapsedMilliseconds.Returns(500);
86 | _stopwatch.Get().Returns(stopwatch);
87 |
88 | var statsd = new Statsd(_udp, _randomGenerator, _stopwatch);
89 | statsd.Add(() => TestMethod(), statName);
90 |
91 | Assert.That(statsd.Commands.Single(), Is.EqualTo("name:500|ms"));
92 | }
93 |
94 | [Test]
95 | public void add_timer_with_lamba_and_sampleRate_passes()
96 | {
97 | const string statName = "name";
98 |
99 | var stopwatch = Substitute.For();
100 | stopwatch.ElapsedMilliseconds.Returns(500);
101 | _stopwatch.Get().Returns(stopwatch);
102 | _randomGenerator = Substitute.For();
103 | _randomGenerator.ShouldSend(0).ReturnsForAnyArgs(true);
104 |
105 | var statsd = new Statsd(_udp, _randomGenerator, _stopwatch);
106 | statsd.Add(() => TestMethod(), statName, 0.1);
107 |
108 | Assert.That(statsd.Commands.Single(), Is.EqualTo("name:500|ms"));
109 | }
110 |
111 | [Test]
112 | public void add_timer_with_lamba_and_sampleRate_fails()
113 | {
114 | const string statName = "name";
115 |
116 | var stopwatch = Substitute.For();
117 | stopwatch.ElapsedMilliseconds.Returns(500);
118 | _stopwatch.Get().Returns(stopwatch);
119 | _randomGenerator = Substitute.For();
120 | _randomGenerator.ShouldSend(0).ReturnsForAnyArgs(false);
121 |
122 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
123 | s.Add(() => TestMethod(), statName, 0.1);
124 |
125 | Assert.That(s.Commands.Count, Is.EqualTo(0));
126 | }
127 |
128 | [Test]
129 | public void add_timer_with_lamba_still_records_on_error_and_still_bubbles_up_exception()
130 | {
131 | const string statName = "name";
132 |
133 | var stopwatch = Substitute.For();
134 | stopwatch.ElapsedMilliseconds.Returns(500);
135 | _stopwatch.Get().Returns(stopwatch);
136 |
137 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
138 |
139 | Assert.Throws(() => s.Add(() => { throw new InvalidOperationException(); }, statName));
140 |
141 | Assert.That(s.Commands.Count, Is.EqualTo(1));
142 | Assert.That(s.Commands.ToArray()[0], Is.EqualTo("name:500|ms"));
143 | }
144 |
145 | [Test]
146 | public void send_timer_with_lambda()
147 | {
148 | const string statName = "name";
149 | var stopwatch = Substitute.For();
150 | stopwatch.ElapsedMilliseconds.Returns(500);
151 | _stopwatch.Get().Returns(stopwatch);
152 |
153 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
154 | s.Send(() => TestMethod(), statName);
155 |
156 | _udp.Received().Send("name:500|ms");
157 | }
158 |
159 | [Test]
160 | public void send_timer_with_lambda_and_sampleRate_passes()
161 | {
162 | const string statName = "name";
163 | var stopwatch = Substitute.For();
164 | stopwatch.ElapsedMilliseconds.Returns(500);
165 | _stopwatch.Get().Returns(stopwatch);
166 | _randomGenerator = Substitute.For();
167 | _randomGenerator.ShouldSend(0).ReturnsForAnyArgs(true);
168 |
169 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
170 | s.Send(() => TestMethod(), statName);
171 |
172 | _udp.Received().Send("name:500|ms");
173 | }
174 |
175 |
176 | [Test]
177 | public void send_timer_with_lambda_and_sampleRate_fails()
178 | {
179 | const string statName = "name";
180 | var stopwatch = Substitute.For();
181 | stopwatch.ElapsedMilliseconds.Returns(500);
182 | _stopwatch.Get().Returns(stopwatch);
183 | _randomGenerator = Substitute.For();
184 | _randomGenerator.ShouldSend(0).ReturnsForAnyArgs(false);
185 |
186 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
187 | s.Send(() => TestMethod(), statName);
188 |
189 | _udp.DidNotReceive().Send("name:500|ms");
190 | }
191 |
192 | [Test]
193 | public void send_timer_with_lamba_still_records_on_error_and_still_bubbles_up_exception()
194 | {
195 | const string statName = "name";
196 | var stopwatch = Substitute.For();
197 | stopwatch.ElapsedMilliseconds.Returns(500);
198 | _stopwatch.Get().Returns(stopwatch);
199 |
200 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
201 | Assert.Throws(() => s.Send(() => { throw new InvalidOperationException(); }, statName));
202 |
203 | _udp.Received().Send("name:500|ms");
204 | }
205 |
206 | [Test]
207 | public void set_return_value_with_send_timer_with_lambda()
208 | {
209 | const string statName = "name";
210 | var stopwatch = Substitute.For();
211 | stopwatch.ElapsedMilliseconds.Returns(500);
212 | _stopwatch.Get().Returns(stopwatch);
213 |
214 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
215 | var returnValue = 0;
216 | s.Send(() => returnValue = TestMethod(), statName);
217 |
218 | _udp.Received().Send("name:500|ms");
219 | Assert.That(returnValue, Is.EqualTo(5));
220 | }
221 | }
222 |
223 | public class Guage : StatsdTests
224 | {
225 | [Test]
226 | public void adds_gauge_with_large_double_values()
227 | {
228 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
229 | s.Send("gauge", 34563478564785);
230 | _udp.Received().Send("gauge:34563478564785.000000000000000|g");
231 | }
232 |
233 | [Test]
234 | public void gauge_exception_fails_silently()
235 | {
236 | GivenUdpSendFails();
237 | var s = new Statsd(_udp);
238 | s.Send("gauge", 5.0);
239 | }
240 |
241 | [Test]
242 | [TestCase(true, 10d, "delta-gauge:+10|g")]
243 | [TestCase(true, -10d, "delta-gauge:-10|g")]
244 | [TestCase(true, 0d, "delta-gauge:+0|g")]
245 | [TestCase(false, 10d, "delta-gauge:10.000000000000000|g")]//because it is looped through to original Gauge send function
246 | public void adds_gauge_with_deltaValue_formatsCorrectly(bool isDeltaValue, double value, string expectedFormattedStatsdMessage)
247 | {
248 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
249 | s.Send("delta-gauge", value, isDeltaValue);
250 | _udp.Received().Send(expectedFormattedStatsdMessage);
251 | }
252 | }
253 |
254 | public class Meter : StatsdTests
255 | {
256 | [Test]
257 | public void adds_meter()
258 | {
259 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
260 | s.Send("meter", 5);
261 | _udp.Received().Send("meter:5|m");
262 | }
263 |
264 | [Test]
265 | public void meter_exception_fails_silently()
266 | {
267 | GivenUdpSendFails();
268 | var s = new Statsd(_udp);
269 | s.Send("meter", 5);
270 | }
271 | }
272 |
273 | public class Historgram : StatsdTests
274 | {
275 | [Test]
276 | public void adds_histogram()
277 | {
278 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
279 | s.Send("histogram", 5);
280 | _udp.Received().Send("histogram:5|h");
281 | }
282 |
283 | [Test]
284 | public void histrogram_exception_fails_silently()
285 | {
286 | GivenUdpSendFails();
287 | var s = new Statsd(_udp);
288 | s.Send("histogram", 5);
289 | }
290 | }
291 |
292 | public class Set : StatsdTests
293 | {
294 | [Test]
295 | public void adds_set_with_string_value()
296 | {
297 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
298 | s.Send("set", "34563478564785xyz");
299 | _udp.Received().Send("set:34563478564785xyz|s");
300 | }
301 |
302 | [Test]
303 | public void set_exception_fails_silently()
304 | {
305 | GivenUdpSendFails();
306 | var s = new Statsd(_udp);
307 | s.Send("set", "silent-exception-test");
308 | }
309 | }
310 |
311 | public class Combination : StatsdTests
312 | {
313 | [Test]
314 | public void add_one_counter_and_one_gauge_shows_in_commands()
315 | {
316 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
317 | s.Add("counter", 1, 0.1);
318 | s.Add("timer", 1);
319 |
320 | Assert.That(s.Commands.Count, Is.EqualTo(2));
321 | Assert.That(s.Commands.ToArray()[0], Is.EqualTo("counter:1|c|@0.1"));
322 | Assert.That(s.Commands.ToArray()[1], Is.EqualTo("timer:1|ms"));
323 | }
324 |
325 | [Test]
326 | public void add_one_counter_and_one_gauge_with_no_sample_rate_shows_in_commands()
327 | {
328 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
329 | s.Add("counter", 1);
330 | s.Add("timer", 1);
331 |
332 | Assert.That(s.Commands.Count, Is.EqualTo(2));
333 | Assert.That(s.Commands.ToArray()[0], Is.EqualTo("counter:1|c"));
334 | Assert.That(s.Commands.ToArray()[1], Is.EqualTo("timer:1|ms"));
335 | }
336 |
337 | [Test]
338 | public void add_one_counter_and_one_timer_sends_in_one_go()
339 | {
340 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
341 | s.Add("counter", 1, 0.1);
342 | s.Add("timer", 1);
343 | s.Send();
344 |
345 | _udp.Received().Send("counter:1|c|@0.1\ntimer:1|ms");
346 | }
347 |
348 | [Test]
349 | public void add_one_counter_and_one_timer_sends_and_removes_commands()
350 | {
351 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
352 | s.Add("counter", 1, 0.1);
353 | s.Add("timer", 1);
354 | s.Send();
355 |
356 | Assert.That(s.Commands.Count, Is.EqualTo(0));
357 | }
358 |
359 | [Test]
360 | public void add_one_counter_and_send_one_timer_sends_only_sends_the_last()
361 | {
362 | var s = new Statsd(_udp, _randomGenerator, _stopwatch);
363 | s.Add("counter", 1);
364 | s.Send("timer", 1);
365 |
366 | _udp.Received().Send("timer:1|ms");
367 | }
368 | }
369 |
370 | public class NamePrefixing : StatsdTests
371 | {
372 | [Test]
373 | public void set_prefix_on_stats_name_when_calling_send()
374 | {
375 | var s = new Statsd(_udp, _randomGenerator, _stopwatch, "a.prefix.");
376 | s.Send("counter", 5);
377 | s.Send("counter", 5);
378 |
379 | _udp.Received(2).Send("a.prefix.counter:5|c");
380 | }
381 |
382 | [Test]
383 | public void add_counter_sets_prefix_on_name()
384 | {
385 | var s = new Statsd(_udp, _randomGenerator, _stopwatch, "another.prefix.");
386 |
387 | s.Add("counter", 1, 0.1);
388 | s.Add("timer", 1);
389 | s.Send();
390 |
391 | _udp.Received().Send("another.prefix.counter:1|c|@0.1\nanother.prefix.timer:1|ms");
392 | }
393 | }
394 |
395 | public class ThreadSafety : StatsdTests
396 | {
397 | private const int ThreadCount = 10000;
398 | private Statsd _stats;
399 |
400 | [SetUp]
401 | public void Before_each()
402 | {
403 | _stats = new Statsd(_udp, _randomGenerator, _stopwatch);
404 | }
405 |
406 | [Test]
407 | public void add_counters()
408 | {
409 | Parallel.For(0, ThreadCount, x => Assert.DoesNotThrow(() => _stats.Add("random-name", 5)));
410 | }
411 |
412 | [Test]
413 | public void add_gauges()
414 | {
415 | Parallel.For(0, ThreadCount, x => Assert.DoesNotThrow(() => _stats.Add("random-name", 5d)));
416 | }
417 |
418 | [Test]
419 | public void send_counters()
420 | {
421 | Parallel.For(0, ThreadCount, x => _stats.Send(Guid.NewGuid().ToString(), 5));
422 | Assert.That(DistinctMetricsSent(), Is.EqualTo(ThreadCount));
423 | }
424 |
425 | [Test]
426 | public void send_absolute_gauge()
427 | {
428 | Parallel.For(0, ThreadCount, x => _stats.Send(Guid.NewGuid().ToString(), 5d));
429 | Assert.That(DistinctMetricsSent(), Is.EqualTo(ThreadCount));
430 | }
431 |
432 | [Test]
433 | public void send_delta_gauge()
434 | {
435 | Parallel.For(0, ThreadCount, x => _stats.Send(Guid.NewGuid().ToString(), 5d, true));
436 | Assert.That(DistinctMetricsSent(), Is.EqualTo(ThreadCount));
437 | }
438 |
439 | [Test]
440 | public void send_set()
441 | {
442 | Parallel.For(0, ThreadCount, x => _stats.Send(Guid.NewGuid().ToString(), "foo"));
443 | Assert.That(DistinctMetricsSent(), Is.EqualTo(ThreadCount));
444 | }
445 |
446 | [Test]
447 | public void send_sampled_timer()
448 | {
449 | Parallel.For(0, ThreadCount, x => _stats.Send(Guid.NewGuid().ToString(), 5, 1d));
450 | Assert.That(DistinctMetricsSent(), Is.EqualTo(ThreadCount));
451 | }
452 |
453 | private int DistinctMetricsSent()
454 | {
455 | return _udp.ReceivedCalls().Select(x => x.GetArguments()[0]).Distinct().Count();
456 | }
457 | }
458 |
459 | private static int TestMethod()
460 | {
461 | return 5;
462 | }
463 |
464 | private void GivenUdpSendFails()
465 | {
466 | _udp.When(x => x.Send(Arg.Any())).Do(x => { throw new Exception(); });
467 | }
468 | }
469 | }
--------------------------------------------------------------------------------