메트릭 만들기

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

System.Diagnostics.Metrics API를 사용하여 .NET 애플리케이션을 계측하여 중요한 메트릭을 추적할 수 있습니다. 표준 .NET 라이브러리에 일부 메트릭이 포함되어 있지만 애플리케이션 및 라이브러리와 관련된 새로운 사용자 지정 메트릭을 추가할 수 있습니다. 이 자습서에서는 새 메트릭을 추가하고 어떤 유형의 메트릭을 사용할 수 있는지 이해합니다.

참고 항목

.NET에는 EventCountersSystem.Diagnostics.PerformanceCounter과 같은 몇 가지 이전 메트릭 API가 있지만 여기에서 다루지 않습니다. 이러한 대안에 대한 자세한 내용은 메트릭 API 비교를 참조하세요.

사용자 지정 메트릭 만들기

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

System.Diagnostics.DiagnosticSource NuGet 패키지 버전 8 이상을 참조하는 새 콘솔 애플리케이션을 만듭니다. .NET 8 이상을 대상으로 하는 애플리케이션에는 기본적으로 이 참조가 포함됩니다. 그런 다음, Program.cs의 코드를 다음과 일치하도록 업데이트합니다.

> dotnet new console
> dotnet add package System.Diagnostics.DiagnosticSource
using System;
using System.Diagnostics.Metrics;
using System.Threading;

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.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);
        }
    }
}

System.Diagnostics.Metrics.Meter 형식은 라이브러리가 명명된 계측 그룹을 만들기 위한 진입점입니다. 계측은 메트릭을 계산하는 데 필요한 숫자 측정값을 기록합니다. 여기서는 CreateCounter를 사용하여 "hatco.store.hats_sold"라는 카운터 계측을 만들었습니다. 각 가상 거래 중에 코드는 Add을 호출하여 판매된 hats의 측정값(이 경우 4)을 기록합니다. "hatco.store.hats_sold" 계측은 이러한 측정값에서 계산할 수 있는 몇 가지 메트릭(예: 판매된 총 hats 수 또는 판매된 hats 수/초)을 암시적으로 정의합니다. 궁극적으로 계산할 메트릭 및 이러한 계산을 수행하는 방식을 결정하는 것은 메트릭 수집 도구에 달려 있지만 각 계측에는 개발자의 의도를 전달하는 몇 가지 기본 규칙이 있습니다. 카운터 계측의 경우 수집 도구가 총 개수 및/또는 개수가 증가하는 속도를 표시하는 것이 규칙입니다.

Counter<int>CreateCounter<int>(...)의 제네릭 매개 변수 int는 이 카운터가 값을 Int32.MaxValue까지 저장할 수 있어야 한다고 정의합니다. 저장해야 하는 데이터의 크기와 소수 값이 필요한지 여부에 따라 byte, short, int, long, float, double 또는 decimal 중 하나를 사용할 수 있습니다.

앱을 실행하고 지금은 실행 상태로 둡니다. 다음으로 메트릭을 살펴보겠습니다.

> dotnet run
Press any key to exit

