Skip to content

Commit

Permalink
Support new implementation of Windows performance counters on Python 3
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek committed Nov 3, 2021
1 parent aadf1f9 commit c868dba
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 18 deletions.
120 changes: 120 additions & 0 deletions iis/datadog_checks/iis/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# (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 datadog_checks.base.checks.windows.perf_counters.counter import PerfObject
from datadog_checks.base.constants import ServiceCheck

from .metrics import METRICS_CONFIG


class IISCheckV2(PerfCountersBaseCheckWithLegacySupport):
__NAMESPACE__ = 'iis'

def get_default_config(self):
metrics_config = {}
for object_name, config in METRICS_CONFIG.items():
new_config = config.copy()

include = []
if object_name == 'APP_POOL_WAS':
new_config['tag_name'] = 'app_pool'
include.extend(self.instance.get('app_pools', []))
elif object_name == 'Web Service':
new_config['tag_name'] = 'site'
include.extend(self.instance.get('sites', []))

if include:
new_config['include'] = [f'^{instance}$' for instance in include]

metrics_config[object_name] = new_config

return {'server_tag': 'iis_host', 'metrics': metrics_config}

def get_perf_object(self, connection, object_name, object_config, use_localized_counters, tags):
if object_name == 'APP_POOL_WAS':
return CompatibilityPerfObject(
self,
connection,
object_name,
object_config,
use_localized_counters,
tags,
'Current Application Pool Uptime',
'app_pool',
self.instance.get('app_pools', []),
)
elif object_name == 'Web Service':
return CompatibilityPerfObject(
self,
connection,
object_name,
object_config,
use_localized_counters,
tags,
'Service Uptime',
'site',
self.instance.get('sites', []),
)
else:
return super().get_perf_object(connection, object_name, object_config, use_localized_counters, tags)


class CompatibilityPerfObject(PerfObject):
def __init__(
self,
check,
connection,
object_name,
object_config,
use_localized_counters,
tags,
uptime_counter,
instance_type,
instances_included,
):
super().__init__(check, connection, object_name, object_config, use_localized_counters, tags)

self.uptime_counter = uptime_counter
self.instance_type = instance_type
self.instance_service_check_name = f'{self.instance_type}_up'
self.instances_included = set(instances_included)

# Resets during refreshes
self.instances_unseen = set()

def refresh(self):
self.instances_unseen.clear()
self.instances_unseen.update(self.instances_included)

for instance in sorted(self.instances_unseen):
self.logger.debug('Expecting %ss: %s', self.instance_type, instance)

super().refresh()

for instance in sorted(self.instances_unseen):
tags = [f'{self.instance_type}:{instance}']
tags.extend(self.tags)
self.logger.warning('Did not get any data for expected %s: %s', self.instance_type, instance)
self.check.service_check(self.instance_service_check_name, ServiceCheck.CRITICAL, tags=tags)

def _instance_excluded(self, instance):
self.instances_unseen.discard(instance)
return super()._instance_excluded(instance)

def get_custom_transformers(self):
return {self.uptime_counter: self.__get_uptime_transformer}

def __get_uptime_transformer(self, check, metric_name, modifiers):
gauge_method = check.gauge
service_check_method = check.service_check

def submit_uptime(value, tags=None):
gauge_method(metric_name, value, tags=tags)
service_check_method(
self.instance_service_check_name, ServiceCheck.CRITICAL if value == 0 else ServiceCheck.OK, tags=tags
)

del check
del modifiers
return submit_uptime
10 changes: 9 additions & 1 deletion iis/datadog_checks/iis/iis.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# (C) Datadog, Inc. 2010-present
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
from six import iteritems
from six import PY3, iteritems

from datadog_checks.base import PDHBaseCheck

Expand Down Expand Up @@ -46,6 +46,14 @@ class IIS(PDHBaseCheck):
SITE = 'site'
APP_POOL = 'app_pool'

def __new__(cls, name, init_config, instances):
if PY3:
from .check import IISCheckV2

return IISCheckV2(name, init_config, instances)
else:
return super(IIS, cls).__new__(cls)

