Usa métricas de OpenCensus

En esta página, se abordan los conceptos básicos de la creación de métricas de OpenCensus para los SLI de disponibilidad y latencia. También se proporcionan ejemplos de implementación sobre cómo definir los SLO mediante las métricas de OpenCensus.

Conceptos básicos de OpenCensus

OpenCensus es una única distribución de bibliotecas de código abierto, disponible en la página de GitHub de OpenCensus, que recopila de forma automática seguimientos y métricas y los envía a cualquier backend. OpenCensus se puede usar para instrumentar tus servicios a fin de emitir métricas personalizadas que se puedan transferir a Cloud Monitoring. Luego, puedes usar estas métricas como SLI.

Si deseas ver un ejemplo del uso de OpenCensus para crear métricas de Monitoring que no están destinadas específicamente a los SLI, consulta Métricas personalizadas con OpenCensus.

Métricas

Para recopilar datos de métricas de tu servicio mediante OpenCensus, debes usar las siguientes construcciones de OpenCensus:

  • Measure, que representa el tipo de métrica que se registrará, especificado con un nombre de métrica. Una Measure puede registrar los valores Int64 o Float64.
  • Measurement: Registra un dato específico que recopiló y escribió un Measure para un evento en particular. Por ejemplo, un Measurement podría registrar la latencia de una respuesta específica.
  • View, que especifica una agregación aplicada a un Measure. OpenCensus admite los siguientes tipos de agregación:
    • Recuento: un recuento de la cantidad de puntos de medición
    • Distribución: una distribución de histogramas de los puntos de medición
    • Suma: una suma de los valores de medición.
    • LastValue: el último valor registrado por la medición.

Para obtener más información, consulta Estadísticas y métricas de OpenCensus. Ten en cuenta que OpenCensus a menudo se refiere a las métricas como estadísticas.

Instrumentación

Las bibliotecas de OpenCensus están disponibles para varios lenguajes. Si deseas obtener información específica del lenguaje sobre la instrumentación de tu servicio para emitir métricas, consulta Compatibilidad de lenguajes de OpenCensus. Además, las métricas personalizadas con OpenCensus proporcionan ejemplos para los lenguajes que se suelen usar con Monitoring.

En el caso básico, debes hacer lo siguiente:

  • Instrumenta tu servicio para registrar y exportar métricas.
  • Define un exportador para recibir las métricas.

Para cada métrica, debes definir un Measure a fin de especificar el tipo de valor: Int64 o Float64. También debes definir y registrar el View para especificar el tipo de agregación (recuento, distribución, suma o último valor). Para usar el tipo de agregación de distribución, también debes especificar los límites del bucket de histograma de forma explícita. También debes especificar un nombre para tu métrica en View.

Exportador

Por último, debes usar un exportador para recopilar las métricas y escribirlas en Cloud Monitoring o en otro backend. Si deseas obtener información sobre los exportadores específicos del lenguaje disponibles para Monitoring, consulta Exportadores de OpenCensus.

También puedes escribir tu propio exportador. Para obtener más información, consulta Escribe un exportador personalizado.

Crea métricas para los SLI

La aplicación debe crear métricas de OpenCensus que se puedan usar como SLI en Cloud Monitoring:

  • Para los SLI de disponibilidad en recuentos de solicitudes y errores, usa un Measure con agregación de recuento.
  • Para los SLI de latencia, usa un Measure con agregación de distribución.

Métricas de los SLI de disponibilidad

Expresas un SLI de disponibilidad basado en solicitudes en la API de Cloud Monitoring mediante la estructura TimeSeriesRatio para configurar una proporción de solicitudes “correctas” o “incorrectas” en total. Esta proporción se usa en el campo goodTotalRatio de una estructura RequestBasedSli.