모범 사례

  • DI(종속성 주입) 컨테이너에서 사용하도록 설계되지 않은 코드의 경우 미터를 한 번 만들어 정적 변수에 저장합니다. DI 인식 라이브러리에서 사용하는 경우 정적 변수는 안티 패턴으로 간주되며 아래의 DI 예제는 좀 더 관용적인 접근 방식을 보여 줍니다. 각 라이브러리 또는 라이브러리 하위 구성 요소는 자체 Meter를 만들 수 있으며 만들어야 하는 경우도 있습니다. 앱 개발자가 메트릭 그룹을 개별적으로 손쉽게 사용 및 사용 안 함으로 설정하는 기능의 진가를 알아본다면 기존 미터를 다시 사용하는 것보다 새로운 미터를 만드는 것이 좋습니다.

  • Meter 생성자에 전달된 이름은 다른 미터와 구분하기 위해 고유해야 합니다. 점선 계층 이름을 사용하는 OpenTelemetry 명명 지침을 사용하는 것이 좋습니다. 계측되는 코드의 어셈블리 이름 또는 네임스페이스 이름이 일반적으로 적합합니다. 어셈블리가 두 번째 독립 어셈블리에서 코드의 계측을 추가하는 경우 이름은 해당 코드가 계측되고 있는 어셈블리가 아니라 미터를 정의하는 어셈블리를 기반으로 해야 합니다.

  • .NET은 계측에 대한 명명 체계를 적용하지 않지만 소문자 점선 계층 이름과 밑줄('_')을 동일한 요소의 여러 단어 사이의 구분 기호로 사용하는 OpenTelemetry 명명 지침을 따르는 것이 좋습니다. 모든 메트릭 도구가 최종 메트릭 이름의 일부로 미터 이름을 유지하는 것은 아니므로 계측 이름을 전역적으로 고유하게 만드는 것이 좋습니다.

    계측 이름 예:

    • contoso.ticket_queue.duration
    • contoso.reserved_tickets
    • contoso.purchased_tickets
  • 계측을 생성하고 측정값을 기록하는 API는 스레드로부터 안전합니다. .NET 라이브러리에서 대부분의 인스턴스 메서드는 여러 스레드에서 동일한 개체에 대해 호출될 때 동기화가 필요하지만 이 경우에는 필요하지 않습니다.

  • 측정값을 기록하는 계측 API(이 예에서는 Add)는 일반적으로 데이터가 수집되지 않을 때 <10나노초로 실행되거나, 고성능 수집 라이브러리 또는 도구에서 측정값을 수집할 때는 수십에서 수백 나노초로 실행됩니다. 따라서 대부분의 경우 이러한 API를 자유롭게 사용할 수 있지만 성능이 매우 중요한 코드에는 주의해야 합니다.

새 메트릭 보기

메트릭을 저장하고 볼 수 있는 옵션은 많습니다. 이 자습서에서는 임시 분석에 유용한 dotnet-counters 도구를 사용합니다. 메트릭 수집 자습서에서 다른 대안을 참조할 수도 있습니다. dotnet-counters 도구가 아직 설치되지 않은 경우 SDK를 사용하여 설치합니다.

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

예제 앱이 계속 실행되는 동안 dotnet-counters를 사용하여 새 카운터를 모니터링합니다.

> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.hats_sold (Count / 1 sec)                          4

예상대로 HatCo Store는 1초에 4개의 hats를 꾸준히 판매하는 것을 볼 수 있습니다.

종속성 주입을 통해 미터 가져오기

이전 예제에서는 미터를 얻기 위해 new로 생성하고 정적 필드에 할당했습니다. 이러한 방식으로 정적을 사용하는 것은 DI(종속성 주입)를 사용하는 경우 좋은 방법이 아닙니다. ASP.NET Core 또는 제네릭 호스트가 있는 앱과 같은 DI를 사용하는 코드에서 IMeterFactory를 사용하여 미터 개체를 만듭니다. .NET 8부터는 호스트가 서비스 컨테이너에 IMeterFactory를 자동으로 등록하거나, AddMetrics를 호출하여 IServiceCollection에서 형식을 직접 등록할 수도 있습니다. 미터 팩터리는 메트릭을 DI와 통합하여 동일한 이름을 사용하는 경우에도 서로 격리된 서로 다른 서비스 컬렉션의 미터를 유지합니다. 이는 병렬로 실행되는 여러 테스트가 동일한 테스트 사례 내에서 생성된 측정값만 관찰할 수 있도록 하여 테스트에 특히 유용합니다.

DI용으로 디자인된 형식에서 미터를 가져오려면 생성자에 IMeterFactory 매개 변수를 추가한 다음 Create를 호출합니다. 이 예제에서는 ASP.NET Core 앱에서 IMeterFactory를 사용하는 방법을 보여 줍니다.

계측을 저장할 형식을 정의합니다.

public class HatCoMetrics
{
    private readonly Counter<int> _hatsSold;

    public HatCoMetrics(IMeterFactory meterFactory)
    {
        var meter = meterFactory.Create("HatCo.Store");
        _hatsSold = meter.CreateCounter<int>("hatco.store.hats_sold");
    }

