Skip to content

chore(instrument): log warning when background daemon cant run at requested rate - #338

Open
maxleblang wants to merge 5 commits into
mainfrom
337-choreinstrument-log-warnings-when-background-daemon-cant-run-at-requested-rate
Open

chore(instrument): log warning when background daemon cant run at requested rate#338
maxleblang wants to merge 5 commits into
mainfrom
337-choreinstrument-log-warnings-when-background-daemon-cant-run-at-requested-rate

Conversation

@maxleblang

Copy link
Copy Markdown
Contributor

Summary

Based on the average work time of 10 cycles of the background daemon, warn the user if their requested rate isn't achievable (avg_work_time > requested_rate). We only warn once per instrument run.

Type of change

  • Bug fix (fix)
  • New feature (feat)
  • Breaking change (feat! / fix!)
  • Refactor (refactor)
  • Documentation (docs)
  • Chore / tooling (chore)

Verification

ver

Entire test suite passes

Tests

  • Unit tests added or updated
  • Existing tests cover this change
  • No tests — explain why:

Checklist

  • PR title follows Conventional Commits (e.g. feat(driver): add support for Keysight E36300)
  • I have read CONTRIBUTING.md
  • Documentation updated if user-facing behavior changed
  • Code follows the style/conventions of the surrounding code

Notes for reviewers

Because of the way we pace the loop by calling wait() and the GIL, the daemon loop often runs at a slightly slower rate than the requested interval (loop_time = work_time + sw_latency). This is a pitfall of a sw timed loop in Python that we can't really get around. I just wanted to bring attention to the fact that this is only checking if the work time of the daemon is higher than the requested rate, not that the daemon is delivering on the requested loop interval (loop_time > requested_interval?)

@greptile-apps

greptile-apps Bot commented Aug 3, 2026

Copy link
Copy Markdown

PR author is not in the allowed authors list.

@claude

claude Bot commented Aug 3, 2026

Copy link
Copy Markdown

Claude finished @maxleblang's task in 1m 42s —— View job


Review

  • Gather context (PR diff, files changed)
  • Run /code-review --comment
  • Post findings

Overview

Small, well-scoped addition: _check_achievable_interval tracks a 10-cycle rolling window of daemon work time and logs one warning if the average exceeds the requested background_interval, re-arming on interval change via _reset_daemon_timing. Stays within Instrument's existing state/lifecycle (no new abstraction), and the test (test_warns_only_when_requested_interval_is_unachievable) exercises both the no-warning and warning paths using caplog, including a live interval change while the daemon thread is running — good coverage for the behavior. The PR description already flags the work-time-vs-loop-time caveat honestly; no concern there.

One inline nit posted: the new _check_achievable_interval docstring is multi-line, which the repo's INSTRO-248 cleanup deliberately moved away from (one line max).

Comment thread instro/lib/instrument.py
Comment on lines +383 to +386
"""Warn once if the averaged work time shows the requested interval isn't achievable.

This doesn't give timing guarantees about the background daemon rate,
just warn when work_time > requested interval.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-paragraph docstring — repo convention is one short line max (INSTRO-248 cleanup, see AGENTS.md). Fold the caveat into the summary line or drop it.

Comment thread instro/lib/instrument.py
self._background_config.interval = seconds
self._reset_daemon_timing()

def _reset_daemon_timing(self):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also call this method whenever the background daemon is redefined by the user

Comment thread instro/lib/instrument.py
return

average_work_time_s = sum(self._daemon_work_times) / len(self._daemon_work_times)
if average_work_time_s <= self._background_config.interval:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check will always fail for InstroDAQ when it has the default 0 sec background interval for hardware-paced reads. We should short-circuit for interval <=0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

Comment thread instro/lib/instrument.py Outdated
Comment on lines +397 to +398
"Background daemon for instrument '%s' cannot achieve the requested interval of %.6f s; "
"average work time over %d cycles is %.6f s, which is the fastest achievable interval.",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically not true that it is the fastest achievable interval, can we change the wording to be more accurate? Instead of saying it is the fastest achievable interval, we can provide guidance on how to fix? "Set background daemon interval to a value greater than the work time" or something like that

Comment thread instro/lib/instrument.py Outdated
if self._interval_warning_issued or len(self._daemon_work_times) != self._daemon_work_times.maxlen:
return

average_work_time_s = sum(self._daemon_work_times) / len(self._daemon_work_times)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible that len(self._daemon_work_times) == 0 due to a reset between the previous check? We might want to grab a static copy of self._daemon_work_times in a local variable just in case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the scenario you're talking about. We should never be able to reach the division if self._daemon_work_times isn't 10 (maxlen in this case). What would grabbing a static topic buy us?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's technically possible that self._reset_daemon_timing() gets called in between the check on line 394 and computation of average_work_time_s on line 397., in which case self._daemon_work_times becomes empty with length 0. I'm not seeing any guarantees of protection against that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand now. I'll have reset just create a new deque pointer instead of clearing the shared one.

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.

chore(instrument): log warnings when background daemon can't run at requested rate

2 participants