메트릭 수집

이 문서의 적용 대상: ✔️ .NET Core 3.1 이상 버전 ✔️ .NET Framework 4.6.1 이상 버전

계측된 코드는 수치 측정값을 기록할 수 있지만 모니터링에 유용한 메트릭을 생성하려면 일반적으로 측정값을 집계, 전송 및 저장해야 합니다. 이렇게 데이터를 집계, 전송 및 저장하는 프로세스를 수집이라고 합니다. 이 자습서에서는 메트릭을 수집하는 방법에 대한 몇 가지 예를 볼 수 있습니다.

  • OpenTelemetry 및 Prometheus를 사용하여 Grafana에서 메트릭 채우기
  • dotnet-counters 명령줄 도구를 사용하여 실시간으로 메트릭 보기
  • 기본 .NET MeterListener API를 사용하여 사용자 지정 수집 도구 만들기

사용자 지정 메트릭 계측 및 계측 옵션 개요에 대한 자세한 내용은 메트릭 API 비교를 참조하세요.

예제 애플리케이션 만들기

사전 요구 사항: .NET Core 3.1 SDK 이상 버전

메트릭을 수집하려면 그 전에 몇 가지 측정값을 생성해야 합니다. 간단히 하기 위해 몇 가지 간단한 메트릭 계측이 있는 작은 앱을 만들겠습니다. .NET 런타임에는 다양한 메트릭이 기본 제공됩니다. 여기에 표시된 System.Diagnostics.Metrics.Meter API를 사용하여 새 메트릭을 만드는 방법은 계측 자습서를참조하세요.

dotnet new console
dotnet add package System.Diagnostics.DiagnosticSource

Program.cs의 코드를 다음으로 대체합니다.

using System;
using System.Diagnostics.Metrics;
using System.Threading;

class Program
{
    static Meter s_meter = new Meter("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hats-sold");

    static void Main(string[] args)
    {
        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has a transaction each second that sells 4 hats
            Thread.Sleep(1000);
            s_hatsSold.Add(4);
        }
    }
}

dotnet-counters를 사용하여 메트릭 보기

dotnet-counters는 요청 시 모든 .NET Core 애플리케이션에 대한 라이브 메트릭을 볼 수 있는 간단한 명령줄 도구입니다. 사전 설정이 필요하지 않기 때문에 임시 조사에 또는 메트릭 계측이 제대로 작동하는지 확인하는 데 유용합니다. System.Diagnostics.Metrics 기반 API 및 EventCounters 모두에서 작동합니다.

dotnet-counters 도구가 아직 설치되지 않은 경우 SDK를 사용하여 설치합니다.

> dotnet tool update -g dotnet-counters
You can invoke the tool using the following command: dotnet-counters
Tool 'dotnet-counters' (version '5.0.251802') was successfully installed.

예제 앱이 계속 실행되는 동안 두 번째 셸에서 실행 중인 프로세스를 나열하여 프로세스 ID를 확인합니다.

> dotnet-counters ps
     10180 dotnet     C:\Program Files\dotnet\dotnet.exe
     19964 metric-instr E:\temp\metric-instr\bin\Debug\netcoreapp3.1\metric-instr.exe

예제 앱과 일치하는 프로세스 이름의 ID를 찾고 dotnet-counters가 "HatCo.HatStore" 미터의 모든 메트릭을 모니터링하도록 합니다. 미터 이름은 대/소문자를 구분입니다.

> dotnet-counters monitor -p 19964 HatCo.HatStore
Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.HatStore]
    hats-sold (Count / 1 sec)                          4

다른 메트릭 집합을 지정하는 dotnet-counters를 실행하여 .NET 런타임의 기본 제공 계측 중 일부를 볼 수도 있습니다.

> dotnet-counters monitor -p 19964 System.Runtime
Press p to pause, r to resume, q to quit.
    Status: Running

[System.Runtime]
    % Time in GC since last GC (%)                                 0
    Allocation Rate (B / 1 sec)                                8,168
    CPU Usage (%)                                                  0
    Exception Count (Count / 1 sec)                                0
    GC Heap Size (MB)                                              2
    Gen 0 GC Count (Count / 1 sec)                                 0
    Gen 0 Size (B)                                         2,216,256
    Gen 1 GC Count (Count / 1 sec)                                 0
    Gen 1 Size (B)                                           423,392
    Gen 2 GC Count (Count / 1 sec)                                 0
    Gen 2 Size (B)                                           203,248
    LOH Size (B)                                             933,216
    Monitor Lock Contention Count (Count / 1 sec)                  0
    Number of Active Timers                                        1
    Number of Assemblies Loaded                                   39
    ThreadPool Completed Work Item Count (Count / 1 sec)           0
    ThreadPool Queue Length                                        0
    ThreadPool Thread Count                                        3
    Working Set (MB)                                              30