    public void HatsSold(int quantity)
    {
        _hatsSold.Add(quantity);
    }
}

Program.cs에서 DI 컨테이너에 형식을 등록합니다.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<HatCoMetrics>();

필요한 경우 메트릭 형식 및 레코드 값을 주입합니다. 메트릭 형식은 DI에 등록되므로 MVC 컨트롤러, 최소 API 또는 DI에서 만든 다른 형식과 함께 사용할 수 있습니다.

app.MapPost("/complete-sale", ([FromBody] SaleModel model, HatCoMetrics metrics) =>
{
    // ... business logic such as saving the sale to a database ...

    metrics.HatsSold(model.QuantitySold);
});

모범 사례

  • System.Diagnostics.Metrics.Meter는 IDisposable을 구현하지만 IMeterFactory는 만드는 모든 Meter 개체의 수명을 자동으로 관리하여 DI 컨테이너가 삭제될 때 삭제합니다. Meter에서 Dispose()를 호출할 코드를 더 추가할 필요는 없으며 아무런 영향도 주지 않습니다.

계측 유형

지금까지는 Counter<T> 계측만 설명했지만 더 많은 계측 유형을 사용할 수 있습니다. 계측은 두 가지 면에서 다릅니다.

  • 기본 메트릭 계산 - 계측 측정값을 수집하고 분석하는 도구는 계측에 따라 다른 기본 메트릭을 계산합니다.
  • 집계된 데이터의 스토리지 - 가장 유용한 메트릭은 많은 측정값에서 집계된 데이터가 필요합니다. 한 가지 옵션은 호출자가 임의의 시간에 개별 측정값을 제공하고 수집 도구가 집계를 관리하는 것입니다. 또는 호출자가 집계 측정값을 관리하고 요청 시 콜백에서 제공할 수 있습니다.

현재 사용 가능한 계측 유형:

  • Counter(CreateCounter) - 이 계측은 시간이 지남에 따라 증가하는 값을 추적하고 호출자는 Add를 사용하여 증분을 보고합니다. 대부분의 도구는 총계와 총계의 변동률을 계산합니다. 한 가지만 표시하는 도구의 경우 변동률을 권장합니다. 예를 들어 호출자가 연속적인 값 1, 2, 4, 5, 4, 3으로 1초에 한 번씩 Add()를 호출한다고 가정합니다. 수집 도구가 3초마다 업데이트되는 경우 3초 후의 합계는 1+2+4=7이고 6초 후의 합계는 1+2+4+5+4+3=19입니다. 변동률은 (current_total - previous_total)이므로 3초 후에 도구는 7-0=7을 보고하고 6초 후에는 19-7=12를 보고합니다.

  • UpDownCounter(CreateUpDownCounter) - 이 계측기는 시간이 지남에 따라 증가하거나 감소할 수 있는 값을 추적합니다. 호출자는 Add를 사용하여 증분 및 감소값을 보고합니다. 예를 들어 호출자가 연속적인 값 1, 5, -2, 3, -1, -3으로 1초에 한 번씩 Add()를 호출한다고 가정합니다. 수집 도구가 3초마다 업데이트되는 경우 3초 후의 합계는 1+5-2=4이고 6초 후의 합계는 1+5-2+3-1-3=3입니다.

  • ObservableCounter(CreateObservableCounter) - 이 계측은 호출자가 집계된 총계를 유지 관리해야 한다는 점을 제외하고 Counter와 비슷합니다. ObservableCounter가 생성될 때 호출자가 콜백 대리자를 제공하고 도구가 현재 총계를 관찰해야 할 때마다 콜백이 호출됩니다. 예를 들어 수집 도구가 3초마다 업데이트되면 콜백 함수도 3초마다 호출됩니다. 대부분의 도구에서 총계와 총계 변동률을 모두 사용할 수 있습니다. 하나만 표시할 수 있다면 변동률이 권장됩니다. 콜백이 초기 호출에서 0을 반환하고, 3초 후에 다시 호출될 때 7을 반환하고, 6초 후에 호출될 때 19를 반환하는 경우 도구는 해당 값을 합계로 변경하지 않고 보고합니다. 변동률의 경우 도구는 3초 후에 7-0=7을 표시하고 6초 후에는 19-7=12를 표시합니다.

  • ObservableUpDownCounter(CreateObservableUpDownCounter) - 이 계측은 호출자가 집계된 총계를 유지 관리해야 한다는 점을 제외하고 UpDownCounter와 비슷합니다. ObservableUpDownCounter가 생성될 때 호출자가 콜백 대리자를 제공하고 도구가 현재 총계를 관찰해야 할 때마다 콜백이 호출됩니다. 예를 들어 수집 도구가 3초마다 업데이트되면 콜백 함수도 3초마다 호출됩니다. 콜백에서 반환되는 값은 합계로 변경되지 않은 컬렉션 도구에 표시됩니다.

  • ObservableGauge(CreateObservableGauge) - 이 계측을 사용하면 호출자가 측정된 값이 메트릭으로 직접 전달되는 콜백을 제공할 수 있습니다. 수집 도구가 업데이트될 때마다 콜백이 호출되고 콜백에 의해 반환된 값이 무엇이든 도구에 표시됩니다.

  • Histogram(CreateHistogram) - 이 계측은 측정값의 분포를 추적합니다. 일련의 측정값을 설명하는 한 가지 정식 방법은 없지만 도구에서 히스토그램 또는 계산된 백분위수를 사용하는 것이 좋습니다. 예를 들어 호출자가 수집 도구의 업데이트 간격 동안 이러한 측정값을 기록하기 위해 Record을 호출했다고 가정합니다(1,5,2,3,10,9,7,4,6,8). 수집 도구는 이러한 측정값의 50번째, 90번째 및 95번째 백분위수가 각각 5, 9 및 9라고 보고할 수 있습니다.

