Skip to content

Commit 4168c69

Browse files
roypatmdhadukLDagnachew
committed
test: add test to ensure we do not have unused metrics
Add a test that fails if there are metrics that are never incremented in production code (e.g. outside of aggregation code and constructors). Closes #5182 Co-authored-by: Milan Dhaduk <mdhaduk7@gmail.com> Signed-off-by: Milan Dhaduk <mdhaduk7@gmail.com> Co-authored-by: LDagnachew <leulmdagnachew@gmail.com> Signed-off-by: LDagnachew <leulmdagnachew@gmail.com> Signed-off-by: Patrick Roy <roypat@amazon.co.uk>
1 parent 825fd8a commit 4168c69

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

tests/host_tools/fcmetrics.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import jsonschema
1919
import pytest
2020

21+
from framework import utils
2122
from framework.properties import global_props
23+
from framework.utils_repo import git_repo_files
2224
from host_tools.metrics import get_metrics_logger
2325

2426

@@ -563,3 +565,61 @@ def run(self):
563565
time.sleep(1)
564566
if self.running is False:
565567
break
568+
569+
570+
def find_metrics_files():
571+
"""Gets a list of all Firecracker sources files ending with 'metrics.rs'"""
572+
return list(git_repo_files(root="..", glob="*metrics.rs"))
573+
574+
575+
def extract_fields(file_path):
576+
"""Gets a list of all metrics defined in the given file, in the form tuples (name, type)"""
577+
fields = utils.run_cmd(
578+
rf'grep -Po "(?<=pub )(\w+): (Shared(?:Inc|Store)Metric|LatencyAggregateMetrics)" {file_path}'
579+
).stdout.strip()
580+
581+
return [field.split(": ", maxsplit=1) for field in fields.splitlines()]
582+
583+
584+
def is_file_production(filepath):
585+
"""Returns True iff accesses to metric fields in the given file should cause the metric be considered 'used in production code'. Excludes, for example, files in which the metrics are defined, where accesses happen as part of copy constructors, etc."""
586+
path = filepath.lower()
587+
return (
588+
"/test/" in path
589+
or "/tests/" in path
590+
or path.endswith("_test.rs")
591+
or "test_" in path
592+
or "tests.rs" in path
593+
or ("metrics.rs" in path and "vmm" in path)
594+
)
595+
596+
597+
KNOWN_FALSE_POSITIVES = [
598+
"min_us",
599+
"max_us",
600+
"sum_us",
601+
"process_startup_time_us",
602+
"process_startup_time_cpu_us",
603+
]
604+
605+
606+
def is_metric_used(field, field_type):
607+
"""Returns True iff the given metric has a production use in the firecracker codebase"""
608+
if field in KNOWN_FALSE_POSITIVES:
609+
return True
610+
611+
if field_type in ("SharedIncMetric", "SharedStoreMetric"):
612+
pattern = rf"{field}\s*\.\s*store|{field}\s*\.\s*inc|{field}\s*\.\s*add|{field}\s*\.\s*fetch|METRICS.*{field}"
613+
elif field_type == "LatencyAggregateMetrics":
614+
pattern = rf"{field}\s*\.\s*record_latency_metrics"
615+
else:
616+
raise RuntimeError(f"Unknown metric type: {field_type}")
617+
618+
result = utils.run_cmd(f'grep -RPzo "{pattern}" ../src')
619+
620+
for line in result.stdout.strip().split("\0"):
621+
if not line:
622+
continue
623+
if not is_file_production(line.split(":", maxsplit=1)[0]):
624+
return True
625+
return False

tests/integration_tests/style/test_rust.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""Tests ensuring codebase style compliance for Rust."""
4+
from collections import defaultdict
45

56
from framework import utils
7+
from host_tools.fcmetrics import extract_fields, find_metrics_files, is_metric_used
68

79

810
def test_rust_order():
@@ -24,3 +26,34 @@ def test_rust_style():
2426

2527
# rustfmt prepends `"Diff in"` to the reported output.
2628
assert "Diff in" not in stdout
29+
30+
31+
def test_unused_metrics():
32+
"""Tests that all metrics defined in Firecracker's metrics.rs files actually have code
33+
paths that increment them."""
34+
metrics_files = find_metrics_files()
35+
unused = defaultdict(list)
36+
37+
assert metrics_files
38+
39+
for file_path in metrics_files:
40+
fields = extract_fields(file_path)
41+
if not fields:
42+
continue
43+
44+
for field, ty in fields:
45+
if not is_metric_used(field, ty):
46+
unused[file_path].append((field, ty))
47+
48+
# Grouped output
49+
for file_path, fields in unused.items():
50+
print(f"📄 Defined in: {file_path}")
51+
print("Possibly Unused: \n")
52+
for field, field_type in fields:
53+
print(f" ❌ {field} ({field_type})")
54+
55+
print()
56+
57+
assert (
58+
not unused
59+
), "Unused metrics founds, see stdout. Please either hook them up, or remove them"

0 commit comments

Comments
 (0)