다음을 통해 공유


메트릭 만들기

이 문서는 다음에 적용됩니다: ✔️ .NET Core 6 이상 버전 ✔️ .NET Framework 4.6.1 이상 버전.

.NET 애플리케이션은 System.Diagnostics.Metrics API를 사용하여 계측하여 중요한 메트릭을 추적할 수 있습니다. 일부 메트릭은 표준 .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을 호출하여 판매된 모자의 측정값(이 경우 4)을 기록하도록 되어 있습니다. "hatco.store.hats_sold" 계측기는 판매된 총 모자 수 또는 초당 판매된 모자 수와 같이 이러한 측정값에서 계산할 수 있는 몇 가지 메트릭을 암시적으로 정의합니다. 궁극적으로는 어떤 메트릭을 계산할지와 어떻게 그 계산을 수행할지를 결정하는 것은 메트릭 수집 도구에 달려 있지만, 각 계측기에는 개발자의 의도를 전달하는 몇 가지 기본적인 관례가 있습니다. 카운터 계측기의 경우, 콜렉션 도구는 총 개수와/또는 개수가 증가하는 속도를 표시하는 것이 일반적입니다.

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 ns로 실행되거나 고성능 수집 라이브러리 또는 도구에서 측정을 수집할 때 수십~수백 나노초로 실행됩니다. 이렇게 하면 대부분의 경우 이러한 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 스토어는 매초 4개의 모자를 꾸준히 판매하고 있음을 알 수 있습니다.

종속성 주입을 통해 측정기 가져오기

이전 예제에서 미터는 new을 사용하여 생성하고 정적 필드에 할당함으로써 구했습니다. 이러한 방식으로 정적을 사용하는 것은 DI(종속성 주입)를 사용하는 경우 좋은 방법이 아닙니다. ASP.NET Core 또는 제네릭 호스트을 사용하는 앱과 같은 DI 코드에서, IMeterFactory로 Meter 개체를 만드십시오. .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> 계측기만 시연했지만 더 많은 계측 유형을 사용할 수 있습니다. 악기는 다음과 같은 두 가지 방법으로 다릅니다.

  • 기본 메트릭 계산 - 계측 측정값을 수집하고 분석하는 도구는 계측에 따라 다른 기본 메트릭을 계산합니다.
  • 집계된 데이터 저장 - 가장 유용한 메트릭은 여러 측정값으로부터 데이터를 집계해야 합니다. 한 가지 옵션은 호출자가 임의 시간에 개별 측정값을 제공하고 컬렉션 도구가 집계를 관리하는 것입니다. 또는 호출자는 집계 측정값을 관리하고 필요할 때 콜백을 통해 이를 제공할 수 있습니다.

현재 사용할 수 있는 계측 유형:

  • 카운터(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초마다 호출됩니다. 콜백에서 반환된 값은 변경되지 않은 채 합계로 컬렉션 도구에 표시됩니다.

  • 계기(CreateGauge) - 이 계측을 사용하면 호출자가 Record 메서드를 사용하여 메트릭의 현재 값을 설정할 수 있습니다. 메서드를 다시 호출하여 언제든지 값을 업데이트할 수 있으며 메트릭 컬렉션 도구에 가장 최근에 설정된 값이 표시됩니다.

  • ObservableGauge(CreateObservableGauge) - 이 계측기는 호출자가 측정된 값을 메트릭으로 직접 전달할 수 있는 콜백을 제공할 수 있게 합니다. 컬렉션 도구가 업데이트될 때마다 콜백이 호출되고 콜백에서 반환되는 값이 도구에 표시됩니다.

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

    메모

    히스토그램 도구를 만들 때 권장하는 버킷 경계를 설정하는 방법에 대한 자세한 내용은 '조언을 사용하여 히스토그램 도구를 사용자 지정하기'을 참조하세요.

계측 유형을 선택할 때의 모범 사례

  • 항목을 세거나 시간에 따라 전적으로 증가하는 다른 값의 경우 Counter 또는 ObservableCounter를 사용하세요. 기존 코드에 더 쉽게 추가할 수 있는 항목(각 증분 작업에 대한 API 호출 또는 코드가 유지 관리하는 변수에서 현재 합계를 읽는 콜백)에 따라 Counter와 ObservableCounter 중에서 선택합니다. 성능이 중요하고 Add 사용하면 스레드당 초당 100만 개 이상의 호출이 생성되는 매우 핫 코드 경로에서 ObservableCounter를 사용하면 최적화에 더 많은 기회를 제공할 수 있습니다.

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

  • 캐시 적중률, 캐시 크기, 큐 및 파일과 같은 다른 일반적인 경우는 보통 UpDownCounter 또는 ObservableUpDownCounter에 잘 적합합니다. 기존 코드에 더 쉽게 추가할 수 있는 항목(각 증가 및 감소 작업에 대한 API 호출 또는 코드가 유지 관리하는 변수에서 현재 값을 읽는 콜백)에 따라 둘 중에서 선택합니다.

메모

이전 버전의 .NET 또는 버전 7 이전의 UpDownCounterObservableUpDownCounter을 지원하지 않는 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(5, 15)/1000.0);
        }
    }
}

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

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

