diff --git a/hyperv/assets/configuration/spec.yaml b/hyperv/assets/configuration/spec.yaml index a57673d379a3f..33f70196f37b9 100644 --- a/hyperv/assets/configuration/spec.yaml +++ b/hyperv/assets/configuration/spec.yaml @@ -4,8 +4,9 @@ files: options: - template: init_config options: + - template: init_config/perf_counters - template: init_config/default - template: instances options: - - template: instances/pdh_legacy + - template: instances/perf_counters - template: instances/default diff --git a/hyperv/datadog_checks/hyperv/check.py b/hyperv/datadog_checks/hyperv/check.py new file mode 100644 index 0000000000000..aa4d44f2a2697 --- /dev/null +++ b/hyperv/datadog_checks/hyperv/check.py @@ -0,0 +1,13 @@ +# (C) Datadog, Inc. 2021-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from datadog_checks.base.checks.windows.perf_counters.base import PerfCountersBaseCheckWithLegacySupport + +from .metrics import METRICS_CONFIG + + +class HypervCheckV2(PerfCountersBaseCheckWithLegacySupport): + __NAMESPACE__ = 'hyperv' + + def get_default_config(self): + return {'metrics': METRICS_CONFIG} diff --git a/hyperv/datadog_checks/hyperv/config_models/defaults.py b/hyperv/datadog_checks/hyperv/config_models/defaults.py index c69640f4be2a2..f43f5947a42ad 100644 --- a/hyperv/datadog_checks/hyperv/config_models/defaults.py +++ b/hyperv/datadog_checks/hyperv/config_models/defaults.py @@ -8,12 +8,8 @@ def shared_service(field, value): return get_default_field_value(field, value) -def instance_additional_metrics(field, value): - return get_default_field_value(field, value) - - -def instance_counter_data_types(field, value): - return get_default_field_value(field, value) +def shared_use_localized_counters(field, value): + return False def instance_disable_generic_tags(field, value): @@ -24,18 +20,38 @@ def instance_empty_default_hostname(field, value): return False -def instance_host(field, value): - return '.' +def instance_enable_health_service_check(field, value): + return True + + +def instance_extra_metrics(field, value): + return get_default_field_value(field, value) + + +def instance_metrics(field, value): + return get_default_field_value(field, value) def instance_min_collection_interval(field, value): return 15 +def instance_namespace(field, value): + return get_default_field_value(field, value) + + def instance_password(field, value): return get_default_field_value(field, value) +def instance_server(field, value): + return get_default_field_value(field, value) + + +def instance_server_tag(field, value): + return get_default_field_value(field, value) + + def instance_service(field, value): return get_default_field_value(field, value) diff --git a/hyperv/datadog_checks/hyperv/config_models/instance.py b/hyperv/datadog_checks/hyperv/config_models/instance.py index f7149d10c9678..547843deab57c 100644 --- a/hyperv/datadog_checks/hyperv/config_models/instance.py +++ b/hyperv/datadog_checks/hyperv/config_models/instance.py @@ -3,9 +3,9 @@ # Licensed under a 3-clause BSD style license (see LICENSE) from __future__ import annotations -from typing import Optional, Sequence +from typing import Literal, Mapping, Optional, Sequence, Union -from pydantic import BaseModel, root_validator, validator +from pydantic import BaseModel, Extra, Field, root_validator, validator from datadog_checks.base.utils.functions import identity from datadog_checks.base.utils.models import validation @@ -13,17 +13,88 @@ from . import defaults, validators +class Counter(BaseModel): + class Config: + extra = Extra.allow + allow_mutation = False + + aggregate: Optional[Union[bool, Literal['only']]] + average: Optional[bool] + metric_name: Optional[str] + name: Optional[str] + type: Optional[str] + + +class InstanceCounts(BaseModel): + class Config: + allow_mutation = False + + monitored: Optional[str] + total: Optional[str] + unique: Optional[str] + + +class ExtraMetrics(BaseModel): + class Config: + allow_mutation = False + + counters: Sequence[Mapping[str, Union[str, Counter]]] + exclude: Optional[Sequence[str]] + include: Optional[Sequence[str]] + instance_counts: Optional[InstanceCounts] + name: str + tag_name: Optional[str] + use_localized_counters: Optional[bool] + + +class Counter1(BaseModel): + class Config: + extra = Extra.allow + allow_mutation = False + + aggregate: Optional[Union[bool, Literal['only']]] + average: Optional[bool] + metric_name: Optional[str] + name: Optional[str] + type: Optional[str] + + +class InstanceCounts1(BaseModel): + class Config: + allow_mutation = False + + monitored: Optional[str] + total: Optional[str] + unique: Optional[str] + + +class Metrics(BaseModel): + class Config: + allow_mutation = False + + counters: Sequence[Mapping[str, Union[str, Counter1]]] + exclude: Optional[Sequence[str]] + include: Optional[Sequence[str]] + instance_counts: Optional[InstanceCounts1] + name: str + tag_name: Optional[str] + use_localized_counters: Optional[bool] + + class InstanceConfig(BaseModel): class Config: allow_mutation = False - additional_metrics: Optional[Sequence[Sequence[str]]] - counter_data_types: Optional[Sequence[str]] disable_generic_tags: Optional[bool] empty_default_hostname: Optional[bool] - host: Optional[str] + enable_health_service_check: Optional[bool] + extra_metrics: Optional[Mapping[str, ExtraMetrics]] + metrics: Optional[Mapping[str, Metrics]] min_collection_interval: Optional[float] + namespace: Optional[str] = Field(None, regex='\\w*') password: Optional[str] + server: Optional[str] + server_tag: Optional[str] service: Optional[str] tags: Optional[Sequence[str]] username: Optional[str] diff --git a/hyperv/datadog_checks/hyperv/config_models/shared.py b/hyperv/datadog_checks/hyperv/config_models/shared.py index d1c10eced36ca..f5f839962daf7 100644 --- a/hyperv/datadog_checks/hyperv/config_models/shared.py +++ b/hyperv/datadog_checks/hyperv/config_models/shared.py @@ -18,6 +18,7 @@ class Config: allow_mutation = False service: Optional[str] + use_localized_counters: Optional[bool] @root_validator(pre=True) def _initial_validation(cls, values): diff --git a/hyperv/datadog_checks/hyperv/data/conf.yaml.example b/hyperv/datadog_checks/hyperv/data/conf.yaml.example index e4c95fb9336fd..4c53650105bea 100644 --- a/hyperv/datadog_checks/hyperv/data/conf.yaml.example +++ b/hyperv/datadog_checks/hyperv/data/conf.yaml.example @@ -2,6 +2,12 @@ # init_config: + ## @param use_localized_counters - boolean - optional - default: false + ## Whether or not performance object and counter names should refer to their + ## locale-specific versions rather than by their English name. + # + # use_localized_counters: false + ## @param service - string - optional ## Attach the tag `service:` to every metric, event, and service check emitted by this integration. ## @@ -14,51 +20,101 @@ init_config: instances: - - ## @param host - string - optional - default: . - ## The host the current check connects to. - ## "." means the current host + ## @param server - string - optional + ## The server with which to connect, defaulting to the local machine. # - # host: . + # server: ## @param username - string - optional - ## The username from the credentials needed to connect to the host. + ## The username used to connect to the `server`. # # username: ## @param password - string - optional - ## The password from the credentials needed to connect to the host. + ## The password of `username`. # # password: - ## @param additional_metrics - list of lists - optional - ## The additional metrics is a list of items that represent additional counters to collect. - ## Each item is a list of strings, formatted as follows: - ## - ## ['', , '', , ] - ## - ## is the name of the PDH counter set (the name of the counter). - ## is the specific counter instance to collect, for example - ## "Default Web Site". Specify 'none' for all instances of - ## the counter. - ## is the individual counter to report. - ## is the name that displays in Datadog. - ## is from the standard choices for all Agent checks, such as gauge, - ## rate, histogram, or count. + ## @param enable_health_service_check - boolean - optional - default: true + ## Whether or not to send a service check named `.windows.perf.health` which reports + ## the health of the `server`. # - # additional_metrics: - # - [Processor, none, '% Processor Time', processor.time, gauge] - # - [Processor, none, '% User Time', processor.user.time, gauge] + # enable_health_service_check: true - ## @param counter_data_types - list of strings - optional - ## counter_data_types is a list of , elements that - ## allow the precision in which counters are queried on a per metric basis. - ## : The name of your metric - ## : The type of your metric (int or float) + ## @param server_tag - string - optional + ## The name used for tagging `server`. The value defined here replaces the `server:` tag key. + # + # server_tag: + + ## @param extra_metrics - mapping - optional + ## This mapping defines which metrics to collect from the performance + ## counters on the `server`. For more information, see: + ## https://docs.microsoft.com/en-us/windows/win32/perfctrs/about-performance-counters + ## + ## The top-level keys are the names of the desired performance objects: + ## + ## metrics: + ## System: + ## : ... + ## : ... + ## LogicalDisk: + ## : ... + ## : ... + ## + ## The available performance object options are: + ## + ## name (required): This becomes the prefix of all metrics submitted for each counter. + ## counters (required): This is the list of counters to collect. + ## tag_name: This is the name of the tag used for instances. For example, if the tag name for + ## the `LogicalDisk` performance object is `disk`, a possible tag would be `disk:C`. + ## If not set, the default tag name is `instance`. + ## include: This is the list of regular expressions used to select which instances to monitor. + ## If not set, all instances are monitored. + ## exclude: This is the list of regular expressions used to select which instances to ignore. + ## If not set, no instances are ignored. Note: `_Total` instances are always ignored. + ## instance_counts: This is a mapping used to select the count of instances to submit, where each + ## key is a count type and the value is the metric name to use, ignoring `name`. + ## The `total` count type represents the total number of encountered instances. + ## The `monitored` count type represents the number of monitored instances after + ## `include`/`exclude` filtering. The `unique` count type represents the number + ## of unique instance names that are monitored. + ## use_localized_counters: Whether or not performance object and counter names should refer to their + ## locale-specific versions rather than by their English name. This overrides + ## any defined value in `init_config`. + ## + ## The key for each counter object represents the name of the desired counter. + ## Counters may be defined in the following ways: + ## + ## 1. If a value is a string, then it represents the suffix of the sent metric name, for example: + ## + ## counters: + ## - '% Free Space': usable + ## - Current Disk Queue Length: queue_length.current + ## + ## 2. If a value is a mapping, then it must have a `name` key that represents the suffix of the + ## sent metric name, for example: + ## + ## counters: + ## - '% Free Space': + ## name: usable + ## - Current Disk Queue Length: + ## name: queue_length.current + ## + ## The available counter options are: + ## + ## type: This represents how the metric is handled, defaulting to `gauge`. The available types are: + ## gauge, rate, count, monotonic_count, service_check, temporal_percent, time_elapsed + ## average: When there are multiple values for the same instance name (e.g. multiple processes + ## spawned with the same name) the check submits the sum. Setting this option to `true` + ## instructs the check to calculate the average instead. + ## aggregate: Whether or not to send an additional metric that is the aggregation of all values for + ## every monitored instance. If `average` is set to `true` the check submits the average as + ## a metric suffixed by `avg`, otherwise it submits the sum as a metric suffixed by `sum`. + ## If this is set to `only`, the check does not submit a metric per instance. + ## metric_name: This represents the full metric name in lieu of a `name` key and is not be prefixed by + ## the parent object's `name` key. # - # counter_data_types: - # - , - # - processor.time,int - # - processor.user.time,float + # extra_metrics: {} ## @param tags - list of strings - optional ## A list of tags to attach to every metric and service check emitted by this instance. diff --git a/hyperv/datadog_checks/hyperv/hyperv.py b/hyperv/datadog_checks/hyperv/hyperv.py index 1387bfe74285a..4bdd026d55adc 100644 --- a/hyperv/datadog_checks/hyperv/hyperv.py +++ b/hyperv/datadog_checks/hyperv/hyperv.py @@ -1,11 +1,21 @@ # (C) Datadog, Inc. 2018-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) +from six import PY3 + from datadog_checks.base import PDHBaseCheck from .metrics import DEFAULT_COUNTERS class HypervCheck(PDHBaseCheck): + def __new__(cls, name, init_config, instances): + if PY3: + from .check import HypervCheckV2 + + return HypervCheckV2(name, init_config, instances) + else: + return super(HypervCheck, cls).__new__(cls) + def __init__(self, name, init_config, instances=None): super(HypervCheck, self).__init__(name, init_config, instances=instances, counter_list=DEFAULT_COUNTERS) diff --git a/hyperv/datadog_checks/hyperv/metrics.py b/hyperv/datadog_checks/hyperv/metrics.py index 51e3f744729a5..b6be1abd0f4ee 100644 --- a/hyperv/datadog_checks/hyperv/metrics.py +++ b/hyperv/datadog_checks/hyperv/metrics.py @@ -122,3 +122,55 @@ 'gauge', ], ] + +METRICS_CONFIG = { + 'Hyper-V Dynamic Memory Balancer': { + 'name': 'dynamic_memory_balancer', + 'counters': [{'Available Memory': 'available_memory', 'Average Pressure': 'average_pressure'}], + }, + 'Hyper-V Virtual Network Adapter': { + 'name': 'virtual_network_adapter', + 'counters': [{'Bytes/sec': 'bytes_per_sec'}], + }, + 'Hyper-V Hypervisor Logical Processor': { + 'name': 'hypervisor_logical_processor', + 'counters': [ + { + '% Guest Run Time': 'guest_run_time', + '% Hypervisor Run Time': 'hypervisor_run_time', + '% Idle Time': 'idle_time', + '% Total Run Time': 'total_run_time', + 'Context Switches/sec': 'context_switches_per_sec', + } + ], + }, + 'Hyper-V Hypervisor Root Virtual Processor': { + 'name': 'hypervisor_root_virtual_processor', + 'counters': [ + { + '% Guest Run Time': 'guest_run_time', + '% Hypervisor Run Time': 'hypervisor_run_time', + '% Total Run Time': 'total_run_time', + } + ], + }, + 'Hyper-V Hypervisor Virtual Processor': { + 'name': 'hypervisor_virtual_processor', + 'counters': [ + { + '% Guest Run Time': 'guest_run_time', + '% Hypervisor Run Time': 'hypervisor_run_time', + '% Total Run Time': 'total_run_time', + } + ], + }, + 'Hyper-V VM Vid Partition': { + 'name': 'vm_vid_partition', + 'counters': [ + { + 'Physical Pages Allocated': 'physical_pages_allocated', + 'Remote Physical Pages': 'remote_physical_pages', + } + ], + }, +} diff --git a/hyperv/tests/test_hyperv.py b/hyperv/tests/test_hyperv.py index 0fd4123db7047..112b26f560b6f 100644 --- a/hyperv/tests/test_hyperv.py +++ b/hyperv/tests/test_hyperv.py @@ -3,21 +3,44 @@ # Licensed under a 3-clause BSD style license (see LICENSE) import pytest +from datadog_checks.base.constants import ServiceCheck +from datadog_checks.dev.testing import requires_py2, requires_py3 from datadog_checks.hyperv import HypervCheck from datadog_checks.hyperv.metrics import DEFAULT_COUNTERS -def test_check(aggregator, instance_refresh, dd_run_check): +@requires_py3 +def test_check(aggregator, dd_default_hostname, dd_run_check): + check = HypervCheck('hyperv', {}, [{}]) + check.hostname = dd_default_hostname + + # Run twice for counters that require 2 data points + dd_run_check(check) + dd_run_check(check) + + aggregator.assert_service_check( + 'hyperv.windows.perf.health', ServiceCheck.OK, count=2, tags=['server:{}'.format(dd_default_hostname)] + ) + _assert_metrics(aggregator) + + +@requires_py2 +def test_check_legacy(aggregator, instance_refresh, dd_run_check): check = HypervCheck('hyperv', {}, [instance_refresh]) dd_run_check(check) - for counter_data in DEFAULT_COUNTERS: - aggregator.assert_metric(counter_data[3]) + _assert_metrics(aggregator) @pytest.mark.e2e def test_check_e2e(dd_agent_check, instance_refresh): aggregator = dd_agent_check(instance_refresh) + _assert_metrics(aggregator) + + +def _assert_metrics(aggregator): for counter_data in DEFAULT_COUNTERS: aggregator.assert_metric(counter_data[3]) + + aggregator.assert_all_metrics_covered()