Compartir a través de


Recopilación de métricas

Este artículo se aplica a: ✔️.NET Core 3.1 y versiones posteriores ✔️ .NET Framework 4.6.1 y versiones posteriores

El código instrumentado puede registrar medidas numéricas, pero las medidas normalmente deben agregarse, transmitirse y almacenarse para crear métricas útiles para la supervisión. Este proceso de agregación, transmisión y almacenamiento de los datos se denomina "colección". En este tutorial se muestran varios ejemplos de recopilación de métricas:

Para obtener más información sobre la instrumentación y las opciones de métricas personalizada, consulte Comparación de las API de métricas.

Prerrequisitos

Creación de una aplicación de ejemplo

Para poder recopilar métricas, es necesario generar algunas medidas. En este tutorial se crea una aplicación que tiene una instrumentación de métricas básica. El entorno de ejecución de .NET también tiene varias métricas integradas. Para obtener más información sobre cómo crear métricas mediante la API de System.Diagnostics.Metrics.Meter, consulte el tutorial de instrumentación.

dotnet new console -o metric-instr
cd metric-instr
dotnet add package System.Diagnostics.DiagnosticSource

Reemplace el contenido de Program.cs por el código siguiente:

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>("hats-sold");

    static void Main(string[] args)
    {
        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }
}

El código anterior simula la venta de sombreros a intervalos aleatorios y horas aleatorias.

Visualización de métricas con dotnet-counters

dotnet-counters es una herramienta de línea de comandos que puede mostrar las métricas en directo de cualquier aplicación de .NET Core a petición. No requiere ninguna configuración, lo que la hace útil para investigaciones ad hoc o para comprobar que la instrumentación de métricas funciona. Funciona tanto con API basadas en System.Diagnostics.Metrics como con EventCounters.

Si la herramienta dotnet-counters no está instalada, ejecute el siguiente comando:

dotnet tool update -g dotnet-counters

Mientras se ejecuta la aplicación de ejemplo, inicie dotnet-counters. El comando siguiente muestra un ejemplo de cómo dotnet-counters supervisa todas las métricas del medidor de HatCo.HatStore. El nombre del medidor distingue mayúsculas de minúsculas. Nuestra aplicación de ejemplo era metric-instr.exe, sustituya esto por el nombre de su aplicación de ejemplo.

dotnet-counters monitor -n metric-instr HatCo.HatStore

Esto genera una salida similar a la siguiente:

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

[HatCo.HatStore]
    hats-sold (Count / 1 sec)                          4

dotnet-counters se puede ejecutar con un conjunto diferente de métricas para ver parte de la instrumentación integrada desde el entorno de ejecución de .NET:

dotnet-counters monitor -n metric-instr

Esto genera una salida similar a la siguiente:

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

[System.Runtime]
    % Time in GC since last GC (%)                                 0
    Allocation Rate (B / 1 sec)                                8,168
    CPU Usage (%)                                                  0
    Exception Count (Count / 1 sec)                                0
    GC Heap Size (MB)                                              2
    Gen 0 GC Count (Count / 1 sec)                                 0
    Gen 0 Size (B)                                         2,216,256
    Gen 1 GC Count (Count / 1 sec)                                 0
    Gen 1 Size (B)                                           423,392
    Gen 2 GC Count (Count / 1 sec)                                 0
    Gen 2 Size (B)                                           203,248
    LOH Size (B)                                             933,216
    Monitor Lock Contention Count (Count / 1 sec)                  0
    Number of Active Timers                                        1
    Number of Assemblies Loaded                                   39
    ThreadPool Completed Work Item Count (Count / 1 sec)           0
    ThreadPool Queue Length                                        0
    ThreadPool Thread Count                                        3
    Working Set (MB)                                              30

Para obtener más información, vea dotnet-counters. Para obtener más información sobre las métricas en .NET, vea el tema sobre métricas integradas.

Visualización de métricas en Grafana con OpenTelemetry y Prometheus

Información general

OpenTelemetry:

  • Es un proyecto de código abierto independiente del proveedor administrado por Cloud Native Computing Foundation.
  • Pretende estandarizar la generación y la recopilación de telemetría para software nativo de nube.
  • Funciona con .NET mediante las API de métricas de .NET.
  • Está aprobado por Azure Monitor y muchos proveedores de APM.

Este tutorial muestra una de las integraciones que hay disponibles para las métricas de OpenTelemetry mediante los proyectos de OSS Prometheus y Grafana. El flujo de datos de métricas:

  1. Las API de métricas de .NET registran medidas de la aplicación de ejemplo.

  2. La biblioteca de OpenTelemetry que se ejecuta en la aplicación agrega las medidas.

  3. La biblioteca exportadora de Prometheus hace que los datos agregados estén disponibles a través de un punto de conexión de métricas HTTP. "Exportador" es el término que OpenTelemetry usa para denominar a las bibliotecas que transmiten telemetría a back-ends específicos del proveedor.

  4. Un servidor de Prometheus:

    • Sondea el punto de conexión de métricas
    • Lee los datos
    • Almacena los datos en una base de datos para la persistencia a largo plazo. Prometheus hace referencia a la lectura y el almacenamiento de datos como extracción de un punto de conexión.
    • Se puede ejecutar en una máquina diferente
  5. El servidor de Grafana:

    • Consulta los datos almacenados en Prometheus y los muestra en un panel de supervisión basado en web.
    • Se puede ejecutar en una máquina diferente.

