Skip to content

Commit e8c0454

Browse files
authored
feat(receiver/azuremonitor): Adds filtering by metric and/or aggregation (#37421)
#### Description Adds optional setting `metrics` that allows to limit scrapping to the specific metrics and aggregations. #### Link to tracking issue Fixes #37420 #### Testing Unit test coverage extended to verify default behaviour and to cover a new functionality #### Documentation Documentation updated respectfully to reflect changes
1 parent cdd2c16 commit e8c0454

File tree

7 files changed

+699
-12
lines changed

7 files changed

+699
-12
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: azuremonitorreceiver
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Adds filtering by metric and/or aggregation
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [37420]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]
28+

receiver/azuremonitorreceiver/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ The following settings are optional:
2525
- `auth` (default = service_principal): Specifies the used authentication method. Supported values are `service_principal`, `workload_identity`, `managed_identity`, `default_credentials`.
2626
- `resource_groups` (default = none): Filter metrics for specific resource groups, not setting a value will scrape metrics for all resources in the subscription.
2727
- `services` (default = none): Filter metrics for specific services, not setting a value will scrape metrics for all services integrated with Azure Monitor.
28+
- `metrics` (default = none): Filter metrics by name and aggregations. Not setting a value will scrape all metrics and their aggregations.
2829
- `cache_resources` (default = 86400): List of resources will be cached for the provided amount of time in seconds.
2930
- `cache_resources_definitions` (default = 86400): List of metrics definitions will be cached for the provided amount of time in seconds.
3031
- `maximum_number_of_metrics_in_a_call` (default = 20): Maximum number of metrics to fetch in per API call, current limit in Azure is 20 (as of 03/27/2023).
@@ -50,6 +51,26 @@ Authenticating using managed identities has the following optional settings:
5051

5152
- `client_id`
5253

