-
Notifications
You must be signed in to change notification settings - Fork 15
[codex] test(telemetry): deduplicate fork-cloned heartbeats #7304
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8468890
test(telemetry): deduplicate fork-cloned heartbeats
pawelchcki cfb4e71
test(telemetry): follow test file naming convention
pawelchcki d1bc20e
Merge branch 'main' into codex/deduplicate-fork-heartbeats
pawelchcki 94f46fd
test(telemetry): unskip heartbeat delay on uwsgi
pawelchcki 34b0516
test(telemetry): guard heartbeat dedup against message-batch collisions
mabdinur a14b892
test(telemetry): remove message-batch regression test
mabdinur 1319851
test(telemetry): exclude short-lived runtimes from heartbeat cadence …
mabdinur cc5c177
test(telemetry): simplify heartbeat comments, fix CI scenario selection
mabdinur File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import itertools | ||
| from collections import defaultdict | ||
| from collections.abc import Iterable | ||
| from typing import Any | ||
|
|
||
| from dateutil.parser import isoparse | ||
|
|
||
|
|
||
| def heartbeat_delays_by_runtime( | ||
| telemetry_data: Iterable[dict[str, Any]], | ||
| *, | ||
| min_lifespan: float = 0.0, | ||
| ) -> tuple[dict[str, list[float]], dict[str, int]]: | ||
| """Return heartbeat delays and heartbeat counts, grouped by runtime ID. | ||
|
|
||
| Expects unflattened telemetry data (`get_telemetry_data(flatten_message_batches=False)`): | ||
| a flattened message-batch entry inherits the outer batch's seq_id, so heartbeats packed | ||
| in the same batch would otherwise collide on (runtime_id, seq_id) alone and get deduped | ||
| into one. | ||
|
|
||
| `min_lifespan` drops runtimes whose heartbeats span less time than that (e.g. a forked | ||
| child that exits after only 2-3 heartbeats). With so few samples, a real (non-duplicate) | ||
| heartbeat sent during shutdown can dominate the average delay -- dropping the runtime is | ||
| the only way to avoid measuring it. A long-lived runtime with the same anomaly is still | ||
| measured, and can still fail. | ||
| """ | ||
| heartbeats_by_runtime: dict[str, dict[tuple[int, int], dict[str, Any]]] = defaultdict(dict) | ||
|
|
||
| for data in telemetry_data: | ||
| content: dict[str, Any] = data["request"]["content"] | ||
| runtime_id: str = content.get("runtime_id", "") | ||
| seq_id: int = content.get("seq_id", 0) | ||
|
|
||
| if content.get("request_type") == "message-batch": | ||
| entries = list(enumerate(content.get("payload", []))) | ||
| else: | ||
| entries = [(0, content)] | ||
|
|
||
| for batch_index, entry in entries: | ||
| if entry.get("request_type") != "app-heartbeat": | ||
| continue | ||
|
|
||
| # Dedup key (seq_id, batch_index) catches two cases: a real retry (identical | ||
| # resend), and a forked child's clone heartbeat. A child briefly shares its | ||
| # parent's runtime_id and copies its seq_id counter at fork time, so it can | ||
| # independently emit a heartbeat with the same seq_id. Not a guaranteed | ||
| # invariant, but the best signal we have to tell "fork clone" apart from | ||
| # "genuinely fast heartbeat" without tracking PIDs. | ||
| heartbeats_by_runtime[runtime_id].setdefault((seq_id, batch_index), data) | ||
|
|
||
| heartbeat_counts = {runtime_id: len(heartbeats) for runtime_id, heartbeats in heartbeats_by_runtime.items()} | ||
| delays_by_runtime: dict[str, list[float]] = {} | ||
|
|
||
| for runtime_id, heartbeats_by_key in heartbeats_by_runtime.items(): | ||
| if len(heartbeats_by_key) <= 2: | ||
| continue | ||
|
|
||
| heartbeats = sorted( | ||
| heartbeats_by_key.values(), | ||
| key=lambda data: isoparse(data["request"]["timestamp_start"]), | ||
| ) | ||
| times = [isoparse(data["request"]["timestamp_start"]) for data in heartbeats] | ||
| lifespan = (times[-1] - times[0]).total_seconds() | ||
| if lifespan < min_lifespan: | ||
| continue | ||
|
|
||
| delays_by_runtime[runtime_id] = [ | ||
| (current - previous).total_seconds() for previous, current in itertools.pairwise(times) | ||
| ] | ||
|
|
||
| return delays_by_runtime, heartbeat_counts | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| from datetime import datetime, timedelta, UTC | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
|
|
||
| from tests.test_telemetry_heartbeat_utils import heartbeat_delays_by_runtime | ||
|
|
||
|
|
||
| pytestmark = pytest.mark.scenario("TEST_THE_TEST") | ||
|
|
||
| BASE_TIME = datetime(2026, 1, 1, tzinfo=UTC) | ||
|
|
||
|
|
||
| def _heartbeat(runtime_id: str, seq_id: int, offset: float) -> dict[str, Any]: | ||
| timestamp = BASE_TIME + timedelta(seconds=offset) | ||
| return { | ||
| "request": { | ||
| "timestamp_start": timestamp.isoformat(), | ||
| "content": { | ||
| "request_type": "app-heartbeat", | ||
| "runtime_id": runtime_id, | ||
| "seq_id": seq_id, | ||
| }, | ||
| }, | ||
| "log_filename": f"{runtime_id}-{seq_id}-{offset}.json", | ||
| } | ||
|
|
||
|
|
||
| def test_fork_duplicate_does_not_affect_parent_cadence() -> None: | ||
| messages = [ | ||
| _heartbeat("parent", 1, 0), | ||
| _heartbeat("parent", 2, 2), | ||
| _heartbeat("parent", 2, 2.05), | ||
| _heartbeat("parent", 3, 4), | ||
| _heartbeat("parent", 4, 6), | ||
| _heartbeat("child", 1, 2.1), | ||
| _heartbeat("child", 2, 4.1), | ||
| ] | ||
| delays, heartbeat_counts = heartbeat_delays_by_runtime(messages) | ||
|
|
||
| assert heartbeat_counts == {"parent": 4, "child": 2} | ||
| assert set(delays) == {"parent"} | ||
| assert delays["parent"] == pytest.approx([2.0, 2.0, 2.0]) | ||
|
|
||
|
|
||
| def test_distinct_fast_heartbeats_remain_measurable() -> None: | ||
| messages = [ | ||
| _heartbeat("parent", 1, 0), | ||
| _heartbeat("parent", 2, 1), | ||
| _heartbeat("parent", 3, 2), | ||
| _heartbeat("parent", 4, 3), | ||
| ] | ||
| delays, heartbeat_counts = heartbeat_delays_by_runtime(messages) | ||
|
|
||
| assert heartbeat_counts == {"parent": 4} | ||
| assert delays["parent"] == pytest.approx([1.0, 1.0, 1.0]) | ||
|
|
||
|
|
||
| def test_short_lived_runtime_excluded_by_min_lifespan() -> None: | ||
| """A short-lived runtime with too few samples shouldn't be measured at all: a shutdown | ||
| heartbeat right before exit isn't a duplicate, so dedup can't drop it, and with only | ||
| 2-3 samples it would skew the average. | ||
| """ | ||
| messages = [ | ||
| _heartbeat("parent", 1, 0), | ||
| _heartbeat("parent", 2, 2), | ||
| _heartbeat("parent", 3, 4), | ||
| _heartbeat("parent", 4, 6), | ||
| _heartbeat("parent", 5, 8), | ||
| # child lives ~2.05s: two normal-ish heartbeats, then an out-of-cadence one | ||
| # right before it exits | ||
| _heartbeat("child", 1, 0.1), | ||
| _heartbeat("child", 2, 2.05), | ||
| _heartbeat("child", 3, 2.12), | ||
| ] | ||
| delays, heartbeat_counts = heartbeat_delays_by_runtime(messages, min_lifespan=6.0) | ||
|
|
||
| assert heartbeat_counts == {"parent": 5, "child": 3} | ||
| assert set(delays) == {"parent"} | ||
|
|
||
|
|
||
| def test_long_lived_runtime_anomaly_still_measured() -> None: | ||
| """min_lifespan only excludes runtimes too short-lived to measure. A long-lived runtime | ||
| with the same anomalous fast heartbeat is still measured, and can still fail. | ||
| """ | ||
| messages = [ | ||
| _heartbeat("parent", 1, 0), | ||
| _heartbeat("parent", 2, 2), | ||
| _heartbeat("parent", 3, 4), | ||
| _heartbeat("parent", 4, 6), | ||
| _heartbeat("parent", 5, 8), | ||
| _heartbeat("parent", 6, 8.05), # anomalous fast heartbeat, but lifespan is 8.05s | ||
| ] | ||
| delays, heartbeat_counts = heartbeat_delays_by_runtime(messages, min_lifespan=6.0) | ||
|
|
||
| assert heartbeat_counts == {"parent": 6} | ||
| assert delays["parent"] == pytest.approx([2.0, 2.0, 2.0, 2.0, 0.05]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.