계측 유형 선택 시 모범 사례

  • 개수를 세거나 시간이 지남에 따라 증가하는 다른 값을 계산하려면 Counter 또는 ObservableCounter를 사용합니다. 각 증분 작업에 대한 API 호출 또는 코드가 유지 관리하는 변수에서 현재 총계를 읽는 콜백 중에 어떤 것이 기존 코드에 추가하기 더 쉬운지에 따라 Counter와 ObservableCounter 중에서 선택합니다. 성능이 중요하고 Add를 사용하면 스레드당 초당 1백만 개 넘는 호출이 생성되는 극도의 핫 코드 경로에서 ObservableCounter를 사용하면 최적화에 더 많은 기회를 제공할 수 있습니다.

  • 타이밍을 위해 일반적으로 히스토그램이 선호됩니다. 평균이나 합계보다 이러한 분포의 꼬리(90번째, 95번째, 99번째 백분위수)를 이해하는 것이 유용한 경우가 많습니다.

  • 캐시 적중률이나 캐시, 큐, 파일의 크기와 같은 다른 일반적인 사례는 일반적으로 UpDownCounter 또는 ObservableUpDownCounter에 적합합니다. 각 증분 및 감소 작업에 대한 API 호출 또는 코드가 유지 관리하는 변수에서 현재 총계를 읽는 콜백 중에 어떤 것이 기존 코드에 추가하기 더 쉬운지에 따라 둘 중에서 선택합니다.

참고 항목

UpDownCounterObservableUpDownCounter(버전 7 이전)를 지원하지 않는 이전 버전의 .NET 또는 DiagnosticSource NuGet 패키지를 사용하는 경우 ObservableGauge를 대신 사용하면 적절한 경우가 많습니다.

다양한 계측 유형의 예

