利用AppMetrics對Web進行監控教程

利用AppMetrics對Web進行監控教程

1、基礎準備

1. 安裝依賴

這裏能夠經過nuget或使用命令行進行安裝,具體須要安裝的類庫以下(注意版本):html

Install-Package App.Metrics.AspNetCore.Mvc -Version 2.0.0

因爲咱們須要兼容Prometheus進行監控,因此咱們還須要安裝對應的格式化庫,具體以下:web

Install-Package App.Metrics.Formatters.Prometheus -Version 2.0.0

以上就是須要的類庫,接下來咱們開始進行其餘初始化部分。數據庫

2. 初始配置

爲了保證其可以正常工做,咱們須要根據不一樣的環境設定對應的appsettings.json文件從而讓度量指標能夠根據不一樣的環境進行輸出,這裏考慮到實際狀況還沒有存在不一樣的配置可能性故統一配置便可,打開appsettings.json輸入下配置項:json

{
  "MetricsOptions": {
    "DefaultContextLabel": "MetricsApplication",
    "Enabled": true
  },
  "MetricsWebTrackingOptions": {
    "ApdexTrackingEnabled": true,
    "ApdexTSeconds": 0.3,
    "IgnoredHttpStatusCodes": [ 404 ],
    "IgnoreRoutesRegexPatterns": [],
    "OAuth2TrackingEnabled": false
  },
  "MetricEndpointsOptions": {
    "MetricsEndpointEnabled": true,
    "MetricsTextEndpointEnabled": true,
    "EnvironmentInfoEndpointEnabled": true
  }
}

參數DefaultContextLabel能夠設定爲咱們指望其餘名稱,這裏建議採用項目的簡寫名稱,保證項目之間不存在衝突便可。參數ApdexTSeconds用於設定應用的響應能力標準,其採用了當前流行的Apdex標準,這裏使用者能夠根據自身應用的實際狀況調整對應的參數,其餘相關參數建議默認便可。api

3. 啓用度量指標

由於咱們的數據須要符合Promethues格式,因此後續教程咱們會替換默認的格式採用符合的格式。首先咱們須要Program.cs裏輸入如下內容:bash

public static IWebHost BuildWebHost(string[] args)
        {
            Metrics = AppMetrics.CreateDefaultBuilder()
                .OutputMetrics.AsPrometheusPlainText()
                .OutputMetrics.AsPrometheusProtobuf()
                .Build();

            return WebHost.CreateDefaultBuilder(args)
                    .ConfigureMetrics(Metrics)
                    .UseMetrics(options =>
                    {
                        options.EndpointOptions = endpointsOptions =>
                        {
                            endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();
                            endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();
                        };
                    })
                    .UseStartup<Startup>()
                    .Build();
        }

其中爲了可以支持其餘格式,咱們須要手動實例化Metrics對象完成相關初始化而後將其注入到asp.net core中,其中相關格式的代碼主要是由如下這幾部分組成:app

OutputMetrics.AsPrometheusPlainText()
OutputMetrics.AsPrometheusProtobuf()

endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();
endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();

完成以上操做後,咱們最後還須要進行其餘配置,打開Startup.cs文件增長以下內容:asp.net

services.AddMvc().AddMetrics();

至此咱們就完成了基本的初始化了,經過啓動程序並訪問localhost:5000/metrics-text便可查看最終的輸出內容。dom

2、自定義指標

因爲其內部已經默認提供了若干的指標,可是並不能符合實際業務的需求故如下將對經常使用的度量指標類型以及用法進行介紹,這裏這裏你們經過注入IMetrics接口對象便可訪問,因此下面這部分代碼不在闡述。async

1. 儀表盤(Gauge)

最多見的類型,主要是用於直接反應當前的指標狀況,好比咱們常見的CPU和內存基本都是使用這種方式進行顯示的,能夠直觀的看到當前的實際的狀態狀況。對於全部的指標咱們都須要定義對應的Options,固然這能夠完成攜程靜態變量供應用程序全局使用。

好比下面咱們定義一個表示當前發生錯誤次數的指標:

GaugeOptions Errors = new GaugeOptions()
{
    Name = "Errors"
};

完成指標的定義後,咱們就能夠在須要使用的地方進行指標數據的修改,好比下面咱們將錯誤數量設置爲10:

metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, 10);

這樣咱們就完成了指標的設定,可是有時候咱們還想卻分具體的Error是那個層面發起的,這個時候咱們須要使用到Tag了,下面咱們在設定值的同時設定指標,固然也能夠在新建指標的時候經過Tags變量,而且通用於其餘全部指標:

var tags = new MetricTags("fromt", "db");
metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, tags, 10);

至此咱們就完成了一個基本的指標,下面咱們繼續其餘類型指標。

2. 計數值(Counter)

