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

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

Métricas

Para recopilar datos de métricas de tu servicio con 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. Un Measure puede registrar valores Int64 o Float64.
  • Measurement: registra registros de datos específicos recopilados y escritos por 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 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/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 cómo instrumentar tu servicio para emitir métricas, consulta Compatibilidad de lenguajes de OpenCensus. Además, en Métricas personalizadas con OpenCensus, se proporcionan ejemplos para los lenguajes que se suelen usar con Monitoring.

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

  • Instrumenta el 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 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 histogramas de manera 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 SLI

Tu 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 de TimeSeriesRatio para configurar una proporción de solicitudes “buenas” o “mallas” al total. solicitudes. Esta proporción se usa en el campo goodTotalRatio de una estructura RequestBasedSli.

Tu 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 recuentos, 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 recuentos, en la que registras un valor de 1 para cada solicitud fallida o 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 recuentos, en la que registras un valor de 1 para cada respuesta exitosa.

Métricas para los SLI de latencia

Debes expresar 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 de tu depósito. Ten en cuenta que es fundamental definir los depósitos 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 cómo implementar SLO en el libro de trabajo de 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 SLI básicos de disponibilidad y latencia mediante OpenCensus en Node.js.

Instrumentación

Si deseas instrumentar tu servicio para que emita métricas mediante 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 el View para cada Measure con el tipo de agregación adecuado y, para la latencia de respuesta, los límites del depósito:

    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. Registra los valores de las métricas de recuento de solicitudes y 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 del 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 tus 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.

Después de que el servicio se ejecute, puedes confirmar que las métricas se transfieren a Monitoring si las buscas en el Explorador de métricas.

SLI de disponibilidad

En Cloud Monitoring, expresas un SLI de disponibilidad basado en solicitudes con una estructura TimeSeriesRatio. En el siguiente ejemplo, se muestra un SLO que usa las métricas de OpenCensus transferidas y espera que el servicio tenga una disponibilidad del 98%, calculada por una proporción de error_count_sli y request_count_sli, en un Período continuo 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 con una estructura DistributionCut. En el siguiente ejemplo, se muestra un SLO que usa la métrica de latencia de OpenCensus transferida y se espera que el 98% de las solicitudes se completen en menos de 1,000 ms durante un período 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"
}