Skip to content

[1/4] feat(power): add the DCGM power artifact data layer - #288

Merged
ishandhanani merged 10 commits into
NVIDIA:mainfrom
edwingao28:ladder/a1a-power-artifacts
Aug 11, 2026
Merged

ishandhanani merged 10 commits into
NVIDIA:mainfrom
edwingao28:ladder/a1a-power-artifacts

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

Data layer for multinode DCGM power artifacts: the file formats, parsing, and validation that a follow-up collector runtime writes and an offline validator audits. No runtime behavior changes — nothing imports this package yet; it lands first so the collector PR stays reviewable.

Contents (src/srtctl/core/power/)

  • contract.py — schema version, filenames, reason codes, sample-gap limit (MAX_SAMPLE_GAP_SECONDS), finite-number checks. Single source of truth for the on-disk contract.
  • parser.py — Prometheus scrape → per-GPU power readings via prometheus_client.parser (new dependency prometheus-client>=0.20.0; parsing the exposition format by hand is strictly worse).
  • samples.py — append-only CSV sample stream: writer, reader, observed-device derivation.
  • topology.py — expected-device construction from the Slurm process topology; device identity keys; role/het-group resolution.
  • manifest.pymanifest.json writer: producer identity, expected windows, validation rollup, atomic writes.
  • windows.py — measurement-window validation: coverage against the sample stream, gap checks, running→interrupted conversion.

Evidence

The full series (this contract plus the collector and windows that follow) has been running in production CI on real multinode deployments:

  • GB200 and GB300 1P1D disaggregated deployments (4 prefill + 4 decode GPUs), N=3 independent full concurrency ladders (c1–c128) per platform; 8/8 measurement windows strictly valid in every run.
  • Reproducibility across the N=3 runs: J/output-token CV ≤ 1.59% (GB200), ≤ 0.67% (GB300).
  • The exact tree this series is cut from also passed a GB200 canary where the manifest's stored rollup was recomputed offline from raw artifacts and agreed to float precision.

Happy to attach a sample artifact bundle (samples CSV + windows + manifest) to the PR if useful for review.

Tests

tests/test_power_artifacts.py — 45 tests over the contract: round-trips, malformed-input reasons, device-set mismatches. Window coverage/gap edge cases are exercised by the consumers' suites in [3/4] and [4/4], which drive the real validate_expected_windows (including boolean-typed concurrency/schema_version traps — bool is an int subclass and must not key as concurrency 1).

pytest tests/ passes (same 3 pre-existing environment-dependent failures as main on my machine: fingerprint CPU probe, apply_mock CPU allocation, mcp_spec explain-field).

Series

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
@edwingao28
edwingao28 force-pushed the ladder/a1a-power-artifacts branch from f45c9a2 to 203fa17 Compare August 5, 2026 20:58
@FrankD412

Copy link
Copy Markdown
Collaborator

@edwingao28 -- thanks for the PR. I'm taking a look and running initial reviews. I'm planning to have feedback for you sometime Monday or early on Tuesday; I'm feeling under the weather today. Does that timeline work for you?

This includes #288 #289 #290 #291

@edwingao28

Copy link
Copy Markdown
Contributor Author

@edwingao28 -- thanks for the PR. I'm taking a look and running initial reviews. I'm planning to have feedback for you sometime Monday or early on Tuesday; I'm feeling under the weather today. Does that timeline work for you?

This includes #288 #289 #290 #291

Yes, that timeline works for me. Thanks for taking a look, and hope you feel better soon!

@FrankD412

Copy link
Copy Markdown
Collaborator

Code Review

Confirmed Bugs

windows.py (_scan) — When 3+ window files share the same (benchmark_type, concurrency) key, the first file (A) ends up in artifact_errors twice. When B arrives, both A and B are correctly flagged as MEASUREMENT_WINDOW_DUPLICATE. When C arrives, parsed[key] still holds A (the continue never updates it), so A is flagged a second time. Fix: pop A from parsed immediately on the first duplicate detection instead of deferring to the post-loop cleanup.

parser.py (parse_power_scrape) — A prometheus text-format parse error (ValueError from text_string_to_metric_families) is reported as Reason.ENDPOINT_HTTP_ERROR. That reason code means "HTTP transport failed" — operators will chase network/port configuration instead of the exporter's output format. A separate PARSE_ERROR (or similar) reason code is needed.