도구에 대한 자세한 내용은 dotnet-counters를 참조하십시오. .NET에서 바로 사용할 수 있는 메트릭에 대한 자세한 내용은 기본 제공 메트릭을 참조하세요.

OpenTelemetry 및 Prometheus를 사용하여 Grafana에서 메트릭 보기

사전 요구 사항

개요

OpenTelemetry는 클라우드 네이티브 소프트웨어에 관한 원격 분석 생성 및 수집의 표준화를 목표로 하는 Cloud Native Computing Foundation에서 지원하는 공급업체 중립 오픈 소스 프로젝트입니다. 기본 제공 플랫폼 메트릭 API는 이 표준을 사용하려는 .NET 개발자가 쉽게 통합할 수 있도록 이 표준과 호환되도록 설계되었습니다. 작성 당시에는 OpenTelemetry 메트릭에 대한 지원이 비교적 새로웠지만 Azure Monitor 및 많은 주요 APM 공급업체가 이를 승인했으며 통합 계획이 진행 중입니다.

이 예제에서는 널리 사용되는 OSS PrometheusGrafana 프로젝트를 사용하여 OpenTelemetry 메트릭에 사용할 수 있는 통합 중 하나를 보여줍니다. 메트릭 데이터는 다음과 같이 흐릅니다.

  1. .NET 메트릭 API는 예제 애플리케이션에서 측정값을 수집합니다.
  2. 동일한 프로세스 내에서 실행되는 OpenTelemetry 라이브러리는 이러한 측정값을 집계합니다.
  3. Prometheus exporter 라이브러리는 HTTP 메트릭 엔드포인트를 통해 집계된 데이터를 사용할 수 있게 합니다. 'Exporter'는 OpenTelemetry가 공급업체별 백 엔드에 원격 분석을 전송하는 라이브러리라고 부르는 것입니다.
  4. 다른 컴퓨터에서 실행될 수 있는 Prometheus 서버는 메트릭 엔드포인트를 폴링하고, 데이터를 읽고, 장기 지속성을 위해 데이터베이스에 저장합니다. Prometheus는 이것을 엔드포인트 '스크래핑'이라고 합니다.
  5. 다른 컴퓨터에서 실행될 수 있는 Grafana 서버는 Prometheus에 저장된 데이터를 쿼리하고 웹 기반 모니터링 대시보드에서 엔지니어에게 표시합니다.

OpenTelemetry의 Prometheus exporter를 사용하도록 예제 애플리케이션 구성

OpenTelemetry Prometheus exporter에 대한 참조를 예제 애플리케이션에 추가합니다.

dotnet add package OpenTelemetry.Exporter.Prometheus --version 1.2.0-beta1

참고

Prometheus 내보내기 라이브러리에는 OpenTelemetry의 공유 라이브러리에 대한 참조가 포함되어 있으므로 이 명령은 두 라이브러리를 애플리케이션에 암시적으로 추가합니다.

참고

이 자습서에서는 작성 시 사용할 수 있는 OpenTelemetry의 Prometheus 지원의 시험판 빌드를 사용합니다. OpenTelemetry 프로젝트 유지 관리자는 공식 릴리스 전에 변경할 수 있습니다.

Main() 시작 부분에 OpenTelemetry를 구성하는 추가 코드가 포함되도록 Program.cs 코드를 수정합니다.

using System;
using System.Diagnostics.Metrics;
using System.Threading;
using OpenTelemetry;
using OpenTelemetry.Metrics;

class Program
{
    static Meter s_meter = new Meter("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hats-sold",
                                                                unit: "Hats",
                                                                description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
                .AddMeter("HatCo.HatStore")
                .AddPrometheusExporter(opt =>
                {
                    opt.StartHttpListener = true;
                    opt.HttpListenerPrefixes = new string[] { $"http://localhost:9184/" };
                })
                .Build();

        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has a transaction each second that sells 4 hats
            Thread.Sleep(1000);
            s_hatsSold.Add(4);
        }
    }
}

AddMeter("HatCo.HatStore")는 앱에서 정의한 미터에서 수집한 모든 메트릭을 전송하도록 OpenTelemetry를 구성합니다. AddPrometheusExporter(...)는 포트 9184에서 Prometheus의 메트릭 엔드포인트를 노출하고 HttpListener를 사용하도록 OpenTelemetry를 구성합니다. OpenTelemetry 구성 옵션, 특히 ASP.NET 애플리케이션에 유용한 대체 호스팅 옵션에 대한 자세한 내용은 OpenTelemetry 설명서를 참조하세요.

