Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/integrations/azure_exporter/config.go
5371 views
1
package azure_exporter
2
3
import (
4
"crypto/md5"
5
"encoding/hex"
6
"errors"
7
"fmt"
8
"net/url"
9
"strings"
10
11
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
12
"github.com/go-kit/log"
13
azure_config "github.com/webdevops/azure-metrics-exporter/config"
14
"github.com/webdevops/azure-metrics-exporter/metrics"
15
"github.com/webdevops/go-common/azuresdk/cloudconfig"
16
"gopkg.in/yaml.v3"
17
18
"github.com/grafana/agent/pkg/integrations"
19
integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"
20
"github.com/grafana/agent/pkg/integrations/v2/metricsutils"
21
)
22
23
func init() {
24
integrations.RegisterIntegration(&Config{})
25
integrations_v2.RegisterLegacy(&Config{}, integrations_v2.TypeMultiplex, metricsutils.NewNamedShim("azure"))
26
}
27
28
// DefaultConfig holds the default settings for the azure_exporter integration.
29
var DefaultConfig = Config{
30
Timespan: "PT1M",
31
MetricNameTemplate: "azure_{type}_{metric}_{aggregation}_{unit}",
32
MetricHelpTemplate: "Azure metric {metric} for {type} with aggregation {aggregation} as {unit}",
33
IncludedResourceTags: []string{"owner"},
34
AzureCloudEnvironment: "azurecloud",
35
}
36
37
type Config struct {
38
Subscriptions []string `yaml:"subscriptions"` // Required
39
ResourceGraphQueryFilter string `yaml:"resource_graph_query_filter"` // Optional
40
41
// Valid values can be derived from https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported
42
// Required: Root level names ex. Microsoft.DataShare/accounts
43
ResourceType string `yaml:"resource_type"`
44
// Required: Metric in the table for a ResourceType
45
Metrics []string `yaml:"metrics"`
46
// Optional: If not provided Aggregation Type is used for the Metric
47
// Valid values are minimum, maximum, average, total, and count
48
MetricAggregations []string `yaml:"metric_aggregations"`
49
50
// All fields below are optional
51
// Must be an ISO8601 Duration - defaults to PT1M if not specified
52
Timespan string `yaml:"timespan"`
53
IncludedDimensions []string `yaml:"included_dimensions"`
54
IncludedResourceTags []string `yaml:"included_resource_tags"`
55
56
// MetricNamespace is used for ResourceTypes which have multiple levels of metrics
57
// As an example the ResourceType Microsoft.Storage/storageAccounts has metrics for
58
// Microsoft.Storage/storageAccounts (generic metrics which apply to all storage accounts)
59
// Microsoft.Storage/storageAccounts/blobServices (generic metrics + metrics which only apply to blob stores)
60
// Microsoft.Storage/storageAccounts/fileServices (generic metrics + metrics which only apply to file stores)
61
// Microsoft.Storage/storageAccounts/queueServices (generic metrics + metrics which only apply to queue stores)
62
// Microsoft.Storage/storageAccounts/tableServices (generic metrics + metrics which only apply to table stores)
63
// If you want blob store metrics you will need to set
64
// ResourceType = Microsoft.Storage/storageAccounts
65
// MetricNamespace = Microsoft.Storage/storageAccounts/blobServices
66
MetricNamespace string `yaml:"metric_namespace"`
67
68
MetricNameTemplate string `yaml:"metric_name_template"`
69
MetricHelpTemplate string `yaml:"metric_help_template"`
70
71
AzureCloudEnvironment string `yaml:"azure_cloud_environment"`
72
}
73
74
// UnmarshalYAML implements yaml.Unmarshaler for Config.
75
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
76
*c = DefaultConfig
77
78
type plain Config
79
return unmarshal((*plain)(c))
80
}
81
82
func (c *Config) InstanceKey(_ string) (string, error) {
83
// Running the integration in v2 as a TypeMultiplex requires the instance key is unique per instance
84
// There's no good unique identifier in the config, so we use a hash instead
85
return getHash(c)
86
}
87
88
func (c *Config) NewIntegration(l log.Logger) (integrations.Integration, error) {
89
concurrencyConfig := azure_config.Opts{
90
// Necessary to match OSS definition
91
Prober: struct {
92
// Limits the number of subscriptions which can concurrently be sending metric requests - value taken from OSS exporter
93
ConcurrencySubscription int `long:"concurrency.subscription" env:"CONCURRENCY_SUBSCRIPTION" description:"Concurrent subscription fetches" default:"5"`
94
// Limits the number of concurrent metric requests for a single subscription - value taken from OSS exporter
95
ConcurrencySubscriptionResource int `long:"concurrency.subscription.resource" env:"CONCURRENCY_SUBSCRIPTION_RESOURCE" description:"Concurrent requests per resource (inside subscription requests)" default:"10"`
96
Cache bool `long:"enable-caching" env:"ENABLE_CACHING" description:"Enable internal caching"`
97
}{5, 10, false},
98
}
99
100
return Exporter{
101
cfg: *c,
102
logger: integrations.NewLogger(l),
103
ConcurrencyConfig: concurrencyConfig,
104
}, nil
105
}
106
107
func (c *Config) Validate() error {
108
var configErrors []string
109
110
if c.Subscriptions == nil || len(c.Subscriptions) == 0 {
111
configErrors = append(configErrors, "subscriptions cannot be empty")
112
}
113
114
if c.ResourceType == "" {
115
configErrors = append(configErrors, "resource_type cannot be empty")
116
}
117
118
if c.Metrics == nil || len(c.Metrics) == 0 {
119
configErrors = append(configErrors, "metrics cannot be empty")
120
}
121
122
validAggregations := []string{"minimum", "maximum", "average", "total", "count"}
123
124
for _, aggregation := range c.MetricAggregations {
125
lowerCaseAggregation := strings.ToLower(aggregation)
126
found := false
127
for _, validAggregation := range validAggregations {
128
if validAggregation == lowerCaseAggregation {
129
found = true
130
break
131
}
132
}
133
if !found {
134
configErrors = append(configErrors, fmt.Sprintf("%s is an invalid value for metric_aggregations. Valid options are %s", aggregation, strings.Join(validAggregations, ",")))
135
}
136
}
137
138
if _, err := cloudconfig.NewCloudConfig(c.AzureCloudEnvironment); err != nil {
139
configErrors = append(configErrors, fmt.Errorf("failed to create an azure cloud configuration from azure cloud environment %s, %v", c.AzureCloudEnvironment, err).Error())
140
}
141
142
if len(configErrors) != 0 {
143
return errors.New(strings.Join(configErrors, ","))
144
}
145
146
return nil
147
}
148
149
func (c *Config) Name() string {
150
return "azure_exporter"
151
}
152
153
func (c *Config) ToScrapeSettings() (*metrics.RequestMetricSettings, error) {
154
settings := metrics.RequestMetricSettings{
155
Subscriptions: c.Subscriptions,
156
Metrics: c.Metrics,
157
ResourceType: c.ResourceType,
158
TagLabels: c.IncludedResourceTags,
159
Aggregations: c.MetricAggregations,
160
Filter: c.ResourceGraphQueryFilter,
161
MetricNamespace: c.MetricNamespace,
162
MetricTemplate: c.MetricNameTemplate,
163
HelpTemplate: c.MetricHelpTemplate,
164
165
// Interval controls data aggregation timeframe ie 1 minute or 5 minutes aggregations
166
// Timespan controls query start and end time
167
// Interval == Timespan to ensure we only get a single data point any more than that is useless
168
Timespan: c.Timespan,
169
Interval: to.Ptr[string](c.Timespan),
170
171
// Unused settings just here to capture they are intentionally not set
172
Name: "",
173
Cache: nil,
174
}
175
176
// Dimensions can only be retrieved via an obscure manner of including a "metric filter" on the query
177
// This isn't documented in the Azure API only the exporter: https://github.com/webdevops/azure-metrics-exporter#virtualnetworkgateway-connections-dimension-support
178
if len(c.IncludedDimensions) > 0 {
179
builder := strings.Builder{}
180
for index, dimension := range c.IncludedDimensions {
181
if _, err := builder.WriteString(dimension); err != nil {
182
return nil, err
183
}
184
if _, err := builder.WriteString(" eq '*'"); err != nil {
185
return nil, err
186
}
187
// Not the last dimension add an `and`
188
if index != (len(c.IncludedDimensions) - 1) {
189
if _, err := builder.WriteString(" and "); err != nil {
190
return nil, err
191
}
192
}
193
}
194
settings.MetricFilter = builder.String()
195
196
// The metric filter introduces a secondary complexity where data is limited by a "top" parameter (default 10)
197
// We don't get any knowledge if the result is cut off and there's no support for paging, so we set the value as
198
// high as possible to hopefully prevent issues. The API doesn't have a documented limit but any higher values
199
// cause an OOM from the API
200
settings.MetricTop = to.Ptr[int32](100_000_000)
201
settings.MetricOrderBy = "" // Order is only relevant if top won't return all the results our high value should prevent this
202
}
203
return &settings, nil
204
}
205
206
// MergeConfigWithQueryParams will map values from params which where the key
207
// matches a yaml tag of the Config struct
208
func MergeConfigWithQueryParams(cfg Config, params url.Values) Config {
209
if subscriptions, exists := params["subscriptions"]; exists {
210
cfg.Subscriptions = subscriptions
211
}
212
213
graphQueryFilters := params.Get("resource_graph_query_filter")
214
if len(graphQueryFilters) != 0 {
215
cfg.ResourceGraphQueryFilter = graphQueryFilters
216
}
217
218
resourceType := params.Get("resource_type")
219
if len(resourceType) != 0 {
220
cfg.ResourceType = resourceType
221
}
222
223
if metricsToScrape, exists := params["metrics"]; exists {
224
cfg.Metrics = metricsToScrape
225
}
226
227
if aggregations, exists := params["metric_aggregations"]; exists {
228
cfg.MetricAggregations = aggregations
229
}
230
231
timespan := params.Get("timespan")
232
if len(timespan) != 0 {
233
cfg.Timespan = timespan
234
}
235
236
if dimensions, exists := params["included_dimensions"]; exists {
237
cfg.IncludedDimensions = dimensions
238
}
239
240
if tags, exists := params["included_resource_tags"]; exists {
241
cfg.IncludedResourceTags = tags
242
}
243
244
namespace := params.Get("metric_namespace")
245
if len(namespace) != 0 {
246
cfg.MetricNamespace = namespace
247
}
248
249
nameTemplate := params.Get("metric_name_template")
250
if len(nameTemplate) != 0 {
251
cfg.MetricNameTemplate = nameTemplate
252
}
253
254
helpTemplate := params.Get("metric_help_template")
255
if len(helpTemplate) != 0 {
256
cfg.MetricHelpTemplate = helpTemplate
257
}
258
259
return cfg
260
}
261
262
// getHash calculates the MD5 hash of the yaml representation of the config
263
func getHash(c *Config) (string, error) {
264
bytes, err := yaml.Marshal(c)
265
if err != nil {
266
return "", err
267
}
268
hash := md5.Sum(bytes)
269
return hex.EncodeToString(hash[:]), nil
270
}
271
272