Path: blob/main/pkg/integrations/azure_exporter/config.go
5371 views
package azure_exporter12import (3"crypto/md5"4"encoding/hex"5"errors"6"fmt"7"net/url"8"strings"910"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"11"github.com/go-kit/log"12azure_config "github.com/webdevops/azure-metrics-exporter/config"13"github.com/webdevops/azure-metrics-exporter/metrics"14"github.com/webdevops/go-common/azuresdk/cloudconfig"15"gopkg.in/yaml.v3"1617"github.com/grafana/agent/pkg/integrations"18integrations_v2 "github.com/grafana/agent/pkg/integrations/v2"19"github.com/grafana/agent/pkg/integrations/v2/metricsutils"20)2122func init() {23integrations.RegisterIntegration(&Config{})24integrations_v2.RegisterLegacy(&Config{}, integrations_v2.TypeMultiplex, metricsutils.NewNamedShim("azure"))25}2627// DefaultConfig holds the default settings for the azure_exporter integration.28var DefaultConfig = Config{29Timespan: "PT1M",30MetricNameTemplate: "azure_{type}_{metric}_{aggregation}_{unit}",31MetricHelpTemplate: "Azure metric {metric} for {type} with aggregation {aggregation} as {unit}",32IncludedResourceTags: []string{"owner"},33AzureCloudEnvironment: "azurecloud",34}3536type Config struct {37Subscriptions []string `yaml:"subscriptions"` // Required38ResourceGraphQueryFilter string `yaml:"resource_graph_query_filter"` // Optional3940// Valid values can be derived from https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported41// Required: Root level names ex. Microsoft.DataShare/accounts42ResourceType string `yaml:"resource_type"`43// Required: Metric in the table for a ResourceType44Metrics []string `yaml:"metrics"`45// Optional: If not provided Aggregation Type is used for the Metric46// Valid values are minimum, maximum, average, total, and count47MetricAggregations []string `yaml:"metric_aggregations"`4849// All fields below are optional50// Must be an ISO8601 Duration - defaults to PT1M if not specified51Timespan string `yaml:"timespan"`52IncludedDimensions []string `yaml:"included_dimensions"`53IncludedResourceTags []string `yaml:"included_resource_tags"`5455// MetricNamespace is used for ResourceTypes which have multiple levels of metrics56// As an example the ResourceType Microsoft.Storage/storageAccounts has metrics for57// Microsoft.Storage/storageAccounts (generic metrics which apply to all storage accounts)58// Microsoft.Storage/storageAccounts/blobServices (generic metrics + metrics which only apply to blob stores)59// Microsoft.Storage/storageAccounts/fileServices (generic metrics + metrics which only apply to file stores)60// Microsoft.Storage/storageAccounts/queueServices (generic metrics + metrics which only apply to queue stores)61// Microsoft.Storage/storageAccounts/tableServices (generic metrics + metrics which only apply to table stores)62// If you want blob store metrics you will need to set63// ResourceType = Microsoft.Storage/storageAccounts64// MetricNamespace = Microsoft.Storage/storageAccounts/blobServices65MetricNamespace string `yaml:"metric_namespace"`6667MetricNameTemplate string `yaml:"metric_name_template"`68MetricHelpTemplate string `yaml:"metric_help_template"`6970AzureCloudEnvironment string `yaml:"azure_cloud_environment"`71}7273// UnmarshalYAML implements yaml.Unmarshaler for Config.74func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {75*c = DefaultConfig7677type plain Config78return unmarshal((*plain)(c))79}8081func (c *Config) InstanceKey(_ string) (string, error) {82// Running the integration in v2 as a TypeMultiplex requires the instance key is unique per instance83// There's no good unique identifier in the config, so we use a hash instead84return getHash(c)85}8687func (c *Config) NewIntegration(l log.Logger) (integrations.Integration, error) {88concurrencyConfig := azure_config.Opts{89// Necessary to match OSS definition90Prober: struct {91// Limits the number of subscriptions which can concurrently be sending metric requests - value taken from OSS exporter92ConcurrencySubscription int `long:"concurrency.subscription" env:"CONCURRENCY_SUBSCRIPTION" description:"Concurrent subscription fetches" default:"5"`93// Limits the number of concurrent metric requests for a single subscription - value taken from OSS exporter94ConcurrencySubscriptionResource int `long:"concurrency.subscription.resource" env:"CONCURRENCY_SUBSCRIPTION_RESOURCE" description:"Concurrent requests per resource (inside subscription requests)" default:"10"`95Cache bool `long:"enable-caching" env:"ENABLE_CACHING" description:"Enable internal caching"`96}{5, 10, false},97}9899return Exporter{100cfg: *c,101logger: integrations.NewLogger(l),102ConcurrencyConfig: concurrencyConfig,103}, nil104}105106func (c *Config) Validate() error {107var configErrors []string108109if c.Subscriptions == nil || len(c.Subscriptions) == 0 {110configErrors = append(configErrors, "subscriptions cannot be empty")111}112113if c.ResourceType == "" {114configErrors = append(configErrors, "resource_type cannot be empty")115}116117if c.Metrics == nil || len(c.Metrics) == 0 {118configErrors = append(configErrors, "metrics cannot be empty")119}120121validAggregations := []string{"minimum", "maximum", "average", "total", "count"}122123for _, aggregation := range c.MetricAggregations {124lowerCaseAggregation := strings.ToLower(aggregation)125found := false126for _, validAggregation := range validAggregations {127if validAggregation == lowerCaseAggregation {128found = true129break130}131}132if !found {133configErrors = append(configErrors, fmt.Sprintf("%s is an invalid value for metric_aggregations. Valid options are %s", aggregation, strings.Join(validAggregations, ",")))134}135}136137if _, err := cloudconfig.NewCloudConfig(c.AzureCloudEnvironment); err != nil {138configErrors = append(configErrors, fmt.Errorf("failed to create an azure cloud configuration from azure cloud environment %s, %v", c.AzureCloudEnvironment, err).Error())139}140141if len(configErrors) != 0 {142return errors.New(strings.Join(configErrors, ","))143}144145return nil146}147148func (c *Config) Name() string {149return "azure_exporter"150}151152func (c *Config) ToScrapeSettings() (*metrics.RequestMetricSettings, error) {153settings := metrics.RequestMetricSettings{154Subscriptions: c.Subscriptions,155Metrics: c.Metrics,156ResourceType: c.ResourceType,157TagLabels: c.IncludedResourceTags,158Aggregations: c.MetricAggregations,159Filter: c.ResourceGraphQueryFilter,160MetricNamespace: c.MetricNamespace,161MetricTemplate: c.MetricNameTemplate,162HelpTemplate: c.MetricHelpTemplate,163164// Interval controls data aggregation timeframe ie 1 minute or 5 minutes aggregations165// Timespan controls query start and end time166// Interval == Timespan to ensure we only get a single data point any more than that is useless167Timespan: c.Timespan,168Interval: to.Ptr[string](c.Timespan),169170// Unused settings just here to capture they are intentionally not set171Name: "",172Cache: nil,173}174175// Dimensions can only be retrieved via an obscure manner of including a "metric filter" on the query176// This isn't documented in the Azure API only the exporter: https://github.com/webdevops/azure-metrics-exporter#virtualnetworkgateway-connections-dimension-support177if len(c.IncludedDimensions) > 0 {178builder := strings.Builder{}179for index, dimension := range c.IncludedDimensions {180if _, err := builder.WriteString(dimension); err != nil {181return nil, err182}183if _, err := builder.WriteString(" eq '*'"); err != nil {184return nil, err185}186// Not the last dimension add an `and`187if index != (len(c.IncludedDimensions) - 1) {188if _, err := builder.WriteString(" and "); err != nil {189return nil, err190}191}192}193settings.MetricFilter = builder.String()194195// The metric filter introduces a secondary complexity where data is limited by a "top" parameter (default 10)196// We don't get any knowledge if the result is cut off and there's no support for paging, so we set the value as197// high as possible to hopefully prevent issues. The API doesn't have a documented limit but any higher values198// cause an OOM from the API199settings.MetricTop = to.Ptr[int32](100_000_000)200settings.MetricOrderBy = "" // Order is only relevant if top won't return all the results our high value should prevent this201}202return &settings, nil203}204205// MergeConfigWithQueryParams will map values from params which where the key206// matches a yaml tag of the Config struct207func MergeConfigWithQueryParams(cfg Config, params url.Values) Config {208if subscriptions, exists := params["subscriptions"]; exists {209cfg.Subscriptions = subscriptions210}211212graphQueryFilters := params.Get("resource_graph_query_filter")213if len(graphQueryFilters) != 0 {214cfg.ResourceGraphQueryFilter = graphQueryFilters215}216217resourceType := params.Get("resource_type")218if len(resourceType) != 0 {219cfg.ResourceType = resourceType220}221222if metricsToScrape, exists := params["metrics"]; exists {223cfg.Metrics = metricsToScrape224}225226if aggregations, exists := params["metric_aggregations"]; exists {227cfg.MetricAggregations = aggregations228}229230timespan := params.Get("timespan")231if len(timespan) != 0 {232cfg.Timespan = timespan233}234235if dimensions, exists := params["included_dimensions"]; exists {236cfg.IncludedDimensions = dimensions237}238239if tags, exists := params["included_resource_tags"]; exists {240cfg.IncludedResourceTags = tags241}242243namespace := params.Get("metric_namespace")244if len(namespace) != 0 {245cfg.MetricNamespace = namespace246}247248nameTemplate := params.Get("metric_name_template")249if len(nameTemplate) != 0 {250cfg.MetricNameTemplate = nameTemplate251}252253helpTemplate := params.Get("metric_help_template")254if len(helpTemplate) != 0 {255cfg.MetricHelpTemplate = helpTemplate256}257258return cfg259}260261// getHash calculates the MD5 hash of the yaml representation of the config262func getHash(c *Config) (string, error) {263bytes, err := yaml.Marshal(c)264if err != nil {265return "", err266}267hash := md5.Sum(bytes)268return hex.EncodeToString(hash[:]), nil269}270271272