Skip to content

Commit

Permalink
Add testing for dimensional metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
TimPansino committed May 11, 2023
1 parent ed33957 commit 459e085
Show file tree
Hide file tree
Showing 5 changed files with 227 additions and 4 deletions.
105 changes: 105 additions & 0 deletions tests/agent_features/test_dimensional_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from newrelic.api.application import application_instance
from newrelic.api.background_task import background_task
from newrelic.api.transaction import record_dimensional_metric, record_dimensional_metrics
from newrelic.common.metric_utils import create_metric_identity

from testing_support.fixtures import reset_core_stats_engine
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
from testing_support.validators.validate_dimensional_metrics_outside_transaction import validate_dimensional_metrics_outside_transaction


_test_tags_examples = [
(None, None),
({}, None),
([], None),
({"str": "a"}, frozenset({("str", "a")})),
({"int": 1}, frozenset({("int", 1)})),
({"float": 1.0}, frozenset({("float", 1.0)})),
({"bool": True}, frozenset({("bool", True)})),
({"list": [1]}, frozenset({("list", "[1]")})),
({"dict": {"subtag": 1}}, frozenset({("dict", "{'subtag': 1}")})),
]


@pytest.mark.parametrize("tags,expected", _test_tags_examples)
def test_create_metric_identity(tags, expected):
name = "Metric"
output_name, output_tags = create_metric_identity(name, tags=tags)
assert output_name == name, "Name does not match."
assert output_tags == expected, "Output tags do not match."


@pytest.mark.parametrize("tags,expected", _test_tags_examples)
def test_record_dimensional_metric_inside_transaction(tags, expected):
@validate_transaction_metrics("test_record_dimensional_metric_inside_transaction", background_task=True, dimensional_metrics=[
("Metric", expected, 1),
])
@background_task(name="test_record_dimensional_metric_inside_transaction")
def _test():
record_dimensional_metric("Metric", 1, tags=tags)

_test()


@pytest.mark.parametrize("tags,expected", _test_tags_examples)
@reset_core_stats_engine()
def test_record_dimensional_metric_outside_transaction(tags, expected):
@validate_dimensional_metrics_outside_transaction([("Metric", expected, 1)])
def _test():
app = application_instance()
record_dimensional_metric("Metric", 1, tags=tags, application=app)

_test()


@pytest.mark.parametrize("tags,expected", _test_tags_examples)
def test_record_dimensional_metrics_inside_transaction(tags, expected):
@validate_transaction_metrics("test_record_dimensional_metrics_inside_transaction", background_task=True, dimensional_metrics=[("Metric/1", expected, 1), ("Metric/2", expected, 1)])
@background_task(name="test_record_dimensional_metrics_inside_transaction")
def _test():
record_dimensional_metrics([("Metric/1", 1, tags), ("Metric/2", 1, tags)])

_test()


@pytest.mark.parametrize("tags,expected", _test_tags_examples)
@reset_core_stats_engine()
def test_record_dimensional_metrics_outside_transaction(tags, expected):
@validate_dimensional_metrics_outside_transaction([("Metric/1", expected, 1), ("Metric/2", expected, 1)])
def _test():
app = application_instance()
record_dimensional_metrics([("Metric/1", 1, tags), ("Metric/2", 1, tags)], application=app)

_test()


def test_dimensional_metrics_different_tags():
@validate_transaction_metrics("test_dimensional_metrics_different_tags", background_task=True, dimensional_metrics=[
("Metric", frozenset({("tag", 1)}), 1),
("Metric", frozenset({("tag", 2)}), 2),
])
@background_task(name="test_dimensional_metrics_different_tags")
def _test():
record_dimensional_metrics([
("Metric", 1, {"tag": 1}),
("Metric", 1, {"tag": 2}),
])
record_dimensional_metric("Metric", 1, {"tag": 2})

_test()
8 changes: 6 additions & 2 deletions tests/agent_unittests/test_harvest_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from newrelic.core.function_node import FunctionNode
from newrelic.core.log_event_node import LogEventNode
from newrelic.core.root_node import RootNode
from newrelic.core.stats_engine import CustomMetrics, SampledDataSet
from newrelic.core.stats_engine import CustomMetrics, SampledDataSet, DimensionalMetrics
from newrelic.core.transaction_node import TransactionNode
from newrelic.network.exceptions import RetryDataForRequest

Expand Down Expand Up @@ -126,6 +126,7 @@ def transaction_node(request):
apdex_t=0.5,
suppress_apdex=False,
custom_metrics=CustomMetrics(),
dimensional_metrics=DimensionalMetrics(),
guid="4485b89db608aece",
cpu_time=0.0,
suppress_transaction_trace=False,
Expand Down Expand Up @@ -818,13 +819,15 @@ def test_flexible_events_harvested(allowlist_event):
app._stats_engine.log_events.add(LogEventNode(1653609717, "WARNING", "A", {}))
app._stats_engine.span_events.add("span event")
app._stats_engine.record_custom_metric("CustomMetric/Int", 1)
app._stats_engine.record_dimensional_metric("DimensionalMetric/Int", 1, tags={"tag": "tag"})

assert app._stats_engine.transaction_events.num_seen == 1
assert app._stats_engine.error_events.num_seen == 1
assert app._stats_engine.custom_events.num_seen == 1
assert app._stats_engine.log_events.num_seen == 1
assert app._stats_engine.span_events.num_seen == 1
assert app._stats_engine.record_custom_metric("CustomMetric/Int", 1)
assert app._stats_engine.record_dimensional_metric("DimensionalMetric/Int", 1, tags={"tag": "tag"})

app.harvest(flexible=True)

Expand All @@ -844,7 +847,8 @@ def test_flexible_events_harvested(allowlist_event):
assert app._stats_engine.span_events.num_seen == num_seen

