Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/integrations/gcp_exporter/gcp_exporter.go
5414 views
1
package gcp_exporter
2
3
import (
4
"context"
5
"crypto/md5"
6
"encoding/hex"
7
"errors"
8
"fmt"
9
"net/http"
10
"strings"
11
"time"
12
13
"github.com/PuerkitoBio/rehttp"
14
"github.com/go-kit/log"
15
"github.com/grafana/dskit/multierror"
16
"github.com/prometheus-community/stackdriver_exporter/collectors"
17
"github.com/prometheus-community/stackdriver_exporter/utils"
18
"github.com/prometheus/client_golang/prometheus"
19
"golang.org/x/oauth2/google"
20
"google.golang.org/api/monitoring/v3"
21
"google.golang.org/api/option"
22
"gopkg.in/yaml.v2"
23
24
"github.com/grafana/agent/pkg/integrations"
25
integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"
26
"github.com/grafana/agent/pkg/integrations/v2/metricsutils"
27
)
28
29
func init() {
30
integrations.RegisterIntegration(&Config{})
31
integrations_v2.RegisterLegacy(&Config{}, integrations_v2.TypeMultiplex, metricsutils.NewNamedShim("gcp"))
32
}
33
34
type Config struct {
35
// Google Cloud project ID from where we want to scrape metrics from
36
ProjectIDs []string `yaml:"project_ids"`
37
// Comma separated Google Monitoring Metric Type prefixes.
38
MetricPrefixes []string `yaml:"metrics_prefixes"`
39
// Filters. i.e: pubsub.googleapis.com/subscription:resource.labels.subscription_id=monitoring.regex.full_match("my-subs-prefix.*")
40
ExtraFilters []string `yaml:"extra_filters"`
41
// Interval to request the Google Monitoring Metrics for. Only the most recent data point is used.
42
RequestInterval time.Duration `yaml:"request_interval"`
43
// Offset for the Google Stackdriver Monitoring Metrics interval into the past.
44
RequestOffset time.Duration `yaml:"request_offset"`
45
// Offset for the Google Stackdriver Monitoring Metrics interval into the past by the ingest delay from the metric's metadata.
46
IngestDelay bool `yaml:"ingest_delay"`
47
// Drop metrics from attached projects and fetch `project_id` only.
48
DropDelegatedProjects bool `yaml:"drop_delegated_projects"`
49
// How long should the collector wait for a result from the API.
50
ClientTimeout time.Duration `yaml:"gcp_client_timeout"`
51
}
52
53
var DefaultConfig = Config{
54
ClientTimeout: 15 * time.Second,
55
RequestInterval: 5 * time.Minute,
56
RequestOffset: 0,
57
IngestDelay: false,
58
DropDelegatedProjects: false,
59
}
60
61
// UnmarshalYAML implements yaml.Unmarshaler for Config
62
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
63
*c = DefaultConfig
64
65
type plain Config
66
return unmarshal((*plain)(c))
67
}
68
69
func (c *Config) Name() string {
70
return "gcp_exporter"
71
}
72
73
func (c *Config) InstanceKey(_ string) (string, error) {
74
// We use a hash of the config so our key is unique when leveraged with v2
75
// The config itself doesn't have anything which can uniquely identify it.
76
bytes, err := yaml.Marshal(c)
77
if err != nil {
78
return "", err
79
}
80
hash := md5.Sum(bytes)
81
return hex.EncodeToString(hash[:]), nil
82
}
83
84
func (c *Config) NewIntegration(l log.Logger) (integrations.Integration, error) {
85
if err := c.Validate(); err != nil {
86
return nil, err
87
}
88
89
svc, err := createMonitoringService(context.Background(), c.ClientTimeout)
90
if err != nil {
91
return nil, err
92
}
93
94
var gcpCollectors []prometheus.Collector
95
for _, projectID := range c.ProjectIDs {
96
monitoringCollector, err := collectors.NewMonitoringCollector(
97
projectID,
98
svc,
99
collectors.MonitoringCollectorOptions{
100
MetricTypePrefixes: c.MetricPrefixes,
101
ExtraFilters: parseMetricExtraFilters(c.ExtraFilters),
102
RequestInterval: c.RequestInterval,
103
RequestOffset: c.RequestOffset,
104
IngestDelay: c.IngestDelay,
105
DropDelegatedProjects: c.DropDelegatedProjects,
106
107
// If FillMissingLabels ensures all metrics with the same name have the same label set. It's not often
108
// that GCP metrics have different labels but if it happens the prom registry will panic.
109
FillMissingLabels: true,
110
111
// 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-metrics
112
// for more info
113
AggregateDeltas: true,
114
},
115
l,
116
collectors.NewInMemoryDeltaCounterStore(l, 30*time.Minute),
117
collectors.NewInMemoryDeltaDistributionStore(l, 30*time.Minute),
118
)
119
if err != nil {
120
return nil, fmt.Errorf("failed to create monitoring collector: %w", err)
121
}
122
gcpCollectors = append(gcpCollectors, monitoringCollector)
123
}
124
125
return integrations.NewCollectorIntegration(
126
c.Name(), integrations.WithCollectors(gcpCollectors...),
127
), nil
128
}
129
130
func (c *Config) Validate() error {
131
configErrors := multierror.MultiError{}
132
133
if c.ProjectIDs == nil || len(c.ProjectIDs) == 0 {
134
configErrors.Add(errors.New("no project_ids defined"))
135
}
136
137
if c.MetricPrefixes == nil || len(c.MetricPrefixes) == 0 {
138
configErrors.Add(errors.New("at least 1 metrics_prefixes is required"))
139
}
140
141
if len(c.ExtraFilters) > 0 {
142
filterPrefixToFilter := map[string][]string{}
143
for _, filter := range c.ExtraFilters {
144
splitFilter := strings.Split(filter, ":")
145
if len(splitFilter) <= 1 {
146
configErrors.Add(fmt.Errorf("%s is an invalid filter a filter must be of the form <metric_type>:<filter_expression>", filter))
147
continue
148
}
149
filterPrefix := splitFilter[0]
150
if _, exists := filterPrefixToFilter[filterPrefix]; !exists {
151
filterPrefixToFilter[filterPrefix] = []string{}
152
}
153
filterPrefixToFilter[filterPrefix] = append(filterPrefixToFilter[filterPrefix], filter)
154
}
155
156
for filterPrefix, filters := range filterPrefixToFilter {
157
validFilterPrefix := false
158
for _, metricPrefix := range c.MetricPrefixes {
159
if strings.HasPrefix(metricPrefix, filterPrefix) {
160
validFilterPrefix = true
161
break
162
}
163
}
164
if !validFilterPrefix {
165
configErrors.Add(fmt.Errorf("no metric_prefixes started with %s which means the extra_filters %s will not have any effect", filterPrefix, strings.Join(filters, ",")))
166
}
167
}
168
}
169
170
return configErrors.Err()
171
}
172
173
func createMonitoringService(ctx context.Context, httpTimeout time.Duration) (*monitoring.Service, error) {
174
googleClient, err := google.DefaultClient(ctx, monitoring.MonitoringReadScope)
175
if err != nil {
176
return nil, fmt.Errorf("error creating Google client: %v", err)
177
}
178
179
googleClient.Timeout = httpTimeout
180
googleClient.Transport = rehttp.NewTransport(
181
googleClient.Transport,
182
rehttp.RetryAll(
183
rehttp.RetryMaxRetries(4),
184
rehttp.RetryStatuses(http.StatusServiceUnavailable)),
185
rehttp.ExpJitterDelay(time.Second, 5*time.Second),
186
)
187
188
monitoringService, err := monitoring.NewService(ctx,
189
option.WithHTTPClient(googleClient),
190
)
191
if err != nil {
192
return nil, fmt.Errorf("error creating Google Stackdriver Monitoring service: %v", err)
193
}
194
195
return monitoringService, nil
196
}
197
198
func parseMetricExtraFilters(filters []string) []collectors.MetricFilter {
199
var extraFilters []collectors.MetricFilter
200
for _, ef := range filters {
201
efPrefix, efModifier := utils.GetExtraFilterModifiers(ef, ":")
202
if efPrefix != "" {
203
extraFilter := collectors.MetricFilter{
204
Prefix: efPrefix,
205
Modifier: efModifier,
206
}
207
extraFilters = append(extraFilters, extraFilter)
208
}
209
}
210
return extraFilters
211
}
212
213