Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Normalization Rules #894

Merged
merged 7 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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