이전에 시작된 예제 프로세스를 중지하고 Program.cs의 예제 코드를 다음으로 대체합니다.

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

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");
    static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>("hatco.store.order_processing_time");
    static int s_coatsSold;
    static int s_ordersPending;

    static Random s_rand = new Random();

    static void Main(string[] args)
    {
        s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_coatsSold);
        s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", () => s_ordersPending);

        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has one transaction each 100ms that each sell 4 hats
            Thread.Sleep(100);
            s_hatsSold.Add(4);

            // Pretend we also sold 3 coats. For an ObservableCounter we track the value in our variable and report it
            // on demand in the callback
            s_coatsSold += 3;

            // Pretend we have some queue of orders that varies over time. The callback for the orders_pending gauge will report
            // this value on-demand.
            s_ordersPending = s_rand.Next(0, 20);

            // Last we pretend that we measured how long it took to do the transaction (for example we could time it with Stopwatch)
            s_orderProcessingTime.Record(s_rand.Next(0.005, 0.015));
        }
    }
}

새 프로세스를 실행하고 두 번째 셸에서 이전처럼 dotnet-counters를 사용하여 메트릭을 봅니다.

> dotnet-counters monitor -n metric-demo.exe --counters HatCo.Store
Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.coats_sold (Count / 1 sec)                                27
    hatco.store.hats_sold (Count / 1 sec)                                 36
    hatco.store.order_processing_time
        Percentile=50                                                      0.012
        Percentile=95                                                      0.014
        Percentile=99                                                      0.014
    hatco.store.orders_pending                                             5

이 예에서는 임의로 생성된 숫자를 사용하므로 값이 약간 다를 수 있습니다. hatco.store.hats_sold(Counter) 및 hatco.store.coats_sold(ObservableCounter)가 모두 비율로 표시되는 것을 볼 수 있습니다. ObservableGauge, hatco.store.orders_pending은 절대값으로 나타납니다. dotnet-counters는 히스토그램 계측을 세 개의 백분위수 통계(50번째, 95번째 및 99번째)로 렌더링하지만 다른 도구는 분포를 다르게 요약하거나 더 많은 구성 옵션을 제공할 수 있습니다.

모범 사례

  • 히스토그램은 다른 메트릭 형식보다 메모리에 훨씬 더 많은 데이터를 저장하는 경향이 있습니다. 그러나 정확한 메모리 사용량은 사용 중인 컬렉션 도구에 의해 결정됩니다. 많은 수(>100)의 히스토그램 메트릭을 정의하는 경우에는 동시에 모든 메트릭을 사용하도록 설정하지 않도록 사용자에게 지침을 제공하거나 전체 자릿수를 줄여서 메모리를 절약하도록 도구를 구성해야 할 수 있습니다. 일부 수집 도구에는 과도한 메모리 사용을 방지하기 위해 모니터링할 동시 히스토그램 수에 대해 하드 한도가 있을 수 있습니다.

  • 관찰 가능한 모든 계측에 대한 콜백은 순서대로 호출되므로 시간이 오래 걸리는 콜백은 지연하거나 모든 메트릭이 수집되지 않도록 방지할 수 있습니다. 캐시된 값을 빠르게 읽거나, 측정값을 반환하지 않거나, 잠재적 장기 실행 또는 블로킹 작업을 수행하기 보다는 예외를 throw하는 것이 선호됩니다.

  • ObservableCounter, ObservableUpDownCounter 및 ObservableGauge 콜백은 일반적으로 값을 업데이트하는 코드와 동기화되지 않는 스레드에서 발생합니다. 메모리 액세스를 동기화하거나 동기화되지 않은 액세스를 사용하여 발생할 수 있는 일관되지 않은 값을 수락해야 합니다. 액세스를 동기화하는 일반적인 방법은 잠금 또는 호출 Volatile.ReadVolatile.Write를 사용하는 것입니다.

  • CreateObservableGaugeCreateObservableCounter 함수는 계측 개체를 반환하지만 대부분의 경우 개체와 더 이상 상호 작용할 필요가 없기 때문에 변수에 저장할 필요가 없습니다. 다른 계측에 대해 했듯이 정적 변수에 할당하는 것은 적합하지만 오류가 발생하기 쉽습니다. C# 정적 초기화가 지연되고 변수가 일반적으로 참조되지 않기 때문입니다. 문제의 예는 다음과 같습니다.

    using System;
    using System.Diagnostics.Metrics;
    
    class Program
    {
        // BEWARE! Static initializers only run when code in a running method refers to a static variable.
        // These statics will never be initialized because none of them were referenced in Main().
        //
        static Meter s_meter = new Meter("HatCo.Store");
        static ObservableCounter<int> s_coatsSold = s_meter.CreateObservableCounter<int>("hatco.store.coats_sold", () => s_rand.Next(1,10));
        static Random s_rand = new Random();
    
        static void Main(string[] args)
        {
            Console.ReadLine();
        }
    }
    