對於HTTP類型的網站來講,存在很是多的數量須要統計記錄,因此計數值此時就特別適合這類狀況,好比咱們須要統計請求數量就能夠利用這類指標類型,下面咱們就以請求數來定義這個指標:

var requestCounter = new CounterOptions()
{
    Name = "httpRequest",
    MeasurementUnit = Unit.Calls
};

以上咱們定義了一個計數指標,其中咱們能夠看到咱們這裏使用了一個新變量MeasurementUnit主要是用於定義指標單位的,固然這個只是輔助信息會一同輸出到結果,下面咱們須要進行增長和減小,考慮到大多數狀況都是減1和增1的狀況:

metrics.Measure.Counter.Increment(requestCounter);

實際狀況可能咱們都是統計請求可是指望還能單獨統計特定接口的請求,這個時候咱們在本來調用方式基礎上增長額外的參數:

metrics.Measure.Counter.Increment(requestCounter, "api");

若是嫌每次增加1比較慢,咱們經過其函數的重載形式填寫咱們但願增加的具體值。

3. 計量值(Meter)

有點相似於計數值可是相比來講,它能夠提供更加豐富的信息,好比每一、五、15分鐘的增加率等,因此對於一些須要經過增加率觀察的數據特別時候,這裏咱們以請求的反應狀態碼進行記錄來體現其用途:

var httpStatusMeter = new MeterOptions()
{
    Name = "Http Status",
    MeasurementUnit = Unit.Calls
};

以上咱們完成了一個指標的定義,下面咱們開始使用其而且定義不一樣的狀態的碼的發生狀況,具體以下:

metrics.Measure.Meter.Mark(httpStatusMeter, "200");
metrics.Measure.Meter.Mark(httpStatusMeter, "500");
metrics.Measure.Meter.Mark(httpStatusMeter, "401");

固然若是但願增長的數量自定控制也可使用其提供的重載形式進行。

4. 柱狀圖(Histogram)

顧名思義,主要反應數據的分佈狀況,因此這裏不在重複闡述,你們對於這種數據表現形式仍是比較瞭解的,因此下面就直接以實際代碼的實列進行介紹,便於你們的理解:

var postAndPutRequestSize = new HistogramOptions()
{
    Name = "Web Request Post & Put Size",
    MeasurementUnit = Unit.Bytes
};

以上咱們定義一個體現Post和Put請求的數據尺寸的指標,下面咱們利用隨機數來進行數據的模擬對其進行數據填充,便於顯示數據:

var rnd = new Random();

foreach (var i in Enumerable.Range(0, 50))
{
    var t = rnd.Next(0, 10);

    metrics.Measure.Histogram.Update(postAndPutRequestSize, t);
}

5. 時間線(Timer)

對應指標的監控閉然少不了對於時間的記錄,特別對於HTTP來講,直接影響到用戶的體驗就是響應時間,素以咱們也須要時刻關於這類指標的變化狀況及時作出反應,下面咱們就以數據庫的響應時間的狀況做爲指標進行監控:

TimerOptions DatabaseTimer = new TimerOptions()
{
    Name = "Database Timer",
    MeasurementUnit = Unit.Items,
    DurationUnit = TimeUnit.Milliseconds,
    RateUnit = TimeUnit.Milliseconds
};

上面咱們經過特別的屬性指定了改指標記錄時間的單位,下面咱們使用其指標進行數據的記錄:

using(metrics.Measure.Timer.Time(DatabaseTimer))
{
    //to do sonmething
}

咱們能夠看到爲了方便的記錄請求的時間,咱們使用using進行涵括,並將須要記錄耗時的請求操做放入其中,在請求完成操做後就能夠正確的記錄其須要的時間。

6. apdex

採用了一種標準的性能指標計算方式,用法相似與上述,這裏僅僅列舉用法:

ApdexOptions SampleApdex = new ApdexOptions
{
    Name = "Sample Apdex"
};

using(metrics.Measure.Apdex.Track(SampleApdex))
{
    Thread.Sleep(100);
}

3、高級指標

1. 平均響應

不少時候咱們僅僅依靠一個指標很難完成一個實際的需求,是因此咱們就須要將多個指標進行組合進行,好比咱們指望獲得請求次數,同時還有請求的總時間和平均響應時間等,爲此咱們能夠特殊的指標將多個指標進行組合,具體操做以下:

var cacheHitRatioGauge = new GaugeOptions
{
    Name = "Cache Gauge",
    MeasurementUnit = Unit.Calls
};

var cacheHitsMeter = new MeterOptions
{
    Name = "Cache Hits Meter",
    MeasurementUnit = Unit.Calls
};

var databaseQueryTimer = new TimerOptions
{
    Name = "Database Query Timer",
    MeasurementUnit = Unit.Calls,
    DurationUnit = TimeUnit.Milliseconds,
    RateUnit = TimeUnit.Milliseconds
};

