Histograms
Histograms measure the statistical distribution of a set of values including the min, max, mean, median, standard deviation and quantiles i.e. the 75th percentile, 90th percentile, 95th percentile, 99th percentile and 99.9th percentile allowing us to sample observations for things like response sizes.
A use case for a Histogram could be tracking the POST and PUT requests sizes made to a web service or tracking the file sizes uploaded to an endpoint.
Using Histograms
var postAndPutRequestSize = new HistogramOptions
{
Name = "Web Request Post & Put Size",
MeasurementUnit = Unit.Bytes
};
public async Task Invoke(HttpContext context)
{
var httpMethod = context.Request.Method.ToUpperInvariant();
if (httpMethod == "POST" || httpMethod == "PUT")
{
if (context.Request.Headers != null && context.Request.Headers.ContainsKey("Content-Length"))
{
_metrics.Measure.Histogram.Update(long.Parse(context.Request.Headers["Content-Length"].First()));
}
}
await Next(context);
}
Histograms also allow us to track the min, max and last value that has been recorded in cases when a "user value" is provided when updating the histogram. For example if we wanted to measure the statistical distribution of files uploaded to a file service and track which applications are uploading the largest files, we could use an applications client id when updating the histgoram:
var fileSize = new HistogramOptions
{
Name = "Document File Size",
MeasurementUnit = Unit.Calls
};
_metrics.Measure.Histogram.Update(httpStatusMeter, CalculateDocumentSize(), "client_1");
_metrics.Measure.Histogram.Update(httpStatusMeter, CalculateDocumentSize(), "client_2");
Which for example when using the JSON formatter would result in something similar to:
{
"context": "Application",
"histograms": [
{
"lastUserValue": "client_1",
"lastValue": 26.5984,
"max": 26.5984,
"sum": 1.0,
"maxUserValue": "value2",
"mean": 23.1988,
"median": 22.2101,
"min": 20.2660,
"minUserValue": "client_2",
"percentile75": 25.4645,
"percentile95": 26.5987,
"percentile98": 26.5984,
"percentile99": 26.5984,
"percentile999": 26.5987,
"sampleSize": 20,
"stdDev": 2.4749,
"name": "File Sizes",
"unit": "Mb"
}
]
}
Reservoir Sampling
In high performance applications it is not possible to keep the entire data stream of a histogram in memory. To work around this reservoir sampling algorithms allow us to maintain a small, manageable reservoir which is statistically representative of an entire data stream.
Out-of-box App.Metrics supports three types of reserreservoirvoir sampling, Uniform, Exponentially Decaying and Sliding Window. By default all timers will use Exponentially Decaying Reservoir Sampling.
It is possible to change the reservoir both globally for all metrics and for an individual metric:
Change the default Reservoir Globally
services
.AddMetrics()
// Uniform Reservoir Sampling
.AddDefaultReservoir(() => new Lazy<IReservoir>(() => new DefaultAlgorithmRReservoir(sampleSize: 1028)))
// Or, Exponentially Decaying Reservoir Sampling
.AddDefaultReservoir(() => new Lazy<IReservoir>(() => new DefaultForwardDecayingReservoir(sampleSize: 1028, alpha: 0.015)))
// Or, Sliding Window Reservoir Sampling
.AddDefaultReservoir(() => new Lazy<IReservoir>(() => new DefaultSlidingWindowReservoir(sampleSize: 1028)))
.AddHealthChecks();
Set the Reservoir for a specific metric
public static readonly HistogramOptions PostRequestSizeHistogram = new HistogramOptions
{
Context = ContextName,
Name = "POST Size",
MeasurementUnit = Unit.Bytes,
Reservoir = new Lazy<IReservoir>(() => new DefaultSlidingWindowReservoir(sampleSize: 1028))
};
Note
You can also implement custom Reservoirs by implementing IReservoir
.