assert ("CustomMetric/Int", "") in app._stats_engine.stats_table
assert app._stats_engine.metrics_count() > 1
assert ("DimensionalMetric/Int", frozenset({("tag", "tag")})) in app._stats_engine.dimensional_stats_table
assert app._stats_engine.metrics_count() > 3


@pytest.mark.parametrize(
Expand Down
5 changes: 4 additions & 1 deletion tests/testing_support/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ def wrap_shutdown_agent(wrapped, instance, args, kwargs):

def wrap_record_custom_metric(wrapped, instance, args, kwargs):
def _bind_params(name, value, *args, **kwargs):
return name
if isinstance(name, tuple):
return name[0]
else:
return name

metric_name = _bind_params(*args, **kwargs)
if (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy

from testing_support.fixtures import catch_background_exceptions
from newrelic.common.object_wrapper import transient_function_wrapper, function_wrapper


def validate_dimensional_metrics_outside_transaction(dimensional_metrics=None):
dimensional_metrics = dimensional_metrics or []

@function_wrapper
def _validate_wrapper(wrapped, instance, args, kwargs):

record_dimensional_metric_called = []
recorded_metrics = [None]

@transient_function_wrapper("newrelic.core.stats_engine", "StatsEngine.record_dimensional_metric")
@catch_background_exceptions
def _validate_dimensional_metrics_outside_transaction(wrapped, instance, args, kwargs):
record_dimensional_metric_called.append(True)
try:
result = wrapped(*args, **kwargs)
except:
raise
else:
metrics = instance.dimensional_stats_table
# Record a copy of the metric value so that the values aren't
# merged in the future
_metrics = {}
for k, v in metrics.items():
_metrics[k] = copy.copy(v)
recorded_metrics[0] = _metrics

return result

def _validate(metrics, name, tags, count):
key = (name, tags)
metric = metrics.get(key)

def _metrics_table():
out = [""]
out.append("Expected: {0}: {1}".format(key, count))
for metric_key, metric_value in metrics.items():
out.append("{0}: {1}".format(metric_key, metric_value[0]))
return "\n".join(out)

def _metric_details():
return "metric=%r, count=%r" % (key, metric.call_count)

if count is not None:
assert metric is not None, _metrics_table()
if count == "present":
assert metric.call_count > 0, _metric_details()
else:
assert metric.call_count == count, _metric_details()

assert metric.total_call_time >= 0, (key, metric)
assert metric.total_exclusive_call_time >= 0, (key, metric)
assert metric.min_call_time >= 0, (key, metric)
assert metric.sum_of_squares >= 0, (key, metric)

else:
assert metric is None, _metrics_table()

_new_wrapper = _validate_dimensional_metrics_outside_transaction(wrapped)
val = _new_wrapper(*args, **kwargs)
assert record_dimensional_metric_called
metrics = recorded_metrics[0]

record_dimensional_metric_called[:] = []
recorded_metrics[:] = []

for dimensional_metric, dimensional_tags, count in dimensional_metrics:
if isinstance(dimensional_tags, dict):
dimensional_tags = frozenset(dimensional_tags.items())
_validate(metrics, dimensional_metric, dimensional_tags, count)

return val

return _validate_wrapper
20 changes: 19 additions & 1 deletion tests/testing_support/validators/validate_transaction_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ def validate_transaction_metrics(
scoped_metrics=None,
rollup_metrics=None,
custom_metrics=None,
dimensional_metrics=None,
index=-1,
):
scoped_metrics = scoped_metrics or []
rollup_metrics = rollup_metrics or []
custom_metrics = custom_metrics or []
dimensional_metrics = dimensional_metrics or []

if background_task:
unscoped_metrics = [
Expand All @@ -56,6 +58,7 @@ def _validate_wrapper(wrapped, instance, args, kwargs):

record_transaction_called = []
recorded_metrics = []
recorded_dimensional_metrics = []

@transient_function_wrapper("newrelic.core.stats_engine", "StatsEngine.record_transaction")
@catch_background_exceptions
Expand All @@ -74,6 +77,14 @@ def _validate_transaction_metrics(wrapped, instance, args, kwargs):
_metrics[k] = copy.copy(v)
recorded_metrics.append(_metrics)

metrics = instance.dimensional_stats_table
# Record a copy of the metric value so that the values aren't
# merged in the future
_metrics = {}
for k, v in metrics.items():
_metrics[k] = copy.copy(v)
recorded_dimensional_metrics.append(_metrics)

return result

def _validate(metrics, name, scope, count):
Expand Down Expand Up @@ -109,9 +120,11 @@ def _metric_details():
val = _new_wrapper(*args, **kwargs)
assert record_transaction_called
metrics = recorded_metrics[index]
captured_dimensional_metrics = recorded_dimensional_metrics[index]

record_transaction_called[:] = []
recorded_metrics[:] = []
recorded_dimensional_metrics[:] = []

for unscoped_metric in unscoped_metrics:
_validate(metrics, unscoped_metric, "", 1)
Expand All @@ -125,11 +138,16 @@ def _metric_details():
for custom_name, custom_count in custom_metrics:
_validate(metrics, custom_name, "", custom_count)

for dimensional_name, dimensional_tags, dimensional_count in dimensional_metrics:
if isinstance(dimensional_tags, dict):
dimensional_tags = frozenset(dimensional_tags.items())
_validate(captured_dimensional_metrics, dimensional_name, dimensional_tags, dimensional_count)

custom_metric_names = {name for name, _ in custom_metrics}
for name, _ in metrics:
if name not in custom_metric_names:
assert not name.startswith("Supportability/api/"), name

return val

return _validate_wrapper
return _validate_wrapper

0 comments on commit 459e085

Please sign in to comment.