Skip to content

Commit df14d8f

Browse files
authored
[datadog_checks_base] Add include_total option for Windows perf-counter framework (#23530)
* [datadog_checks_base] Add include_total option for Windows perf-counter framework Add a per-performance-object include_total option (default false) to the Windows perf-counter framework. When set to true, the _Total aggregate instance is no longer excluded by default and can be collected. The previous behaviour (always exclude _Total) is preserved, so this is strictly opt-in and backwards-compatible. * Rename changelog stub to PR number * Fix CI: changelog, formatting, regenerated spec models - Add datadog_checks_dev changelog entry for the perf_counters template change - Reformat counter.py and test_filter.py per ruff 0.11.10 - Regenerate config_models/instance.py and data/conf.yaml.example for the 7 integrations using the perf_counters template (active_directory, aspdotnet, dotnetclr, exchange_server, hyperv, iis, windows_performance_counters) so that include_total is exposed. * Add per-integration changelog entries for include_total exposure
1 parent 864138f commit df14d8f

26 files changed

Lines changed: 164 additions & 10 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose the `include_total` option in the perf-counter spec, allowing the `_Total` aggregate instance to be collected for selected performance objects.

active_directory/datadog_checks/active_directory/config_models/instance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class ExtraMetrics(BaseModel):
5252
counters: tuple[MappingProxyType[str, Union[str, Counters]], ...]
5353
exclude: Optional[tuple[str, ...]] = None
5454
include: Optional[tuple[str, ...]] = None
55+
include_total: Optional[bool] = None
5556
instance_counts: Optional[InstanceCounts] = None
5657
name: str
5758
tag_name: Optional[str] = None
@@ -75,6 +76,7 @@ class Metrics(BaseModel):
7576
counters: tuple[MappingProxyType[str, Union[str, Counters]], ...]
7677
exclude: Optional[tuple[str, ...]] = None
7778
include: Optional[tuple[str, ...]] = None
79+
include_total: Optional[bool] = None
7880
instance_counts: Optional[InstanceCounts] = None
7981
name: str
8082
tag_name: Optional[str] = None

active_directory/datadog_checks/active_directory/data/conf.yaml.example

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ instances:
7171
## include: This is the list of regular expressions used to select which instances to monitor.
7272
## If not set, all instances are monitored.
7373
## exclude: This is the list of regular expressions used to select which instances to ignore.
74-
## If not set, no instances are ignored. Note: `_Total` instances are always ignored.
74+
## If not set, no instances are ignored. Note: `_Total` instances are ignored by default;
75+
## set `include_total` to `true` to collect them.
76+
## include_total: Whether to collect the `_Total` aggregate instance for this performance object.
77+
## Defaults to `false`. Most performance objects report `_Total` as the sum of the
78+
## individual instances, in which case it is preferable to compute the total in
79+
## Datadog. However, some perf objects (e.g. `MSExchangeTransport Queues`) report
80+
## data on `_Total` that is not derivable from the visible instances, and in that
81+
## case opting in restores the missing data without forcing the collection of every
82+
## individual instance.
7583
## include_fast: This is the list of wildcards or exact instance names used to select which
7684
## instances to monitor. It is faster than the regular expression `include` filter
7785
## because it relies on the Windows PDH built-in wildcard filtering.

aspdotnet/changelog.d/23530.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose the `include_total` option in the perf-counter spec, allowing the `_Total` aggregate instance to be collected for selected performance objects.

aspdotnet/datadog_checks/aspdotnet/config_models/instance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class ExtraMetrics(BaseModel):
5252
counters: tuple[MappingProxyType[str, Union[str, Counters]], ...]
5353
exclude: Optional[tuple[str, ...]] = None
5454
include: Optional[tuple[str, ...]] = None
55+
include_total: Optional[bool] = None
5556
instance_counts: Optional[InstanceCounts] = None
5657
name: str
5758
tag_name: Optional[str] = None
@@ -75,6 +76,7 @@ class Metrics(BaseModel):
7576
counters: tuple[MappingProxyType[str, Union[str, Counters]], ...]
7677
exclude: Optional[tuple[str, ...]] = None
7778
include: Optional[tuple[str, ...]] = None
79+
include_total: Optional[bool] = None
7880
instance_counts: Optional[InstanceCounts] = None
7981
name: str
8082
tag_name: Optional[str] = None

aspdotnet/datadog_checks/aspdotnet/data/conf.yaml.example

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ instances:
7171
## include: This is the list of regular expressions used to select which instances to monitor.
7272
## If not set, all instances are monitored.
7373
## exclude: This is the list of regular expressions used to select which instances to ignore.
74-
## If not set, no instances are ignored. Note: `_Total` instances are always ignored.
74+
## If not set, no instances are ignored. Note: `_Total` instances are ignored by default;
75+
## set `include_total` to `true` to collect them.
76+
## include_total: Whether to collect the `_Total` aggregate instance for this performance object.
77+
## Defaults to `false`. Most performance objects report `_Total` as the sum of the
78+
## individual instances, in which case it is preferable to compute the total in
79+
## Datadog. However, some perf objects (e.g. `MSExchangeTransport Queues`) report
80+
## data on `_Total` that is not derivable from the visible instances, and in that
81+
## case opting in restores the missing data without forcing the collection of every
82+
## individual instance.
7583
## include_fast: This is the list of wildcards or exact instance names used to select which
7684
## instances to monitor. It is faster than the regular expression `include` filter
7785
## because it relies on the Windows PDH built-in wildcard filtering.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a per-performance-object `include_total` option (default `false`) to the Windows perf-counter framework. When set to `true`, the `_Total` aggregate instance is collected instead of being excluded by default.

datadog_checks_base/datadog_checks/base/checks/windows/perf_counters/counter.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ def __init__(self, check, connection, name, config, use_localized_counters, tags
5555
# See: https://learn.microsoft.com/en-us/windows/win32/perfctrs/about-performance-counters
5656
self.include_pattern = re.compile('|'.join(include_patterns), re.IGNORECASE)
5757

58+
# Opt in to collect the `_Total` aggregate instance, which is excluded by default because
59+
# it is usually derivable from the per-instance values. Some perf objects (e.g.
60+
# `MSExchangeTransport Queues`) report data on `_Total` that is not the sum of the
61+
# visible instances, in which case excluding it loses information.
62+
include_total = config.get('include_total', False)
63+
if not isinstance(include_total, bool):
64+
raise ConfigTypeError(f'Option `include_total` for performance object `{self.name}` must be a boolean')
65+
5866
# List of regex patterns to filter multi-instance counters AFTER ALL data
5967
# is collected and retrieved from PDH layer
6068
exclude_patterns = config.get('exclude', [])
@@ -67,11 +75,13 @@ def __init__(self, check, connection, name, config, use_localized_counters, tags
6775
f'Pattern #{i} of option `exclude` for performance object `{self.name}` must be a string'
6876
)
6977

70-
final_exclude_patterns = [r'\b_Total\b']
78+
final_exclude_patterns = [] if include_total else [r'\b_Total\b']
7179
final_exclude_patterns.extend(exclude_patterns)
80+
# `(?!)` is a never-matching pattern, used when there is nothing to exclude so that
81+
# `self.exclude_pattern.search(...)` always returns None without special-casing.
7282
# Instance names are not case-sensitive, so instances should not have names that differ only in case.
7383
# See: https://learn.microsoft.com/en-us/windows/win32/perfctrs/about-performance-counters
74-
self.exclude_pattern = re.compile('|'.join(final_exclude_patterns), re.IGNORECASE)
84+
self.exclude_pattern = re.compile('|'.join(final_exclude_patterns) or r'(?!)', re.IGNORECASE)
7585

7686
# List of wildcards or instance name directly to filter multi-instance counters by PDH layer itself.
7787
# Thus it is faster and and less resource intensive than regex-based include filtering.

datadog_checks_base/tests/base/checks/windows/perf_counters/test_filter.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,58 @@ def test_include_case_insensitive(aggregator, dd_run_check, mock_performance_obj
7575
aggregator.assert_metric_has_tag('test.foo.bar', 'instance:Barbat', count=0)
7676

7777
aggregator.assert_all_metrics_covered()
78+
79+
80+
def test_include_total(aggregator, dd_run_check, mock_performance_objects):
81+
mock_performance_objects({'Foo': (['_Total', 'baz'], {'Bar': [1, 2]})})
82+
check = get_check({'metrics': {'Foo': {'name': 'foo', 'include_total': True, 'counters': [{'Bar': 'bar'}]}}})
83+
dd_run_check(check)
84+
85+
tags = ['instance:_Total']
86+
tags.extend(GLOBAL_TAGS)
87+
aggregator.assert_metric('test.foo.bar', 1, tags=tags)
88+
89+
tags = ['instance:baz']
90+
tags.extend(GLOBAL_TAGS)
91+
aggregator.assert_metric('test.foo.bar', 2, tags=tags)
92+
93+
aggregator.assert_all_metrics_covered()
94+
95+
96+
def test_include_total_with_lowercase_instance(aggregator, dd_run_check, mock_performance_objects):
97+
mock_performance_objects({'Foo': (['_total', 'baz'], {'Bar': [1, 2]})})
98+
check = get_check({'metrics': {'Foo': {'name': 'foo', 'include_total': True, 'counters': [{'Bar': 'bar'}]}}})
99+
dd_run_check(check)
100+
101+
tags = ['instance:_total']
102+
tags.extend(GLOBAL_TAGS)
103+
aggregator.assert_metric('test.foo.bar', 1, tags=tags)
104+
105+
aggregator.assert_metric_has_tag('test.foo.bar', 'instance:baz', count=1)
106+
107+
aggregator.assert_all_metrics_covered()
108+
109+
110+
def test_include_total_respects_user_exclude(aggregator, dd_run_check, mock_performance_objects):
111+
mock_performance_objects({'Foo': (['_Total', 'baz'], {'Bar': [1, 2]})})
112+
check = get_check(
113+
{
114+
'metrics': {
115+
'Foo': {
116+
'name': 'foo',
117+
'include_total': True,
118+
'exclude': ['baz'],
119+
'counters': [{'Bar': 'bar'}],
120+
}
121+
}
122+
}
123+
)
124+
dd_run_check(check)
125+
126+
tags = ['instance:_Total']
127+
tags.extend(GLOBAL_TAGS)
128+
aggregator.assert_metric('test.foo.bar', 1, tags=tags)
129+
130+
aggregator.assert_metric_has_tag('test.foo.bar', 'instance:baz', count=0)
131+
132+
aggregator.assert_all_metrics_covered()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `include_total` option to the Windows perf-counter spec template, allowing integrations to opt in to collecting the `_Total` aggregate instance.

0 commit comments

Comments
 (0)