Skip to content

Commit f240dfa

Browse files
committed
fix(perf): use pytest-benchmark for more reliable timers
1 parent 10fdceb commit f240dfa

File tree

3 files changed

+84
-74
lines changed

3 files changed

+84
-74
lines changed

Diff for: poetry.lock

+33-16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ mypy-boto3-secretsmanager = "^1.24.0"
5959
mypy-boto3-ssm = "^1.24.0"
6060
mypy-boto3-appconfig = "^1.24.0"
6161
mypy-boto3-dynamodb = "^1.24.0"
62-
pytest-rerunfailures = "^10.2"
62+
pytest-benchmark = "^3.4.1"
6363

6464

6565
[tool.poetry.extras]

Diff for: tests/performance/test_high_level_imports.py

+50-57
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,95 @@
11
import importlib
2-
import os
3-
import time
4-
from contextlib import contextmanager
52
from types import ModuleType
6-
from typing import Generator, Tuple
3+
from typing import Tuple
74

85
import pytest
96

107
LOGGER_INIT_SLA: float = 0.005
118
METRICS_INIT_SLA: float = 0.005
129
TRACER_INIT_SLA: float = 0.5
1310
IMPORT_INIT_SLA: float = 0.035
11+
PARENT_PACKAGE = "aws_lambda_powertools"
12+
TRACING_PACKAGE = "aws_lambda_powertools.tracing"
13+
LOGGING_PACKAGE = "aws_lambda_powertools.logging"
14+
METRICS_PACKAGE = "aws_lambda_powertools.metrics"
1415

15-
IS_CI = os.getenv("CI", False)
1616

17+
def import_core_utilities() -> Tuple[ModuleType, ModuleType, ModuleType]:
18+
"""Dynamically imports and return Tracing, Logging, and Metrics modules"""
19+
return (
20+
importlib.import_module(TRACING_PACKAGE),
21+
importlib.import_module(LOGGING_PACKAGE),
22+
importlib.import_module(METRICS_PACKAGE),
23+
)
1724

18-
@contextmanager
19-
def timing() -> Generator:
20-
""" "Generator to quickly time operations. It can add 5ms so take that into account in elapsed time
2125

22-
Examples
23-
--------
26+
@pytest.fixture(autouse=True)
27+
def clear_cache():
28+
importlib.invalidate_caches()
2429

25-
with timing() as t:
26-
print("something")
27-
elapsed = t()
28-
"""
29-
start = time.perf_counter()
30-
yield lambda: time.perf_counter() - start # gen as lambda to calculate elapsed time
3130

31+
def import_init_tracer():
32+
tracing = importlib.import_module(TRACING_PACKAGE)
33+
tracing.Tracer(disabled=True)
3234

33-
def core_utilities() -> Tuple[ModuleType, ModuleType, ModuleType]:
34-
"""Return Tracing, Logging, and Metrics module"""
35-
tracing = importlib.import_module("aws_lambda_powertools.tracing")
36-
logging = importlib.import_module("aws_lambda_powertools.logging")
37-
metrics = importlib.import_module("aws_lambda_powertools.metrics")
3835

39-
return tracing, logging, metrics
36+
def import_init_metrics():
37+
metrics = importlib.import_module(METRICS_PACKAGE)
38+
metrics.Metrics()
39+
40+
41+
def import_init_logger():
42+
logging = importlib.import_module(LOGGING_PACKAGE)
43+
logging.Logger()
4044

4145