54+
### Filtering metrics
55+
56+
The `metrics` configuration setting is designed to limit scraping to specific metrics and their particular aggregations. It accepts a nested map where the key of the top-level is the Azure Metric Namespace, the key of the nested map is an Azure Metric Name, and the map values are a list of aggregation methods (e.g., Average, Minimum, Maximum, Total, Count). Additionally, the metric map value can be an empty array or an array with one element `*` (asterisk). In this case, the scraper will fetch all supported aggregations for a metric. The letter case of the Namespaces, Metric names, and Aggregations does not affect the functionality.
57+
58+
Scraping limited metrics and aggregations:
59+
60+
```yaml
61+
receivers:
62+
azuremonitor:
63+
resource_groups:
64+
- ${resource_groups}
65+
services:
66+
- Microsoft.EventHub/namespaces
67+
- Microsoft.AAD/DomainServices # scraper will fetch all metrics from this namespace since there are no limits under the "metrics" option
68+
metrics:
69+
"microsoft.eventhub/namespaces": # scraper will fetch only the metrics listed below:
70+
IncomingMessages: [total] # metric IncomingMessages with aggregation "Total"
71+
NamespaceCpuUsage: [*] # metric NamespaceCpuUsage with all known aggregations
72+
```
73+
5374
### Example Configurations
5475
5576
Using [Service Principal](https://learn.microsoft.com/en-us/azure/developer/go/azure-sdk-authentication?tabs=bash#service-principal-with-a-secret) for authentication:
@@ -104,6 +125,7 @@ receivers:
104125
```
105126
106127
Overriding dimensions for a particular metric:
128+
107129
```yaml
108130
receivers:
109131
azuremonitor:
@@ -119,7 +141,6 @@ receivers:
119141
"NetworkRuleHit": [Reason, Status]
120142
```
121143
122-
123144
## Metrics
124145
125146
Details about the metrics scraped by this receiver can be found in [Supported metrics with Azure Monitor](https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported). This receiver adds the prefix "azure_" to all scraped metrics.

receiver/azuremonitorreceiver/config.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,11 @@ var (
228228
}
229229
)
230230

231+
type NestedListAlias = map[string]map[string][]string
232+
231233
type DimensionsConfig struct {
232-
Enabled *bool `mapstructure:"enabled"`
233-
Overrides map[string]map[string][]string `mapstructure:"overrides"`
234+
Enabled *bool `mapstructure:"enabled"`
235+
Overrides NestedListAlias `mapstructure:"overrides"`
234236
}
235237

236238
// Config defines the configuration for the various elements of the receiver agent.
@@ -246,6 +248,7 @@ type Config struct {
246248
FederatedTokenFile string `mapstructure:"federated_token_file"`
247249
ResourceGroups []string `mapstructure:"resource_groups"`
248250
Services []string `mapstructure:"services"`
251+
Metrics NestedListAlias `mapstructure:"metrics"`
249252
CacheResources float64 `mapstructure:"cache_resources"`
250253
CacheResourcesDefinitions float64 `mapstructure:"cache_resources_definitions"`
251254
MaximumNumberOfMetricsInACall int `mapstructure:"maximum_number_of_metrics_in_a_call"`

receiver/azuremonitorreceiver/dimension_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestFilterDimensions(t *testing.T) {
6565
},
6666
cfg: DimensionsConfig{
6767
Enabled: to.Ptr(true),
68-
Overrides: map[string]map[string][]string{
68+
Overrides: NestedListAlias{
6969
"rt1": {
7070
"m1": {
7171
"foo",

receiver/azuremonitorreceiver/scraper.go

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"fmt"
99
"regexp"
10+
"slices"
1011
"strings"
1112
"sync"
1213
"time"
@@ -56,6 +57,7 @@ const (
5657
metadataPrefix = "metadata_"
5758
tagPrefix = "tags_"
5859
truncateTimeGrain = time.Minute
60+
filterAllAggregations = "*"
5961
)
6062

6163
type azureResource struct {
@@ -67,8 +69,9 @@ type azureResource struct {
6769
}
6870

6971
type metricsCompositeKey struct {
70-
dimensions string // comma separated sorted dimensions
71-
timeGrain string
72+
dimensions string // comma separated sorted dimensions
73+
aggregations string // comma separated sorted aggregations
74+
timeGrain string
7275
}
7376

7477
type azureResourceMetrics struct {
@@ -350,12 +353,18 @@ func (s *azureScraper) getResourceMetricsDefinitions(ctx context.Context, resour
350353
}
351354

352355
for _, v := range nextResult.Value {
353-
timeGrain := *v.MetricAvailabilities[0].TimeGrain
354356
metricName := *v.Name.Value
357+
metricAggregations := getMetricAggregations(*v.Namespace, metricName, s.cfg.Metrics)
358+
if len(metricAggregations) == 0 {
359+
continue
360+
}
361+
362+
timeGrain := *v.MetricAvailabilities[0].TimeGrain
355363
dimensions := filterDimensions(v.Dimensions, s.cfg.Dimensions, *s.resources[resourceID].resourceType, metricName)
356364
compositeKey := metricsCompositeKey{
357-
timeGrain: timeGrain,
358-
dimensions: serializeDimensions(dimensions),
365+
timeGrain: timeGrain,
366+
dimensions: serializeDimensions(dimensions),
367+
aggregations: strings.Join(metricAggregations, ","),
359368
}
360369
s.storeMetricsDefinition(resourceID, metricName, compositeKey)
361370
}
@@ -395,6 +404,7 @@ func (s *azureScraper) getResourceMetricsValues(ctx context.Context, resourceID
395404
metricsByGrain.metrics,
396405
compositeKey.dimensions,
397406
compositeKey.timeGrain,
407+
compositeKey.aggregations,
398408
start,
399409
end,
400410
s.cfg.MaximumNumberOfRecordsPerResource,
@@ -442,6 +452,7 @@ func getResourceMetricsValuesRequestOptions(
442452
metrics []string,
443453
dimensionsStr string,
444454
timeGrain string,
455+
aggregationsStr string,
445456
start int,
446457
end int,
447458
top int32,
@@ -450,7 +461,7 @@ func getResourceMetricsValuesRequestOptions(
450461
Metricnames: to.Ptr(strings.Join(metrics[start:end], ",")),
451462
Interval: to.Ptr(timeGrain),
452463
Timespan: to.Ptr(timeGrain),
453-
Aggregation: to.Ptr(strings.Join(aggregations, ",")),
464+
Aggregation: to.Ptr(aggregationsStr),
454465
Top: to.Ptr(top),
455466
Filter: buildDimensionsFilter(dimensionsStr),
456467
}
@@ -491,3 +502,49 @@ func (s *azureScraper) processTimeseriesData(
491502
}
492503
}
493504
}
505+
506+
func getMetricAggregations(metricNamespace, metricName string, filters NestedListAlias) []string {
507+
// default behavior when no metric filters specified: pass all metrics with all aggregations
508+
if len(filters) == 0 {
509+
return aggregations
510+
}
511+
512+
metricsFilters, ok := mapFindInsensitive(filters, metricNamespace)
513+
// metric namespace not found or it's empty: pass all metrics from the namespace
514+
if !ok || len(metricsFilters) == 0 {
515+
return aggregations
516+
}
517+
518+
aggregationsFilters, ok := mapFindInsensitive(metricsFilters, metricName)
519+
// if target metric is absent in metrics map: filter out metric
520+
if !ok {
521+
return []string{}
522+
}
523+
// allow all aggregations if others are not specified
524+
if len(aggregationsFilters) == 0 || slices.Contains(aggregationsFilters, filterAllAggregations) {
525+
return aggregations
526+
}
527+
528+
// collect known supported aggregations
529+
out := []string{}
530+
for _, filter := range aggregationsFilters {
531+
for _, aggregation := range aggregations {
532+
if strings.EqualFold(aggregation, filter) {
533+
out = append(out, aggregation)
534+
}
535+
}
536+
}
537+
538+
return out
539+
}
540+
541+
func mapFindInsensitive[T any](m map[string]T, key string) (T, bool) {
542+
for k, v := range m {
543+
if strings.EqualFold(key, k) {
544+
return v, true
545+
}
546+
}
547+
548+
var got T
549+
return got, false
550+
}

0 commit comments

Comments
 (0)