From 672d499afa6ae1eb059af8bbc8900ff5b86a1893 Mon Sep 17 00:00:00 2001 From: Christian Gottinger Date: Mon, 2 Mar 2020 17:17:37 +0100 Subject: [PATCH 01/12] feature: use metricwithtimestamp --- main.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index db0ce72..b7f9e2b 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os" "regexp" "strings" + "time" "github.com/RobustPerception/azure_metrics_exporter/config" @@ -82,36 +83,43 @@ func (c *Collector) extractMetrics(ch chan<- prometheus.Metric, rm resourceMeta, metricValue := value.Timeseries[0].Data[len(value.Timeseries[0].Data)-1] labels := CreateResourceLabels(rm.resourceURL) + layout := "2006-01-02T15:04:05Z" + t, err := time.Parse(layout, value.Timeseries[0].Data[0].TimeStamp) + + if err != nil { + fmt.Println(err) + } + if hasAggregation(rm.aggregations, "Total") { - ch <- prometheus.MustNewConstMetric( + ch <- prometheus.NewMetricWithTimestamp(t, prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_total", metricName+"_total", nil, labels), prometheus.GaugeValue, metricValue.Total, - ) + )) } if hasAggregation(rm.aggregations, "Average") { - ch <- prometheus.MustNewConstMetric( + ch <- prometheus.NewMetricWithTimestamp(t,prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_average", metricName+"_average", nil, labels), prometheus.GaugeValue, metricValue.Average, - ) + )) } if hasAggregation(rm.aggregations, "Minimum") { - ch <- prometheus.MustNewConstMetric( + ch <- prometheus.NewMetricWithTimestamp(t,prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_min", metricName+"_min", nil, labels), prometheus.GaugeValue, metricValue.Minimum, - ) + )) } if hasAggregation(rm.aggregations, "Maximum") { - ch <- prometheus.MustNewConstMetric( + ch <- prometheus.NewMetricWithTimestamp(t,prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_max", metricName+"_max", nil, labels), prometheus.GaugeValue, metricValue.Maximum, - ) + )) } } } From 4a31a817b03b27dc26101a576c375218b4bf2223 Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Wed, 7 Oct 2020 19:43:07 +0530 Subject: [PATCH 02/12] Add Dimension Support and List Namespace/Definitions using Tags --- azure.go | 46 +++++++++++++++++++++++++++++++++++++++++++++- config/config.go | 3 +++ main.go | 16 ++++++++++++---- 3 files changed, 60 insertions(+), 5 deletions(-) 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..a6c3f94 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..3917a3f 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) } } @@ -334,6 +339,9 @@ func main() { log.Printf("Resource: %s\n\nAvailable Metrics:\n", k) for _, r := range v.MetricDefinitionResponses { log.Printf("- %s\n", r.Name.Value) + for _, d := range r.Dimensions { + log.Printf("- %s\n", d.Value) + } } } os.Exit(0) From daaa1265367ce8d482a45a0186928b5e0f23029c Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Thu, 8 Oct 2020 15:54:27 +0530 Subject: [PATCH 03/12] Reformat defination list with dimensions --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 3917a3f..bb1b609 100644 --- a/main.go +++ b/main.go @@ -338,7 +338,9 @@ 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) } From 9c1e83ac9b9b7b414a7b9bcc6ab2bae056ebf792 Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Thu, 8 Oct 2020 16:53:32 +0530 Subject: [PATCH 04/12] Fix vet warnings and add documentation --- README.md | 11 +++++++++++ config/config.go | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) 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/config/config.go b/config/config.go index a6c3f94..3d826a4 100644 --- a/config/config.go +++ b/config/config.go @@ -149,7 +149,7 @@ type Target struct { MetricNamespace string `yaml:"metric_namespace"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -163,7 +163,7 @@ type ResourceGroup struct { ResourceNameExcludeRe []Regexp `yaml:"resource_name_exclude_re"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -176,7 +176,7 @@ type ResourceTag struct { ResourceTypes []string `yaml:"resource_types"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } From 666d04aa9e6ebff62e5ac10d9da8621579058351 Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Wed, 7 Oct 2020 19:43:07 +0530 Subject: [PATCH 05/12] Add Dimension Support and List Namespace/Definitions using Tags --- azure.go | 46 +++++++++++++++++++++++++++++++++++++++++++++- config/config.go | 3 +++ main.go | 10 +++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/azure.go b/azure.go index e079c58..eb4de07 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..a6c3f94 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 b7f9e2b..702ebfe 100644 --- a/main.go +++ b/main.go @@ -77,6 +77,7 @@ 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 { @@ -242,7 +243,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) } @@ -267,7 +268,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) } @@ -295,7 +296,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) } } @@ -342,6 +343,9 @@ func main() { log.Printf("Resource: %s\n\nAvailable Metrics:\n", k) for _, r := range v.MetricDefinitionResponses { log.Printf("- %s\n", r.Name.Value) + for _, d := range r.Dimensions { + log.Printf("- %s\n", d.Value) + } } } os.Exit(0) From 7e1e6aca9b9d0d535d29d2321d2e92b287a85a58 Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Thu, 8 Oct 2020 15:54:27 +0530 Subject: [PATCH 06/12] Reformat defination list with dimensions --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 702ebfe..f44714d 100644 --- a/main.go +++ b/main.go @@ -342,7 +342,9 @@ 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) } From 8351c1be28ff0443ec2879877953b462671326e6 Mon Sep 17 00:00:00 2001 From: Neeraj Mangal Date: Thu, 8 Oct 2020 16:53:32 +0530 Subject: [PATCH 07/12] Fix vet warnings and add documentation --- README.md | 11 +++++++++++ config/config.go | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) 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/config/config.go b/config/config.go index a6c3f94..3d826a4 100644 --- a/config/config.go +++ b/config/config.go @@ -149,7 +149,7 @@ type Target struct { MetricNamespace string `yaml:"metric_namespace"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -163,7 +163,7 @@ type ResourceGroup struct { ResourceNameExcludeRe []Regexp `yaml:"resource_name_exclude_re"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } @@ -176,7 +176,7 @@ type ResourceTag struct { ResourceTypes []string `yaml:"resource_types"` Metrics []Metric `yaml:"metrics"` Aggregations []string `yaml:"aggregations"` - Dimensions string `yaml:dimensions` + Dimensions string `yaml:"dimensions"` XXX map[string]interface{} `yaml:",inline"` } From 351289e6278ed09c4c7dee6dd74d28597dce00e8 Mon Sep 17 00:00:00 2001 From: Gotti Date: Fri, 8 Oct 2021 16:48:41 +0200 Subject: [PATCH 08/12] merge: dimension support --- main.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.go b/main.go index f44714d..cf83505 100644 --- a/main.go +++ b/main.go @@ -91,6 +91,12 @@ func (c *Collector) extractMetrics(ch chan<- prometheus.Metric, rm resourceMeta, fmt.Println(err) } + 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.NewMetricWithTimestamp(t, prometheus.MustNewConstMetric( prometheus.NewDesc(metricName+"_total", metricName+"_total", nil, labels), From af9b645100d7fce0284d4cf592dfb3cad9bd57f1 Mon Sep 17 00:00:00 2001 From: Gotti Date: Sat, 9 Oct 2021 22:21:53 +0200 Subject: [PATCH 09/12] feat: ensure metrics are called separate --- Dockerfile | 5 ++- azure.go | 5 ++- main.go | 116 +++++++++++++++++++++++++---------------------------- 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5616524..3e49bf0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ -FROM golang:1.11 as builder +FROM golang:1.16 as builder + WORKDIR /go/src/github.com/RobustPerception/azure_metrics_exporter + COPY . . + RUN make build FROM quay.io/prometheus/busybox:latest AS app diff --git a/azure.go b/azure.go index eb4de07..b5c04a0 100644 --- a/azure.go +++ b/azure.go @@ -353,7 +353,7 @@ func (ac *AzureClient) getMetricNamespaces() (map[string]MetricNamespaceCollecti // Returns AzureMetricDefinitionResponse for a given resource func (ac *AzureClient) getAzureMetricDefinitionResponse(resource string, metricNamespace string) (*AzureMetricDefinitionResponse, error) { - apiVersion := "2018-01-01" + apiVersion := "2019-07-01" metricsResource := fmt.Sprintf("subscriptions/%s%s", sc.C.Credentials.SubscriptionID, resource) metricsTarget := fmt.Sprintf("%s/%s/providers/microsoft.insights/metricDefinitions?api-version=%s", sc.C.ResourceManagerURL, metricsResource, apiVersion) @@ -626,7 +626,7 @@ type batchRequest struct { } func resourceURLFrom(resource string, metricNamespace string, metricNames string, aggregations []string, dimensions string) string { - apiVersion := "2018-01-01" + apiVersion := "2019-07-01" path := fmt.Sprintf( "/subscriptions/%s%s/providers/microsoft.insights/metrics", @@ -655,6 +655,7 @@ func resourceURLFrom(resource string, metricNamespace string, metricNames string Path: path, RawQuery: values.Encode(), } + return url.String() } diff --git a/main.go b/main.go index cf83505..2170cad 100644 --- a/main.go +++ b/main.go @@ -79,20 +79,24 @@ func (c *Collector) extractMetrics(ch chan<- prometheus.Metric, rm resourceMeta, } metricName = invalidMetricChars.ReplaceAllString(metricName, "_") + if len(value.Timeseries) == 0 { + return + } + + for _, item := range value.Timeseries { - if len(value.Timeseries) > 0 { - metricValue := value.Timeseries[0].Data[len(value.Timeseries[0].Data)-1] + metricValue := item.Data[len(item.Data)-1] labels := CreateResourceLabels(rm.resourceURL) layout := "2006-01-02T15:04:05Z" - t, err := time.Parse(layout, value.Timeseries[0].Data[0].TimeStamp) + t, err := time.Parse(layout, item.Data[0].TimeStamp) if err != nil { fmt.Println(err) } - if len(value.Timeseries[0].Dimensions) > 0 { - for _, dimension := range value.Timeseries[0].Dimensions { + if len(item.Dimensions) > 0 { + for _, dimension := range item.Dimensions { labels[dimension.Name.Value] = dimension.Value } } @@ -167,6 +171,7 @@ func (c *Collector) batchCollectMetrics(ch chan<- prometheus.Metric, resources [ var batchData AzureBatchMetricResponse err = json.Unmarshal(batchBody, &batchData) + if err != nil { ch <- prometheus.NewInvalidMetric(azureErrorDesc, err) return @@ -238,73 +243,62 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) { var incompleteResources []resourceMeta for _, target := range sc.C.Targets { + for _, metrics := range target.Metrics{ var rm resourceMeta - metrics := []string{} - for _, metric := range target.Metrics { - metrics = append(metrics, metric.Name) - } - - rm.resourceID = target.Resource - 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, target.Dimensions) - incompleteResources = append(incompleteResources, rm) + rm.resourceID = target.Resource + rm.metricNamespace = target.MetricNamespace + rm.metrics = metrics.Name + rm.aggregations = filterAggregations(target.Aggregations) + rm.resourceURL = resourceURLFrom(target.Resource, rm.metricNamespace, rm.metrics, rm.aggregations, target.Dimensions) + incompleteResources = append(incompleteResources, rm) + } } for _, resourceGroup := range sc.C.ResourceGroups { - metrics := []string{} for _, metric := range resourceGroup.Metrics { - metrics = append(metrics, metric.Name) - } - metricsStr := strings.Join(metrics, ",") - - filteredResources, err := ac.filteredListFromResourceGroup(resourceGroup) - if err != nil { - log.Printf("Failed to get resources for resource group %s and resource types %s: %v", - resourceGroup.ResourceGroup, resourceGroup.ResourceTypes, err) - ch <- prometheus.NewInvalidMetric(azureErrorDesc, err) - return - } - - for _, f := range filteredResources { - var rm resourceMeta - rm.resourceID = f.ID - rm.metricNamespace = resourceGroup.MetricNamespace - rm.metrics = metricsStr - rm.aggregations = filterAggregations(resourceGroup.Aggregations) - rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceGroup.Dimensions) - rm.resource = f - resources = append(resources, rm) - } + filteredResources, err := ac.filteredListFromResourceGroup(resourceGroup) + if err != nil { + log.Printf("Failed to get resources for resource group %s and resource types %s: %v", + resourceGroup.ResourceGroup, resourceGroup.ResourceTypes, err) + ch <- prometheus.NewInvalidMetric(azureErrorDesc, err) + return + } + + for _, f := range filteredResources { + var rm resourceMeta + rm.resourceID = f.ID + rm.metricNamespace = resourceGroup.MetricNamespace + rm.metrics = metric.Name + rm.aggregations = filterAggregations(resourceGroup.Aggregations) + rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceGroup.Dimensions) + rm.resource = f + resources = append(resources, rm) + } + } } resourcesCache := make(map[string][]byte) for _, resourceTag := range sc.C.ResourceTags { - metrics := []string{} for _, metric := range resourceTag.Metrics { - metrics = append(metrics, metric.Name) - } - metricsStr := strings.Join(metrics, ",") - - filteredResources, err := ac.filteredListByTag(resourceTag, resourcesCache) - if err != nil { - log.Printf("Failed to get resources for tag name %s, tag value %s: %v", - resourceTag.ResourceTagName, resourceTag.ResourceTagValue, err) - ch <- prometheus.NewInvalidMetric(azureErrorDesc, err) - return - } - - for _, f := range filteredResources { - var rm resourceMeta - rm.resourceID = f.ID - rm.metricNamespace = resourceTag.MetricNamespace - rm.metrics = metricsStr - rm.aggregations = filterAggregations(resourceTag.Aggregations) - rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceTag.Dimensions) - incompleteResources = append(incompleteResources, rm) - } + filteredResources, err := ac.filteredListByTag(resourceTag, resourcesCache) + if err != nil { + log.Printf("Failed to get resources for tag name %s, tag value %s: %v", + resourceTag.ResourceTagName, resourceTag.ResourceTagValue, err) + ch <- prometheus.NewInvalidMetric(azureErrorDesc, err) + return + } + + for _, f := range filteredResources { + var rm resourceMeta + rm.resourceID = f.ID + rm.metricNamespace = resourceTag.MetricNamespace + rm.metrics = metric.Name + rm.aggregations = filterAggregations(resourceTag.Aggregations) + rm.resourceURL = resourceURLFrom(f.ID, rm.metricNamespace, rm.metrics, rm.aggregations, resourceTag.Dimensions) + incompleteResources = append(incompleteResources, rm) + } + } } completeResources, err := c.batchLookupResources(incompleteResources) From a926339e55602e86451944a511078af0e266dffd Mon Sep 17 00:00:00 2001 From: Gotti Date: Sat, 9 Oct 2021 22:23:54 +0200 Subject: [PATCH 10/12] docu: add escaping in dimension query --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b6214f..11bfbdb 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ resource_tags: metrics: - name: "TotalRequestUnits" - name: "TotalRequests" - dimensions: "CollectionName eq '*' and StatusCode eq '*'" + dimensions: "CollectionName eq \'*\' and StatusCode eq \'*\'" ``` From 8f245b723166f1fc900b41333fed30074bb5e818 Mon Sep 17 00:00:00 2001 From: Christian Gottinger Date: Thu, 28 Oct 2021 11:15:20 +0200 Subject: [PATCH 11/12] feat: git action for build / push --- .github/workflows/docker-publish.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/docker-publish.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..3ca2372 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,23 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + push: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Build & Publish to Registry + uses: elgohr/Publish-Docker-Github-Action@master + with: + name: conplementag/azure_metrics_exporter + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + no_push: ${{ github.event_name == 'push' }} From 145226b1a760fb6de90541f34bc006f56590745c Mon Sep 17 00:00:00 2001 From: Christian Gottinger Date: Thu, 28 Oct 2021 11:29:48 +0200 Subject: [PATCH 12/12] feat: adapt release trigger to tag pushed --- .github/workflows/docker-publish.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3ca2372..11c50d8 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -7,17 +7,20 @@ name: Docker on: push: - branches: [ master ] + tags: + - '*.*.*' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@master + - name: Set Tag as env variable + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Build & Publish to Registry uses: elgohr/Publish-Docker-Github-Action@master with: name: conplementag/azure_metrics_exporter username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - no_push: ${{ github.event_name == 'push' }} + tags: "latest,${{ env.RELEASE_VERSION }}"