var cacheHits = metrics.Provider.Meter.Instance(cacheHitsMeter);
var calls = metrics.Provider.Timer.Instance(databaseQueryTimer);

var cacheHit = new Random().Next(0, 2) == 0;

using(calls.NewContext())
{
    if (cacheHit)
    {
        cacheHits.Mark(5);
    }

    Thread.Sleep(cacheHit ? 10 : 100);
}

metrics.Measure.Gauge.SetValue(cacheHitRatioGauge, () => new HitRatioGauge(cacheHits, calls, m => m.OneMinuteRate));

4、利用Promethues和Grafana進行監控

1. 環境準備

這裏須要使用到PrometheusGrafana,爲了不版本致使的區別這裏提供了對應百度雲的下載地址,你們能夠自行進行下載。

Prometheus對應提取碼爲2b1r

Grafana對應提取碼爲mjym

完成以上下載後須要解壓到對應文件夾下便可。

2. 配置服務

首先咱們須要針對Prometheus進行配置,咱們打開prometheus.yml文件新增基於AppMetrics的監控指標。

- job_name: 'appweb'
    scrape_interval: 5s
    metrics_path: '/metrics-text'
    static_configs:
    - targets: ['localhost:5000']

完成以後咱們能夠先打開採集讓其在後臺持續採集,後面咱們須要針對AppMetrics暴露的數據進行調整。

3. 應用指標輸出

經過實際的測試發現基於2.0.0版本的Prometheus存在問題,由於指標類型被大寫了,致使Prometheus沒法正確讀取,因此咱們須要將源碼複製出來進行操做,這裏直接給出了對應的源碼文件, 主要的工做就是將AsciiFormatter.cs中的HELPTYPE進行了小寫而已,對應文件以下。

PS:考慮到不少基於2.0的因此這裏保留了基於HTTP的文本實現方式發佈了一個對應的版本庫:

Install-Package Sino.Metrics.Formatters.Prometheus -Version 0.1.2
  • AsciiFormatter.cs
internal static class AsciiFormatter
    {
        public static void Format(Stream destination, IEnumerable<MetricFamily> metrics)
        {
            var metricFamilys = metrics.ToArray();
            using (var streamWriter = new StreamWriter(destination, Encoding.UTF8))
            {
                streamWriter.NewLine = "\n";
                foreach (var metricFamily in metricFamilys)
                {
                    WriteFamily(streamWriter, metricFamily);
                }
            }
        }

        internal static string Format(IEnumerable<MetricFamily> metrics, NewLineFormat newLine)
        {
            var newLineChar = GetNewLineChar(newLine);
            var metricFamilys = metrics.ToArray();
            var s = new StringBuilder();
            foreach (var metricFamily in metricFamilys)
            {
                s.Append(WriteFamily(metricFamily, newLineChar));
            }

            return s.ToString();
        }

        private static void WriteFamily(StreamWriter streamWriter, MetricFamily metricFamily)
        {
            streamWriter.WriteLine("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower());
            streamWriter.WriteLine("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower());
            foreach (var metric in metricFamily.metric)
            {
                WriteMetric(streamWriter, metricFamily, metric);
            }
        }

        private static string WriteFamily(MetricFamily metricFamily, string newLine)
        {
            var s = new StringBuilder();
            s.Append(string.Format("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower()), newLine);
            s.Append(string.Format("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower()), newLine);
            foreach (var metric in metricFamily.metric)
            {
                s.Append(WriteMetric(metricFamily, metric, newLine), newLine);
            }

            return s.ToString();
        }

        private static void WriteMetric(StreamWriter streamWriter, MetricFamily family, Metric metric)
        {
            var familyName = family.name;

            if (metric.gauge != null)
            {
                streamWriter.WriteLine(SimpleValue(familyName, metric.gauge.value, metric.label));
            }
            else if (metric.counter != null)
            {
                streamWriter.WriteLine(SimpleValue(familyName, metric.counter.value, metric.label));
            }
            else if (metric.summary != null)
            {
                streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"));
                streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"));

                foreach (var quantileValuePair in metric.summary.quantile)
                {
                    var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)
                        ? "+Inf"
                        : quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);
                    streamWriter.WriteLine(
                        SimpleValue(
                            familyName,
                            quantileValuePair.value,
                            metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })));
                }
            }
            else if (metric.histogram != null)
            {
                streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"));
                streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"));
                foreach (var bucket in metric.histogram.bucket)
                {
                    var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);
                    streamWriter.WriteLine(
                        SimpleValue(
                            familyName,
                            bucket.cumulative_count,
                            metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),
                            "_bucket"));
                }
            }
            else
            {
                // not supported
            }
        }

        private static string WriteMetric(MetricFamily family, Metric metric, string newLine)
        {
            var s = new StringBuilder();
            var familyName = family.name;

            if (metric.gauge != null)
            {
                s.Append(SimpleValue(familyName, metric.gauge.value, metric.label), newLine);
            }
            else if (metric.counter != null)
            {
                s.Append(SimpleValue(familyName, metric.counter.value, metric.label), newLine);
            }
            else if (metric.summary != null)
            {
                s.Append(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"), newLine);
                s.Append(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"), newLine);

                foreach (var quantileValuePair in metric.summary.quantile)
                {
                    var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)
                        ? "+Inf"
                        : quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);
                    s.Append(
                        SimpleValue(
                            familyName,
                            quantileValuePair.value,
                            metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })), newLine);
                }
            }
            else if (metric.histogram != null)
            {
                s.Append(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"), newLine);
                s.Append(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"), newLine);
                foreach (var bucket in metric.histogram.bucket)
                {
                    var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);
                    s.Append(
                        SimpleValue(
                            familyName,
                            bucket.cumulative_count,
                            metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),
                            "_bucket"), newLine);
                }
            }
            else
            {
                // not supported
            }

            return s.ToString();
        }

        private static string WithLabels(string familyName, IEnumerable<LabelPair> labels)
        {
            var labelPairs = labels as LabelPair[] ?? labels.ToArray();

            if (labelPairs.Length == 0)
            {
                return familyName;
            }

            return string.Format("{0}{{{1}}}", familyName, string.Join(",", labelPairs.Select(l => string.Format("{0}=\"{1}\"", l.name, l.value))));
        }

        private static string SimpleValue(string family, double value, IEnumerable<LabelPair> labels, string namePostfix = null)
        {
            return string.Format("{0} {1}", WithLabels(family + (namePostfix ?? string.Empty), labels), value.ToString(CultureInfo.InvariantCulture));
        }

        private static string GetNewLineChar(NewLineFormat newLine)
        {
            switch (newLine)
            {
                case NewLineFormat.Auto:
                    return Environment.NewLine;
                case NewLineFormat.Windows:
                    return "\r\n";
                case NewLineFormat.Unix:
                case NewLineFormat.Default:
                    return "\n";
                default:
                    throw new ArgumentOutOfRangeException(nameof(newLine), newLine, null);
            }
        }

        private static void Append(this StringBuilder sb, string line, string newLineChar)
        {
            sb.Append(line + newLineChar);
        }
    }
  • MetricsPrometheusTextOutputFormatter.cs
