-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
178 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# -*- coding: utf-8 -*- | ||
# (C) Datadog, Inc. 2021-present | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
|
||
import time | ||
|
||
required_attrs = [ | ||
'log', | ||
'count', | ||
'gauge', | ||
'histogram', | ||
] | ||
|
||
|
||
def tracked_method(namespace, agent_check_getter=None, track_result_length=False): | ||
""" | ||
Decorates a method to provide debug metrics and logging for troubleshooting. | ||
Tracks execution time, errors, and result length. | ||
The function being decorated must be a method on a class that receives the self pointer. This cannot decorate | ||
plain functions. | ||
If the check has a `debug_stats_kwargs` function, that function is called to get a set of kwargs to pass to | ||
the statsd methods (i.e. histogram, count, gauge, etc). This is useful when specific tags need to be added to | ||
these debug metrics in a standardized way. | ||
:param namespace: the namespace to report as (i.e. if "sqlserver" then all metrics have the "dd.sqlserver.*" prefix) | ||
:param agent_check_getter: a function that gets the agent check from the class. The function must receive only a | ||
single parameter, `self`, and it must return a reference to the agent check. If the function is not provided then | ||
`self` must refer to the agent check. | ||
:param track_result_length: if true, the length of the result is tracked | ||
:return: a decorator | ||
""" | ||
|
||
def decorator(function): | ||
def wrapper(self, *args, **kwargs): | ||
start_time = time.time() | ||
|
||
try: | ||
check = agent_check_getter(self) if agent_check_getter else self | ||
except Exception: | ||
print( | ||
"[{}.{}] invalid dbm_tracked_method. failed to get check reference.".format( | ||
namespace, function.__name__ | ||
) | ||
) | ||
return function(self, *args, **kwargs) | ||
|
||
for attr in required_attrs: | ||
if not hasattr(check, attr): | ||
print( | ||
"[{}.{}] invalid check reference. Missing required attribute {}.".format( | ||
namespace, function.__name__, attr | ||
) | ||
) | ||
return function(self, *args, **kwargs) | ||
|
||
stats_kwargs = {} | ||
if hasattr(check, 'debug_stats_kwargs'): | ||
stats_kwargs = dict(check.debug_stats_kwargs()) | ||
|
||
stats_kwargs['tags'] = stats_kwargs.get('tags', []) + ["operation:{}".format(function.__name__)] | ||
|
||
try: | ||
result = function(self, *args, **kwargs) | ||
|
||
elapsed_ms = (time.time() - start_time) * 1000 | ||
check.histogram( | ||
"dd.{}.operation.time".format(namespace), | ||
elapsed_ms, | ||
**stats_kwargs, | ||
) | ||
|
||
check.log.debug("[%s.%s] operation completed in %s ms", elapsed_ms) | ||
|
||
if track_result_length and result is not None: | ||
check.log.debug("[%s.%s] received result length %s", namespace, function.__name__, len(result)) | ||
check.gauge( | ||
"dd.{}.operation.result.length".format(namespace), | ||
len(result), | ||
**stats_kwargs, | ||
) | ||
|
||
return result | ||
except Exception as e: | ||
check.log.exception("operation %s error", function.__name__) | ||
stats_kwargs['tags'] += ["error:{}".format(type(e))] | ||
check.count( | ||
"dd.{}.operation.error".format(namespace), | ||
1, | ||
**stats_kwargs, | ||
) | ||
raise | ||
|
||
return wrapper | ||
|
||
return decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
# -*- coding: utf-8 -*- | ||
# (C) Datadog, Inc. 2021-present | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
|
||
import pytest | ||
|
||
from datadog_checks.base import AgentCheck | ||
from datadog_checks.base.utils.tracking import tracked_method | ||
|
||
|
||
def agent_check_getter(self): | ||
return self.check | ||
|
||
|
||
class TestAgentCheck(AgentCheck): | ||
def __init__(self, debug_stats_kwargs): | ||
self._debug_stats_kwargs = debug_stats_kwargs | ||
super(TestAgentCheck, self).__init__() | ||
|
||
def debug_stats_kwargs(self): | ||
return self._debug_stats_kwargs | ||
|
||
|
||
class TestJob: | ||
def __init__(self, check): | ||
self.check = check | ||
|
||
def run_job(self): | ||
self.do_work() | ||
self.do_work_return_list() | ||
try: | ||
self.test_tracked_exception() | ||
except Exception: | ||
return | ||
|
||
@tracked_method("hello", agent_check_getter=agent_check_getter) | ||
def do_work(self): | ||
return 5 | ||
|
||
@tracked_method("hello", agent_check_getter=agent_check_getter, track_result_length=True) | ||
def do_work_return_list(self): | ||
return list(range(5)) | ||
|
||
@tracked_method("hello", agent_check_getter=agent_check_getter) | ||
def test_tracked_exception(self): | ||
raise Exception("oops") | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"debug_stats_kwargs", | ||
[ | ||
{}, | ||
{ | ||
"tags": ["hey:there"], | ||
"hostname": "tiberius", | ||
}, | ||
], | ||
) | ||
def test_tracked_method(aggregator, debug_stats_kwargs): | ||
check = TestAgentCheck(debug_stats_kwargs) if debug_stats_kwargs else AgentCheck() | ||
job = TestJob(check) | ||
job.run_job() | ||
|
||
tags = debug_stats_kwargs.pop('tags', []) | ||
hostname = debug_stats_kwargs.pop('hostname', None) | ||
|
||
aggregator.assert_metric("dd.hello.operation.time", hostname=hostname, tags=tags + ["operation:do_work"]) | ||
aggregator.assert_metric( | ||
"dd.hello.operation.time", hostname=hostname, tags=tags + ["operation:do_work_return_list"] | ||
) | ||
aggregator.assert_metric( | ||
"dd.hello.operation.result.length", hostname=hostname, tags=tags + ["operation:do_work_return_list"] | ||
) | ||
aggregator.assert_metric( | ||
"dd.hello.operation.error", | ||
hostname=hostname, | ||
tags=tags + ["operation:test_tracked_exception", "error:<class 'Exception'>"], | ||
) |