| // Copyright 2021 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package metrics provides a service for reporting metrics to |
| // Stackdriver, or locally during development. |
| package metrics |
| |
| import ( |
| "errors" |
| "fmt" |
| "net/http" |
| "os" |
| "time" |
| |
| "cloud.google.com/go/compute/metadata" |
| "contrib.go.opencensus.io/exporter/prometheus" |
| "contrib.go.opencensus.io/exporter/stackdriver" |
| "go.opencensus.io/stats/view" |
| "golang.org/x/build/buildenv" |
| mrpb "google.golang.org/genproto/googleapis/api/monitoredres" |
| ) |
| |
| // NewService initializes a *Service. |
| // |
| // The Service returned is configured to send metric data to |
| // StackDriver. When not running on GCE, it will host metrics through |
| // a prometheus HTTP handler. |
| // |
| // views will be passed to view.Register for export to the metric |
| // service. |
| func NewService(resource *MonitoredResource, views []*view.View) (*Service, error) { |
| err := view.Register(views...) |
| if err != nil { |
| return nil, err |
| } |
| |
| if !metadata.OnGCE() { |
| view.SetReportingPeriod(5 * time.Second) |
| pe, err := prometheus.NewExporter(prometheus.Options{}) |
| if err != nil { |
| return nil, fmt.Errorf("prometheus.NewExporter: %w", err) |
| } |
| view.RegisterExporter(pe) |
| return &Service{pExporter: pe}, nil |
| } |
| |
| projID, err := metadata.ProjectID() |
| if err != nil { |
| return nil, err |
| } |
| if resource == nil { |
| return nil, errors.New("resource is required, got nil") |
| } |
| sde, err := stackdriver.NewExporter(stackdriver.Options{ |
| ProjectID: projID, |
| MonitoredResource: resource, |
| ReportingInterval: time.Minute, // Minimum interval for Stackdriver is 1 minute. |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| // Minimum interval for Stackdriver is 1 minute. |
| view.SetReportingPeriod(time.Minute) |
| // Start the metrics exporter. |
| if err := sde.StartMetricsExporter(); err != nil { |
| return nil, err |
| } |
| |
| return &Service{sdExporter: sde}, nil |
| } |
| |
| // Service controls metric exporters. |
| type Service struct { |
| sdExporter *stackdriver.Exporter |
| pExporter *prometheus.Exporter |
| } |
| |
| func (m *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| if m.pExporter != nil { |
| m.pExporter.ServeHTTP(w, r) |
| return |
| } |
| http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) |
| } |
| |
| // Stop flushes metrics and stops exporting. Stop should be called |
| // before exiting. |
| func (m *Service) Stop() { |
| if sde := m.sdExporter; sde != nil { |
| // Flush any unsent data before exiting. |
| sde.Flush() |
| |
| sde.StopMetricsExporter() |
| } |
| } |
| |
| // MonitoredResource wraps a *mrpb.MonitoredResource to implement the |
| // monitoredresource.MonitoredResource interface. |
| type MonitoredResource mrpb.MonitoredResource |
| |
| func (r *MonitoredResource) MonitoredResource() (resType string, labels map[string]string) { |
| return r.Type, r.Labels |
| } |
| |
| // GKEResource populates a MonitoredResource with GKE Metadata. |
| // |
| // The returned MonitoredResource will have the type set to "k8s_container". |
| func GKEResource(containerName string) (*MonitoredResource, error) { |
| projID, err := metadata.ProjectID() |
| if err != nil { |
| return nil, err |
| } |
| // https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#gke_mds |
| location, err := metadata.InstanceAttributeValue("cluster-location") |
| if err != nil { |
| return nil, err |
| } |
| // https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity#gke_mds |
| clusterName, err := metadata.InstanceAttributeValue("cluster-name") |
| if err != nil { |
| return nil, err |
| } |
| podName, err := os.Hostname() |
| if err != nil { |
| return nil, err |
| } |
| |
| return (*MonitoredResource)(&mrpb.MonitoredResource{ |
| Type: "k8s_container", // See: https://cloud.google.com/monitoring/api/resources#tag_k8s_container |
| Labels: map[string]string{ |
| "project_id": projID, |
| "location": location, |
| "cluster_name": clusterName, |
| "namespace_name": buildenv.ByProjectID(projID).KubeServices.Namespace, |
| "pod_name": podName, |
| "container_name": containerName, |
| }, |
| }), nil |
| } |