La aplicación debe crear métricas de OpenCensus que se puedan usar para construir esta proporción. En tu aplicación, debes crear al menos dos de los siguientes elementos:

  1. Una métrica que contabiliza el total de eventos. Usa esta métrica en la totalServiceFilter de la proporción.

    Puedes crear una métrica de OpenCensus de tipo Int64 con agregación de recuento, en la que registras un valor de 1 para cada solicitud recibida.

  2. En una métrica que cuenta los eventos “incorrectos”, usa esta métrica en la badServiceFilter de la proporción.

    Puedes crear una métrica de OpenCensus de tipo Int64 con agregación de recuento, en la que registras un valor de 1 para cada error o solicitud con errores.

  3. En una métrica que cuenta los eventos “correctos”, usa esta métrica en el goodServiceFilter de la proporción.

    Puedes crear una métrica de OpenCensus de tipo Int64 con agregación de recuento, en la que registras un valor de 1 para cada respuesta exitosa.

Métricas de los SLI de latencia

Expresas un SLI de latencia basado en solicitudes en la API de Cloud Monitoring mediante una estructura DistributionCut. Esta estructura se usa en el campo distributionCut de una estructura RequestBasedSli.

Puedes crear un Measure de Int64 o Float64 con un View mediante el tipo de agregación de distribución. También debes definir de forma explícita los límites del bucket. Ten en cuenta que es fundamental definir los buckets de una manera que te permita medir con precisión el porcentaje de solicitudes que se encuentran dentro del límite deseado. Para ver un análisis de este tema, consulta Implementa SLO en el Libro de actividades sobre ingeniería de confiabilidad de sitios.

Ejemplo de implementación

En esta sección, se presenta un ejemplo en el que se implementan métricas para los SLI básicos de disponibilidad y latencia mediante OpenCensus en Node.js.

Instrumentación

