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 }