Name                                                  Current Value
[HatCo.Store]
    hatco.store.coats_sold (Count)                        8,181    
    hatco.store.hats_sold (Count)                           548    
    hatco.store.order_processing_time
        Percentile
        50                                                    0.012    
        95                                                    0.013   
        99                                                    0.013
    hatco.store.orders_pending                                9    

이 예제에서는 임의로 생성된 숫자를 사용하므로 값이 약간 달라집니다. Dotnet-counters는 히스토그램 계측기를 세 개의 백분위수 통계(50번째, 95번째 및 99번째)로 렌더링하지만 다른 도구는 배포를 다르게 요약하거나 더 많은 구성 옵션을 제공할 수 있습니다.

모범 사례

  • 히스토그램은 다른 메트릭 형식보다 메모리에 훨씬 더 많은 데이터를 저장하는 경향이 있습니다. 그러나 정확한 메모리 사용량은 사용 중인 컬렉션 도구에 의해 결정됩니다. 히스토그램 메트릭의 개수(>100)를 정의하는 경우, 사용자가 동시에 모든 메트릭을 활성화하지 않도록 조언하거나, 정밀도를 줄여 메모리를 절약하도록 도구를 구성하도록 안내할 필요가 있을 수 있습니다. 일부 컬렉션 도구는 과도한 메모리 사용을 방지하기 위해 모니터링할 동시 히스토그램 수에 하드 제한이 있을 수 있습니다.

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

  • 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

Name                                                       Current Value
[HatCo.Store]
    hatco.store.hats_sold ({hats})                                40

dotnet-counters는 현재 UI의 설명 텍스트를 사용하지 않지만 제공될 때 단위를 표시합니다. 이 경우 "{hats}"이(가) 이전 설명에 표시된 제네릭 용어 "Count"를 대체했습니다.

모범 사례

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

  • 생성자에 지정된 단위는 개별 측정에 적합한 단위를 설명해야 합니다. 이는 보고되는 최종 메트릭의 단위와 다를 수 있습니다. 이 예제에서 각 측정은 모자의 수를 의미하므로, "{hats}"는 생성자에 전달할 적절한 단위입니다. 컬렉션 도구는 변경 속도를 계산하고 계산 속도 메트릭에 적합한 단위가 {hats}/sec라는 것을 자체적으로 파생할 수 있습니다.

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

다차원 메트릭

측정값은 분석을 위해 데이터를 분류할 수 있는 태그라는 키-값 쌍과 연결할 수도 있습니다. 예를 들어 HatCo는 판매된 모자의 수뿐만 아니라 어떤 크기와 색을 기록하려고 할 수도 있습니다. 나중에 데이터를 분석할 때 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 카운터에 기본 분류가 표시됩니다.

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

Name                                                  Current Value
[HatCo.Store]
    hatco.store.hats_sold (Count)
        product.color product.size
        blue          19                                     73
        red           12                                    146    

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

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