참고

작성 시 OpenTelemetry는 API를 사용하여 System.Diagnostics.Metrics 내보낸 메트릭만 지원합니다. 그러나 EventCounters 에 대한 지원은 계획되어 있습니다.

예제 앱을 실행하고 백그라운드에서 실행되도록 둡니다.

> dotnet run
Press any key to exit

Prometheus 설정 및 구성

Prometheus 첫 번째 단계에 따라 Prometheus 서버를 설정하고 작동하는지 확인합니다.

Prometheus가 예제 앱이 노출하는 메트릭 엔드포인트를 스크래핑하도록 prometheus.yml 구성 파일을 수정합니다. scrape_configs 섹션에 다음 텍스트를 추가합니다.

  - job_name: 'OpenTelemetryTest'
    scrape_interval: 1s # poll very quickly for a more responsive demo
    static_configs:
      - targets: ['localhost:9184']

기본 구성에서 시작하는 경우 scrape_configs는 다음과 같이 표시됩니다.

scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    static_configs:
      - targets: ["localhost:9090"]

  - job_name: 'OpenTelemetryTest'
    scrape_interval: 1s # poll very quickly for a more responsive demo
    static_configs:
      - targets: ['localhost:9184']

구성을 다시 로드하거나 Prometheus 서버를 다시 시작한 다음, Prometheus 웹 포털의 상태>대상 페이지에서 OpenTelemetryTest가 UP 상태인지 확인합니다.

Prometheus 웹 포털의 그래프 페이지에서 표현식 텍스트 상자에 hats_sold를 입력합니다. 그래프 탭에서 Prometheus는 예제 애플리케이션에서 내보내는 "hats-sold" 카운터의 값이 지속적으로 증가하는 것이 보여야 합니다.

Prometheus hats sold 그래프

Prometheus 서버가 예제 앱을 오랫동안 스크래핑하지 않은 경우 데이터가 누적될 때까지 잠시 기다려야 할 수 있습니다. 또한 왼쪽 위에 있는 시간 범위 컨트롤을 "1m"(1분)으로 조정하면 최근 데이터를 더 잘 볼 수 있습니다.

Grafana 대시보드에 메트릭 표시

  1. 표준 지침에 따라 Grafana를 설치하고 Prometheus 데이터 소스에 연결합니다.

  2. Grafana 웹 포털의 왼쪽 도구 모음에 있는 + 아이콘을 클릭하여 Grafana 대시보드를 만든 다음, 대시보드를 선택합니다. 대시보드 편집기가 나타나면 제목으로 'Hats Sold/Sec'를 입력하고 PromQL 식 필드에 'rate(hats_sold[5m])'를 입력합니다. 다음과 같이 표시됩니다.

    Hats sold Grafana 대시보드 편집기

  3. 적용을 클릭하여 저장하고 간단한 새 대시보드를 봅니다.

    Hats sold Grafana 대시보드

.NET MeterListener API를 사용하여 사용자 지정 컬렉션 도구 만들기

.NET MeterListener API를 사용하면 사용자 지정 In Process 논리를 만들어서 System.Diagnostics.Metrics.Meter에 의해 기록되는 측정값을 관찰할 수 있습니다. 이전 EventCounters 계측과 호환되는 사용자 지정 논리를 만드는 지침은 EventCounters를 참조하세요.

다음과 같이 MeterListener를 사용하도록 Program.cs 코드를 수정합니다.

using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Threading;

class Program
{
    static Meter s_meter = new Meter("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(name: "hats-sold",
                                                                unit: "Hats",
                                                                description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterListener meterListener = new MeterListener();
        meterListener.InstrumentPublished = (instrument, listener) =>
        {
            if(instrument.Meter.Name == "HatCo.HatStore")
            {
                listener.EnableMeasurementEvents(instrument);
            }
        };
        meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
        meterListener.Start();

        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has a transaction each second that sells 4 hats
            Thread.Sleep(1000);
            s_hatsSold.Add(4);
        }
    }

    static void OnMeasurementRecorded<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string,object>> tags, object state)
    {
        Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
    }
}

실행되면 애플리케이션은 이제 각 측정에서 사용자 지정 콜백을 실행합니다.

