agones.dev/agones@v1.54.0/pkg/metrics/exporter.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package metrics
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"os"
    21  	"time"
    22  
    23  	"agones.dev/agones/pkg/util/httpserver"
    24  	"cloud.google.com/go/compute/metadata"
    25  	"contrib.go.opencensus.io/exporter/prometheus"
    26  	"contrib.go.opencensus.io/exporter/stackdriver"
    27  	"github.com/heptiolabs/healthcheck"
    28  	"github.com/pkg/errors"
    29  	prom "github.com/prometheus/client_golang/prometheus"
    30  	"github.com/prometheus/client_golang/prometheus/collectors"
    31  	"go.opencensus.io/stats/view"
    32  	"google.golang.org/genproto/googleapis/api/monitoredres"
    33  )
    34  
    35  // Config holds configuration for metrics reporting
    36  type Config struct {
    37  	GCPProjectID      string
    38  	StackdriverLabels string
    39  	Stackdriver       bool
    40  	PrometheusMetrics bool
    41  }
    42  
    43  // RegisterPrometheusExporter register a prometheus exporter to OpenCensus with a given prometheus metric registry.
    44  // It will automatically add go runtime and process metrics using default prometheus collectors.
    45  // The function return an http.handler that you can use to expose the prometheus endpoint.
    46  func RegisterPrometheusExporter(registry *prom.Registry) (http.Handler, error) {
    47  	pe, err := prometheus.NewExporter(prometheus.Options{
    48  		Namespace: "agones",
    49  		Registry:  registry,
    50  	})
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	if err := registry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})); err != nil {
    55  		return nil, err
    56  	}
    57  	if err := registry.Register(collectors.NewGoCollector()); err != nil {
    58  		return nil, err
    59  	}
    60  	view.RegisterExporter(pe)
    61  
    62  	return pe, nil
    63  }
    64  
    65  // RegisterStackdriverExporter register a Stackdriver exporter to OpenCensus.
    66  // It will add Agones metrics into Stackdriver on Google Cloud.
    67  func RegisterStackdriverExporter(projectID string, defaultLabels string) (*stackdriver.Exporter, error) {
    68  	monitoredRes, err := getMonitoredResource(projectID)
    69  	if err != nil {
    70  		logger.WithError(err).Warn("error discovering monitored resource")
    71  	}
    72  	labels, err := parseLabels(defaultLabels)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	sd, err := stackdriver.NewExporter(stackdriver.Options{
    78  		ProjectID: projectID,
    79  		// MetricPrefix helps uniquely identify your metrics.
    80  		MetricPrefix:            "agones",
    81  		Resource:                monitoredRes,
    82  		DefaultMonitoringLabels: labels,
    83  	})
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	// Register it as a metrics exporter
    89  	view.RegisterExporter(sd)
    90  	return sd, nil
    91  }
    92  
    93  // SetReportingPeriod set appropriate reporting period which depends on exporters
    94  // we are going to use
    95  func SetReportingPeriod(forPrometheus, forStackdriver bool) {
    96  	// if we're using only prometheus we can report faster as we're only exposing metrics in memory
    97  	reportingPeriod := 15 * time.Second
    98  	if forStackdriver {
    99  		// There is a limitation on Stackdriver that reporting should
   100  		// be equal or more than 1 minute
   101  		reportingPeriod = 60 * time.Second
   102  	}
   103  
   104  	if forStackdriver || forPrometheus {
   105  		view.SetReportingPeriod(reportingPeriod)
   106  	}
   107  }
   108  
   109  func getMonitoredResource(projectID string) (*monitoredres.MonitoredResource, error) {
   110  	zone, err := metadata.ZoneWithContext(context.TODO())
   111  	if err != nil {
   112  		return nil, errors.Wrap(err, "error getting zone")
   113  	}
   114  	clusterName, err := metadata.InstanceAttributeValueWithContext(context.TODO(), "cluster-name")
   115  	if err != nil {
   116  		return nil, errors.Wrap(err, "error getting cluster-name")
   117  	}
   118  
   119  	return &monitoredres.MonitoredResource{
   120  		Type: "k8s_container",
   121  		Labels: map[string]string{
   122  			"project_id":   projectID,
   123  			"location":     zone,
   124  			"cluster_name": clusterName,
   125  
   126  			// See: https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/
   127  			"namespace_name": os.Getenv("POD_NAMESPACE"),
   128  			"pod_name":       os.Getenv("POD_NAME"),
   129  			"container_name": os.Getenv("CONTAINER_NAME"),
   130  		},
   131  	}, nil
   132  }
   133  
   134  // SetupMetrics initializes metrics reporting with the provided configuration
   135  func SetupMetrics(conf Config, server *httpserver.Server) (healthcheck.Handler, func()) {
   136  	var health healthcheck.Handler
   137  	var closer = func() {}
   138  
   139  	// Stackriver Metrics
   140  	if conf.Stackdriver {
   141  		sd, err := RegisterStackdriverExporter(conf.GCPProjectID, conf.StackdriverLabels)
   142  		if err != nil {
   143  			logger.WithError(err).Fatal("Could not register Stackdriver exporter")
   144  		}
   145  		closer = func() { sd.Flush() }
   146  	}
   147  
   148  	// Prometheus Metrics
   149  	if conf.PrometheusMetrics {
   150  		registry := prom.NewRegistry()
   151  		metricHandler, err := RegisterPrometheusExporter(registry)
   152  		if err != nil {
   153  			logger.WithError(err).Fatal("Could not register Prometheus exporter")
   154  		}
   155  		server.Handle("/metrics", metricHandler)
   156  		health = healthcheck.NewMetricsHandler(registry, "agones")
   157  	} else {
   158  		health = healthcheck.NewHandler()
   159  	}
   160  
   161  	return health, closer
   162  }