Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zero allocating StatsDPublisher #104

Merged
merged 24 commits into from
Oct 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
os: Visual Studio 2017
version: 3.1.1.{build}
version: 3.2.0.{build}
configuration: Release
environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
Expand Down
52 changes: 41 additions & 11 deletions src/Benchmark/StatSendingBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
using System;
using BenchmarkDotNet.Attributes;
using JustEat.StatsD;
using JustEat.StatsD.Buffered;
using JustEat.StatsD.EndpointLookups;

namespace Benchmark
{
[MemoryDiagnoser]
public class StatSendingBenchmark
{
private StatsDPublisher _udpSender;
private StatsDPublisher _ipSender;
private BufferBasedStatsDPublisher _pooledUdpSender;
private StringBasedStatsDPublisher _udpSender;
private StringBasedStatsDPublisher _ipSender;

private static readonly TimeSpan Timed = TimeSpan.FromMinutes(1);

Expand All @@ -28,39 +30,67 @@ public void Setup()
config.Host, config.Port, config.DnsLookupInterval);

var udpTransport = new UdpTransport(endpointSource);
var ipTransport = new IpTransport(endpointSource);

_udpSender = new StatsDPublisher(config, udpTransport);
_udpSender = new StringBasedStatsDPublisher(config, udpTransport);
_udpSender.Increment("startup.ud");

_ipSender = new StatsDPublisher(config, ipTransport);
_udpSender.Increment("startup.ip");
var ipTransport = new IpTransport(endpointSource);
_ipSender = new StringBasedStatsDPublisher(config, ipTransport);
_ipSender.Increment("startup.ip");

var pooledUdpTransport = new PooledUdpTransport(endpointSource);
_pooledUdpSender = new BufferBasedStatsDPublisher(config, pooledUdpTransport);
_pooledUdpSender.Increment("startup.v2");
}

[Benchmark]
public void RunUdp()
{
_udpSender.Increment("increment.ud");
_udpSender.MarkEvent("hello.ud");
_udpSender.Increment(20, "increment.ud");
_udpSender.Timing(Timed, "timer.ud");
_udpSender.Gauge(354654, "gauge.ud");
_udpSender.Gauge(25.1, "free-space.ud");
}

[Benchmark]
public void RunUdpWithSampling()
{
_udpSender.Increment(2, 0.5, "increment.ud");
_udpSender.Increment(2, 0.2, "increment.ud");
_udpSender.Timing(2, 0.2, "increment.ud");
}

[Benchmark]
public void RunIp()
{
_ipSender.Increment("increment.ip");
_ipSender.MarkEvent("hello.ip");
_ipSender.Increment(20, "increment.ip");
_ipSender.Timing(Timed, "timer.ip");
_ipSender.Gauge(354654, "gauge.ip");
_ipSender.Gauge(25.1, "free-space.ip");
}

[Benchmark]
public void RunIpWithSampling()
{
_ipSender.Increment(2, 0.5, "increment.ip");
_udpSender.Increment(2, 0.2, "increment.ip");
_udpSender.Timing(2, 0.2, "increment.ip");
}

[Benchmark]
public void RunBuffered()
{
_pooledUdpSender.MarkEvent("hello.v2");
_pooledUdpSender.Increment(20, "increment.v2");
_pooledUdpSender.Timing(Timed, "timer.v2");
_pooledUdpSender.Gauge(354654, "gauge.v2");
_pooledUdpSender.Gauge(25.1, "free-space.v2");
}

[Benchmark]
public void RunBufferedWithSampling()
{
_pooledUdpSender.Increment(2, 0.2, "increment.v2");
_pooledUdpSender.Timing(2, 0.2, "increment.v2");
}
}
}
31 changes: 27 additions & 4 deletions src/Benchmark/UdpStatSendingBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using BenchmarkDotNet.Attributes;
using JustEat.StatsD;
using JustEat.StatsD.Buffered;
using JustEat.StatsD.EndpointLookups;

