다음을 통해 공유


.NET의 네트워킹 메트릭

메트릭은 시간에 따라 보고된 숫자 측정값입니다. 일반적으로 앱 상태를 모니터링하고 경고를 생성하는 데 사용됩니다.

.NET 8부터 System.Net.HttpSystem.Net.NameResolution 구성 요소가 계측되어 .NET의 새 System.Diagnostics.Metrics API을 사용하여 메트릭을 게시합니다. 이러한 메트릭은 OpenTelemetry 협력하여 표준과 일치하고 PrometheusGrafana와 같은 인기 있는 도구와 잘 작동하도록 설계되었습니다. 또한 다차원입니다. 즉, 측정값은 분석을 위해 데이터를 분류할 수 있는 태그(즉, 특성 또는 레이블)라는 키-값 쌍과 연결됩니다.

특성과 함께 모든 기본 제공 계측의 포괄적인 목록은 System.Net 메트릭을 참조하세요.

System.Net 메트릭 수집

.NET 앱에서 메트릭을 사용하는 데는 다음과 같은 두 부분이 있습니다.

  • 계측: .NET 라이브러리의 코드는 측정을 수행하여 이러한 측정값을 메트릭 이름과 연결합니다. .NET 및 ASP.NET Core에는 많은 기본 제공 메트릭이 포함됩니다.
  • 컬렉션: .NET 앱은 외부 스토리지 및 분석을 위해 앱에서 전송될 명명된 메트릭을 구성합니다. 일부 도구는 구성 파일 또는 UI 도구를 사용하여 앱 외부에서 구성을 수행할 수 있습니다.

이 섹션에서는 System.Net 메트릭을 수집하고 보는 다양한 방법을 보여 줍니다.

예제 앱

이 자습서를 위해 다양한 엔드포인트에 HTTP 요청을 병렬로 보내는 간단한 앱을 만듭니다.

dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics

Program.cs 내용을 다음 샘플 코드로 바꿉니다.

using System.Net;

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

Console.WriteLine("Press any key to start.");
Console.ReadKey();

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

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

dotnet-counters는 임시 상태 모니터링 및 1단계 수준 성능 조사를 위한 플랫폼 간 성능 모니터링 도구입니다.

dotnet tool install --global dotnet-counters

.NET 8+ 프로세스에 대해 실행하는 경우 dotnet-counters(은)는 --counters 인수로 정의된 계측을 사용하도록 하고 측정값을 표시합니다. 최신 숫자로 콘솔을 지속적으로 새로 고칩니다.

dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics

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

개요

OpenTelemetry:

  • Cloud Native Computing Foundation에서 지원하는 공급업체 중립적인 오픈 소스 프로젝트입니다.
  • 클라우드 기반 소프트웨어에 대한 원격 분석 생성 및 수집을 표준화합니다.
  • .NET 메트릭 API를 사용하여 .NET에서 작동합니다.
  • 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 내보내기 도구를 사용하도록 예제 앱 구성

OpenTelemetry Prometheus exporter에 대한 참조를 예제 앱에 추가합니다.

dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease

참고 항목

이 자습서에서는 빌드 당시 사용 가능한 OpenTelemetry Prometheus 지원의 시험판 빌드를 사용합니다.

OpenTelemetry 구성으로 Program.cs를 업데이트합니다.

using OpenTelemetry.Metrics;
using OpenTelemetry;
using System.Net;

using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
    .AddMeter("System.Net.Http", "System.Net.NameResolution")
    .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
    .Build();

string[] uris = ["http://example.com", "http://httpbin.org/get", "https://example.com", "https://httpbin.org/get"];
using HttpClient client = new()
{
    DefaultRequestVersion = HttpVersion.Version20
};

while (!Console.KeyAvailable)
{
    await Parallel.ForAsync(0, Random.Shared.Next(20), async (_, ct) =>
    {
        string uri = uris[Random.Shared.Next(uris.Length)];
        byte[] bytes = await client.GetByteArrayAsync(uri, ct);
        await Console.Out.WriteLineAsync($"{uri} - received {bytes.Length} bytes.");
    });
}

위의 코드에서

  • AddMeter("System.Net.Http", "System.Net.NameResolution")(은)는 기본 제공된 System.Net.HttpSystem.Net.NameResolution 미터에서 수집한 모든 메트릭을 전송하도록 OpenTelemetry를 구성합니다.
  • AddPrometheusHttpListener(은)는 포트 9184에서 Prometheus의 메트릭 HTTP 엔드포인트를 노출하도록 OpenTelemetry를 구성합니다.

참고 항목

이 구성은 HttpListener 대신 OpenTelemetry.Exporter.Prometheus.AspNetCore(을)를 사용하여 메트릭을 내보내는 ASP.NET Core 앱에 대해 다릅니다. 관련 ASP.NET Core 예제를 참조하세요.

측정값을 수집할 수 있도록 앱을 실행하고 실행 상태로 둡니다.

dotnet run

Prometheus 설정 및 구성

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

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

# my global config
global:
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093

# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
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 시작

  1. 구성을 다시 로드하거나 Prometheus 서버를 다시 시작합니다.

  2. Prometheus 웹 포털의 상태>대상 페이지에서 OpenTelemetryTest가 UP 상태인지 확인합니다. Prometheus status

  3. Prometheus 웹 포털의 Graph 페이지에서 식 텍스트 상자에 http(을)를 입력하고 http_client_active_requests(을)를 선택합니다. http_client_active_requests 그래프 탭에서 Prometheus는 예제 앱에서 내보낸 http.client.active_requests 카운터의 값을 표시합니다. Prometheus active requests graph

Grafana 대시보드에 메트릭 표시

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

  2. 위쪽 도구 모음에서 + 아이콘을 선택한 다음 대시보드를 선택하여 Grafana 대시보드를 만듭니다. 표시되는 대시보드 편집기에서 제목 상자에 열기 HTTP/1.1 연결을 입력하고 PromQL 식 필드에 다음 쿼리를 입력합니다.

sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})

Grafana HTTP/1.1 Connections

  1. 적용을 선택하여 새 대시보드를 저장하고 봅니다. 풀에서 활성 및 유휴 HTTP/1.1 연결 수를 표시합니다.

보강

보강은 메트릭에 사용자 지정 태그(즉, 특성 또는 레이블)를 추가하는 것입니다. 이는 앱이 메트릭을 사용하여 빌드된 대시보드 또는 경고에 사용자 지정 분류를 추가하려는 경우에 유용합니다. http.client.request.duration 계측기는 콜백을 HttpMetricsEnrichmentContext(으)로 등록하여 보강을 지원합니다. 이는 하위 수준 API이며 각 HttpRequestMessage에 대해 별도의 콜백 등록이 필요합니다.

단일 위치에서 콜백 등록을 수행하는 간단한 방법은 사용자 지정 DelegatingHandler(을)를 구현하는 것입니다. 이렇게 하면 내부 처리기로 전달되고 서버로 전송되기 전에 요청을 가로채고 수정할 수 있습니다.

using System.Net.Http.Metrics;

using HttpClient client = new(new EnrichmentHandler() { InnerHandler = new HttpClientHandler() });

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

sealed class EnrichmentHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        HttpMetricsEnrichmentContext.AddCallback(request, static context =>
        {
            if (context.Response is not null) // Response is null when an exception occurs.
            {
                // Use any information available on the request or the response to emit custom tags.
                string? value = context.Response.Headers.GetValues("Enrichment-Value").FirstOrDefault();
                if (value != null)
                {
                    context.AddCustomTag("enrichment_value", value);
                }
            }
        });
        return base.SendAsync(request, cancellationToken);
    }
}

IHttpClientFactory(으)로 사용하는 경우 AddHttpMessageHandler(을)를 사용하여 EnrichmentHandler에 등록할 수 있습니다.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Net.Http.Metrics;

ServiceCollection services = new();
services.AddHttpClient(Options.DefaultName).AddHttpMessageHandler(() => new EnrichmentHandler());

ServiceProvider serviceProvider = services.BuildServiceProvider();
HttpClient client = serviceProvider.GetRequiredService<HttpClient>();

await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=A");
await client.GetStringAsync("https://httpbin.org/response-headers?Enrichment-Value=B");

참고 항목

성능상의 이유로 보강 콜백은 http.client.request.duration 계측이 활성화된 경우에만 호출됩니다. 즉, 메트릭을 수집해야 합니다. dotnet-monitor, Prometheus 내보내기, MeterListener 또는 MetricCollector<T>일 수 있습니다.