Configuración de la aplicación de ejemplo para usar el exportador de Prometheus de OpenTelemetry

Agregue una referencia al exportador de Prometheus de OpenTelemetry en la aplicación de ejemplo:

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

Nota

En este tutorial se usa una compilación de versión previa de la compatibilidad con Prometheus de OpenTelemetry disponible en el momento de la redacción de este contenido.

Actualice Program.cs con la configuración de OpenTelemetry:

using OpenTelemetry;
using OpenTelemetry.Metrics;
using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterProvider meterProvider = Sdk.CreateMeterProviderBuilder()
                .AddMeter("HatCo.HatStore")
                .AddPrometheusHttpListener(options => options.UriPrefixes = new string[] { "http://localhost:9184/" })
                .Build();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0,1000));
        }
    }
}

En el código anterior:

  • AddMeter("HatCo.HatStore") configura OpenTelemetry para transmitir todas las métricas que ha recopilado el medidor definido en la aplicación.
  • AddPrometheusHttpListener configura OpenTelemetry para:
    • Exponer el punto de conexión de métricas de Prometheus en el puerto 9184
    • Usar HttpListener.

Consulte la documentación de OpenTelemetry para obtener más información sobre las opciones de configuración de OpenTelemetry. La documentación de OpenTelemetry muestra las opciones de hospedaje de aplicaciones de ASP.NET.

Ejecute la aplicación y déjela en ejecución para que se puedan recopilar medidas:

dotnet run

Configuración de Prometheus

Siga los primeros pasos de Prometheus para configurar un servidor de Prometheus y confirmar que funciona.

Modifique el archivo de configuración prometheus.yml para que Prometheus extraiga el punto de conexión de métricas que se expone en la aplicación de ejemplo. Agregue el siguiente texto resaltado en la sección 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']

Inicio de Prometheus

  1. Vuelva a cargar la configuración o reinicie el servidor de Prometheus.

  2. Confirme que OpenTelemetryTest se encuentre en el estado UP en la página Estado>Objetivos del portal web de Prometheus. Prometheus status

  3. En la página Gráfico del portal web de Prometheus, escriba hats en el cuadro de texto expresión y seleccione hats_sold_Hatshat En la pestaña gráfico, Prometheus muestra el valor creciente del contador "hats-sold" que emite la aplicación de ejemplo. Prometheus hats sold graph

En la imagen anterior, el tiempo del gráfico se establece en 5m, que es de 5 minutos.

Si el servidor de Prometheus no ha estado extrayendo la aplicación de ejemplo desde hace tiempo, es posible que tenga que esperar a que se acumulen los datos.

Visualización de métricas en un panel de Grafana

  1. Siga las instrucciones estándar para instalar Grafana y conectarlo a un origen de datos de Prometheus.

  2. Cree un panel de Grafana haciendo clic en el icono + de la barra de herramientas izquierda en el portal web de Grafana y seleccione Panel. En el editor del panel que aparecerá, escriba Hats Sold/Sec en el cuadro de entrada Título y rate(hats_sold[5m]) en el campo de expresión de PromQL:

    Hats sold Grafana dashboard editor

  3. Haga clic en Aplicar para guardar y ver el nuevo panel.

    Hats sold Grafana dashboard]

Creación de una herramienta de recopilación personalizada mediante la API de .NET MeterListener

La API de MeterListener .NET permite crear lógica personalizada en proceso para observar las medidas que registra System.Diagnostics.Metrics.Meter. Para obtener instrucciones sobre cómo crear lógica personalizada compatible con la instrumentación de EventCounters anterior, consulte EventCounters.

Modifique el código de Program.cs para usar MeterListener:

using System.Diagnostics.Metrics;

class Program
{
    static Meter s_meter = new("HatCo.HatStore", "1.0.0");
    static Counter<int> s_hatsSold = s_meter.CreateCounter<int>(
        name: "hats-sold",
        unit: "Hats",
        description: "The number of hats sold in our store");

    static void Main(string[] args)
    {
        using MeterListener meterListener = new();
        meterListener.InstrumentPublished = (instrument, listener) =>
        {
            if (instrument.Meter.Name is "HatCo.HatStore")
            {
                listener.EnableMeasurementEvents(instrument);
            }
        };

        meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
        // Start the meterListener, enabling InstrumentPublished callbacks.
        meterListener.Start();

        var rand = Random.Shared;
        Console.WriteLine("Press any key to exit");
        while (!Console.KeyAvailable)
        {
            //// Simulate hat selling transactions.
            Thread.Sleep(rand.Next(100, 2500));
            s_hatsSold.Add(rand.Next(0, 1000));
        }
    }