samples.py (_has_non_monotonic_device) — The non-monotonic check uses <= instead of <. Equal consecutive timestamps — possible at 1 Hz on a 1-second-resolution clock — satisfy the condition and produce a false TIMESTAMP_NON_MONOTONIC, marking valid artifacts as invalid.

Plausible Bugs

parser.py (parse_power_scrape) — The except ValueError catch on list(text_string_to_metric_families(text)) is too narrow. Non-ValueError exceptions from internal parser state (e.g. IndexError, KeyError) or a future prometheus_client version escape uncaught to the collector thread and crash it rather than recording a reason code and continuing.

samples.py (SampleWriter.__init__) — If writerow or flush raises after open() succeeds (e.g. disk full), self._handle is never closed. The class has no __del__, __enter__/__exit__, or except clause in __init__ to handle this. Under repeated retries the process could hit EMFILE.

contract.py (atomic_write_json) — If os.fdopen raises after mkstemp succeeds (e.g. at the EMFILE boundary), the raw fd is leaked. The except BaseException block unlinks the temp path but never calls os.close(fd).

Minor

windows.py (_check_coverage)if device.gpu_uuids else "" in the gap key f-string is dead code. The len(device.gpu_uuids) != 1 guard above guarantees exactly one UUID at that point. Should be removed to avoid confusion if the guard is ever refactored.

manifest.py (mark_terminal) — The docstring says "Freeze lifecycle state" but mark_terminal itself has no guard against a second call. In practice this is safe because stop_and_finalize (the only caller path) has its own if self._outcome is not None: return guard — but the protection lives one level up, not in the method that claims to freeze. Worth either adding a guard to mark_terminal directly or updating the docstring to reflect where the invariant is actually enforced.

Integration Gap (series-level)

