메트릭 수집
이 문서의 적용 대상: ✔️ .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에서 메트릭 보기
사전 요구 사항
- .NET Core 3.1 SDK 이상 버전
개요
OpenTelemetry는 클라우드 네이티브 소프트웨어에 관한 원격 분석 생성 및 수집의 표준화를 목표로 하는 Cloud Native Computing Foundation에서 지원하는 공급업체 중립 오픈 소스 프로젝트입니다. 기본 제공 플랫폼 메트릭 API는 이 표준을 사용하려는 .NET 개발자가 쉽게 통합할 수 있도록 이 표준과 호환되도록 설계되었습니다. 작성 당시에는 OpenTelemetry 메트릭에 대한 지원이 비교적 새로웠지만 Azure Monitor 및 많은 주요 APM 공급업체가 이를 승인했으며 통합 계획이 진행 중입니다.
이 예제에서는 널리 사용되는 OSS Prometheus 및 Grafana 프로젝트를 사용하여 OpenTelemetry 메트릭에 사용할 수 있는 통합 중 하나를 보여줍니다. 메트릭 데이터는 다음과 같이 흐릅니다.
- .NET 메트릭 API는 예제 애플리케이션에서 측정값을 수집합니다.
- 동일한 프로세스 내에서 실행되는 OpenTelemetry 라이브러리는 이러한 측정값을 집계합니다.
- Prometheus exporter 라이브러리는 HTTP 메트릭 엔드포인트를 통해 집계된 데이터를 사용할 수 있게 합니다. 'Exporter'는 OpenTelemetry가 공급업체별 백 엔드에 원격 분석을 전송하는 라이브러리라고 부르는 것입니다.
- 다른 컴퓨터에서 실행될 수 있는 Prometheus 서버는 메트릭 엔드포인트를 폴링하고, 데이터를 읽고, 장기 지속성을 위해 데이터베이스에 저장합니다. Prometheus는 이것을 엔드포인트 '스크래핑'이라고 합니다.
- 다른 컴퓨터에서 실행될 수 있는 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 서버가 예제 앱을 오랫동안 스크래핑하지 않은 경우 데이터가 누적될 때까지 잠시 기다려야 할 수 있습니다. 또한 왼쪽 위에 있는 시간 범위 컨트롤을 "1m"(1분)으로 조정하면 최근 데이터를 더 잘 볼 수 있습니다.
Grafana 대시보드에 메트릭 표시
표준 지침에 따라 Grafana를 설치하고 Prometheus 데이터 소스에 연결합니다.
Grafana 웹 포털의 왼쪽 도구 모음에 있는 + 아이콘을 클릭하여 Grafana 대시보드를 만든 다음, 대시보드를 선택합니다. 대시보드 편집기가 나타나면 제목으로 'Hats Sold/Sec'를 입력하고 PromQL 식 필드에 'rate(hats_sold[5m])'를 입력합니다. 다음과 같이 표시됩니다.
적용을 클릭하여 저장하고 간단한 새 대시보드를 봅니다.
.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로 설계되었습니다.