Skip to content

Commit

Permalink
Add optional windows_service_startup_type tag (#12932)
Browse files Browse the repository at this point in the history
Add a new tag to each service check that indicates that service's
startup type
  • Loading branch information
clarkb7 authored Sep 15, 2022
1 parent b40969d commit be04343
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 0 deletions.
14 changes: 14 additions & 0 deletions windows_service/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,18 @@ files:
value:
type: boolean
example: false
- name: windows_service_startup_type_tag
description: |
Whether or not to submit the `windows_service_startup_type` tag along
with each service check.
This tag indicates the Windows service startup type as shown in a service's properties,
and can be used as a filter/scope in Monitors.
Possible values for this tag are:
- disabled
- manual
- automatic
- automatic_delayed_start
value:
type: boolean
example: false
- template: instances/default
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,7 @@ def instance_service(field, value):

def instance_tags(field, value):
return get_default_field_value(field, value)


def instance_windows_service_startup_type_tag(field, value):
return False
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Config:
service: Optional[str]
services: Sequence[str]
tags: Optional[Sequence[str]]
windows_service_startup_type_tag: Optional[bool]

@root_validator(pre=True)
def _initial_validation(cls, values):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ instances:
#
# disable_legacy_service_tag: false

## @param windows_service_startup_type_tag - boolean - optional - default: false
## Whether or not to submit the `windows_service_startup_type` tag along
## with each service check.
## This tag indicates the Windows service startup type as shown in a service's properties,
## and can be used as a filter/scope in Monitors.
## Possible values for this tag are:
## - disabled
## - manual
## - automatic
## - automatic_delayed_start
#
# windows_service_startup_type_tag: false

## @param tags - list of strings - optional
## A list of tags to attach to every metric and service check emitted by this instance.
##
Expand Down
47 changes: 47 additions & 0 deletions windows_service/datadog_checks/windows_service/windows_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class WindowsService(AgentCheck):
# PAUSED
7: AgentCheck.WARNING,
}
# windows_service_startup_type tag values
STARTUP_TYPE_TO_STRING = {
win32service.SERVICE_AUTO_START: "automatic",
win32service.SERVICE_DEMAND_START: "manual",
win32service.SERVICE_DISABLED: "disabled",
}
STARTUP_TYPE_DELAYED_AUTO = "automatic_delayed_start"
STARTUP_TYPE_UNKNOWN = "unknown"

def check(self, instance):
services = set(instance.get('services', []))
Expand Down Expand Up @@ -73,6 +81,10 @@ def check(self, instance):
tags = ['windows_service:{}'.format(short_name)]
tags.extend(custom_tags)

if instance.get('windows_service_startup_type_tag', False):
startup_type_string = self._get_service_startup_type_tag(scm_handle, short_name)
tags.append('windows_service_startup_type:{}'.format(startup_type_string))

if not instance.get('disable_legacy_service_tag', False):
self._log_deprecation('service_tag', 'windows_service')
tags.append('service:{}'.format(short_name))
Expand All @@ -84,13 +96,48 @@ def check(self, instance):
for service in services_unseen:
# if a name doesn't match anything (wrong name or no permission to access the service), report UNKNOWN
status = self.UNKNOWN
startup_type_string = self.STARTUP_TYPE_UNKNOWN

tags = ['windows_service:{}'.format(service)]

tags.extend(custom_tags)

if instance.get('windows_service_startup_type_tag', False):
tags.append('windows_service_startup_type:{}'.format(startup_type_string))

if not instance.get('disable_legacy_service_tag', False):
self._log_deprecation('service_tag', 'windows_service')
tags.append('service:{}'.format(service))

self.service_check(self.SERVICE_CHECK_NAME, status, tags=tags)
self.log.debug('service state for %s %s', service, status)

def _get_service_startup_type(self, scm_handle, service_name):
"""
Returns a tuple that describes the startup type for the service
- QUERY_SERVICE_CONFIG.dwStartType
- SERVICE_CONFIG_DELAYED_AUTO_START_INFO.fDelayedAutostart
"""
hSvc = win32service.OpenService(scm_handle, service_name, win32service.SERVICE_QUERY_CONFIG)
service_config = win32service.QueryServiceConfig(hSvc)
startup_type = service_config[1]
if startup_type == win32service.SERVICE_AUTO_START:
# Query if auto start is delayed
is_delayed_auto = win32service.QueryServiceConfig2(
hSvc, win32service.SERVICE_CONFIG_DELAYED_AUTO_START_INFO
)
else:
is_delayed_auto = False
return startup_type, is_delayed_auto

def _get_service_startup_type_tag(self, scm_handle, service_name):
try:
startup_type, is_delayed_auto = self._get_service_startup_type(scm_handle, service_name)
startup_type_string = self.STARTUP_TYPE_TO_STRING.get(startup_type, self.STARTUP_TYPE_UNKNOWN)
if startup_type == win32service.SERVICE_AUTO_START and is_delayed_auto:
startup_type_string = self.STARTUP_TYPE_DELAYED_AUTO
except Exception as e:
self.warning("Failed to query service config for %s: %s", service_name, str(e))
startup_type_string = self.STARTUP_TYPE_UNKNOWN

return startup_type_string
10 changes: 10 additions & 0 deletions windows_service/tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ def test_run(benchmark, instance_all):
check.check(instance_all)

benchmark(check.check, instance_all)


def test_all_startup_type_tag(benchmark, instance_all):
instance_all['windows_service_startup_type_tag'] = True
check = WindowsService('windows_service', {}, [instance_all])

# Run once to get any set up out of the way.
check.check(instance_all)

benchmark(check.check, instance_all)
39 changes: 39 additions & 0 deletions windows_service/tests/test_windows_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,45 @@ def test_basic_disable_service_tag(aggregator, check, instance_basic_disable_ser
)


def test_startup_type(aggregator, check, instance_basic):
instance_basic['windows_service_startup_type_tag'] = True
c = check(instance_basic)
c.check(instance_basic)
aggregator.assert_service_check(
c.SERVICE_CHECK_NAME,
status=c.OK,
tags=[
'service:EventLog',
'windows_service:EventLog',
'windows_service_startup_type:automatic',
'optional:tag1',
],
count=1,
)
aggregator.assert_service_check(
c.SERVICE_CHECK_NAME,
status=c.OK,
tags=[
'service:Dnscache',
'windows_service:Dnscache',
'windows_service_startup_type:automatic',
'optional:tag1',
],
count=1,
)
aggregator.assert_service_check(
c.SERVICE_CHECK_NAME,
status=c.UNKNOWN,
tags=[
'service:NonExistentService',
'windows_service:NonExistentService',
'windows_service_startup_type:unknown',
'optional:tag1',
],
count=1,
)


@pytest.mark.e2e
def test_basic_e2e(dd_agent_check, check, instance_basic):
aggregator = dd_agent_check(instance_basic)
Expand Down

0 comments on commit be04343

Please sign in to comment.