- determine-jobs: trigger on changes to requirements_test.txt too; that
file is hashed into the venv cache key and installed during
restore-python, so a change there can alter the import environment.
- ci.yml: merge the --check and --har steps so we only run
importtime-waterfall once per job (was measuring twice: ~7-8s of
wasted CI time per run). Uses the new script/check_import_time.py
--check --har <path> combination; the HAR reflects the same
measurement that produced the pass/fail decision.
- script/check_import_time.py: refactor the CLI so --har is a standalone
option rather than a mutually-exclusive mode. --check and --update
each accept an optional --har PATH that writes the HAR from the same
subprocess invocation. Plain --har is still supported for local use.
- tests: add tests/script/test_check_import_time.py covering HAR parsing,
root lookup, offender ranking/dedup, budget round-trip, and the three
--check exit paths (pass, regression, missing budget) plus the new
--check --har combined write. Add requirements_test.txt case to the
should_run_import_time parametrized test.
Seeing the top contributors every run — regression or not — gives
reviewers and future-PR authors a running reference for which imports
are currently most expensive. That's the data we need to prioritize
lazy-import refactors, so there's no reason to hide it on pass.
CI measured 126.2ms against 123ms baseline + 25% ceiling (153.8ms). That's
27.6ms of headroom — enough slack that a real regression could hide under
it. Drop to 15% (ceiling 141.5ms, ~15ms headroom over observed). Still
well above GHA runner variance, tight enough to catch the class of
regression we care about (zeroconf-style top-level import additions).
The previous parser walked `-X importtime` stderr by hand (string-splitting
on `|`, indent-width math). Replace it with a load of the HAR JSON that
importtime-waterfall already produces: each entry carries the module name,
self-time, and cumulative — no tree reconstruction needed. Drops the
hand-rolled retry loop too, since importtime-waterfall does best-of-6
internally.
The committed budget (75.2ms) was seeded on a fast local machine; GHA
runners measured 122.8ms, so the first CI run tripped the check. Reset
the baseline to 123ms with a 25% margin (ceiling ~154ms) to absorb GHA
variance. Tighten later once we have several data points.
Adds unit tests for should_run_import_time across the trigger matrix and
wires the new mock into the existing test_main_* suite.
Adds a CI gate that runs `python -X importtime -c "import esphome.__main__"`
via importtime-waterfall's best-of-N harness, compares the root cumulative
time against a checked-in budget (script/import_time_budget.json, seeded
at 75.2ms with a 15% margin), and fails the build when top-level imports
regress.
The CLI pays this cost on every invocation before the requested command
even runs, so silently adding a top-level dep chain (the recent zeroconf
move in #13135 being the motivating case) hurts every user. The check
gives us a signal without waiting for user reports.
- script/check_import_time.py: --check / --update / --har PATH modes.
On regression, prints a ranked top-15 offenders table by self-time.
- script/import_time_budget.json: baseline + margin_pct.
- script/determine-jobs.py: should_run_import_time() gates the job on
esphome/**/*.py, requirements.txt, requirements_dev.txt, pyproject.toml,
or changes to the check itself.
- .github/workflows/ci.yml: new import-time job, runs when gated and
uploads a waterfall HAR artifact (14-day retention) for inspection.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>