Skip to content

Commit

Permalink
Fix Normalization Rules (#894)
Browse files Browse the repository at this point in the history
* Fix cross agent tests to run from anywhere

* Cover failures in rules engine with testing

Co-authored-by: Lalleh Rafeei <lrafeei@users.noreply.github.com>
Co-authored-by: Hannah Stepanek <hmstepanek@users.noreply.github.com>
Co-authored-by: Uma Annamalai <umaannamalai@users.noreply.github.com>

* Patch metrics not being properly ignored

* Patch normalization rule init default arguments

* Clean up to match other fixture setups

---------

Co-authored-by: Lalleh Rafeei <lrafeei@users.noreply.github.com>
Co-authored-by: Hannah Stepanek <hmstepanek@users.noreply.github.com>
Co-authored-by: Uma Annamalai <umaannamalai@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
5 people committed Aug 16, 2023
1 parent 7d76243 commit f1a673e
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 16 deletions.
21 changes: 21 additions & 0 deletions newrelic/core/rules_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,27 @@


class NormalizationRule(_NormalizationRule):
def __new__(
cls,
match_expression="",
replacement="",
ignore=False,
eval_order=0,
terminate_chain=False,
each_segment=False,
replace_all=False,
):
return _NormalizationRule.__new__(
cls,
match_expression=match_expression,
replacement=replacement,
ignore=ignore,
eval_order=eval_order,
terminate_chain=terminate_chain,
each_segment=each_segment,
replace_all=replace_all,
)

def __init__(self, *args, **kwargs):
self.match_expression_re = re.compile(self.match_expression, re.IGNORECASE)

Expand Down
6 changes: 5 additions & 1 deletion newrelic/core/stats_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,11 @@ def metric_data(self, normalizer=None):

if normalizer is not None:
for key, value in six.iteritems(self.__stats_table):
key = (normalizer(key[0])[0], key[1])
normalized_name, ignored = normalizer(key[0])
if ignored:
continue

key = (normalized_name, key[1])
stats = normalized_stats.get(key)
if stats is None:
normalized_stats[key] = copy.copy(value)
Expand Down
3 changes: 2 additions & 1 deletion tests/cross_agent/test_agent_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def _default_settings():
'browser_monitoring.attributes.exclude': [],
}

FIXTURE = os.path.join(os.curdir, 'fixtures', 'attribute_configuration.json')
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
FIXTURE = os.path.join(CURRENT_DIR, 'fixtures', 'attribute_configuration.json')

def _load_tests():
with open(FIXTURE, 'r') as fh:
Expand Down
3 changes: 2 additions & 1 deletion tests/cross_agent/test_datstore_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
from newrelic.core.database_node import DatabaseNode
from newrelic.core.stats_engine import StatsEngine

FIXTURE = os.path.join(os.curdir,
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
FIXTURE = os.path.join(CURRENT_DIR,
'fixtures', 'datastores', 'datastore_instances.json')

_parameters_list = ['name', 'system_hostname', 'db_hostname',
Expand Down
3 changes: 2 additions & 1 deletion tests/cross_agent/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

import newrelic.common.utilization as u

DOCKER_FIXTURE = os.path.join(os.curdir, 'fixtures', 'docker_container_id')
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
DOCKER_FIXTURE = os.path.join(CURRENT_DIR, 'fixtures', 'docker_container_id')


def _load_docker_test_attributes():
Expand Down
3 changes: 2 additions & 1 deletion tests/cross_agent/test_labels_and_rollups.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@

from testing_support.fixtures import override_application_settings

FIXTURE = os.path.join(os.curdir, 'fixtures', 'labels.json')
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
FIXTURE = os.path.join(CURRENT_DIR, 'fixtures', 'labels.json')

def _load_tests():
with open(FIXTURE, 'r') as fh:
Expand Down
61 changes: 52 additions & 9 deletions tests/cross_agent/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@
import os
import pytest

from newrelic.core.rules_engine import RulesEngine, NormalizationRule
from newrelic.api.application import application_instance
from newrelic.api.background_task import background_task
from newrelic.api.transaction import record_custom_metric
from newrelic.core.rules_engine import RulesEngine

from testing_support.validators.validate_metric_payload import validate_metric_payload

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
FIXTURE = os.path.normpath(os.path.join(
CURRENT_DIR, 'fixtures', 'rules.json'))


def _load_tests():
with open(FIXTURE, 'r') as fh:
js = fh.read()
return json.loads(js)

def _prepare_rules(test_rules):
# ensure all keys are present, if not present set to an empty string
for rule in test_rules:
for key in NormalizationRule._fields:
rule[key] = rule.get(key, '')
return test_rules

def _make_case_insensitive(rules):
# lowercase each rule
Expand All @@ -42,14 +42,14 @@ def _make_case_insensitive(rules):
rule['replacement'] = rule['replacement'].lower()
return rules


@pytest.mark.parametrize('test_group', _load_tests())
def test_rules_engine(test_group):

# FIXME: The test fixture assumes that matching is case insensitive when it
# is not. To avoid errors, just lowercase all rules, inputs, and expected
# values.
insense_rules = _make_case_insensitive(test_group['rules'])
test_rules = _prepare_rules(insense_rules)
test_rules = _make_case_insensitive(test_group['rules'])
rules_engine = RulesEngine(test_rules)

for test in test_group['tests']:
Expand All @@ -66,3 +66,46 @@ def test_rules_engine(test_group):
assert expected == ''
else:
assert result == expected


@pytest.mark.parametrize('test_group', _load_tests())
def test_rules_engine_metric_harvest(test_group):
# FIXME: The test fixture assumes that matching is case insensitive when it
# is not. To avoid errors, just lowercase all rules, inputs, and expected
# values.
test_rules = _make_case_insensitive(test_group['rules'])
rules_engine = RulesEngine(test_rules)

# Set rules engine on core application
api_application = application_instance(activate=False)
api_name = api_application.name
core_application = api_application._agent.application(api_name)
old_rules = core_application._rules_engine["metric"] # save previoius rules
core_application._rules_engine["metric"] = rules_engine

def send_metrics():
# Send all metrics in this test batch in one transaction, then harvest so the normalizer is run.
@background_task(name="send_metrics")
def _test():
for test in test_group['tests']:
# lowercase each value
input_str = test['input'].lower()
record_custom_metric(input_str, {"count": 1})
_test()
core_application.harvest()

try:
# Create a map of all result metrics to validate after harvest
test_metrics = []
for test in test_group['tests']:
expected = (test['expected'] or '').lower()
if expected == '': # Ignored
test_metrics.append((expected, None))
else:
test_metrics.append((expected, 1))

# Harvest and validate resulting payload
validate_metric_payload(metrics=test_metrics)(send_metrics)()
finally:
# Replace original rules engine
core_application._rules_engine["metric"] = old_rules
5 changes: 3 additions & 2 deletions tests/cross_agent/test_rum_client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@
)
from newrelic.api.wsgi_application import wsgi_application

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
FIXTURE = os.path.join(CURRENT_DIR, "fixtures", "rum_client_config.json")

def _load_tests():
fixture = os.path.join(os.curdir, "fixtures", "rum_client_config.json")
with open(fixture, "r") as fh:
with open(FIXTURE, "r") as fh:
js = fh.read()
return json.loads(js)

Expand Down

0 comments on commit f1a673e

Please sign in to comment.