> dotnet run
Press any key to exit
hats-sold recorded measurement 4
hats-sold recorded measurement 4
hats-sold recorded measurement 4
hats-sold recorded measurement 4
...

위의 예제에서 어떤 일이 발생했는지 분석해 보겠습니다.

using MeterListener meterListener = new MeterListener();

먼저 측정값을 수신하는 데 사용할 MeterListener 인스턴스를 만들었습니다.

meterListener.InstrumentPublished = (instrument, listener) =>
{
    if(instrument.Meter.Name == "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

여기서는 수신기가 측정값을 수신할 계측을 구성했습니다. InstrumentPublished는 앱 내에서 새 계측이 만들어질 때마다 호출되는 대리자입니다. 대리자는 계측을 검사하여(예: 이름, 미터 또는 기타 공용 속성 확인) 구독 여부를 확인할 수 있습니다. 이 계측에서 측정값을 수신하려면 EnableMeasurementEvents를 호출하여 이를 나타냅니다. 코드에 계측에 대한 참조를 가져오는 다른 방법이 있는 경우 언제든지 해당 참조를 사용하여 EnableMeasurementEvents()를 호출하는 것이 좋지만 이 방식은 일반적이지 않을 수 있습니다.

meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
...
static void OnMeasurementRecorded<T>(Instrument instrument, T measurement, ReadOnlySpan<KeyValuePair<string,object>> tags, object state)
{
    Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}

다음으로 SetMeasurementEventCallback을 호출하여 계측에서 측정값을 받을 때 호출되는 대리자를 구성했습니다. 제네릭 매개 변수는 콜백에서 수신할 측정 데이터 형식을 제어합니다. 예를 들어 Counter<int>int 측정값을 생성하는 반면 Counter<double>double 측정값을 생성합니다. 계측은 byte, short, int, long, float, double, decimal 형식으로 만들 수 있습니다. 모든 데이터 형식이 필요한 것은 아니라는 시나리오별 지식이 없다면 이 예제와 같이 모든 데이터 형식에 대한 콜백을 등록하는 것이 좋습니다. 다른 제네릭 인수를 사용하여 SetMeasurementEventCallback()을 반복적으로 호출하는 것은 약간 이상하게 보일 수 있습니다. API는 MeterListeners가 매우 낮은 성능 오버헤드(일반적으로 몇 나노초)로 측정값을 수신할 수 있도록 이렇게 설계되었습니다.

MeterListener.EnableMeasurementEvents()가 처음에 호출되었을 때 state 개체를 매개 변수 중 하나로 제공할 기회가 있었습니다. 해당 개체는 원하는 무엇이든 가능합니다. 해당 호출에서 상태 개체를 제공하는 경우에는 해당 계측과 함께 저장되고 콜백에서 state 매개 변수로 반환됩니다. 편의 및 성능 최적화를 위해서 입니다. 수신기는 메모리에 측정값을 저장하고 이 측정값에 대한 계산을 수행하는 코드가 있는 각 계측에 대한 개체를 만들어야 하는 경우가 많습니다. 계측에서 스토리지 개체로 매핑되는 사전을 만들고 모든 측정값에서 조회할 수 있지만 state에서 액세스하는 것보다 훨씬 느립니다.

meterListener.Start();

MeterListener가 구성되면 이것을 시작하여 콜백을 트리거하고 시작해야 합니다. InstrumentPublished 대리자는 프로세스의 모든 기존 계측에 대해 호출됩니다. 앞으로 새로 생성된 모든 계측은 InstrumentPublished도 호출되도록 트리거합니다.

using MeterListener meterListener = new MeterListener();

수신을 완료한 후 수신기를 삭제하면 콜백 흐름이 중지되고 수신기 개체에 대한 내부 참조가 해제됩니다. meterListener를 선언할 때 사용한 using 키워드로 인해 변수가 범위를 벗어나면 Dispose()가 자동으로 호출됩니다. Dispose()는 새로운 콜백을 시작하지 않을 것이라고 약속할 뿐입니다. 콜백은 다른 스레드에서 발생하기 때문에 Dispose()에 대한 호출이 반환된 후에도 여전히 진행 중인 콜백이 있을 수 있습니다. 콜백의 특정 코드 영역이 현재 실행되고 있지 않으며 앞으로 다시 실행되지 않을 것이라는 보장이 필요한 경우에는 이것을 적용하기 위해 몇 가지 추가 스레드 동기화가 필요합니다. Dispose() 는 모든 측정 콜백 MeterListener 에 성능 오버헤드를 추가하므로 기본적으로 동기화를 포함하지 않으며 고성능에 민감한 API로 설계되었습니다.