namespace Benchmark
Expand All @@ -11,8 +12,10 @@ public class UdpStatSendingBenchmark
private static readonly TimeSpan Timed = TimeSpan.FromMinutes(1);

private PooledUdpTransport _pooledTransport;
private StatsDPublisher _udpSender;
private StatsDPublisher _pooledUdpSender;
private StringBasedStatsDPublisher _udpSender;
private StringBasedStatsDPublisher _pooledUdpSender;
private BufferBasedStatsDPublisher _bufferBasedStatsDPublisher;
private IStatsDPublisher _adaptedStatsDPublisher;

[GlobalSetup]
public void Setup()
Expand All @@ -33,11 +36,17 @@ public void Setup()
_pooledTransport = new PooledUdpTransport(endpointSource);
var udpTransport = new UdpTransport(endpointSource);

_udpSender = new StatsDPublisher(config, udpTransport);
_udpSender = new StringBasedStatsDPublisher(config, udpTransport);
_udpSender.Increment("startup.ud");

_pooledUdpSender = new StatsDPublisher(config, _pooledTransport);
_pooledUdpSender = new StringBasedStatsDPublisher(config, _pooledTransport);
_pooledUdpSender.Increment("startup.ud");

_adaptedStatsDPublisher = new StatsDPublisher(config);
_adaptedStatsDPublisher.Increment("startup.ud");

_bufferBasedStatsDPublisher = new BufferBasedStatsDPublisher(config, _pooledTransport);
_bufferBasedStatsDPublisher.Increment("startup.ud");
}

[GlobalCleanup]
Expand All @@ -59,5 +68,19 @@ public void SendStatPooledUdp()
_pooledUdpSender.Increment("increment.ud");
_pooledUdpSender.Timing(Timed, "timer.ud");
}

[Benchmark]
public void SendStatPooledUdpBuffered()
{
_bufferBasedStatsDPublisher.Increment("increment.ud");
_bufferBasedStatsDPublisher.Timing(Timed, "timer.ud");
}

[Benchmark]
public void SendStatPooledUdpCoveredByAdapter()
{
_adaptedStatsDPublisher.Increment("increment.ud");
_adaptedStatsDPublisher.Timing(Timed, "timer.ud");
}
}
}
32 changes: 32 additions & 0 deletions src/Benchmark/Utf8FormatterBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Text;
using BenchmarkDotNet.Attributes;
using JustEat.StatsD;
using JustEat.StatsD.Buffered;

namespace Benchmark
{
[MemoryDiagnoser]
public class Utf8FormatterBenchmark
{
private static readonly StatsDMessageFormatter FormatterString = new StatsDMessageFormatter("hello.world");
private static readonly StatsDUtf8Formatter FormatterBuffer = new StatsDUtf8Formatter("hello.world");

private static readonly byte[] Buffer = new byte[512];

[Benchmark(Baseline = true)]
public void StringBased()
{
Encoding.UTF8.GetBytes(FormatterString.Gauge(255, "some.neat.bucket"));
Encoding.UTF8.GetBytes(FormatterString.Timing(255, "some.neat.bucket"));
Encoding.UTF8.GetBytes(FormatterString.Increment(255, "some.neat.bucket"));
}

[Benchmark]
public void BufferBased()
{
FormatterBuffer.TryFormat(StatsDMessage.Gauge(255, "some.neat.bucket"), 1, Buffer, out _);
FormatterBuffer.TryFormat(StatsDMessage.Timing(255, "some.neat.bucket"), 1, Buffer, out _);
FormatterBuffer.TryFormat(StatsDMessage.Counter(255, "some.neat.bucket"), 1, Buffer, out _);
}
}
}
2 changes: 1 addition & 1 deletion src/JustEat.StatsD.Tests/UdpListenersCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ namespace JustEat.StatsD
public class UdpListenersCollection : ICollectionFixture<UdpListeners>
{
}
}
}
123 changes: 123 additions & 0 deletions src/JustEat.StatsD.Tests/Utf8FormatterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using System;
using System.Text;
using JustEat.StatsD.Buffered;
using Shouldly;
using Xunit;