모범 사례

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

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

    예제 태그 이름:

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

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

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

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

메모

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

조언을 사용하여 히스토그램 도구 사용자 지정

히스토그램을 사용하는 경우 기록된 값의 분포를 가장 잘 나타내는 방법을 결정하기 위해 데이터를 수집하는 도구 또는 라이브러리의 책임입니다. 일반적인 전략(및 OpenTelemetry사용하는 경우 기본 모드)은 가능한 값 범위를 버킷이라는 하위 범위로 나누고 각 버킷에 기록된 값의 수를 보고하는 것입니다. 예를 들어 도구는 숫자를 1보다 작은 버킷, 1-10 사이의 버킷 및 10보다 큰 버킷으로 나눌 수 있습니다. 앱에서 값 0.5, 6, 0.1, 12를 기록한 경우 첫 번째 버킷에 두 개의 데이터 요소가 있고, 두 번째 버킷에 하나씩, 3번째 버킷에 하나씩이 있습니다.

히스토그램 데이터를 수집하는 도구 또는 라이브러리는 사용할 버킷을 정의합니다. OpenTelemetry를 사용하는 경우 기본 버킷 구성은 [ 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 ].

기본값은 모든 히스토그램에 대한 최상의 세분성으로 이어지지 않을 수 있습니다. 예를 들어 하위 초 요청 기간은 모두 0 버킷에 속합니다.

히스토그램 데이터를 수집하는 도구 또는 라이브러리는 사용자가 버킷 구성을 사용자 지정할 수 있도록 하는 메커니즘을 제공할 수 있습니다. 예를 들어 OpenTelemetry는 View API를 정의합니다. 그러나 이를 위해서는 최종 사용자 작업이 필요하며 올바른 버킷을 선택할 수 있을 만큼 데이터 배포를 잘 이해하는 것은 사용자의 책임입니다.

환경을 개선하기 위해 System.Diagnostics.DiagnosticSource 패키지의 9.0.0 버전에는 (InstrumentAdvice<T>) API가 도입되었습니다.

InstrumentAdvice API는 계측 작성자가 지정된 히스토그램에 권장되는 기본 버킷 경계 집합을 지정하는 데 사용할 수 있습니다. 히스토그램 데이터를 수집하는 도구 또는 라이브러리는 집계를 구성할 때 해당 값을 사용하도록 선택하여 사용자에게 보다 원활한 온보딩 환경을 제공할 수 있습니다. 이는 버전 1.10.0OpenTelemetry .NET SDK에서 지원됩니다.

중요하다

일반적으로 버킷이 많을수록 지정된 히스토그램에 대한 더 정확한 데이터가 생성되지만 각 버킷은 집계된 세부 정보를 저장하기 위해 메모리가 필요하며 측정을 처리할 때 올바른 버킷을 찾는 데 CPU 비용이 발생합니다. InstrumentAdvice API를 통해 권장할 버킷 수를 선택할 때 정밀도와 CPU/메모리 사용 간의 절충을 이해하는 것이 중요합니다.

다음 코드에서는 InstrumentAdvice API를 사용하여 권장되는 기본 버킷을 설정하는 예제를 보여 줍니다.

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

class Program
{
    static Meter s_meter = new Meter("HatCo.Store");
    static Histogram<double> s_orderProcessingTime = s_meter.CreateHistogram<double>(
        name: "hatco.store.order_processing_time",
        unit: "s",
        description: "Order processing duration",
        advice: new InstrumentAdvice<double> { HistogramBucketBoundaries = [0.01, 0.05, 0.1, 0.5, 1, 5] });

    static Random s_rand = new Random();

    static void Main(string[] args)
    {
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            // Pretend our store has one transaction each 100ms
            Thread.Sleep(100);

            // 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(5, 15) / 1000.0);
        }
    }
}

추가 정보

OpenTelemetry의 명시적 버킷 히스토그램에 대한 자세한 내용은 다음을 참조하세요.

사용자 지정 메트릭 테스트

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