CodeGym /Kursy /C# SELF /Monitorowanie wydajności i zbieranie metryk

Monitorowanie wydajności i zbieranie metryk

C# SELF
Poziom 64 , Lekcja 3
Dostępny

1. Wprowadzenie

Wyobraź sobie, że jesteś administratorem serwisu i codziennie przychodzi do ciebie tysiąc użytkowników. W logach widzisz, że wszystko działa, prawie nie ma błędów (chyba że ktoś czasem zapomni hasło albo potknie się o captcha). „Wszystko świetnie!” — myślisz.

I nagle ktoś pisze do supportu: strona strasznie zamula, strony ładują się po 10 sekund. Zaglądasz do logów — błędów brak! Hurra? Nie do końca, bo logi opowiadają co się stało (lub nie), ale nie mówią, jak szybko lub wolno to się działo, ile zasobów to potrzebowało i jak to zachowanie zmieniało się wraz ze wzrostem obciążenia.

Tu wchodzą metryki — mierzalne charakterystyki działania aplikacji. To nie tylko liczba błędów, ale średni czas odpowiedzi, ilość zużytej pamięci, liczba requestów na sekundę i inne wskaźniki, po których można ocenić zdrowie systemu.

Porównanie:
Logi — to "co się stało".
Metryki — to "jak dobrze/źle działa system".
Trace — to "dlaczego system tak działa (w szczegółach)".

Jakie są metryki i co warto zbierać?

Główne rodzaje metryk:

Typ metryki Przykład Do czego służy
Licznik (Counters) Liczba requestów, błędów, awarii Trendy, alerty, obciążenie
Histogramy Czas odpowiedzi, rozmiar pakietu Rozkład wartości, percentyle
Gauges (Gauge) Użycie pamięci, CPU Bieżący stan zasobu
Sums (Sum) Całkowita objętość danych, bajtów Całkowita objętość operacji w okresie

Przykłady:

  • Średni i 95-ty percentyl czasu odpowiedzi na GET.
  • Liczba użytkowników online w tej chwili.
  • Użycie pamięci (Private Bytes, Working Set).
  • Częstość występowania błędów typu 500/503.
  • Zapytania do DB na minutę.

Te wskaźniki pozwalają nie tylko znajdować problemy, ale i zapobiegać im — bo zwiększone obciążenie serwera lub „pełzający” wzrost czasu odpowiedzi mogą sygnalizować przyszłe awarie.

2. Jak działa zbieranie metryk w .NET i ekosystemie OpenTelemetry

Ogólna architektura

We współczesnym .NET (od .NET 6, szczególnie w .NET 8/9) istnieje standardowy system zbierania metryk oparty na OpenTelemetry.

Oto jak to działa:

  1. Kod aplikacji wywołuje metody do zwiększania liczników, rejestracji gauge'ów, zapisywania histogramów.
  2. SDK OpenTelemetry Metrics zbiera te metryki (w pamięci) i okresowo je wysyła.
  3. Eksporter metryk przekazuje je do wybranego systemu monitoringu (Prometheus, Application Insights, Grafana Cloud, Datadog itp.).
  4. Backend monitoringu agreguje, przechowuje, wizualizuje, buduje alerty i dashboardy.
Schemat blokowy:

[Twoja aplikacja] 
       ⬇ 
 [Zbieranie metryk (OpenTelemetry SDK)]
       ⬇
 [Eksporter metryk (Prometheus, AI, Datadog, ...)]
       ⬇
 [System monitoringu/dashboardy/alerty]

3. Praktyka: Podstawy pracy z metrykami w C#

Proste metryki wewnętrzne: System.Diagnostics.Metrics

.NET dostarcza wbudowany mechanizm do metryk — System.Diagnostics.Metrics.

Główni gracze: Meter, Counter<T>, Histogram<T>, ObservableGauge<T>.

Przykład: licznik odwiedzin strony


// Tworzymy Meter (zwykle jeden dla całej aplikacji)
using System.Diagnostics.Metrics;

static Meter meter = new Meter("MyCompany.MyApp", "1.0");

// Rejestrujemy licznik
static Counter<long> homePageVisits = meter.CreateCounter<long>("HomePageVisits");

// Gdzieś w kodzie kontrolera lub serwisu...
public void HomePageRequested()
{
    homePageVisits.Add(1);
    // Dalej reszta kodu obsługi strony
}

Komentarze:

  • Meter — to „fabryka” metryk, z unikalną nazwą (namespace aplikacji/firmy).
  • CreateCounter<long> — tworzy licznik; inkrementacja przez Add(1).

Przykład: mierzenie czasu odpowiedzi


static Histogram<double> pageLoadTime = meter.CreateHistogram<double>("PageLoadTimeMs");

// W handlerze żądania:
public void OnRequest()
{
    var stopwatch = System.Diagnostics.Stopwatch.StartNew();

    // ...tu samo żądanie...

    stopwatch.Stop();
    pageLoadTime.Record(stopwatch.Elapsed.TotalMilliseconds);
}

Zbieramy gauge'y dla wartości dynamicznych

Gauge — wskaźnik, który zmienia się w czasie: liczba podłączonych użytkowników, bieżący rozmiar pamięci itp.


static ObservableGauge<int> onlineUserGauge = meter.CreateObservableGauge(
    "OnlineUsers", 
    () => GetOnlineUserCount());