IMeterFactoryIHttpClientFactory 통합

HTTP 메트릭은 격리 및 테스트 가능성을 염두에 두고 설계되었습니다. 이러한 측면은 IMeterFactory(으)로 사용하여 지원되며, 이를 통해 미터를 서로 격리하기 위해 사용자 지정 Meter 인스턴스에서 메트릭을 게시할 수 있습니다. 기본적으로 모든 메트릭은 System.Net.Http 라이브러리 내부 전역 Meter(에) 의해 내보내집니다. 사용자 지정 IMeterFactory 인스턴스를 SocketsHttpHandler.MeterFactory 또는 HttpClientHandler.MeterFactory에 할당하여 이 동작을 재정의할 수 있습니다.

참고 항목

Meter.Name(은)는 HttpClientHandlerSocketsHttpHandler에 내보내는 모든 메트릭에 대해 System.Net.Http입니다.

.NET 8 이상에서 Microsoft.Extensions.HttpIHttpClientFactory(을)를 사용하는 경우 기본 IHttpClientFactory 구현은 IServiceCollection에 등록된 IMeterFactory 인스턴스를 자동으로 선택하고 내부적으로 만드는 기본 처리기에 할당합니다.

참고 항목

.NET 8부터 AddHttpClient 메서드는 자동으로 AddMetrics(을)를 호출하여 메트릭 서비스를 초기화하고 기본 IMeterFactory 구현을 IServiceCollection에 등록합니다. 기본 IMeterFactory 이름으로 Meter 인스턴스를 캐시합니다. 즉, 이름이 IServiceCollectionSystem.Net.HttpMeter하나가 있습니다.

테스트 메트릭

다음 예제에서는 Microsoft.Extensions.Diagnostics.Testing NuGet 패키지에서 xUnit, IHttpClientFactoryMetricCollector<T>(을)를 사용하여 단위 테스트에서 기본 제공 메트릭의 유효성을 검사하는 방법을 보여 줍니다.

[Fact]
public async Task RequestDurationTest()
{
    // Arrange
    ServiceCollection services = new();
    services.AddHttpClient();
    ServiceProvider serviceProvider = services.BuildServiceProvider();
    var meterFactory = serviceProvider.GetService<IMeterFactory>();
    var collector = new MetricCollector<double>(meterFactory,
        "System.Net.Http", "http.client.request.duration");
    var client = serviceProvider.GetRequiredService<HttpClient>();

    // Act
    await client.GetStringAsync("http://example.com");

    // Assert
    await collector.WaitForMeasurementsAsync(minCount: 1).WaitAsync(TimeSpan.FromSeconds(5));
    Assert.Collection(collector.GetMeasurementSnapshot(),
        measurement =>
        {
            Assert.Equal("http", measurement.Tags["url.scheme"]);
            Assert.Equal("GET", measurement.Tags["http.request.method"]);
        });
}

메트릭 대 EventCounters

메트릭은 EventCounters보다 기능이 풍부합니다. 특히 다차원 특성 때문입니다. 이 다차원을 사용하면 Prometheus와 같은 도구에서 정교한 쿼리를 만들고 EventCounters에서 불가능한 수준에서 인사이트를 얻을 수 있습니다.

그럼에도 불구하고 .NET 8부터는 메트릭을 사용하여 System.Net.HttpSystem.Net.NameResolutions 구성 요소만 계측됩니다. 즉, System.Net.Sockets 또는 System.Net.Security 같은 스택의 하위 수준에서 카운터가 필요한 경우 EventCounters를 사용해야 합니다.

또한 메트릭과 일치하는 EventCounters 간에는 몇 가지 의미상 차이가 있습니다. 예를 들어 HttpCompletionOption.ResponseContentRead(을)를 사용하는 경우 current-requests EventCounter 요청 본문의 마지막 바이트를 읽은 순간까지 요청을 활성으로 간주합니다. 해당 메트릭 http.client.active_requests(은)는 활성 요청을 계산할 때 응답 본문을 읽는 데 소요된 시간을 포함하지 않습니다.

더 많은 메트릭이 필요하세요?

메트릭을 통해 노출될 수 있는 다른 유용한 정보에 대한 제안이 있는 경우 dotnet/runtime 문제를 만듭니다.