None of the four PRs in this series (#287#291) touch the vendored benchmark_serving.py at src/srtctl/benchmarks/scripts/sa-bench/benchmark_serving.py. The MeasurementWindow stamping integration exists only on the dev branch. As-is, any power-enabled run on main after this series lands will produce MEASUREMENT_WINDOW reason codes and be publication-invalid — the production evidence (8/8 valid windows) came from that dev branch integration, not anything shipping here.

Is there a tracking issue for the stamping integration? And is there a plan to block required mode publication until it lands?

@FrankD412

FrankD412 commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Oh -- I hadn't seen the note about vendor integration being a follow-on where eventually it seems like srt-slurm will pull from a central location (from #290). I think that means the integration gap critique is moot.

@edwingao28

Copy link
Copy Markdown
Contributor Author

Oh -- I hadn't seen the note about vendor integration being a follow-on where eventually it seems like srt-slurm will pull from a central location (from #290). I think that means the integration gap critique is moot.

Thanks for confirming. Yes, that is the intended split: this series lands the artifact contract, collector, measurement-window plumbing, and validator, while the benchmark-side adapter will follow the migration to the centrally sourced benchmark implementation

@edwingao28

edwingao28 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Code Review

Confirmed Bugs

windows.py (_scan) — When 3+ window files share the same (benchmark_type, concurrency) key, the first file (A) ends up in artifact_errors twice. When B arrives, both A and B are correctly flagged as MEASUREMENT_WINDOW_DUPLICATE. When C arrives, parsed[key] still holds A (the continue never updates it), so A is flagged a second time. Fix: pop A from parsed immediately on the first duplicate detection instead of deferring to the post-loop cleanup.

parser.py (parse_power_scrape) — A prometheus text-format parse error (ValueError from text_string_to_metric_families) is reported as Reason.ENDPOINT_HTTP_ERROR. That reason code means "HTTP transport failed" — operators will chase network/port configuration instead of the exporter's output format. A separate PARSE_ERROR (or similar) reason code is needed.

samples.py (_has_non_monotonic_device) — The non-monotonic check uses <= instead of <. Equal consecutive timestamps — possible at 1 Hz on a 1-second-resolution clock — satisfy the condition and produce a false TIMESTAMP_NON_MONOTONIC, marking valid artifacts as invalid.

Plausible Bugs

parser.py (parse_power_scrape) — The except ValueError catch on list(text_string_to_metric_families(text)) is too narrow. Non-ValueError exceptions from internal parser state (e.g. IndexError, KeyError) or a future prometheus_client version escape uncaught to the collector thread and crash it rather than recording a reason code and continuing.

samples.py (SampleWriter.__init__) — If writerow or flush raises after open() succeeds (e.g. disk full), self._handle is never closed. The class has no __del__, __enter__/__exit__, or except clause in __init__ to handle this. Under repeated retries the process could hit EMFILE.

contract.py (atomic_write_json) — If os.fdopen raises after mkstemp succeeds (e.g. at the EMFILE boundary), the raw fd is leaked. The except BaseException block unlinks the temp path but never calls os.close(fd).

Minor

windows.py (_check_coverage)if device.gpu_uuids else "" in the gap key f-string is dead code. The len(device.gpu_uuids) != 1 guard above guarantees exactly one UUID at that point. Should be removed to avoid confusion if the guard is ever refactored.

manifest.py (mark_terminal) — The docstring says "Freeze lifecycle state" but mark_terminal itself has no guard against a second call. In practice this is safe because stop_and_finalize (the only caller path) has its own if self._outcome is not None: return guard — but the protection lives one level up, not in the method that claims to freeze. Worth either adding a guard to mark_terminal directly or updating the docstring to reflect where the invariant is actually enforced.

Integration Gap (series-level)

None of the four PRs in this series (#287#291) touch the vendored benchmark_serving.py at src/srtctl/benchmarks/scripts/sa-bench/benchmark_serving.py. The MeasurementWindow stamping integration exists only on the dev branch. As-is, any power-enabled run on main after this series lands will produce MEASUREMENT_WINDOW reason codes and be publication-invalid — the production evidence (8/8 valid windows) came from that dev branch integration, not anything shipping here.

Is there a tracking issue for the stamping integration? And is there a plan to block required mode publication until it lands?

Thanks for the detailed review @FrankD412 . I addressed all eight code findings in fca5e2b:

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
Normalize malformed exposition failures across the supported prometheus-client range, including IndexError from 0.20.x, while leaving BaseException control flow untouched.

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
Reject boolean and non-finite result timing fields before comparing them with the completed measurement window.

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
Reject regular files with non-JSON suffixes during the exhaustive window scan instead of accepting them as valid v1 window records.

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
@FrankD412

Copy link
Copy Markdown
Collaborator

Code Review (second pass)

Correctness Bugs

windows.py (convert_running_windows)_scan guards against a symlinked windows/ directory via _stays_below, which resolves the path and verifies it stays within the power artifact tree. convert_running_windows has no equivalent guard — windows_dir.is_dir() follows symlinks by default, so if windows/ is a symlink to an arbitrary directory, convert_running_windows will atomically overwrite any .json files found there. In a SLURM environment where the benchmark child runs as the same user, a malicious or misconfigured child could replace windows/ with a symlink before teardown. Fix: add the same _stays_below check at the top of convert_running_windows, mirroring _scan.

parser.py (parse_power_scrape)saw_power_sample is set to True before the MIG instance filter, so a node where every GPU is in MIG mode never gets POWER_METRIC_MISSING:

saw_power_sample = True          # set here, before MIG check
if "GPU_I_ID" in sample.labels or "GPU_I_PROFILE" in sample.labels:
    reasons.append(Reason.MIG_INSTANCE_UNSUPPORTED)
    continue                     # sample discarded

Every scrape cycle returns readings=() with only MIG_INSTANCE_UNSUPPORTED. From the manifest's perspective this is indistinguishable from a transient case where a few MIG instances appeared — there's no signal that this node will structurally never contribute whole-GPU power readings. Fix: move saw_power_sample = True to after the MIG check, or use a separate saw_non_mig_power_sample flag, so that an all-MIG node returns both MIG_INSTANCE_UNSUPPORTED and POWER_METRIC_MISSING.

topology.py (resolve_roles, resolve_het_groups) — Both functions use len(distinct) != 1 to detect a device assigned to conflicting roles/het-groups. This correctly catches len == 2+ (genuine conflict), but also fires on len == 0 (empty assignments), which is a misconfiguration, not a conflict. Since validate_devices calls both, an ExpectedDevice with assignments=() produces both CONFLICTING_WORKER_ROLES and CONFLICTING_HET_GROUPS simultaneously — a two-code error that directs debugging toward a scheduling conflict that doesn't exist. Fix:

if len(distinct) == 0:
    return {}, (Reason.NO_DEVICE_ASSIGNMENT,)  # or equivalent
if len(distinct) != 1:
    return {}, (Reason.CONFLICTING_WORKER_ROLES,)

manifest.py (PowerManifest)mark_terminal's double-call guard (if self.status in TERMINAL_STATUSES: raise RuntimeError) is bypassed by direct field assignment. PowerManifest is not frozen, and the orchestrator already writes manifest.status = STATUS_RUNNING directly. Any code path that directly sets manifest.status to a non-terminal value after mark_terminal has committed resets the guard, allowing a second mark_terminal call to silently overwrite stopped_at_unix and publication_valid.

Fragility

windows.py (_bracketing_sequence) — The function relies on times being sorted ascending: before[-1] is the closest sample at or before start, and after[0] is the closest at or after end — but only if the sequence is ordered. The type signature is Sequence[float] with no ordering contract. derive_observed_devices currently provides tuple(sorted(times)) which satisfies this, but there's no assertion or documented invariant. If the call site changes to preserve insertion/scrape-sequence order, gap calculations silently produce wrong results with no error.

samples.py (SampleWriter) — The file handle is opened in __init__ but the class has no __enter__/__exit__. Resource safety depends entirely on every caller explicitly calling close(). A context manager would enforce this at the type level and prevent handle leaks on early exit between construction and the finally block.

Test Coverage

tests/test_power_artifacts.pyconvert_running_windows is not imported in the test file and has no happy-path test. The only related test exercises the OSError path. A regression in the conversion logic (wrong key written, wrong status checked, silent atomic-write failure) would go undetected. Per CLAUDE.md: "when we make a new significant feature change, we should always add a new test."

Simplification

windows.py_is_strict_int (the isinstance(value, int) and not isinstance(value, bool) bool-exclusion pattern) duplicates logic already present in contract.py's is_finite_number. Should be consolidated into contract.py and imported.

windows.py_stays_below is is_safe_relative_subpath's filesystem-resolving companion and belongs in contract.py alongside it, rather than as a private utility in windows.py. Any future module needing symlink-safe path resolution would otherwise have to either import from windows.py (wrong coupling direction) or copy the function.

Signed-off-by: Wenyao Gao <wgao11@u.rochester.edu>
@edwingao28

edwingao28 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Code Review (second pass)

Correctness Bugs

windows.py (convert_running_windows)_scan guards against a symlinked windows/ directory via _stays_below, which resolves the path and verifies it stays within the power artifact tree. convert_running_windows has no equivalent guard — windows_dir.is_dir() follows symlinks by default, so if windows/ is a symlink to an arbitrary directory, convert_running_windows will atomically overwrite any .json files found there. In a SLURM environment where the benchmark child runs as the same user, a malicious or misconfigured child could replace windows/ with a symlink before teardown. Fix: add the same _stays_below check at the top of convert_running_windows, mirroring _scan.

parser.py (parse_power_scrape)saw_power_sample is set to True before the MIG instance filter, so a node where every GPU is in MIG mode never gets POWER_METRIC_MISSING:

saw_power_sample = True          # set here, before MIG check
if "GPU_I_ID" in sample.labels or "GPU_I_PROFILE" in sample.labels:
    reasons.append(Reason.MIG_INSTANCE_UNSUPPORTED)
    continue                     # sample discarded

Every scrape cycle returns readings=() with only MIG_INSTANCE_UNSUPPORTED. From the manifest's perspective this is indistinguishable from a transient case where a few MIG instances appeared — there's no signal that this node will structurally never contribute whole-GPU power readings. Fix: move saw_power_sample = True to after the MIG check, or use a separate saw_non_mig_power_sample flag, so that an all-MIG node returns both MIG_INSTANCE_UNSUPPORTED and POWER_METRIC_MISSING.

topology.py (resolve_roles, resolve_het_groups) — Both functions use len(distinct) != 1 to detect a device assigned to conflicting roles/het-groups. This correctly catches len == 2+ (genuine conflict), but also fires on len == 0 (empty assignments), which is a misconfiguration, not a conflict. Since validate_devices calls both, an ExpectedDevice with assignments=() produces both CONFLICTING_WORKER_ROLES and CONFLICTING_HET_GROUPS simultaneously — a two-code error that directs debugging toward a scheduling conflict that doesn't exist. Fix:

if len(distinct) == 0:
    return {}, (Reason.NO_DEVICE_ASSIGNMENT,)  # or equivalent
if len(distinct) != 1:
    return {}, (Reason.CONFLICTING_WORKER_ROLES,)

manifest.py (PowerManifest)mark_terminal's double-call guard (if self.status in TERMINAL_STATUSES: raise RuntimeError) is bypassed by direct field assignment. PowerManifest is not frozen, and the orchestrator already writes manifest.status = STATUS_RUNNING directly. Any code path that directly sets manifest.status to a non-terminal value after mark_terminal has committed resets the guard, allowing a second mark_terminal call to silently overwrite stopped_at_unix and publication_valid.

Fragility

windows.py (_bracketing_sequence) — The function relies on times being sorted ascending: before[-1] is the closest sample at or before start, and after[0] is the closest at or after end — but only if the sequence is ordered. The type signature is Sequence[float] with no ordering contract. derive_observed_devices currently provides tuple(sorted(times)) which satisfies this, but there's no assertion or documented invariant. If the call site changes to preserve insertion/scrape-sequence order, gap calculations silently produce wrong results with no error.

samples.py (SampleWriter) — The file handle is opened in __init__ but the class has no __enter__/__exit__. Resource safety depends entirely on every caller explicitly calling close(). A context manager would enforce this at the type level and prevent handle leaks on early exit between construction and the finally block.

Test Coverage

tests/test_power_artifacts.pyconvert_running_windows is not imported in the test file and has no happy-path test. The only related test exercises the OSError path. A regression in the conversion logic (wrong key written, wrong status checked, silent atomic-write failure) would go undetected. Per CLAUDE.md: "when we make a new significant feature change, we should always add a new test."

Simplification

windows.py_is_strict_int (the isinstance(value, int) and not isinstance(value, bool) bool-exclusion pattern) duplicates logic already present in contract.py's is_finite_number. Should be consolidated into contract.py and imported.

windows.py_stays_below is is_safe_relative_subpath's filesystem-resolving companion and belongs in contract.py alongside it, rather than as a private utility in windows.py. Any future module needing symlink-safe path resolution would otherwise have to either import from windows.py (wrong coupling direction) or copy the function.

Code Review (second pass)

Correctness Bugs

windows.py (convert_running_windows)_scan guards against a symlinked windows/ directory via _stays_below, which resolves the path and verifies it stays within the power artifact tree. convert_running_windows has no equivalent guard — windows_dir.is_dir() follows symlinks by default, so if windows/ is a symlink to an arbitrary directory, convert_running_windows will atomically overwrite any .json files found there. In a SLURM environment where the benchmark child runs as the same user, a malicious or misconfigured child could replace windows/ with a symlink before teardown. Fix: add the same _stays_below check at the top of convert_running_windows, mirroring _scan.

parser.py (parse_power_scrape)saw_power_sample is set to True before the MIG instance filter, so a node where every GPU is in MIG mode never gets POWER_METRIC_MISSING:

saw_power_sample = True          # set here, before MIG check
if "GPU_I_ID" in sample.labels or "GPU_I_PROFILE" in sample.labels:
    reasons.append(Reason.MIG_INSTANCE_UNSUPPORTED)
    continue                     # sample discarded

Every scrape cycle returns readings=() with only MIG_INSTANCE_UNSUPPORTED. From the manifest's perspective this is indistinguishable from a transient case where a few MIG instances appeared — there's no signal that this node will structurally never contribute whole-GPU power readings. Fix: move saw_power_sample = True to after the MIG check, or use a separate saw_non_mig_power_sample flag, so that an all-MIG node returns both MIG_INSTANCE_UNSUPPORTED and POWER_METRIC_MISSING.

topology.py (resolve_roles, resolve_het_groups) — Both functions use len(distinct) != 1 to detect a device assigned to conflicting roles/het-groups. This correctly catches len == 2+ (genuine conflict), but also fires on len == 0 (empty assignments), which is a misconfiguration, not a conflict. Since validate_devices calls both, an ExpectedDevice with assignments=() produces both CONFLICTING_WORKER_ROLES and CONFLICTING_HET_GROUPS simultaneously — a two-code error that directs debugging toward a scheduling conflict that doesn't exist. Fix:

if len(distinct) == 0:
    return {}, (Reason.NO_DEVICE_ASSIGNMENT,)  # or equivalent
if len(distinct) != 1:
    return {}, (Reason.CONFLICTING_WORKER_ROLES,)

manifest.py (PowerManifest)mark_terminal's double-call guard (if self.status in TERMINAL_STATUSES: raise RuntimeError) is bypassed by direct field assignment. PowerManifest is not frozen, and the orchestrator already writes manifest.status = STATUS_RUNNING directly. Any code path that directly sets manifest.status to a non-terminal value after mark_terminal has committed resets the guard, allowing a second mark_terminal call to silently overwrite stopped_at_unix and publication_valid.

Fragility

windows.py (_bracketing_sequence) — The function relies on times being sorted ascending: before[-1] is the closest sample at or before start, and after[0] is the closest at or after end — but only if the sequence is ordered. The type signature is Sequence[float] with no ordering contract. derive_observed_devices currently provides tuple(sorted(times)) which satisfies this, but there's no assertion or documented invariant. If the call site changes to preserve insertion/scrape-sequence order, gap calculations silently produce wrong results with no error.

samples.py (SampleWriter) — The file handle is opened in __init__ but the class has no __enter__/__exit__. Resource safety depends entirely on every caller explicitly calling close(). A context manager would enforce this at the type level and prevent handle leaks on early exit between construction and the finally block.

Test Coverage

tests/test_power_artifacts.pyconvert_running_windows is not imported in the test file and has no happy-path test. The only related test exercises the OSError path. A regression in the conversion logic (wrong key written, wrong status checked, silent atomic-write failure) would go undetected. Per CLAUDE.md: "when we make a new significant feature change, we should always add a new test."

Simplification

windows.py_is_strict_int (the isinstance(value, int) and not isinstance(value, bool) bool-exclusion pattern) duplicates logic already present in contract.py's is_finite_number. Should be consolidated into contract.py and imported.

windows.py_stays_below is is_safe_relative_subpath's filesystem-resolving companion and belongs in contract.py alongside it, rather than as a private utility in windows.py. Any future module needing symlink-safe path resolution would otherwise have to either import from windows.py (wrong coupling direction) or copy the function.

thanks for the review @FrankD412
addressed all review and fixed in 67dc632

  1. convert_running_windows symlink escape — fixed. Added root confinement and regression coverage proving external JSON is not modified.

  2. MIG semantics — defined and pinned. POWER_METRIC_MISSING means the scrape emitted no DCGM_FI_DEV_POWER_USAGE. A MIG-labeled sample emitted the metric but is unsupported, so it reports only MIG_INSTANCE_UNSUPPORTED. If the session receives no usable samples for an expected GPU, topology validation reports EXPECTED_DEVICE_MISSING. The exact parser result is now pinned by a regression assertion.

  3. Empty ExpectedDevice.assignments — fixed at construction. ExpectedDevice.__post_init__ now rejects the invalid state instead of adding a runtime reason code for something the production builder cannot emit.

  4. Manifest terminal guard — fixed. Terminal commitment is now independent of the mutable status field, so status reassignment cannot enable a second terminal transition.

  5. Unordered bracketing input — fixed. _bracketing_sequence sorts defensively, with public-path regression coverage.

  6. SampleWriter context manager — no change because PowerTelemetrySession is the sole production owner and closes the writer under its lock. A lexical with block cannot span the session’s cross-thread lifecycle and would not enforce ownership.

  7. Conversion happy-path coverage — fixed. convert_running_windows was already imported and exercised by the OSError test; the missing successful running → interrupted case is now covered.

  8. _is_strict_int extraction — no change because it is not equivalent to is_finite_number, which accepts finite floats such as 1.5. The integer-only predicate currently has one consumer.

  9. _stays_below relocation — no change because Its current callers are both in windows.py; moving it would add a public abstraction without a cross-module consumer.

Verification: 69 artifact tests passed; Ruff, format, ty, and git diff --check passed.

Please let me know if the fix looks good to you, thanks!

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.38965% with 50 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@9d8d92b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/srtctl/core/power/windows.py 83.74% 33 Missing ⚠️
src/srtctl/core/power/samples.py 94.20% 8 Missing ⚠️
src/srtctl/core/power/contract.py 96.77% 3 Missing ⚠️
src/srtctl/core/power/manifest.py 96.25% 3 Missing ⚠️
src/srtctl/core/power/parser.py 96.92% 2 Missing ⚠️
src/srtctl/core/power/topology.py 98.71% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #288   +/-   ##
=======================================
  Coverage        ?   69.97%           
=======================================
  Files           ?       76           
  Lines           ?    10068           
  Branches        ?        0           
=======================================
  Hits            ?     7045           
  Misses          ?     3023           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ishandhanani
ishandhanani merged commit cf2aacb into NVIDIA:main Aug 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants