Netzwerkmetriken in .NET
Metriken sind numerische Messungen, die im Laufe der Zeit gemeldet werden. Sie werden in der Regel verwendet, um die Integrität einer App zu überwachen und Warnungen zu generieren.
Ab .NET 8 werden die Komponenten „System.Net.Http
“ und „System.Net.NameResolution
“ zum Veröffentlichen von Metriken mithilfe der neuen System.Diagnostics.Metrics-API von NET.
Diese Metriken wurden in Zusammenarbeit mit OpenTelemetry entwickelt, um sicherzustellen, dass sie dem Standard entsprechen und gut mit beliebten Tools wie Prometheus und Grafana funktionieren.
Sie sind auch mehrdimensional, d. h. Messungen werden Schlüsselwertpaaren zugeordnet, die Tags (a.k.a. Attribute oder Bezeichnungen) genannt werden, mit denen Daten für die Analyse kategorisiert werden können.
Tipp
Eine umfassende Liste aller integrierten Instrumente zusammen mit ihren Attributen finden Sie unter System.Net-Metriken.
Sammeln von System.Net-Metriken
Die Verwendung von Metriken in einer .NET-App besteht aus zwei Teilen:
- Instrumentation:Der Code in den .NET-Bibliotheken nimmt Messungen vor und ordnet diese Messungen einem Metriknamen zu. .NET und ASP.NET Core enthalten viele integrierte Metriken.
- Auflistung: Ein .NET-App-Entwickler konfiguriert benannte Metriken, die zur externen Speicherung und Analyse von der App übertragen werden sollen. Einige Tools führen die Konfiguration möglicherweise außerhalb der App mithilfe von Konfigurationsdateien oder einem Ui-Tool durch.
In diesem Abschnitt werden verschiedene Methoden zum Sammeln und Anzeigen von System.Net-Metriken veranschaulicht.
Beispiel-App
Erstellen Sie im Sinne dieses Lernprogramms eine einfache App, die HTTP-Anforderungen parallel an verschiedene Endpunkte sendet.
dotnet new console -o HelloBuiltinMetrics
cd ..\HelloBuiltinMetrics
Ersetzen Sie den Inhalt von Program.cs
durch den folgenden Code:
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.");
});
}
Anzeigen von Metriken mit Dotnet-Counters
dotnet-counters
ist ein plattformübergreifendes Leistungsüberwachungstool zur Ad-hoc-Überwachung der Integrität und zur Leistungsuntersuchung auf erster Ebene.
dotnet tool install --global dotnet-counters
Bei der Ausführung mit einem .NET 8+-Prozess ermöglicht dotnet-counters
die durch das --counters
-Argument definierten Instrumente und zeigt die Maße an. Er aktualisiert die Konsole kontinuierlich mit den neuesten Zahlen:
dotnet-counters monitor --counters System.Net.Http,System.Net.NameResolution -n HelloBuiltinMetrics
Anzeigen von Metriken in Grafana mit OpenTelemetry und Prometheus
Übersicht
- Ein anbieterneutrales Open-Source-Projekt, das von der Cloud Native Computing Foundation unterstützt wird.
- Standardisiert das Generieren und Sammeln von Telemetriedaten für cloudnative Software.
- Funktioniert mit .NET mithilfe der .NET-Metrik-APIs.
- Wird von Azure Monitor und vielen APM-Anbietern unterstützt.
Dieses Tutorial zeigt eine der Integrationen, die für OpenTelemetry-Metriken mithilfe der OSS-Projekte Prometheus und Grafana verfügbar sind. Der Metrikdatenfluss besteht aus den folgenden Schritten:
Die .NET-Metrik-APIs zeichnen Messungen aus der Beispiel-App auf.
Die OpenTelemetry-Bibliothek, die in der App ausgeführt wird, aggregiert die Messungen.
Die Prometheus-Exporterbibliothek stellt die aggregierten Daten über einen HTTP-Metrikendpunkt zur Verfügung. "Exporter" bezeichnet OpenTelemetry die Bibliotheken, die Telemetriedaten an anbieterspezifische Back-Ends übertragen.
Ein Prometheus-Server:
- Fragt den Metrikendpunkt ab.
- Liest die Daten.
- Speichert die Daten für lange Haltbarkeit in einer Datenbank. Prometheus bezeichnet das Lesen und Speichern von Daten als Scraping eines Endpunkts.
- Kann auf einem anderen Computer ausgeführt werden.
Der Grafana-Server:
- Fragt die in Prometheus gespeicherten Daten ab und zeigt sie auf einem webbasierten Überwachungsdashboard an.
- Kann auf einem anderen Computer ausgeführt werden.
Konfigurieren der Beispielanwendung für die Verwendung des Prometheus-Exporters von OpenTelemetry
Fügen Sie der Beispielanwendung einen Verweis auf den OpenTelemetry Prometheus-Exporter hinzu:
dotnet add package OpenTelemetry.Exporter.Prometheus.HttpListener --prerelease
Hinweis
In diesem Tutorial wird ein Vorversionsbuild der Prometheus-Unterstützung von OpenTelemetry verwendet, der zum Zeitpunkt der Niederschrift verfügbar war.
Aktualisieren sie Program.cs
mit der 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.");
});
}
Für den Code oben gilt:
AddMeter("System.Net.Http", "System.Net.NameResolution")
konfiguriert OpenTelemetry so, dass alle von den integriertenSystem.Net.Http
- undSystem.Net.NameResolution
-Verbrauchseinheiten gesammelten Metriken übertragen werden.AddPrometheusHttpListener
konfiguriert OpenTelemetry so, dass der Metrik-HTTP-Endpunkt von Prometheus am Port „9184
“ verfügbar ist.
Hinweis
Diese Konfiguration unterscheidet sich für ASP.NET Core-Apps, bei denen Metriken mit OpenTelemetry.Exporter.Prometheus.AspNetCore
anstelle von HttpListener
exportiert werden. Sehen Sie sich das zugehörige ASP.NET Core-Beispiel an.
Führen Sie die App aus, und lassen Sie sie laufen, damit Messungen erfasst werden können:
dotnet run
Einrichten und Konfigurieren von Prometheus
Führen Sie die ersten Schritte von Prometheus aus, um einen Prometheus-Server einzurichten und zu bestätigen, dass er funktioniert.
Ändern Sie die Konfigurationsdatei prometheus.yml so ab, dass Prometheus auf den Metrik-Endpunkt, den die Beispiel-App verfügbar macht, zugreifen kann. Fügen Sie den folgenden Text im scrape_configs
-Abschnitt hinzu:
# 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']
Starten von Prometheus
Laden Sie die Konfiguration neu, oder starten Sie den Prometheus-Server neu.
Vergewissern Sie sich auf der Seite Status>Ziele des Prometheus-Webportals, dass OpenTelemetryTest den Status UP aufweist.
Geben Sie auf der Graph-Seite des Prometheus-Webportals
http
in das Textfeld „Ausdruck“ ein, und wählen Siehttp_client_active_requests
aus. Auf der Registerkarte „Diagramm“ zeigt Prometheus den Wert deshttp.client.active_requests
-Zählers an, der von der Beispiel-App ausgegeben wird.
Anzeigen von Metriken auf einem Grafana-Dashboard
Befolgen Sie die Standardanweisungen, um Grafana zu installieren und mit einer Prometheus-Datenquelle zu verbinden.
Erstellen Sie ein Grafana-Dashboard, indem Sie das +-Symbol auf der oberen Symbolleiste auswählen und dann Dashboard auswählen. Geben Sie im daraufhin angezeigten Dashboard-Editor Open HTTP/1.1 Connections in das Feld Titel und die folgende Abfrage im Feld „PromQL-Ausdruck“ ein:
sum by(http_connection_state) (http_client_open_connections{network_protocol_version="1.1"})
- Klicken Sie auf Anwenden, um das neue Dashboard zu speichern und anzuzeigen. Es zeigt die Anzahl aktiver und leerer HTTP/1.1-Verbindungen im Pool an.
Anreicherung
Eine Anreicherung ist das Hinzufügen benutzerdefinierter Tags (a.k.a. Attribute oder Beschriftungen) zu einer Metrik. Dies ist nützlich, wenn eine App eine benutzerdefinierte Kategorisierung zu Dashboards oder Warnungen hinzufügen möchte, die mit Metriken erstellt wurden.
Das http.client.request.duration
-Instrument unterstützt die Anreicherung durch Registrieren von Rückrufen bei der HttpMetricsEnrichmentContext.
Beachten Sie, dass dies eine API mit niedriger Ebene ist und für jedes HttpRequestMessage
eine separate Rückrufregistrierung erforderlich ist.
Eine einfache Möglichkeit zum Ausführen der Rückrufregistrierung an einem zentralen Ort ist die Implementierung eines benutzerdefinierten DelegatingHandler. Dadurch können Sie die Anforderungen abfangen und ändern, bevor sie an den inneren Handler weitergeleitet und an den Server gesendet werden:
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);
}
}
Wenn Sie mit IHttpClientFactory
arbeiten, können Sie AddHttpMessageHandler verwenden, um EnrichmentHandler
zu registrieren:
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");
Hinweis
Aus Leistungsgründen wird der Anreicherungsrückruf nur aufgerufen, wenn das http.client.request.duration
-Instrument aktiviert ist, was bedeutet, dass etwas die Metriken erfasst.
Dies kann dotnet-monitor
, Prometheus-Exporter, ein MeterListener
oder ein MetricCollector<T>
sein.
IMeterFactory
- und IHttpClientFactory
-Integration
HTTP-Metriken wurden mit Isolation und Testfähigkeit entworfen. Diese Aspekte werden durch die Verwendung von IMeterFactory unterstützt, wodurch Veröffentlichungsmetriken von einer benutzerdefinierten Meter-Instanz ermöglicht werden, um Meter voneinander zu isolieren.
Standardmäßig werden alle Metriken von einer globalen Meter, die der System.Net.Http
-Bibliothek intern ist, ausgegeben. Dieses Verhalten kann durch Zuweisen einer benutzerdefinierten IMeterFactory-Instanz zu SocketsHttpHandler.MeterFactory oder HttpClientHandler.MeterFactoryüberschrieben werden.
Hinweis
Meter.Name ist System.Net.Http
für alle Metriken, die von HttpClientHandler
und SocketsHttpHandler
ausgegeben werden.
Bei der Arbeit mit Microsoft.Extensions.Http
und IHttpClientFactory
unter .NET 8+ wählt die Standardimplementierung von IHttpClientFactory
automatisch die IMeterFactory
-Instanz, die in der IServiceCollection registrierte ist, aus und weist sie dem primären Handler zu, den sie intern erstellt.
Hinweis
Ab .NET 8 ruft die AddHttpClient-Methode automatisch AddMetrics auf, um die Metrikdienste zu initialisieren und die Standardimplementierung von IMeterFactory bei IServiceCollection zu registrieren. Der Standard-IMeterFactory speichert Meter-Instanzen anhand des Namens zwischen, d. h., es gibt eine Meter mit dem Namen System.Net.Http
pro IServiceCollection.
Testmetriken
Im folgenden Beispiel wird veranschaulicht, wie integrierte Metriken in Komponententests mithilfe von xUnit, IHttpClientFactory
und MetricCollector<T>
aus dem Microsoft.Extensions.Diagnostics.Testing
NuGet-Paket überprüft werden:
[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"]);
});
}
Metriken vs. EventCounters
Metriken sind funktionsreicher als EventCounters, vor allem aufgrund ihrer mehrdimensionalen Natur. Dank ihrer mehrdimensionalen Eigenschaft können Sie komplexe Abfragen in Tools wie Prometheus erstellen und Einblicke auf eine Ebene erhalten, die mit EventCounters nicht möglich ist.
Ab .NET 8 werden jedoch nur die System.Net.Http
- und System.Net.NameResolutions
-Komponenten mithilfe von Metriken instrumentiert, d. h., wenn Sie Zähler aus den unteren Ebenen des Stapels benötigen, z. B. System.Net.Sockets
oder System.Net.Security
, müssen Sie EventCounters verwenden.
Darüber hinaus gibt es einige semantische Unterschiede zwischen Metriken und deren übereinstimmenden EventCounters.
Wenn Sie beispielsweise HttpCompletionOption.ResponseContentRead
verwenden, betrachtet der current-requests
EventCounter eine Anforderung bis zum Zeitpunkt, zu dem das letzte Byte des Anforderungstexts gelesen wurde, als aktiv.
Das Metrikgegenstück http.client.active_requests
schließt die Zeit, die beim Lesen des Antworttexts aufgewendet wurde, beim Zählen der aktiven Anforderungen nicht ein.
Benötigen Sie weitere Metriken?
Wenn Sie Vorschläge für andere nützliche Informationen haben, die über Metriken verfügbar gemacht werden könnten, erstellen Sie einen Issue in dotnet/runtime.