diff --git a/README.md b/README.md index 5fbcb30..6b6214f 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,14 @@ resource_tags: - "Microsoft.Compute/virtualMachines" metrics: - name: "CPU Credits Consumed" + - resource_tag_name: "dbtype" + resource_tag_value: "document" + resource_types: + - Microsoft.DocumentDB/databaseAccounts + metrics: + - name: "TotalRequestUnits" + - name: "TotalRequests" + dimensions: "CollectionName eq '*' and StatusCode eq '*'" ``` @@ -106,6 +114,9 @@ When the metric namespace is specified, it will be added as a prefix of the metr It can be used to target [custom metrics](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/metrics-custom-overview), such as [guest OS performance counters](https://docs.microsoft.com/en-us/azure/azure-monitor/platform/collect-custom-metrics-guestos-vm-classic). If not specified, the default metric namespace of the resource will apply. +The `dimensions` property is optional for all filtering types. If `dimensions` property is provided, it will add the provided dimensions as label in the metrics. +You can get the available `dimensions` for a given resource metrics using [metrics definitions](#retrieving-metric-definitions). + ### Resource group filtering Resources in a resource group can be filtered using the the following keys: diff --git a/azure.go b/azure.go index fc402ef..bd89ea5 100644 --- a/azure.go +++ b/azure.go @@ -71,6 +71,13 @@ type AzureMetricValueResponse struct { Minimum float64 `json:"minimum"` Maximum float64 `json:"maximum"` } `json:"data"` + Dimensions []struct { + Name struct { + LocalizedValue string `json:"localizedValue"` + Value string `json:"value"` + } `json:"name"` + Value string `json:"value"` + } `json:"metadatavalues"` } `json:"timeseries"` ID string `json:"id"` Name struct { @@ -279,6 +286,25 @@ func (ac *AzureClient) getMetricDefinitions() (map[string]AzureMetricDefinitionR definitions[defKey] = *def } } + resourcesCache := make(map[string][]byte) + for _, resourceTag := range sc.C.ResourceTags { + resources, err := ac.filteredListByTag(resourceTag, resourcesCache) + if err != nil { + return nil, fmt.Errorf("Failed to get resources for resource tag:value %s:%s and resource types %s: %v", + resourceTag.ResourceTagName, resourceTag.ResourceTagValue, resourceTag.ResourceTypes, err) + } + for _, resource := range resources { + def, err := ac.getAzureMetricDefinitionResponse(resource.ID, resourceTag.MetricNamespace) + if err != nil { + return nil, err + } + defKey := resource.ID + if len(resourceTag.MetricNamespace) > 0 { + defKey = fmt.Sprintf("%s (Metric namespace: %s)", defKey, resourceTag.MetricNamespace) + } + definitions[defKey] = *def + } + } return definitions, nil } @@ -307,6 +333,21 @@ func (ac *AzureClient) getMetricNamespaces() (map[string]MetricNamespaceCollecti namespaces[resource.ID] = *namespaceCollection } } + resourcesCache := make(map[string][]byte) + for _, resourceTag := range sc.C.ResourceTags { + resources, err := ac.filteredListByTag(resourceTag, resourcesCache) + if err != nil { + return nil, fmt.Errorf("Failed to get resources for resource tag:value %s:%s and resource types %s: %v", + resourceTag.ResourceTagName, resourceTag.ResourceTagValue, resourceTag.ResourceTypes, err) + } + for _, resource := range resources { + namespaceCollection, err := ac.getMetricNamespaceCollectionResponse(resource.ID) + if err != nil { + return nil, err + } + namespaces[resource.ID] = *namespaceCollection + } + } return namespaces, nil } @@ -584,7 +625,7 @@ type batchRequest struct { Method string `json:"httpMethod"` } -func resourceURLFrom(resource string, metricNamespace string, metricNames string, aggregations []string) string { +func resourceURLFrom(resource string, metricNamespace string, metricNames string, aggregations []string, dimensions string) string { apiVersion := "2018-01-01" path := fmt.Sprintf( @@ -602,6 +643,9 @@ func resourceURLFrom(resource string, metricNamespace string, metricNames string if metricNamespace != "" { values.Add("metricnamespace", metricNamespace) } + if dimensions != "" { + values.Add("$filter", dimensions) + } filtered := filterAggregations(aggregations) values.Add("aggregation", strings.Join(filtered, ",")) values.Add("timespan", fmt.Sprintf("%s/%s", startTime, endTime)) diff --git a/config/config.go b/config/config.go index 768c165..3d826a4 100644 --- a/config/config.go +++ b/config/config.go @@ -149,6 +149,7 @@ type Target struct { MetricNamespace string `yaml:"metric_namespace"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -162,6 +163,7 @@ type ResourceGroup struct { ResourceNameExcludeRe []Regexp `yaml:"resource_name_exclude_re"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -174,6 +176,7 @@ type ResourceTag struct { ResourceTypes []string `yaml:"resource_types"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } diff --git a/main.go b/main.go index db0ce72..bb1b609 100644 --- a/main.go +++ b/main.go @@ -76,12 +76,17 @@ func (c *Collector) extractMetrics(ch chan<- prometheus.Metric, rm resourceMeta, if rm.metricNamespace != "" { metricName = strings.ToLower(rm.metricNamespace + "_" + metricName) } + metricName = invalidMetricChars.ReplaceAllString(metricName, "_") if len(value.Timeseries) > 0 { metricValue := value.Timeseries[0].Data[len(value.Timeseries[0].Data)-1] labels := CreateResourceLabels(rm.resourceURL) - + if len(value.Timeseries[0].Dimensions) > 0 { + for _, dimension := range value.Timeseries[0].Dimensions { + labels[dimension.Name.Value] = dimension.Value + } + } if hasAggregation(rm.aggregations, "Total") { ch <- prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_total", metricName+"_total", nil, labels), @@ -234,7 +239,7 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { rm.metricNamespace = target.MetricNamespace rm.metrics = strings.Join(metrics, ",") rm.aggregations = filterAggregations(target.Aggregations) - rm.resourceURL = resourceURLFrom(target.Resource, rm.metricNamespace, rm.metrics, rm.aggregations) + rm.resourceURL = resourceURLFrom(target.Resource, rm.metricNamespace, rm.metrics, rm.aggregations, target.Dimensions) incompleteResources = append(incompleteResources, rm) } @@ -259,7 +264,7 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { rm.metricNamespace = resourceGroup.MetricNamespace rm.metrics = metricsStr rm.aggregations = filterAggregations(resourceGroup.Aggregations) - rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations) + rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceGroup.Dimensions) rm.resource = f resources = append(resources, rm) } @@ -287,7 +292,7 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { rm.metricNamespace = resourceTag.MetricNamespace rm.metrics = metricsStr rm.aggregations = filterAggregations(resourceTag.Aggregations) - rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations) + rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceTag.Dimensions) incompleteResources = append(incompleteResources, rm) } } @@ -333,7 +338,12 @@ func main() { for k, v := range results { log.Printf("Resource: %s\n\nAvailable Metrics:\n", k) for _, r := range v.MetricDefinitionResponses { - log.Printf("- %s\n", r.Name.Value) + log.Printf("\n\nMetric:\n") + log.Printf("- %s", r.Name.Value) + log.Printf("\nDimensions:\n") + for _, d := range r.Dimensions { + log.Printf("- %s\n", d.Value) + } } } os.Exit(0)