Si deseas instrumentar tu servicio para emitir métricas con OpenCensus, haz lo siguiente:

  1. Incluye las bibliotecas necesarias:

    Comienza a usarlo

    import (
    	"flag"
    	"fmt"
    	"log"
    	"math/rand"
    	"net/http"
    	"time"
    
    	"contrib.go.opencensus.io/exporter/stackdriver"
    	"go.opencensus.io/stats"
    	"go.opencensus.io/stats/view"
    	"go.opencensus.io/tag"
    )
    

    Node.js

    // opencensus setup
    const {globalStats, MeasureUnit, AggregationType} = require('@opencensus/core');
    const {StackdriverStatsExporter} = require('@opencensus/exporter-stackdriver');

    Python

    from flask import Flask
    from opencensus.ext.prometheus import stats_exporter as prometheus
    from opencensus.stats import aggregation as aggregation_module
    from opencensus.stats import measure as measure_module
    from opencensus.stats import stats as stats_module
    from opencensus.stats import view as view_module
    from opencensus.tags import tag_map as tag_map_module
    
    from prometheus_flask_exporter import PrometheusMetrics
    
  2. Define y registra el exportador:

    Comienza a usarlo

    // Sets up Cloud Monitoring exporter.
    sd, err := stackdriver.NewExporter(stackdriver.Options{
    	ProjectID:         *projectID,
    	MetricPrefix:      "opencensus-demo",
    	ReportingInterval: 60 * time.Second,
    })
    if err != nil {
    	log.Fatalf("Failed to create the Cloud Monitoring exporter: %v", err)
    }
    defer sd.Flush()
    
    sd.StartMetricsExporter()
    defer sd.StopMetricsExporter()

    Node.js

    // Stackdriver export interval is 60 seconds
    const EXPORT_INTERVAL = 60;
    const exporter = new StackdriverStatsExporter({
      projectId: projectId,
      period: EXPORT_INTERVAL * 1000,
    });
    globalStats.registerExporter(exporter);

    Python

    def setup_openCensus_and_prometheus_exporter() -> None:
        stats = stats_module.stats
        view_manager = stats.view_manager
        exporter = prometheus.new_stats_exporter(prometheus.Options(namespace="oc_python"))
        view_manager.register_exporter(exporter)
        register_all_views(view_manager)
  3. Define un Measure para cada métrica:

    Comienza a usarlo

    // Sets up metrics.
    var (
    	requestCount       = stats.Int64("oc_request_count", "total request count", "requests")
    	failedRequestCount = stats.Int64("oc_failed_request_count", "count of failed requests", "requests")
    	responseLatency    = stats.Float64("oc_latency_distribution", "distribution of response latencies", "s")
    )
    

    Node.js

    const REQUEST_COUNT = globalStats.createMeasureInt64(
      'request_count',
      MeasureUnit.UNIT,
      'Number of requests to the server'
    );
    const ERROR_COUNT = globalStats.createMeasureInt64(
      'error_count',
      MeasureUnit.UNIT,
      'Number of failed requests to the server'
    );
    const RESPONSE_LATENCY = globalStats.createMeasureInt64(
      'response_latency',
      MeasureUnit.MS,
      'The server response latency in milliseconds'
    );

    Python

    m_request_count = measure_module.MeasureInt(
        "python_request_count", "total requests", "requests"
    )
    m_failed_request_count = measure_module.MeasureInt(
        "python_failed_request_count", "failed requests", "requests"
    )
    m_response_latency = measure_module.MeasureFloat(
        "python_response_latency", "response latency", "s"
    )
  4. Define y registra View para cada Measure con el tipo de agregación adecuado y, para la latencia de respuesta, los límites del bucket:

    Comienza a usarlo

    // Sets up views.
    var (
    	requestCountView = &view.View{
    		Name:        "oc_request_count",
    		Measure:     requestCount,
    		Description: "total request count",
    		Aggregation: view.Count(),
    	}
    	failedRequestCountView = &view.View{
    		Name:        "oc_failed_request_count",
    		Measure:     failedRequestCount,
    		Description: "count of failed requests",
    		Aggregation: view.Count(),
    	}
    	responseLatencyView = &view.View{
    		Name:        "oc_response_latency",
    		Measure:     responseLatency,
    		Description: "The distribution of the latencies",
    		// Bucket definitions must be explicitly specified.
    		Aggregation: view.Distribution(0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000),
    	}
    )
    
    	// Register the views.
    	if err := view.Register(requestCountView, failedRequestCountView, responseLatencyView); err != nil {
    		log.Fatalf("Failed to register the views: %v", err)
    	}

    Node.js

    const request_count_metric = globalStats.createView(
      'request_count_metric',
      REQUEST_COUNT,
      AggregationType.COUNT
    );
    globalStats.registerView(request_count_metric);
    const error_count_metric = globalStats.createView(
      'error_count_metric',
      ERROR_COUNT,
      AggregationType.COUNT
    );
    globalStats.registerView(error_count_metric);
    const latency_metric = globalStats.createView(
      'response_latency_metric',
      RESPONSE_LATENCY,
      AggregationType.DISTRIBUTION,
      [],
      'Server response latency distribution',
      // Latency in buckets:
      [0, 1000, 2000, 3000, 4000, 5000, 10000]
    );
    globalStats.registerView(latency_metric);

    Python

    # set up views
    latency_view = view_module.View(
        "python_response_latency",
        "The distribution of the latencies",
        [],
        m_response_latency,
        aggregation_module.DistributionAggregation(
            [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]
        ),
    )
    
    request_count_view = view_module.View(
        "python_request_count",
        "total requests",
        [],
        m_request_count,
        aggregation_module.CountAggregation(),
    )
    
    failed_request_count_view = view_module.View(
        "python_failed_request_count",
        "failed requests",
        [],
        m_failed_request_count,
        aggregation_module.CountAggregation(),
    )
    
    # register views
    def register_all_views(view_manager: stats_module.stats.view_manager) -> None:
        view_manager.register_view(latency_view)
        view_manager.register_view(request_count_view)
        view_manager.register_view(failed_request_count_view)
  5. Valores de registro para las métricas de recuento de solicitudes y de recuento de errores:

    Comienza a usarlo

    // Counts the request.
    stats.Record(ctx, requestCount.M(1))
    
    // Randomly fails 10% of the time.
    if rand.Intn(100) >= 90 {
    	// Counts the error.
    	stats.Record(ctx, failedRequestCount.M(1))

    Node.js

    // record a request count for every request
    globalStats.record([
      {
        measure: REQUEST_COUNT,
        value: 1,
      },
    ]);
    
    // randomly throw an error 10% of the time
    const randomValue = Math.floor(Math.random() * 9 + 1);
    if (randomValue === 1) {
      // Record a failed request.
      globalStats.record([
        {
          measure: ERROR_COUNT,
          value: 1,
        },
      ]);

    Python

    mmap = stats_recorder.new_measurement_map()
    # count request
    mmap.measure_int_put(m_request_count, 1)
    # fail 10% of the time
    if random.randint(0, 100) > 90:
        mmap.measure_int_put(m_failed_request_count, 1)
        tmap = tag_map_module.TagMap()
        mmap.record(tmap)
        return ("error!", 500)
  6. Valores de latencia de registro:

    Comienza a usarlo

    requestReceived := time.Now()
    // Records latency for failure OR success.
    defer func() {
    	stats.Record(ctx, responseLatency.M(time.Since(requestReceived).Seconds()))
    }()

    Node.js

    globalStats.record([
      {
        measure: RESPONSE_LATENCY,
        value: stopwatch.elapsedMilliseconds,
      },
    ]);

    Python

    start_time = time.perf_counter()
    mmap = stats_recorder.new_measurement_map()
    if random.randint(0, 100) > 90:
        response_latency = time.perf_counter() - start_time
        mmap.measure_float_put(m_response_latency, response_latency)
        tmap = tag_map_module.TagMap()
        mmap.record(tmap)

Métricas transferidas

Cuando las métricas se exportan a Cloud Monitoring, aparecen como tipos de métricas con un prefijo que indica que se originaron en OpenCensus. Por ejemplo, el nombre de cada View de OpenCensus en la implementación de Node.js se asigna de la siguiente manera:

  • request_count_sli se convierte en custom.googleapis.com/opencensus/request_count_sli.
  • error_count_sli se convierte en custom.googleapis.com/opencensus/error_count_sli.
  • response_latency_sli se convierte en custom.googleapis.com/opencensus/response_latency_sli.

Una vez que el servicio esté en ejecución, puedes confirmar que las métricas se transfieren a Monitoring; para ello, búscalas en el Explorador de métricas.

SLI de disponibilidad

En Cloud Monitoring, expresas un SLI de disponibilidad basado en solicitudes mediante una estructura TimeSeriesRatio. En el siguiente ejemplo, se muestra un SLO que usa las métricas de OpenCensus transferidas y se espera que el servicio tenga una disponibilidad del 98%, calculado por una proporción de error_count_sli a request_count_sli, durante un período progresivo de 28 días:

{
  "serviceLevelIndicator": {
    "requestBased": {
      "goodTotalRatio": {
        "totalServiceFilter":
          "metric.type=\"custom.googleapis.com/opencensus/request_count_sli\",
       "badServiceFilter":
          "metric.type=\"custom.googleapis.com/opencensus/error_count_sli\"
      }
    }
  },
  "goal": 0.98,
  "rollingPeriod": "2419200s",
  "displayName": "98% Availability, rolling 28 days"
}

SLI de latencia

En Cloud Monitoring, expresas un SLI de latencia basado en solicitudes mediante una estructura DistributionCut. En el siguiente ejemplo, se muestra un SLO que usa la métrica de latencia de OpenCensus transferida y espera que el 98% de las solicitudes se completen en menos de 1,000 ms durante un período progresivo de un día:

{
  "serviceLevelIndicator": {
    "requestBased": {
      "distributionCut": {
        "distributionFilter":
          "metric.type=\"custom.googleapis.com/opencensus/response_latency_sli\",
        "range": {
          "min": 0,
          "max": 1000
        }
      }
    }
  },
  "goal": 0.98,
  "rollingPeriod": "86400s",
  "displayName": "98% requests under 1000 ms"
}