Prometheus metrics mapper
ksedlazek opened this issue · 2 comments
ksedlazek commented
If anyone is interested, I wrote the following mapping from Metrics.NET to Prometheus (the data contract is the generated C# from the proto file). Check my fork to see the implementation of the Prometheus endpoint, which allows Metrics.NET to be a Prometheus target.
using System;
using System.Collections.Generic;
using Metrics.MetricData;
using Metrics;
using Prometheus.Advanced.DataContracts;
namespace Prometheus
{
public class MetricsMapper
{
private readonly string[] _empty = new string[0];
private readonly List<LabelPair> _noLabel = new List<LabelPair>();
private readonly Namer _defaultNamer = new Namer();
public List<MetricFamily> Convert(MetricsData metricsData, Func<HealthStatus> healthStatus)
{
var metrics = new List<MetricFamily>();
AddMetrics(metricsData, metrics, _defaultNamer);
foreach (var child in metricsData.ChildMetrics)
{
AddMetrics(child, metrics, new Namer(child.Context));
}
return metrics;
}
public void AddMetrics(MetricsData metricsData, List<MetricFamily> metrics, Namer namer)
{
foreach (var g in metricsData.Gauges)
{
metrics.Add(Convert(g, namer));
}
foreach (var c in metricsData.Counters)
{
metrics.Add(Convert(c, namer));
}
foreach (var m in metricsData.Meters)
{
metrics.Add(Convert(m, namer));
}
foreach (var h in metricsData.Histograms)
{
foreach (var metric in Convert(h, namer))
{
metrics.Add(metric);
}
}
foreach (var t in metricsData.Timers)
{
foreach (var metric in Convert(t, namer))
{
metrics.Add(metric);
}
}
}
private MetricFamily Convert(GaugeValueSource gauge, Namer namer)
{
var family = new MetricFamily { name = namer.Name(gauge), help = namer.FormatHelp(gauge), type = Advanced.DataContracts.MetricType.GAUGE };
family.metric.Add(new Advanced.DataContracts.Metric
{
label = _noLabel,
gauge = new Gauge
{
value = gauge.Value
}
});
return family;
}
private MetricFamily Convert(CounterValueSource counter, Namer namer)
{
var family = new MetricFamily { name = namer.Name(counter), help = namer.FormatHelp(counter), type = Advanced.DataContracts.MetricType.COUNTER };
var value = counter.ValueProvider.Value;
if (value.Items.Length == 0)
{
family.metric.Add(new Advanced.DataContracts.Metric
{
label = _noLabel,
counter = new Advanced.DataContracts.Counter
{
value = value.Count
}
});
}
else
{
foreach (var item in counter.ValueProvider.Value.Items)
{
family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair> { new LabelPair { name = "item", value = item.Item } },
counter = new Advanced.DataContracts.Counter
{
value = item.Count
}
});
}
}
return family;
}
private MetricFamily Convert(MeterValueSource meter, Namer namer)
{
var family = new MetricFamily { name = namer.Name(meter), help = namer.FormatHelp(meter), type = Advanced.DataContracts.MetricType.GAUGE };
foreach (var item in meter.ValueProvider.Value.Items)
{
family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair>() { new LabelPair { name = "item", value = item.Item }, new LabelPair { name = "metric", value = "mean" } },
gauge = new Gauge
{
value = item.Value.MeanRate
}
}); family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair>() { new LabelPair { name = "item", value = item.Item }, new LabelPair { name = "metric", value = "1m" } },
gauge = new Gauge
{
value = item.Value.OneMinuteRate
}
}); family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair>() { new LabelPair { name = "item", value = item.Item }, new LabelPair { name = "metric", value = "5m" } },
gauge = new Gauge
{
value = item.Value.FiveMinuteRate
}
}); family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair>() { new LabelPair { name = "item", value = item.Item }, new LabelPair { name = "metric", value = "15m" } },
gauge = new Gauge
{
value = item.Value.FifteenMinuteRate
}
}); family.metric.Add(new Advanced.DataContracts.Metric
{
label = new List<LabelPair>() { new LabelPair { name = "item", value = item.Item }, new LabelPair { name = "metric", value = "count" } },
gauge = new Gauge
{
value = item.Value.Count
}
});
}
return family;
}
private List<MetricFamily> Convert(HistogramValueSource histogram, Namer namer)
{
var value = histogram.Value;
var metric = new Summary
{
sample_count = (ulong)value.SampleSize,
sample_sum = value.SampleSize * value.Mean,
};
metric.quantile.Add(new Quantile { quantile = 0.5, value = value.Median });
metric.quantile.Add(new Quantile { quantile = 0.75, value = value.Percentile75 });
metric.quantile.Add(new Quantile { quantile = 0.95, value = value.Percentile95 });
metric.quantile.Add(new Quantile { quantile = 0.98, value = value.Percentile98 });
metric.quantile.Add(new Quantile { quantile = 0.99, value = value.Percentile99 });
metric.quantile.Add(new Quantile { quantile = 0.999, value = value.Percentile999 });
var list = new List<MetricFamily>();
var family = new MetricFamily { name = namer.Name(histogram), help = namer.FormatHelp(histogram), type = Advanced.DataContracts.MetricType.SUMMARY };
family.metric.Add(new Advanced.DataContracts.Metric { summary = metric, label = _noLabel });
list.Add(family);
var stats = new MetricFamily { name = namer.Name(histogram, "_stats"), help = namer.FormatHelp(histogram) + " statistics", type = Advanced.DataContracts.MetricType.UNTYPED };
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "min" } }, gauge = new Gauge { value = value.Min } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "max" } }, gauge = new Gauge { value = value.Max } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "median" } }, gauge = new Gauge { value = value.Median } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "mean" } }, gauge = new Gauge { value = value.Mean } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "last" } }, gauge = new Gauge { value = value.LastValue } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "stddev" } }, gauge = new Gauge { value = value.StdDev } });
list.Add(stats);
return list;
}
private List<MetricFamily> Convert(TimerValueSource timer, Namer namer)
{
var value = timer.Value.Histogram;
var metric = new Advanced.DataContracts.Histogram
{
sample_count = (ulong)value.SampleSize,
sample_sum = value.SampleSize * value.Mean,
};
metric.bucket.Add(new Bucket { upper_bound = 0.5, cumulative_count = (ulong)value.Median });
metric.bucket.Add(new Bucket { upper_bound = 0.75, cumulative_count = (ulong)value.Percentile75 });
metric.bucket.Add(new Bucket { upper_bound = 0.95, cumulative_count = (ulong)value.Percentile95 });
metric.bucket.Add(new Bucket { upper_bound = 0.98, cumulative_count = (ulong)value.Percentile98 });
metric.bucket.Add(new Bucket { upper_bound = 0.99, cumulative_count = (ulong)value.Percentile99 });
metric.bucket.Add(new Bucket { upper_bound = 0.999, cumulative_count = (ulong)value.Percentile999 });
metric.bucket.Add(new Bucket { upper_bound = double.PositiveInfinity, cumulative_count = (ulong)value.Max });
var list = new List<MetricFamily>();
var family = new MetricFamily { name = namer.Name(timer), help = namer.FormatHelp(timer), type = Advanced.DataContracts.MetricType.HISTOGRAM };
family.metric.Add(new Advanced.DataContracts.Metric { histogram = metric, label = _noLabel });
list.Add(family);
var stats = new MetricFamily { name = namer.Name(timer, "_stats"), help = namer.FormatHelp(timer) + " statistics", type = Advanced.DataContracts.MetricType.UNTYPED };
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "min" } }, gauge = new Gauge { value = value.Min } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "max" } }, gauge = new Gauge { value = value.Max } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "median" } }, gauge = new Gauge { value = value.Median } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "mean" } }, gauge = new Gauge { value = value.Mean } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "last" } }, gauge = new Gauge { value = value.LastValue } });
stats.metric.Add(new Advanced.DataContracts.Metric { label = new List<LabelPair> { new LabelPair { name = "stat", value = "stddev" } }, gauge = new Gauge { value = value.StdDev } });
list.Add(stats);
return list;
}
public class Namer
{
private readonly string _prefix;
public Namer(string prefix = null)
{
if (prefix != null)
{
_prefix = prefix.Replace(".", "").Replace(" ", "_").Replace("/", "_") + "_";
}
}
public string Name<T>(MetricValueSource<T> metric, string suffix = null)
{
var name = metric.Name.Replace(".", "").Replace("%", "_pct").Replace(" ", "_").Replace("/", "_per_");
if (_prefix == null)
{
if (suffix == null)
return name.ToLower();
else
return (name + suffix).ToLower();
}
if (suffix == null)
return (_prefix + name).ToLower();
return (_prefix + name + suffix).ToLower();
}
public string FormatHelp<T>(MetricValueSource<T> metric)
{
if (string.IsNullOrEmpty(metric.Unit.Name))
return $"{metric.Name}";
else
return $"{metric.Name} in {metric.Unit.Name}";
}
}
}
}
vic10us commented
There is already a Prometheus adapter Metrics.NET.Prometheus.