설명 및 단위

계측은 선택적 설명 및 단위를 지정할 수 있습니다. 이 값은 모든 메트릭 계산에 대해 불투명하지만 데이터를 해석하는 방법을 엔지니어가 이해할 수 있도록 수집 도구 UI에 표시될 수 있습니다. 이전에 시작한 예제 프로세스를 중지하고 Program.cs의 예제 코드를 다음으로 대체합니다.

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

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

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

새 프로세스를 실행하고 두 번째 셸에서 이전처럼 dotnet-counters를 사용하여 메트릭을 봅니다.

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.hats_sold ({hats} / 1 sec)                                40

dotnet-counters는 현재 UI에서 설명 텍스트를 사용하지 않지만 제공될 때 단위를 표시합니다. 이 경우 "{hats}"가 이전 설명에 표시된 일반 용어 "Count"를 대체한 것을 볼 수 있습니다.

모범 사례

  • .NET API를 사용하면 모든 문자열을 단위로 사용할 수 있지만 단위 이름에 대한 국제 표준인 UCUM을 사용하는 것이 좋습니다. "{hats}" 주위의 중괄호는 UCUM 표준의 일부로, 초 또는 바이트와 같은 표준화된 의미를 가진 단위 이름이 아닌 설명 주석임을 나타냅니다.

  • 생성자에 지정된 단위는 개별 측정에 적합한 단위를 설명해야 합니다. 이것은 경우에 따라 최종 메트릭의 단위와 다를 수 있습니다. 이 예제에서 각 측정값은 여러 개의 hats이므로 "{hats}"가 생성자에 전달할 적절한 단위입니다. 수집 도구는 비율을 계산했고 계산된 메트릭에 대한 적절한 단위가 {hats}/sec임을 자체적으로 도출했습니다.

  • 시간 측정값을 기록할 때 부동 소수점 또는 이중 값으로 기록된 초 단위를 선호합니다.

다차원 메트릭

측정값은 분석을 위해 데이터를 분류할 수 있는 태그라는 키-값 쌍과도 연결될 수 있습니다. 예를 들어 HatCo는 판매된 hat의 개수뿐만 아니라 판매된 hat의 크기와 색도 기록하려고 할 수 있습니다. 나중에 데이터를 분석할 때 HatCo 엔지니어는 총계를 크기, 색 또는 이 둘의 조합으로 나눌 수 있습니다.

카운터 및 히스토그램 태그는 하나 이상의 KeyValuePair 인수를 사용하는 AddRecord의 오버로드에서 지정할 수 있습니다. 예시:

s_hatsSold.Add(2,
               new KeyValuePair<string, object>("product.color", "red"),
               new KeyValuePair<string, object>("product.size", 12));

Program.cs의 코드를 바꾸고 이전과 같이 앱 및 dotnet-counters를 다시 실행합니다.

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

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");

    static void Main(string[] args)
    {
        Console.WriteLine("Press any key to exit");
        while(!Console.KeyAvailable)
        {
            // Pretend our store has a transaction, every 100ms, that sells two size 12 red hats, and one size 19 blue hat.
            Thread.Sleep(100);
            s_hatsSold.Add(2,
                           new KeyValuePair<string,object>("product.color", "red"),
                           new KeyValuePair<string,object>("product.size", 12));
            s_hatsSold.Add(1,
                           new KeyValuePair<string,object>("product.color", "blue"),
                           new KeyValuePair<string,object>("product.size", 19));
        }
    }
}

이제 dotnet-counters에 기본 분류가 표시됩니다.

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.hats_sold (Count / 1 sec)
        product.color=blue,product.size=19                                 9
        product.color=red,product.size=12                                 18

