Recognos/Metrics.NET

Prometheus metrics mapper

ksedlazek opened this issue · 2 comments

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}";
            }
        }
    }
}

There is already a Prometheus adapter Metrics.NET.Prometheus.