public class MetricsPrometheusTextOutputFormatter : IMetricsOutputFormatter
    {
        private readonly MetricsPrometheusOptions _options;

        public MetricsPrometheusTextOutputFormatter()
        {
            _options = new MetricsPrometheusOptions();
        }

        public MetricsPrometheusTextOutputFormatter(MetricsPrometheusOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); }

        /// <inheritdoc/>
        public MetricsMediaTypeValue MediaType => new MetricsMediaTypeValue("text", "vnd.appmetrics.metrics.prometheus", "v1", "plain");

        /// <inheritdoc/>
        public async Task WriteAsync(
            Stream output,
            MetricsDataValueSource metricsData,
            CancellationToken cancellationToken = default(CancellationToken))
        {
            if (output == null)
            {
                throw new ArgumentNullException(nameof(output));
            }

            using (var streamWriter = new StreamWriter(output))
            {
                await streamWriter.WriteAsync(AsciiFormatter.Format(metricsData.GetPrometheusMetricsSnapshot(_options.MetricNameFormatter), _options.NewLineFormat));
            }
        }
    }

新建好以上兩個文件後咱們接着須要修改Program.cs文件,具體內容以下:

public static IWebHost BuildWebHost(string[] args)
        {
            Metrics = AppMetrics.CreateDefaultBuilder()
                .OutputMetrics.AsPrometheusPlainText()
                .Build();

            return WebHost.CreateDefaultBuilder(args)
                    .ConfigureMetrics(Metrics)
                    .UseMetrics(options =>
                    {
                        options.EndpointOptions = endpointsOptions =>
                        {
                            endpointsOptions.MetricsTextEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
                        };
                    })
                    .UseStartup<Startup>()
                    .Build();
        }

完成以上操做後咱們能夠啓用應用,此時能夠看到不斷用請求到/metrics-text表示已經開始採集指標了。

4. 指標可視化

此時咱們打開Grafana文件夾,經過其中的bin目錄下的grafana-server.exe啓動服務,而後訪問localhost:3000利用初始帳戶密碼進行登陸(admin/admin)。 進入後添加Prometheus數據源。因爲AppMetrics已經提供了對應的看板因此咱們能夠經過ID2204直接導入,並選擇正確的數據源就能夠看到最終的效果了。

原文出處:https://www.cnblogs.com/yaozhenfa/p/12291749.html

相關文章
相關標籤/搜索