4246
@pytest.mark.perf
43-
@pytest.mark.flaky(reruns=1, condition=IS_CI)
44-
def test_import_times_ceiling():
47+
@pytest.mark.benchmark(group="core", disable_gc=True, warmup=False)
48+
def test_import_times_ceiling(benchmark):
4549
# GIVEN Core utilities are imported
4650
# WHEN none are used
4751
# THEN import and any global initialization perf should be below 30ms
4852
# though we adjust to 35ms to take into account different CI machines, etc.
4953
# instead of re-running tests which can lead to false positives
50-
with timing() as t:
51-
core_utilities()
52-
53-
elapsed = t()
54-
if elapsed > IMPORT_INIT_SLA:
55-
pytest.fail(f"High level imports should be below ${IMPORT_INIT_SLA}s: {elapsed}")
54+
benchmark.pedantic(import_core_utilities)
55+
stat = benchmark.stats.stats.max
56+
if stat > IMPORT_INIT_SLA:
57+
pytest.fail(f"High level imports should be below {IMPORT_INIT_SLA}s: {stat}")
5658

5759

5860
@pytest.mark.perf
59-
@pytest.mark.flaky(reruns=1, reruns_delay=1, condition=IS_CI)
60-
def test_tracer_init():
61+
@pytest.mark.benchmark(group="core", disable_gc=True, warmup=False)
62+
def test_tracer_init(benchmark):
6163
# GIVEN Tracer is initialized
6264
# WHEN default options are used
6365
# THEN initialization X-Ray SDK perf should be below 450ms
6466
# though we adjust to 500ms to take into account different CI machines, etc.
6567
# instead of re-running tests which can lead to false positives
66-
with timing() as t:
67-
tracing, _, _ = core_utilities()
68-
tracing.Tracer(disabled=True) # boto3 takes ~200ms, and remaining is X-Ray SDK init
69-
70-
elapsed = t()
71-
if elapsed > TRACER_INIT_SLA:
72-
pytest.fail(f"High level imports should be below ${TRACER_INIT_SLA}s: {elapsed}")
68+
benchmark.pedantic(import_init_tracer)
69+
stat = benchmark.stats.stats.max
70+
if stat > TRACER_INIT_SLA:
71+
pytest.fail(f"High level imports should be below {TRACER_INIT_SLA}s: {stat}")
7372

7473

7574
@pytest.mark.perf
76-
@pytest.mark.flaky(reruns=1, reruns_delay=1, condition=IS_CI)
77-
def test_metrics_init():
75+
@pytest.mark.benchmark(group="core", disable_gc=True, warmup=False)
76+
def test_metrics_init(benchmark):
7877
# GIVEN Metrics is initialized
7978
# WHEN default options are used
8079
# THEN initialization perf should be below 5ms
81-
with timing() as t:
82-
_, _, metrics = core_utilities()
83-
metrics.Metrics()
84-
85-
elapsed = t()
86-
if elapsed > METRICS_INIT_SLA:
87-
pytest.fail(f"High level imports should be below ${METRICS_INIT_SLA}s: {elapsed}")
80+
benchmark.pedantic(import_init_metrics)
81+
stat = benchmark.stats.stats.max
82+
if stat > METRICS_INIT_SLA:
83+
pytest.fail(f"High level imports should be below ${METRICS_INIT_SLA}s: {stat}")
8884

8985

9086
@pytest.mark.perf
91-
@pytest.mark.flaky(reruns=1, reruns_delay=1, condition=IS_CI)
92-
def test_logger_init():
87+
@pytest.mark.benchmark(group="core", disable_gc=True, warmup=False)
88+
def test_logger_init(benchmark):
9389
# GIVEN Logger is initialized
9490
# WHEN default options are used
9591
# THEN initialization perf should be below 5ms
96-
with timing() as t:
97-
_, logging, _ = core_utilities()
98-
logging.Logger()
99-
100-
elapsed = t()
101-
if elapsed > LOGGER_INIT_SLA:
102-
pytest.fail(f"High level imports should be below ${LOGGER_INIT_SLA}s: {elapsed}")
92+
benchmark.pedantic(import_init_logger)
93+
stat = benchmark.stats.stats.max
94+
if stat > LOGGER_INIT_SLA:
95+
pytest.fail(f"High level imports should be below ${LOGGER_INIT_SLA}s: {stat}")

0 commit comments

Comments
 (0)