def __init__(self, name, init_config, instances):
super(IIS, self).__init__(name, init_config, instances, counter_list=DEFAULT_COUNTERS)
self._sites = self.instance.get('sites', [])
Expand Down
49 changes: 49 additions & 0 deletions iis/datadog_checks/iis/metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# (C) Datadog, Inc. 2021-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
METRICS_CONFIG = {
'APP_POOL_WAS': {
'name': 'app_pool',
'counters': [
{
'Current Application Pool State': 'state',
'Current Application Pool Uptime': 'uptime',
'Total Application Pool Recycles': {'name': 'recycle.count', 'type': 'monotonic_count'},
}
],
},
'Web Service': {
'name': 'web_service',
'counters': [
{
'Service Uptime': {'metric_name': 'uptime'},
# Network
'Bytes Sent/sec': {'metric_name': 'net.bytes_sent'},
'Bytes Received/sec': {'metric_name': 'net.bytes_rcvd'},
'Bytes Total/sec': {'metric_name': 'net.bytes_total'},
'Current Connections': {'metric_name': 'net.num_connections'},
'Files Sent/sec': {'metric_name': 'net.files_sent'},
'Files Received/sec': {'metric_name': 'net.files_rcvd'},
'Total Connection Attempts (all instances)': {'metric_name': 'net.connection_attempts'},
'Connection Attempts/sec': {'metric_name': 'net.connection_attempts_sec'},
# HTTP methods
'Get Requests/sec': {'metric_name': 'httpd_request_method.get'},
'Post Requests/sec': {'metric_name': 'httpd_request_method.post'},
'Head Requests/sec': {'metric_name': 'httpd_request_method.head'},
'Put Requests/sec': {'metric_name': 'httpd_request_method.put'},
'Delete Requests/sec': {'metric_name': 'httpd_request_method.delete'},
'Options Requests/sec': {'metric_name': 'httpd_request_method.options'},
'Trace Requests/sec': {'metric_name': 'httpd_request_method.trace'},
# Errors
'Not Found Errors/sec': {'metric_name': 'errors.not_found'},
'Locked Errors/sec': {'metric_name': 'errors.locked'},
# Users
'Anonymous Users/sec': {'metric_name': 'users.anon'},
'NonAnonymous Users/sec': {'metric_name': 'users.nonanon'},
# Requests
'CGI Requests/sec': {'metric_name': 'requests.cgi'},
'ISAPI Extension Requests/sec': {'metric_name': 'requests.isapi'},
}
],
},
}
8 changes: 8 additions & 0 deletions iis/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
from datadog_checks.iis.iis import DEFAULT_COUNTERS
from datadog_checks.iis.metrics import METRICS_CONFIG

CHECK_NAME = 'iis'
MINIMAL_INSTANCE = {'host': '.'}
Expand Down Expand Up @@ -43,3 +44,10 @@

SITE_METRICS = [counter_data[3] for counter_data in DEFAULT_COUNTERS if counter_data[0] == 'Web Service']
APP_POOL_METRICS = [counter_data[3] for counter_data in DEFAULT_COUNTERS if counter_data[0] == 'APP_POOL_WAS']

PERFORMANCE_OBJECTS = {}
for object_name, instances in (('APP_POOL_WAS', ['foo-pool', 'bar-pool']), ('Web Service', ['foo.site', 'bar.site'])):
PERFORMANCE_OBJECTS[object_name] = (
instances,
{counter: [9000, 0] for counter in METRICS_CONFIG[object_name]['counters'][0]},
)
10 changes: 0 additions & 10 deletions iis/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,10 +0,0 @@
# (C) Datadog, Inc. 2010-present
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
import pytest
from datadog_test_libs.win.pdh_mocks import initialize_pdh_tests


@pytest.fixture(scope="function", autouse=True)
def setup_check():
initialize_pdh_tests()
16 changes: 9 additions & 7 deletions iis/tests/test_iis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
import re

import pytest
from datadog_test_libs.win.pdh_mocks import pdh_mocks_fixture # noqa: F401
from datadog_test_libs.win.pdh_mocks import initialize_pdh_tests, pdh_mocks_fixture # noqa: F401

from datadog_checks.dev.testing import requires_py2
from datadog_checks.iis import IIS

from .common import (
Expand All @@ -23,8 +24,14 @@
WIN_SERVICES_MINIMAL_CONFIG,
)

pytestmark = [requires_py2, pytest.mark.usefixtures('pdh_mocks_fixture')]


@pytest.fixture(autouse=True)
def setup_check():
initialize_pdh_tests()


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_additional_metrics(aggregator, caplog, dd_run_check):
instance = copy.deepcopy(MINIMAL_INSTANCE)
instance['additional_metrics'] = [
Expand All @@ -46,7 +53,6 @@ def test_additional_metrics(aggregator, caplog, dd_run_check):
assert 'Unknown IIS counter: HTTP Service Request Queues. Falling back to default submission' in caplog.text


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_basic_check(aggregator, dd_run_check):
instance = MINIMAL_INSTANCE
c = IIS(CHECK_NAME, {}, [instance])
Expand All @@ -68,7 +74,6 @@ def test_basic_check(aggregator, dd_run_check):
aggregator.assert_all_metrics_covered()


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_check_on_specific_websites_and_app_pools(aggregator, dd_run_check):
instance = INSTANCE
c = IIS(CHECK_NAME, {}, [instance])
Expand Down Expand Up @@ -98,7 +103,6 @@ def test_check_on_specific_websites_and_app_pools(aggregator, dd_run_check):
aggregator.assert_all_metrics_covered()


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_service_check_with_invalid_host(aggregator, dd_run_check):
instance = INVALID_HOST_INSTANCE
c = IIS(CHECK_NAME, {}, [instance])
Expand All @@ -109,7 +113,6 @@ def test_service_check_with_invalid_host(aggregator, dd_run_check):
aggregator.assert_service_check('iis.app_pool_up', IIS.CRITICAL, tags=['app_pool:Total', iis_host])


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_check(aggregator, dd_run_check):
"""
Returns the right metrics and service checks
Expand Down Expand Up @@ -160,7 +163,6 @@ def test_check(aggregator, dd_run_check):
aggregator.assert_all_metrics_covered()


@pytest.mark.usefixtures('pdh_mocks_fixture')
def test_check_without_sites_specified(aggregator, dd_run_check):
"""
Returns the right metrics and service checks for the `_Total` site
Expand Down
Loading

0 comments on commit c868dba

Please sign in to comment.