Path: blob/main/pkg/integrations/gcp_exporter/gcp_exporter.go
5414 views
package gcp_exporter12import (3"context"4"crypto/md5"5"encoding/hex"6"errors"7"fmt"8"net/http"9"strings"10"time"1112"github.com/PuerkitoBio/rehttp"13"github.com/go-kit/log"14"github.com/grafana/dskit/multierror"15"github.com/prometheus-community/stackdriver_exporter/collectors"16"github.com/prometheus-community/stackdriver_exporter/utils"17"github.com/prometheus/client_golang/prometheus"18"golang.org/x/oauth2/google"19"google.golang.org/api/monitoring/v3"20"google.golang.org/api/option"21"gopkg.in/yaml.v2"2223"github.com/grafana/agent/pkg/integrations"24integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"25"github.com/grafana/agent/pkg/integrations/v2/metricsutils"26)2728func init() {29integrations.RegisterIntegration(&Config{})30integrations_v2.RegisterLegacy(&Config{}, integrations_v2.TypeMultiplex, metricsutils.NewNamedShim("gcp"))31}3233type Config struct {34// Google Cloud project ID from where we want to scrape metrics from35ProjectIDs []string `yaml:"project_ids"`36// Comma separated Google Monitoring Metric Type prefixes.37MetricPrefixes []string `yaml:"metrics_prefixes"`38// Filters. i.e: pubsub.googleapis.com/subscription:resource.labels.subscription_id=monitoring.regex.full_match("my-subs-prefix.*")39ExtraFilters []string `yaml:"extra_filters"`40// Interval to request the Google Monitoring Metrics for. Only the most recent data point is used.41RequestInterval time.Duration `yaml:"request_interval"`42// Offset for the Google Stackdriver Monitoring Metrics interval into the past.43RequestOffset time.Duration `yaml:"request_offset"`44// Offset for the Google Stackdriver Monitoring Metrics interval into the past by the ingest delay from the metric's metadata.45IngestDelay bool `yaml:"ingest_delay"`46// Drop metrics from attached projects and fetch `project_id` only.47DropDelegatedProjects bool `yaml:"drop_delegated_projects"`48// How long should the collector wait for a result from the API.49ClientTimeout time.Duration `yaml:"gcp_client_timeout"`50}5152var DefaultConfig = Config{53ClientTimeout: 15 * time.Second,54RequestInterval: 5 * time.Minute,55RequestOffset: 0,56IngestDelay: false,57DropDelegatedProjects: false,58}5960// UnmarshalYAML implements yaml.Unmarshaler for Config61func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {62*c = DefaultConfig6364type plain Config65return unmarshal((*plain)(c))66}6768func (c *Config) Name() string {69return "gcp_exporter"70}7172func (c *Config) InstanceKey(_ string) (string, error) {73// We use a hash of the config so our key is unique when leveraged with v274// The config itself doesn't have anything which can uniquely identify it.75bytes, err := yaml.Marshal(c)76if err != nil {77return "", err78}79hash := md5.Sum(bytes)80return hex.EncodeToString(hash[:]), nil81}8283func (c *Config) NewIntegration(l log.Logger) (integrations.Integration, error) {84if err := c.Validate(); err != nil {85return nil, err86}8788svc, err := createMonitoringService(context.Background(), c.ClientTimeout)89if err != nil {90return nil, err91}9293var gcpCollectors []prometheus.Collector94for _, projectID := range c.ProjectIDs {95monitoringCollector, err := collectors.NewMonitoringCollector(96projectID,97svc,98collectors.MonitoringCollectorOptions{99MetricTypePrefixes: c.MetricPrefixes,100ExtraFilters: parseMetricExtraFilters(c.ExtraFilters),101RequestInterval: c.RequestInterval,102RequestOffset: c.RequestOffset,103IngestDelay: c.IngestDelay,104DropDelegatedProjects: c.DropDelegatedProjects,105106// If FillMissingLabels ensures all metrics with the same name have the same label set. It's not often107// that GCP metrics have different labels but if it happens the prom registry will panic.108FillMissingLabels: true,109110// If AggregateDeltas is disabled the data produced is not useful at all. See https://github.com/prometheus-community/stackdriver_exporter#what-to-know-about-aggregating-delta-metrics111// for more info112AggregateDeltas: true,113},114l,115collectors.NewInMemoryDeltaCounterStore(l, 30*time.Minute),116collectors.NewInMemoryDeltaDistributionStore(l, 30*time.Minute),117)118if err != nil {119return nil, fmt.Errorf("failed to create monitoring collector: %w", err)120}121gcpCollectors = append(gcpCollectors, monitoringCollector)122}123124return integrations.NewCollectorIntegration(125c.Name(), integrations.WithCollectors(gcpCollectors...),126), nil127}128129func (c *Config) Validate() error {130configErrors := multierror.MultiError{}131132if c.ProjectIDs == nil || len(c.ProjectIDs) == 0 {133configErrors.Add(errors.New("no project_ids defined"))134}135136if c.MetricPrefixes == nil || len(c.MetricPrefixes) == 0 {137configErrors.Add(errors.New("at least 1 metrics_prefixes is required"))138}139140if len(c.ExtraFilters) > 0 {141filterPrefixToFilter := map[string][]string{}142for _, filter := range c.ExtraFilters {143splitFilter := strings.Split(filter, ":")144if len(splitFilter) <= 1 {145configErrors.Add(fmt.Errorf("%s is an invalid filter a filter must be of the form <metric_type>:<filter_expression>", filter))146continue147}148filterPrefix := splitFilter[0]149if _, exists := filterPrefixToFilter[filterPrefix]; !exists {150filterPrefixToFilter[filterPrefix] = []string{}151}152filterPrefixToFilter[filterPrefix] = append(filterPrefixToFilter[filterPrefix], filter)153}154155for filterPrefix, filters := range filterPrefixToFilter {156validFilterPrefix := false157for _, metricPrefix := range c.MetricPrefixes {158if strings.HasPrefix(metricPrefix, filterPrefix) {159validFilterPrefix = true160break161}162}163if !validFilterPrefix {164configErrors.Add(fmt.Errorf("no metric_prefixes started with %s which means the extra_filters %s will not have any effect", filterPrefix, strings.Join(filters, ",")))165}166}167}168169return configErrors.Err()170}171172func createMonitoringService(ctx context.Context, httpTimeout time.Duration) (*monitoring.Service, error) {173googleClient, err := google.DefaultClient(ctx, monitoring.MonitoringReadScope)174if err != nil {175return nil, fmt.Errorf("error creating Google client: %v", err)176}177178googleClient.Timeout = httpTimeout179googleClient.Transport = rehttp.NewTransport(180googleClient.Transport,181rehttp.RetryAll(182rehttp.RetryMaxRetries(4),183rehttp.RetryStatuses(http.StatusServiceUnavailable)),184rehttp.ExpJitterDelay(time.Second, 5*time.Second),185)186187monitoringService, err := monitoring.NewService(ctx,188option.WithHTTPClient(googleClient),189)190if err != nil {191return nil, fmt.Errorf("error creating Google Stackdriver Monitoring service: %v", err)192}193194return monitoringService, nil195}196197func parseMetricExtraFilters(filters []string) []collectors.MetricFilter {198var extraFilters []collectors.MetricFilter199for _, ef := range filters {200efPrefix, efModifier := utils.GetExtraFilterModifiers(ef, ":")201if efPrefix != "" {202extraFilter := collectors.MetricFilter{203Prefix: efPrefix,204Modifier: efModifier,205}206extraFilters = append(extraFilters, extraFilter)207}208}209return extraFilters210}211212213