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

tagging and gauge operations #229

Closed
wants to merge 18 commits into from
Closed
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ We use this library within our components to publish [StatsD](http://github.com/

### Features

* Easy to use
* Robust and proven
* Easy to use.
* Robust and proven.
* Tuned for high performance and low resource usage using [BenchmarkDotNet](https://benchmarkdotnet.org/). Typically zero allocation on sending a metric on target frameworks where `Span<T>` is available.
* Works well with modern .NET apps - `async ... await`, .NET Core, .NET Standard 2.0.
* Supports standard StatsD primitives: `Increment`, `Decrement`, `Timing` and `Gauge`.
* Supports tagging on `Increment`, `Decrement`, `Timing` and `Gauge`.
* Supports sample rate for cutting down of sends of high-volume metrics.
* Helpers to make it easy to time a delegate such as a `Func<T>` or `Action<T>`, or a code block inside a `using` statement.
* Send stats over UDP or IP
* Send stats to a server by name or IP address
* Send stats over UDP or IP.
* Send stats to a server by name or IP address.

#### Publishing statistics

Expand Down
17 changes: 11 additions & 6 deletions src/JustEat.StatsD/Buffered/BufferBasedStatsDPublisher.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;

namespace JustEat.StatsD.Buffered
{
Expand Down Expand Up @@ -31,19 +32,23 @@ internal BufferBasedStatsDPublisher(StatsDConfiguration configuration, IStatsDTr
_formatter = new StatsDUtf8Formatter(configuration.Prefix);
}

public void Increment(long value, double sampleRate, string bucket)
public void Increment(long value, double sampleRate, string bucket, IDictionary<string, string>? tags = null)
{
SendMessage(sampleRate, StatsDMessage.Counter(value, bucket));
SendMessage(sampleRate, StatsDMessage.Counter(value, bucket, tags));
}

public void Gauge(double value, string bucket)
public void Gauge(
double value,
string bucket,
Operation operation = Operation.Set,
IDictionary<string, string>? tags = null)
{
SendMessage(DefaultSampleRate, StatsDMessage.Gauge(value, bucket));
SendMessage(DefaultSampleRate, StatsDMessage.Gauge(value, bucket, tags, operation));
}

public void Timing(long duration, double sampleRate, string bucket)
public void Timing(long duration, double sampleRate, string bucket, IDictionary<string, string>? tags = null)
{
SendMessage(sampleRate, StatsDMessage.Timing(duration, bucket));
SendMessage(sampleRate, StatsDMessage.Timing(duration, bucket, tags));
}

private void SendMessage(double sampleRate, in StatsDMessage msg)
Expand Down
35 changes: 28 additions & 7 deletions src/JustEat.StatsD/Buffered/StatsDMessage.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,52 @@
using System.Collections.Generic;

namespace JustEat.StatsD.Buffered
{
internal readonly struct StatsDMessage
{
public readonly string StatBucket;
public readonly double Magnitude;
public readonly StatsDMessageKind MessageKind;
public readonly Operation Operation;
public readonly IDictionary<string, string>? Tags;

private StatsDMessage(string statBucket, double magnitude, StatsDMessageKind messageKind)
private StatsDMessage(
string statBucket,
double magnitude,
StatsDMessageKind messageKind,
IDictionary<string, string>? tags,
Operation operation = default)
{
StatBucket = statBucket;
Magnitude = magnitude;
MessageKind = messageKind;
Operation = operation;
Tags = tags;
}

public static StatsDMessage Timing(long milliseconds, string statBucket)
public static StatsDMessage Timing(
long milliseconds,
string statBucket,
IDictionary<string, string>? tags)
{
return new StatsDMessage(statBucket, milliseconds, StatsDMessageKind.Timing);
return new StatsDMessage(statBucket, milliseconds, StatsDMessageKind.Timing, tags);
}

public static StatsDMessage Counter(long magnitude, string statBucket)
public static StatsDMessage Counter(
long magnitude,
string statBucket,
IDictionary<string, string>? tags)
{
return new StatsDMessage(statBucket, magnitude, StatsDMessageKind.Counter);
return new StatsDMessage(statBucket, magnitude, StatsDMessageKind.Counter, tags);
}

public static StatsDMessage Gauge(double magnitude, string statBucket)
public static StatsDMessage Gauge(
double magnitude,
string statBucket,
IDictionary<string, string>? tags,
Operation operation = Operation.Set)
{
return new StatsDMessage(statBucket, magnitude, StatsDMessageKind.Gauge);
return new StatsDMessage(statBucket, magnitude, StatsDMessageKind.Gauge, tags, operation);
}
}
}
44 changes: 39 additions & 5 deletions src/JustEat.StatsD/Buffered/StatsDUtf8Formatter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;

Expand Down Expand Up @@ -37,7 +39,7 @@ public bool TryFormat(in StatsDMessage msg, double sampleRate, Span<byte> destin
var buffer = new Buffer(destination);

bool isFormattingSuccessful =
TryWriteBucketNameWithColon(ref buffer, msg.StatBucket)
TryWriteBucketNameWithColon(ref buffer, msg)
&& TryWritePayloadWithMessageKindSuffix(ref buffer, msg)
&& TryWriteSampleRateIfNeeded(ref buffer, sampleRate);

Expand All @@ -46,15 +48,35 @@ public bool TryFormat(in StatsDMessage msg, double sampleRate, Span<byte> destin
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool TryWriteBucketNameWithColon(ref Buffer buffer, string statBucket)
private bool TryWriteBucketNameWithColon(ref Buffer buffer, StatsDMessage msg)
{
// prefix + msg.Bucket + ":"
// prefix + msg.Bucket + tags + ":"

return buffer.TryWriteBytes(_utf8Prefix)
&& buffer.TryWriteUtf8String(statBucket)
&& buffer.TryWriteUtf8String(msg.StatBucket)
&& TryWriteTags(ref buffer, msg.Tags)
&& buffer.TryWriteByte((byte)':');
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryWriteTags(ref Buffer buffer, IDictionary<string, string>? tags)
{
// key=value,key=value

if (tags == null || tags.Count == 0)
{
return true;
}

var sb = new StringBuilder();
foreach (var tag in tags)
{
sb.AppendFormat(CultureInfo.InvariantCulture, ";{0}={1}", tag.Key, tag.Value);
}

return buffer.TryWriteUtf8String(sb.ToString());
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryWritePayloadWithMessageKindSuffix(ref Buffer buffer, in StatsDMessage msg)
{
Expand All @@ -79,7 +101,19 @@ private static bool TryWritePayloadWithMessageKindSuffix(ref Buffer buffer, in S
// check if magnitude is integral, integers are written significantly faster
bool isMagnitudeIntegral = msg.Magnitude == integralMagnitude;

var successSoFar = isMagnitudeIntegral ?
bool successSoFar = true;

if (msg.Operation == Operation.Increment)
{
successSoFar &= buffer.TryWriteByte((byte) '+');
}

if (msg.Operation == Operation.Decrement)
{
successSoFar &= buffer.TryWriteByte((byte) '-');
}

successSoFar &= isMagnitudeIntegral ?
buffer.TryWriteInt64(integralMagnitude) :
buffer.TryWriteDouble(msg.Magnitude);

Expand Down
12 changes: 9 additions & 3 deletions src/JustEat.StatsD/IStatsDPublisher.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Collections.Generic;

namespace JustEat.StatsD
{
/// <summary>
Expand All @@ -11,21 +13,25 @@ public interface IStatsDPublisher
/// <param name="value">The value to increment the counter by.</param>
/// <param name="sampleRate">The sample rate for the counter.</param>
/// <param name="bucket">The bucket to increment the counter for.</param>
void Increment(long value, double sampleRate, string bucket);
/// <param name="tags">An optional dictionary of tags.</param>
void Increment(long value, double sampleRate, string bucket, IDictionary<string, string>? tags);

/// <summary>
/// Publishes a gauge for the specified bucket and value.
/// </summary>
/// <param name="value">The value to publish for the gauge.</param>
/// <param name="bucket">The bucket to publish the gauge for.</param>
void Gauge(double value, string bucket);
/// <param name="operation">The gauge operation.</param>
/// <param name="tags">An optional dictionary of tags.</param>
void Gauge(double value, string bucket, Operation operation, IDictionary<string, string>? tags);

/// <summary>
/// Publishes a timer for the specified bucket and value.
/// </summary>
/// <param name="duration">The value to publish for the timer.</param>
/// <param name="sampleRate">The sample rate for the timer.</param>
/// <param name="bucket">The bucket to publish the timer for.</param>
void Timing(long duration, double sampleRate, string bucket);
/// <param name="tags">An optional dictionary of tags.</param>
void Timing(long duration, double sampleRate, string bucket, IDictionary<string, string>? tags);
}
}
Loading