    static void OnMeasurementRecorded<T>(
        Instrument instrument,
        T measurement,
        ReadOnlySpan<KeyValuePair<string, object?>> tags,
        object? state)
    {
        Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
    }
}

En la salida siguiente se muestra la salida de la aplicación con una devolución de llamada personalizada en cada medida:

> dotnet run
Press any key to exit
hats-sold recorded measurement 978
hats-sold recorded measurement 775
hats-sold recorded measurement 666
hats-sold recorded measurement 66
hats-sold recorded measurement 914
hats-sold recorded measurement 912
...

Explicación del código de ejemplo

Los fragmentos de código de esta sección proceden del ejemplo anterior.

En el código resaltado siguiente, se crea una instancia de MeterListener para recibir medidas. La palabra clave using hace que se llame a Dispose cuando meterListener sale del ámbito.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

El código resaltado siguiente configura los instrumentos de los que el agente de escucha recibe medidas. InstrumentPublished es un delegado que se invoca cuando se crea un instrumento en la aplicación.

using MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
    if (instrument.Meter.Name is "HatCo.HatStore")
    {
        listener.EnableMeasurementEvents(instrument);
    }
};

El delegado puede examinar el instrumento para decidir si quiere suscribirse. Por ejemplo, el delegado puede comprobar el nombre, el medidor o cualquier otra propiedad pública. EnableMeasurementEvents habilita la recepción de medidas del instrumento especificado. Código que obtiene una referencia a un instrumento mediante otro enfoque:

  • Normalmente, no se hace.
  • Puede invocar EnableMeasurementEvents() en cualquier momento con la referencia.

El delegado que se invoca cuando se reciben medidas de un instrumento se configura mediante una llamada a SetMeasurementEventCallback:

    meterListener.SetMeasurementEventCallback<int>(OnMeasurementRecorded);
    // Start the meterListener, enabling InstrumentPublished callbacks.
    meterListener.Start();

    var rand = Random.Shared;
    Console.WriteLine("Press any key to exit");
    while (!Console.KeyAvailable)
    {
        //// Simulate hat selling transactions.
        Thread.Sleep(rand.Next(100, 2500));
        s_hatsSold.Add(rand.Next(0, 1000));
    }
}

static void OnMeasurementRecorded<T>(
    Instrument instrument,
    T measurement,
    ReadOnlySpan<KeyValuePair<string, object?>> tags,
    object? state)
{
    Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
}

El parámetro genérico controla qué tipo de datos de medida recibe la devolución de llamada. Por ejemplo, Counter<int> genera medidas int, Counter<double> genera medidas double. Los instrumentos se pueden crear con los tipos byte, short, int, long, float, double y decimal. Se recomienda registrar una devolución de llamada para cada tipo de datos, a menos que sepa que en ese escenario específico no se necesitan todos los tipos de datos. La realización de llamadas repetidas a SetMeasurementEventCallback con distintos argumentos genéricos puede parecer un poco inusual. La API está diseñada de esta manera para permitir que MeterListener reciba medidas con una sobrecarga de rendimiento baja, normalmente de unos pocos nanosegundos.

Cuando se llama a MeterListener.EnableMeasurementEvents, se puede proporcionar un objeto state como uno de los parámetros. El objeto state es arbitrario. Si proporciona un objeto de estado en esa llamada, se almacena con ese instrumento y se le devolverá como parámetro state en la devolución de llamada. Esto está pensado tanto para su comodidad como para una optimización del rendimiento. A menudo, los clientes de escucha necesitan:

  • Crear un objeto para cada instrumento que almacena medidas en la memoria.
  • Tener código para realizar cálculos en esas medidas.

Como alternativa, cree un Dictionary que se asigne desde el instrumento al objeto de almacenamiento y lo busque en cada medida. El uso de un Dictionary es mucho más lento que acceder a él desde state.

meterListener.Start();

El código anterior inicia MeterListener, que habilita las devoluciones de llamada. El delegado InstrumentPublished se invoca para cada instrumentación existente en el proceso. Los objetos Instrument recién creados también desencadenan InstrumentPublished para invocarse.

using MeterListener meterListener = new MeterListener();

Cuando la aplicación haya terminado la escucha, al eliminar el cliente de escucha, se detendrá el flujo de devoluciones de llamada y se liberarán las referencias internas al objeto del cliente de escucha. La palabra clave using que hemos usado al declarar meterListener hace que se llame a Dispose cuando la variable sale del ámbito. Tenga en cuenta que Dispose solo promete que no iniciará nuevas devoluciones de llamada. Dado que las devoluciones de llamada se producen en subprocesos diferentes, puede haber devoluciones de llamada en curso después de que se devuelva la llamada a Dispose.

Para garantizar que una determinada región de código en la devolución de llamada no se esté ejecutando actualmente y no se ejecute en el futuro, se debe agregar la sincronización de subprocesos. Dispose no incluye la sincronización de forma predeterminada porque:

  • La sincronización agrega sobrecarga de rendimiento en cada devolución de llamada de medida.
  • MeterListener se diseñó como una API con un gran control del rendimiento.