ObservableCounter 및 ObservableGauge의 경우 생성자에 전달된 콜백에 태그가 지정된 측정을 제공할 수 있습니다.

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

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");

    static void Main(string[] args)
    {
        s_meter.CreateObservableGauge<int>("hatco.store.orders_pending", GetOrdersPending);
        Console.WriteLine("Press any key to exit");
        Console.ReadLine();
    }

    static IEnumerable<Measurement<int>> GetOrdersPending()
    {
        return new Measurement<int>[]
        {
            // pretend these measurements were read from a real queue somewhere
            new Measurement<int>(6, new KeyValuePair<string,object>("customer.country", "Italy")),
            new Measurement<int>(3, new KeyValuePair<string,object>("customer.country", "Spain")),
            new Measurement<int>(1, new KeyValuePair<string,object>("customer.country", "Mexico")),
        };
    }
}

이전과 같이 dotnet-counters로 실행하면 결과는 다음과 같습니다.

Press p to pause, r to resume, q to quit.
    Status: Running

[HatCo.Store]
    hatco.store.orders_pending
        customer.country=Italy                                             6
        customer.country=Mexico                                            1
        customer.country=Spain                                             3

모범 사례

  • API를 사용하면 모든 개체를 태그 값으로 사용할 수 있지만 컬렉션 도구에 숫자 형식 및 문자열이 필요합니다. 다른 형식은 지정된 수집 도구에서 지원되거나 지원되지 않을 수 있습니다.

  • 태그 이름은 '_' 문자가 있는 소문자 점선 계층 이름을 사용하여 동일한 요소의 여러 단어를 구분하는 OpenTelemetry 명명 지침을 따르는 것이 좋습니다. 태그 이름이 다른 메트릭 또는 다른 원격 분석 레코드에서 다시 사용되는 경우 사용되는 모든 위치에서 동일한 의미와 법적 값 집합이 있어야 합니다.

    태그 이름 예:

    • customer.country
    • store.payment_method
    • store.purchase_result
  • 실제로 기록되는 태그 값의 조합이 매우 크거나 무한하지 않도록 주의하십시오. .NET API 구현으로 이를 처리할 수 있지만 수집 도구는 각 태그 조합과 연결된 메트릭 데이터에 대한 스토리지를 할당할 가능성이 높으며 이것이 매우 커질 수 있습니다. 예를 들어, HatCo에 10가지 다른 hat 색과 25가지 hat 크기가 있고 최대 10*25=250개의 판매 총계를 추적할 수 있다면 괜찮습니다. 하지만 HatCo가 세 번째 태그(판매용 CustomerID)를 추가하고 전 세계적으로 1억 명의 고객에게 판매한다면 이제 수십억 개의 다양한 태그 조합이 기록될 가능성이 높습니다. 대부분의 메트릭 수집 도구는 기술적 한도 내에서 유지하기 위해 데이터를 삭제합니다. 그렇지 않으면 데이터 스토리지 및 처리를 충당하기 위해 막대한 금전적 비용이 발생할 수 있습니다. 각 수집 도구의 구현에 따라 한도가 결정되지만 하나의 계측에 대해 1,000개 미만의 조합이 안전할 수 있습니다. 1,000개가 넘는 조합을 사용하려면 컬렉션 도구가 필터링을 적용하거나 대규모로 작동하도록 엔지니어링되어야 합니다. 히스토그램 구현은 다른 메트릭보다 훨씬 더 많은 메모리를 사용하는 경향이 있으므로 안전 한도는 10~100배 정도 낮을 수 있습니다. 고유한 태그 조합이 많을 것으로 예상되는 경우, 필요한 규모로 운영하기 위해서는 로그, 트랜잭션 데이터베이스 또는 빅 데이터 처리 시스템이 더 적합한 솔루션일 수 있습니다.

  • 태그 조합 수가 매우 많은 계측의 경우 메모리 오버헤드를 줄이기 위해 더 작은 스토리지 유형을 사용하는 것이 좋습니다. 예를 들어, Counter<short>에 대한 short을 저장하면 태그 조합당 2바이트만 차지하는 반면 Counter<double>에 대한 double은 태그 조합당 8바이트를 차지합니다.

  • 수집 도구는 동일한 계측에서 측정값을 기록하기 위해 각 호출에 대해 동일한 순서로 동일한 태그 이름 집합을 지정하는 코드에 맞게 최적화하는 것이 좋습니다. AddRecord를 자주 호출해야 하는 고성능 코드의 경우 각 호출에 대해 동일한 태그 이름 시퀀스를 사용하는 것이 좋습니다.

  • .NET API는 3개 이하의 태그를 개별적으로 지정하여 AddRecord 호출에 대해 할당이 없도록 최적화되어 있습니다. 많은 수의 태그가 할당되지 않도록 하려면 TagList를 사용합니다. 일반적으로 태그를 더 많이 사용할수록 이러한 호출의 성능 오버헤드가 증가합니다.