namespace JustEat.StatsD
{
public static class Utf8FormatterTests
{
private static readonly byte[] Buffer = new byte[512];
private static readonly StatsDUtf8Formatter Formatter = new StatsDUtf8Formatter("prefix");

[Fact]
public static void CounterSampled()
{
var message = StatsDMessage.Counter(128, "bucket");
Check(message, 0.5, "prefix.bucket:128|c|@0.5");
}

[Fact]
public static void CounterRegular()
{
var message = StatsDMessage.Counter(128, "bucket");
Check(message, "prefix.bucket:128|c");
}

[Fact]
public static void CounterNegative()
{
var message = StatsDMessage.Counter(-128, "bucket");
Check(message, "prefix.bucket:-128|c");
}

[Fact]
public static void Timing()
{
var message = StatsDMessage.Timing(128, "bucket");
Check(message, "prefix.bucket:128|ms");
}

[Fact]
public static void TimingSampled()
{
var message = StatsDMessage.Timing(128, "bucket");
Check(message, 0.5, "prefix.bucket:128|ms|@0.5");
}

This comment was marked as resolved.

[Fact]
public static void GaugeIntegral()
{
var message = StatsDMessage.Gauge(128, "bucket");
Check(message, "prefix.bucket:128|g");
}

[Fact]
public static void GaugeFloat()
{
var message = StatsDMessage.Gauge(128.5, "bucket");
Check(message, "prefix.bucket:128.5|g");
}

[Fact]
public static void MessagesLargerThenAvailableBufferShouldNotBeFormatted()
{
var buffer = new byte[128];
var hugeBucket = new string('x', 256);
var message = StatsDMessage.Gauge(128.5, hugeBucket);
Formatter.TryFormat(message, 1.0, buffer, out int written).ShouldBe(false);
written.ShouldBe(0);
}

[Theory]
[InlineData(1, 'z')]
[InlineData(2, 'z')]
[InlineData(4, 'z')]
[InlineData(8, 'z')]
[InlineData(16, 'z')]
[InlineData(32, 'z')]
[InlineData(64, 'z')]
[InlineData(128, 'z')]
[InlineData(256, 'z')]
[InlineData(512, 'z')]
[InlineData(1024, 'z')]
[InlineData(2048, 'z')]
[InlineData(1, 'Ж')]
[InlineData(2, 'Ж')]
[InlineData(4, 'Ж')]
[InlineData(8, 'Ж')]
[InlineData(16, 'Ж')]
[InlineData(32, 'Ж')]
[InlineData(64, 'Ж')]
[InlineData(128, 'Ж')]
[InlineData(256, 'Ж')]
[InlineData(512, 'Ж')]
[InlineData(1024, 'Ж')]
[InlineData(2048, 'Ж')]
public static void GetMaxBufferSizeCalculatesValidBufferSizes(int bucketSize, char ch)
{
var hugeBucket = new string(ch, bucketSize);
var message = StatsDMessage.Gauge(128.5, hugeBucket);
var expected = $"prefix.{hugeBucket}:128.5|g";

var buffer = new byte[Formatter.GetMaxBufferSize(message)];

Formatter.TryFormat(message, 1.0, buffer, out int written).ShouldBe(true);
var actual = Encoding.UTF8.GetString(buffer.AsSpan(0, written));
actual.ShouldBe(expected);
}

private static void Check(StatsDMessage message, string expected)
{
Check(message, 1, expected);
}

private static void Check(StatsDMessage message, double sampleRate, string expected)
{
Formatter.TryFormat(message, sampleRate, Buffer, out int written).ShouldBe(true);
var result = Encoding.UTF8.GetString(Buffer.AsSpan(0, written));
result.ShouldBe(expected);
}
}
}
Loading