Skip to content

Commit

Permalink
Sy/om type override (#18054)
Browse files Browse the repository at this point in the history
* Fix tests for openmetrics counter transformer

* Try dropping the '_total' restriction on metric samples

* add type override examples

* add test for type_override

* stricter test

* changelog

* remove f string

* remove f string

* spacing

* lint

* Update datadog_checks_base/changelog.d/18054.added

Co-authored-by: Ilia Kurenkov <ilia.kurenkov@datadoghq.com>

* Update datadog_checks_base/tests/base/checks/openmetrics/test_v2/test_transformers/test_type_override.py

Co-authored-by: Ilia Kurenkov <ilia.kurenkov@datadoghq.com>

* Update datadog_checks_base/tests/base/checks/openmetrics/test_v2/test_transformers/test_type_override.py

Co-authored-by: Ilia Kurenkov <ilia.kurenkov@datadoghq.com>

* Update datadog_checks_base/tests/base/checks/openmetrics/test_v2/test_transformers/test_type_override.py

Co-authored-by: Ilia Kurenkov <ilia.kurenkov@datadoghq.com>

* Update test_type_override.py

* lint

---------

Co-authored-by: Ilia Kurenkov <ilia.kurenkov@datadoghq.com>
  • Loading branch information
steveny91 and iliakur authored Jul 17, 2024
1 parent c4051b2 commit 1e1a1f7
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 93 deletions.
1 change: 1 addition & 0 deletions datadog_checks_base/changelog.d/18054.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow untyped metrics that we coerce to `counter` to be collected regardless if they have `_total` or not.
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ def counter(metric, sample_data, runtime_data):
flush_first_value = runtime_data['flush_first_value']

for sample, tags, hostname in sample_data:
if sample.name.endswith('_total'):
monotonic_count_method(
metric_name,
sample.value,
tags=tags,
hostname=hostname,
flush_first_value=flush_first_value,
)
monotonic_count_method(
metric_name,
sample.value,
tags=tags,
hostname=hostname,
flush_first_value=flush_first_value,
)

del check
del modifiers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,107 +11,38 @@


def test_basic(aggregator, dd_run_check, mock_http_response):
mock_http_response(
"""
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total counter
go_memstats_alloc_bytes_total 9.339544592e+09
# HELP go_memstats_frees_total Total number of frees.
# TYPE go_memstats_frees_total counter
go_memstats_frees_total 1.28219257e+08
"""
)
check = get_check({'metrics': ['.+']})
dd_run_check(check)

aggregator.assert_metric(
'test.go_memstats_alloc_bytes.count', 9339544592, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
)
aggregator.assert_metric(
'test.go_memstats_frees.count', 128219257, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
)

aggregator.assert_all_metrics_covered()


def test_basic_incorrect_suffix(aggregator, dd_run_check, mock_http_response):
"""
We won't collect counter metrics unless they end in '_total'.
Metrics with type 'counter' in the raw payload get collected.
We commonly see metrics that end in '_count' or ones that have no meaningful suffix at all.
Here we make sure that by default these won't be collected.
If they end in '_total' that suffix is dropped.
"""

mock_http_response(
"""
# HELP go_memstats_alloc_bytes_count Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_count counter
go_memstats_alloc_bytes_total 9.339544592e+09
# HELP go_memstats_frees Total number of frees.
# TYPE go_memstats_frees counter
go_memstats_frees_total 1.28219257e+08
# HELP foo_total Example of '_total' getting dropped
# TYPE foo_total counter
foo_total 9.339544592e+09
# HELP bar_count Example of '_count' not getting dropped
# TYPE bar_count counter
bar_count 1.28219257e+08
# HELP baz Example that doesn't end in '_total' nor '_count'
# TYPE baz counter
baz 1.28219257e+08
"""
)
check = get_check({'metrics': ['.+']})
dd_run_check(check)

assert not aggregator.metrics('test.go_memstats_alloc_bytes.count')
assert not aggregator.metrics('test.go_memstats_frees.count')
aggregator.assert_all_metrics_covered()


def test_untyped_correct_suffix(aggregator, dd_run_check, mock_http_response):
"""
We can force a metric that is 'untyped' in the raw payload to a counter as long as it ends in '_total'.
"""

mock_http_response(
"""
# HELP go_memstats_alloc_bytes_total Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_total untyped
go_memstats_alloc_bytes_total 9.339544592e+09
"""
)
check = get_check(
{'metrics': [{'go_memstats_alloc_bytes_total': {'name': 'go_memstats_alloc_bytes', 'type': 'counter'}}]}
)
dd_run_check(check)

aggregator.assert_metric(
'test.go_memstats_alloc_bytes.count', 9339544592, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
'test.foo.count', 9339544592, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
)
aggregator.assert_all_metrics_covered()


def test_untyped_incorrect_suffix(aggregator, dd_run_check, mock_http_response):
"""
Forcing an untyped metric as a counter won't work without it ending in '_total'.
"""

mock_http_response(
"""
# HELP go_memstats_alloc_bytes_count Total number of bytes allocated, even if freed.
# TYPE go_memstats_alloc_bytes_count counter
go_memstats_alloc_bytes_total 9.339544592e+09
# HELP go_memstats_frees Total number of frees.
# TYPE go_memstats_frees counter
go_memstats_frees_total 1.28219257e+08
"""
aggregator.assert_metric(
'test.bar_count.count', 128219257, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
)
check = get_check(
{
'metrics': [
{
'go_memstats_alloc_bytes': {'name': 'go_memstats_alloc_bytes', 'type': 'counter'},
'go_memstats_frees': {'name': 'go_memstats_frees', 'type': 'counter'},
}
]
}
aggregator.assert_metric(
'test.baz.count', 128219257, metric_type=aggregator.MONOTONIC_COUNT, tags=['endpoint:test']
)
dd_run_check(check)

assert not aggregator.metrics('test.go_memstats_alloc_bytes.count')
assert not aggregator.metrics('test.go_memstats_frees.count')
aggregator.assert_all_metrics_covered()


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# (C) Datadog, Inc. 2024-present
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)
import pytest

from datadog_checks.dev.testing import requires_py3

from ..utils import get_check

pytestmark = [requires_py3]


@pytest.mark.parametrize(
'metric_type',
[
'counter',
'gauge',
],
)
def test_untyped_counter(aggregator, dd_run_check, mock_http_response, metric_type):
"""
The Agent's OpenMetrics parser uses the # TYPE line to determine the type of a metric. However, when it encounters
an untyped metric, the Agent won't be able to derive the metric type, and the metric will be skipped. We can,
however, force the Agent to collect it as a specific type. In this scenario, the Agent will ignore the metadata
lines # HELP and # TYPE and will instead only look at the metric sample name.
Below are some of the scenarios we might encounter:
1. The metricset and metric sample does not end in '_total' and is untyped.
2. The metricset and metric sample does end in '_total' and is untyped.
3. The metricset does not end in '_total' and metric sample end in '_total' and is untyped.
4. The metricset and metric samples don't match
5. The metricset and metric samples are a non counter/gauge type
The test will show how these can be configured to be collected as gauge or counters. The metric sample just
has to match what the agent is looking for. The agent will then collect the metric as a we config
provided type. Only testing for counter and gauge here because these are the only 2 types I see we can accurately
collect with overrides.
"""

mock_http_response(
"""
# HELP foo The metricset and metric sample does not end in '_total' and is untyped.
# TYPE foo untyped
foo 0
# HELP bar_total The metricset and metric sample does end in '_total' and is untyped.
# TYPE bar_total untyped
bar_total 1
# HELP baz The metricset does not end in '_total' and metric sample end in '_total' and is untyped.
# TYPE baz untyped
baz_total 2
# HELP qux The metricset and metric samples don't match
# TYPE qux untyped
fiz 3
# HELP bux The metricset and metric samples are a non counter type
# TYPE bux histogram
bux 4
"""
)
check = get_check(
{
'metrics': [
{
'foo': {'name': 'foo', 'type': metric_type},
'bar_total': {'name': 'bar', 'type': metric_type},
'baz_total': {'name': 'baz', 'type': metric_type},
'fiz': {'name': 'fiz', 'type': metric_type},
'bux': {'name': 'bux', 'type': metric_type},
}
]
}
)
dd_run_check(check)

metrics = ["test.foo", "test.bar", "test.baz", "test.fiz", "test.bux"]

for metric in metrics:
aggregator.assert_metric(
'{}.count'.format(metric) if metric_type == 'counter' else metric,
metrics.index(metric),
metric_type=aggregator.MONOTONIC_COUNT if metric_type == 'counter' else aggregator.GAUGE,
tags=['endpoint:test'],
)

aggregator.assert_all_metrics_covered()

0 comments on commit 1e1a1f7

Please sign in to comment.