참고 항목

OpenTelemetry에서는 태그를 'attributes'라고 합니다. 이러한 이름은 동일한 기능에 대해 서로 다른 두 가지 이름입니다.

사용자 지정 메트릭 테스트

MetricCollector<T>를 사용하여 추가하는 사용자 지정 메트릭을 테스트할 수 있습니다. 이 형식을 사용하면 특정 계측의 측정값을 쉽게 기록하고 값이 올바른지 어설션할 수 있습니다.

종속성 주입을 사용하여 테스트

다음 코드에서는 종속성 주입 및 IMeterFactory를 사용하는 코드 구성 요소에 대한 예제 테스트 사례를 보여 줍니다.

public class MetricTests
{
    [Fact]
    public void SaleIncrementsHatsSoldCounter()
    {
        // Arrange
        var services = CreateServiceProvider();
        var metrics = services.GetRequiredService<HatCoMetrics>();
        var meterFactory = services.GetRequiredService<IMeterFactory>();
        var collector = new MetricCollector<int>(meterFactory, "HatCo.Store", "hatco.store.hats_sold");

        // Act
        metrics.HatsSold(15);

        // Assert
        var measurements = collector.GetMeasurementSnapshot();
        Assert.Equal(1, measurements.Count);
        Assert.Equal(15, measurements[0].Value);
    }

    // Setup a new service provider. This example creates the collection explicitly but you might leverage
    // a host or some other application setup code to do this as well.
    private static IServiceProvider CreateServiceProvider()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddMetrics();
        serviceCollection.AddSingleton<HatCoMetrics>();
        return serviceCollection.BuildServiceProvider();
    }
}

각 MetricCollector 개체는 하나의 계측에 대한 모든 측정값을 기록합니다. 여러 계측에서 측정값을 확인해야 하는 경우 각 계측에 대해 하나의 MetricCollector를 만듭니다.

종속성 주입 없이 테스트

정적 필드에서 공유 전역 미터 개체를 사용하는 코드를 테스트할 수도 있지만 이러한 테스트가 병렬로 실행되지 않도록 구성되었는지 확인합니다. 미터 개체가 공유되기 때문에 한 테스트의 MetricCollector는 병렬로 실행되는 다른 테스트에서 만든 측정값을 관찰합니다.

class HatCoMetricsWithGlobalMeter
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hatco.store.hats_sold");

    public void HatsSold(int quantity)
    {
        s_hatsSold.Add(quantity);
    }
}

public class MetricTests
{
    [Fact]
    public void SaleIncrementsHatsSoldCounter()
    {
        // Arrange
        var metrics = new HatCoMetricsWithGlobalMeter();
        // Be careful specifying scope=null. This binds the collector to a global Meter and tests
        // that use global state should not be configured to run in parallel.
        var collector = new MetricCollector<int>(null, "HatCo.Store", "hatco.store.hats_sold");

        // Act
        metrics.HatsSold(15);

        // Assert
        var measurements = collector.GetMeasurementSnapshot();
        Assert.Equal(1, measurements.Count);
        Assert.Equal(15, measurements[0].Value);
    }
}