Nätverksmått i .NET
Mått är numeriska mått som rapporteras över tid. De används vanligtvis för att övervaka hälsotillståndet för en app och generera aviseringar.
Från och med .NET 8 System.Net.Http
instrumenteras komponenterna System.Net.NameResolution
och för att publicera mått med . NET:s nya API för System.Diagnostics.Metrics.
Dessa mått utformades i samarbete med OpenTelemetry för att säkerställa att de överensstämmer med standarden och fungerar bra med populära verktyg som Prometheus och Grafana.
De är också flerdimensionella, vilket innebär att mått associeras med nyckel/värde-par som kallas taggar (a.k.a. attribut eller etiketter) som gör att data kan kategoriseras för analys.
Dricks
En omfattande lista över alla inbyggda instrument tillsammans med deras attribut finns i System.Net mått.
Samla in System.Net mått
Det finns två delar i att använda mått i en .NET-app:
- Instrumentation: Kod i .NET-bibliotek tar mått och associerar dessa mått med ett måttnamn. .NET och ASP.NET Core innehåller många inbyggda mått.
- Samling: En .NET-app konfigurerar namngivna mått som ska överföras från appen för extern lagring och analys. Vissa verktyg kan utföra konfiguration utanför appen med hjälp av konfigurationsfiler eller ett gränssnittsverktyg.
Det här avsnittet visar olika metoder för att samla in och visa System.Net mått.
Exempelapp
För den här självstudien skapar du en enkel app som skickar HTTP-begäranden till olika slutpunkter parallellt.
dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics
Ersätt innehållet i Program.cs
med följande exempelkod:
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.");
});
}
Visa mått med dotnet-counters
dotnet-counters
är ett plattformsoberoende prestandaövervakningsverktyg för ad hoc-hälsoövervakning och prestandaundersökning på första nivån.
dotnet tool install --global dotnet-counters
När du kör mot en .NET 8+-process dotnet-counters
aktiverar du de instrument som definierats av --counters
argumentet och visar måtten. Konsolen uppdateras kontinuerligt med de senaste siffrorna:
dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics
Visa mått i Grafana med OpenTelemetry och Prometheus
Översikt
- Är ett leverantörsneutralt projekt med öppen källkod som stöds av Cloud Native Computing Foundation.
- Standardiserar generering och insamling av telemetri för molnbaserad programvara.
- Fungerar med .NET med hjälp av .NET-mått-API:er.
- Stöds av Azure Monitor och många APM-leverantörer.
Den här självstudien visar en av de integreringar som är tillgängliga för OpenTelemetry-mått med hjälp av OSS Prometheus - och Grafana-projekten . Dataflödet mått består av följande steg:
.NET-mått-API:erna registrerar mått från exempelappen.
OpenTelemetry-biblioteket som körs i appen aggregerar måtten.
Prometheus-exporteringsbiblioteket gör aggregerade data tillgängliga via en HTTP-måttslutpunkt. "Exporter" är vad OpenTelemetry kallar biblioteken som överför telemetri till leverantörsspecifika serverdelar.
En Prometheus-server:
- Avsöker måttslutpunkten.
- Läser data.
- Lagrar data i en databas för långsiktig beständighet. Prometheus refererar till att läsa och lagra data som att skrapa en slutpunkt.
- Kan köras på en annan dator.
Grafana-servern:
- Kör frågor mot data som lagras i Prometheus och visar dem på en webbaserad övervakningsinstrumentpanel.
- Kan köras på en annan dator.
Konfigurera exempelappen så att den använder Prometheus-exportören i OpenTelemetry
Lägg till en referens till OpenTelemetry Prometheus-exportören i exempelappen:
dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease
Kommentar
I den här självstudien används en förhandsversion av OpenTelemetrys Prometheus-stöd som är tillgängligt i skrivande stund.
Uppdatera Program.cs
med OpenTelemetry-konfiguration:
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.");
});
}
I koden ovan:
AddMeter("System.Net.Http", "System.Net.NameResolution")
konfigurerar OpenTelemetry för att överföra alla mått som samlas in av de inbyggdaSystem.Net.Http
mätarna ochSystem.Net.NameResolution
mätarna.AddPrometheusHttpListener
konfigurerar OpenTelemetry för att exponera Prometheus HTTP-slutpunkt för mått på port9184
.
Kommentar
Den här konfigurationen skiljer sig åt för ASP.NET Core-appar, där mått exporteras med OpenTelemetry.Exporter.Prometheus.AspNetCore
i stället HttpListener
för . Se det relaterade ASP.NET Core-exemplet.
Kör appen och låt den köras så att mätningar kan samlas in:
dotnet run
Konfigurera Prometheus
Följ de första stegen i Prometheus för att konfigurera en Prometheus-server och bekräfta att den fungerar.
Ändra prometheus.yml konfigurationsfilen så att Prometheus skrapar måttslutpunkten som exempelappen exponerar. Lägg till följande markerade text i avsnittet 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']
Starta prometheus
Ladda om konfigurationen eller starta om Prometheus-servern.
Bekräfta att OpenTelemetryTest är i UP-tillståndet på sidan Statusmål> i Prometheus-webbportalen.
På sidan Graph i Prometheus-webbportalen anger du
http
i textrutan uttryck och väljerhttp_client_active_requests
. På diagramfliken visar Prometheus värdet för räknarenhttp.client.active_requests
som genereras av exempelappen.
Visa mått på en Grafana-instrumentpanel
Följ standardanvisningarna för att installera Grafana och ansluta det till en Prometheus-datakälla.
Skapa en Grafana-instrumentpanel genom att + välja ikonen i det övre verktygsfältet och sedan välja Instrumentpanel. I instrumentpanelsredigeraren som visas anger du Öppna HTTP/1.1 Anslut ions i rutan Rubrik och följande fråga i fältet PromQL-uttryck:
sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})
- Välj Använd för att spara och visa den nya instrumentpanelen. Det visar antalet aktiva och inaktiva HTTP/1.1-anslutningar i poolen.
Berikning
Berikning är att lägga till anpassade taggar (a.k.a. attribut eller etiketter) i ett mått. Detta är användbart om en app vill lägga till en anpassad kategorisering till instrumentpaneler eller aviseringar som skapats med mått.
Instrumentet http.client.request.duration
stöder berikning genom att registrera återanrop med HttpMetricsEnrichmentContext.
Observera att detta är ett lågnivå-API och att det krävs en separat återanropsregistrering för varje HttpRequestMessage
.
Ett enkelt sätt att göra återanropsregistreringen på en enda plats är att implementera en anpassad DelegatingHandler. På så sätt kan du fånga upp och ändra begäranden innan de vidarebefordras till den inre hanteraren och skickas till servern:
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);
}
}
Om du arbetar med IHttpClientFactory
kan du använda AddHttpMessageHandler för att registrera 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");
Kommentar
Av prestandaskäl anropas återanropet för berikning endast när http.client.request.duration
instrumentet är aktiverat, vilket innebär att något bör samla in måtten.
Detta kan vara dotnet-monitor
, Prometheus-exportör, en MeterListener
, eller en MetricCollector<T>
.
IMeterFactory
och IHttpClientFactory
integrering
HTTP-mått utformades med isolering och testbarhet i åtanke. Dessa aspekter stöds av användningen av IMeterFactory, som gör det möjligt att publicera mått av en anpassad Meter instans för att hålla Meter isolerade från varandra.
Som standard genereras alla mått av en global Meter intern i System.Net.Http
biblioteket. Det här beteendet kan åsidosättas genom att tilldela en anpassad IMeterFactory instans till SocketsHttpHandler.MeterFactory eller HttpClientHandler.MeterFactory.
Kommentar
Meter.Name är System.Net.Http
för alla mått som genereras av HttpClientHandler
och SocketsHttpHandler
.
När du arbetar med Microsoft.Extensions.Http
och IHttpClientFactory
på .NET 8+ väljer standardimplementeringen IHttpClientFactory
automatiskt den IMeterFactory
instans som är registrerad i IServiceCollection och tilldelar den till den primära hanteraren som skapas internt.
Kommentar
Från och med .NET 8 AddHttpClient anropar AddMetrics metoden automatiskt för att initiera måtttjänsterna och registrera standardimplementeringen IMeterFactory med IServiceCollection. Standardcachen IMeterFactory cachelagrar instanser Meter efter namn, vilket innebär att det kommer att finnas en Meter med namnet System.Net.Http
per IServiceCollection.
Testmått
I följande exempel visas hur du validerar inbyggda mått i enhetstester med xUnit, IHttpClientFactory
och MetricCollector<T>
från Microsoft.Extensions.Diagnostics.Testing
NuGet-paketet:
[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"]);
});
}
Mått jämfört med EventCounters
Mått är mer funktionsrika än EventCounters, framför allt på grund av deras flerdimensionella karaktär. Med den här flerdimensionelliteten kan du skapa avancerade frågor i verktyg som Prometheus och få insikter på en nivå som inte är möjlig med EventCounters.
Från och med .NET 8 instrumenteras dock endast System.Net.Http
komponenterna System.Net.NameResolutions
och med hjälp av Mått, vilket innebär att om du behöver räknare från de lägre nivåerna i stacken, System.Net.Sockets
till exempel eller System.Net.Security
, måste du använda EventCounters.
Dessutom finns det vissa semantiska skillnader mellan Mått och deras matchande EventCounters.
När du till exempel använder HttpCompletionOption.ResponseContentRead
current-requests
anser EventCounter att en begäran är aktiv fram till den tidpunkt då den sista byteen av begärandetexten har lästs.
Dess måttmotsvarighet http.client.active_requests
inkluderar inte den tid som ägnas åt att läsa svarstexten när de aktiva begärandena räknas.
Behöver du fler mått?
Om du har förslag på annan användbar information som kan exponeras via mått skapar du ett problem med dotnet/runtime.