// Gdzie GetOnlineUserCount - metoda zwracająca aktualną wartość
static int GetOnlineUserCount()
{
    // Tu powinna być twoja realna logika!
    return ActiveUserList.Count;
}

W realnym świecie wszystko działa asynchronicznie: aplikacja raportuje wartości metryk, a eksporter je pobiera i wystawia na zewnątrz (np. Prometheus scrapuje endpoint "/metrics").

Dodawanie metryk do nowoczesnej aplikacji ASP.NET Core

Dla ASP.NET Core wiele jest dostępne od ręki. Wystarczy dodać pakiet OpenTelemetry.Instrumentation.AspNetCore, i pojawią się metryki HTTP, czasu odpowiedzi, liczby błędów itp.

Przykład konfiguracji w Program.cs:


using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics =>
    {
        metrics
            .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("MyApp"))
            .AddAspNetCoreInstrumentation() // metryki HTTP
            .AddRuntimeInstrumentation()    // metryki środowiska uruchomieniowego .NET CLR
            .AddProcessInstrumentation()    // CPU/pamięć procesu
            .AddMeter("MyCompany.MyApp")    // twoje metryki
            .AddPrometheusExporter();       // eksport do Prometheus
    });

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

Teraz twoja aplikacja będzie wystawiać metryki pod adresem /metrics, które można scrape'ować przez Prometheus lub inne systemy.

4. Praktyczne przykłady użycia metryk

Monitorowanie wydajności w realnych projektach

Podłączamy metryki, żeby dowiedzieć się:

  • Jaki średni i szczytowy RPS (requests per second) wytrzymuje API?
  • Gdzie są „wąskie gardła”: jeden endpoint działa 300 ms, inny — 2000 ms?
  • Ile czasu zajmują wywołania DB? (dodajemy własne histogramy)

Przykład: kontrolujemy czas odpowiedzi do bazy danych


static Histogram<double> dbQueryDuration = meter.CreateHistogram<double>("DbQueryDurationMs");

public async Task<List<Product>> GetProductsAsync()
{
    var sw = Stopwatch.StartNew();
    var result = await _db.Products.ToListAsync();
    sw.Stop();
    dbQueryDuration.Record(sw.Elapsed.TotalMilliseconds);
    return result;
}

Przykład: zliczamy liczbę błędów


static Counter<long> apiErrors = meter.CreateCounter<long>("ApiErrors");

public IActionResult SomeEndpoint()
{
    try
    {
        // jakieś działanie
        return Ok();
    }
    catch (Exception)
    {
        apiErrors.Add(1);
        throw;
    }
}

Praca z labelami (tagami) dla metryk

Ważne jest grupowanie danych według użytecznych atrybutów: endpoint, typ błędu, typ użytkownika itp.


homePageVisits.Add(
    1, 
    KeyValuePair.Create<string, object>("UserType", "Admin"));

A dla histogramu:


dbQueryDuration.Record(
    sw.Elapsed.TotalMilliseconds, 
    KeyValuePair.Create<string, object>("QueryType", "GetProducts"));

Dzięki tagom w Grafanie można budować wykresy nie tylko dla całej aplikacji, ale i dla konkretnych segmentów.

5. Integracja z Prometheus, Application Insights, Datadog, Grafana

Eksportery i integracja

  • Prometheus — popularny open-source monitoring, de-facto standard dla chmur i Kubernetes.
  • Application Insights — integracja chmurowa dla Azure.
  • Datadog, Grafana Cloud — dla profesjonalnej infrastruktury.

Wszystkie te systemy mogą zbierać metryki z .NET przez eksportery OpenTelemetry. Dokumentacja o exporterach OTel

Prometheus (kroki):

  1. Dodać NuGet package: OpenTelemetry.Exporter.Prometheus.
  2. Dodać .AddPrometheusExporter() przy rejestracji metryk.
  3. W Grafanie skonfigurować datasource na Prometheus i zbudować dashboardy.

Przydatne linki:

6. Cechy, pułapki i typowe błędy

Jednym z częstych błędów jest nadmierne szczegółowe tagowanie. Jeśli przypisywać tagom zbyt wiele unikalnych wartości (np. ID użytkownika/zamówienia), liczba time series eksploduje — to prowadzi do przeciążenia storage'u metryk i wzrostu kosztów (tzw. cardinality explosion). Trzymaj tagi „gruboziarniste”.

Programiści czasem ignorują System.Diagnostics.Metrics i gotowe narzędzia, robiąc „rower” przez logi i timery. W rezultacie monitoring gorzej się integruje i trudniej go utrzymać. Używaj standardowych narzędzi i automatycznej instrumentacji.

Kolejny błąd — metryki są zbierane, ale nie eksportowane. Konfiguracja eksportera jest obowiązkowa: dodaj np. .AddPrometheusExporter() i upewnij się, że endpoint /metrics jest dostępny do scrapowania.

I wreszcie, zamieszanie między typami metryk: średni czas odpowiedzi liczony licznikami (Counter), choć trzeba użyć Histogram — wtedy nie zobaczysz pików i rozkładu. Liczniki — do ilości; histogramy — do czasu/rozmiarów/rozkładów; gauge'y — do stanów bieżących.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION