Skip to content

Add parameter observers notified of every get and set - #8684

Merged
Jens Hedegaard Nielsen (jenshnielsen) merged 8 commits into
microsoft:mainfrom
jenshnielsen:jenshnielsen/parameter_observers
Nov 2, 2026
Merged

Jens Hedegaard Nielsen (jenshnielsen) merged 8 commits into
microsoft:mainfrom
jenshnielsen:jenshnielsen/parameter_observers

Conversation

@jenshnielsen

Copy link
Copy Markdown
Collaborator

Add qcodes.parameters.add_parameter_observer, remove_parameter_observer and the observe_parameters context manager. An observer is called with a ParameterAccess after every get and set of every parameter, describing the parameter, value, raw value, start time, duration and any exception. A set that ramps in steps is reported once per step. Any number of observers can be registered, independently of on_set_callback and global_on_set_callback.

This makes it possible to record all instrument communication made by a piece of code without changing it or the drivers it uses.

Add qcodes.parameters.add_parameter_observer, remove_parameter_observer and
the observe_parameters context manager. An observer is called with a
ParameterAccess after every get and set of every parameter, describing the
parameter, value, raw value, start time, duration and any exception. A set
that ramps in steps is reported once per step. Any number of observers can be
registered, independently of on_set_callback and global_on_set_callback.

This makes it possible to record all instrument communication made by a piece
of code without changing it or the drivers it uses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.60140% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.15%. Comparing base (18fcd29) to head (a82e215).
⚠️ Report is 54 commits behind head on main.

Files with missing lines Patch % Lines
src/qcodes/parameters/parameter_access.py 98.31% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8684      +/-   ##
==========================================
+ Coverage   75.02%   75.15%   +0.12%     
==========================================
  Files         307      308       +1     
  Lines       32511    32686     +175     
==========================================
+ Hits        24392    24565     +173     
- Misses       8119     8121       +2     

☔ 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.

Show how to record the parameter accesses of a block of code with
observe_parameters, what a ParameterAccess contains, that ramped sets are
reported per step and failed accesses with their exception, how to see what a
measurement did to the instruments, and how to register an observer for longer
with add_parameter_observer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@asmull

Copy link
Copy Markdown
Contributor

If you're worried about making this too general and abusable, one thing you could do is for now restrict the observer to be a concrete type that just appends to a list. Or maybe that and logging, to avoid the full general callback-like behavior.

Replace the parameter observer API (add_parameter_observer and friends) with
recording that can only store what happened, so that it cannot be used as a
callback, for example to set one parameter in reaction to another:

- record_parameter_accesses() appends each access to a plain list (subclasses
  are refused, so append cannot be overridden); log_parameter_accesses() logs
  them to the qcodes.parameters.parameter_access logger instead. No user code
  runs while a parameter is accessed.
- A ParameterAccess holds data only: the names of the parameter and its root
  instrument, the value, raw value, unit, timing and a description of any
  error, but not the parameter, instrument or exception.
- Recording is local to the context it was started in (thread or asyncio
  task), held in a ContextVar, and can be nested.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

🟡 Changes recommended

Access-log dispatch can recurse or propagate handler failures into parameter operations, while inherited thread and task contexts allow recording beyond the documented scope and lifetime.

4 open issues
What changed in this PR

This PR adds parameter-access recording to QCoDeS, allowing callers to inspect parameter activity without modifying measurement code or instrument drivers.

Changes:

  • Introduces ParameterAccess records and context managers for list recording and logging.
  • Instruments get/set wrappers to capture values, timing, errors, and individual ramp steps.
  • Adds automated tests, an example notebook, and a release note.
File Description
tests/​parameter/​test_parameter_access.py Tests recording, logging, failures, nesting, and context behavior.
src/​qcodes/​parameters/​parameter_base.py Records accesses through get/set wrappers.
src/​qcodes/​parameters/​parameter_access.py Defines access records and recording/logging context managers.
src/​qcodes/​parameters/​__init__.py Exports the new public recording API.
docs/​examples/​Parameters/​Recording_Parameter_Access.ipynb Demonstrates recording and logging parameter activity.
docs/​changes/​newsfragments/​8684.new Announces the recording feature.

Review effort: Balanced

Address the review of the parameter access recording:

- An access made by a logging handler or filter while another access is
  logged is recorded in lists but not logged, so it cannot recurse.
- A sink that raises no longer changes the outcome of the access: the
  error is logged to qcodes.parameters.parameter_access_errors, the access
  returns or raises its original error, a ramp continues and later sinks
  still receive the access. An exception whose str() fails is described
  as traceback does.
- Recording ends when its block exits, also for asyncio tasks that
  inherited it, and accesses from other threads are never recorded, even
  from a copy of the recording context such as asyncio.to_thread.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

🟡 Changes recommended

Recording can trigger instrument I/O when reading unit metadata, allowing timeouts to turn successful accesses into failures or replace their original exceptions.

2 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Retain converted values when validation fails

src/​qcodes/​parameters/​parameter_base.py:1011

A get can obtain and convert a value successfully, then fail validation—for example, a ParameterWithSetpoints returning an array with the wrong shape. This branch always records value=None, discarding the converted reading despite ParameterAccess.value documenting None for failures before a value was obtained. Initialize value before the try block and record its current value so post-conversion failures retain the reading alongside the error.

4 resolved since last review

Review effort: Balanced

Address the review of the parameter access recording:

- Drop the unit from ParameterAccess. unit is a property that drivers may
  implement by querying the instrument (e.g. the Infiniium DSOTraceParam),
  so recording could add communication of its own. Building the record is
  now also isolated, so a failure there never affects the access.
- Append an access to every list before logging it, so a logging handler
  that accesses a parameter cannot make simultaneous recordings see the
  accesses in different orders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

🔵 Needs a closer look

Handler-created tasks can incorrectly suppress parameter-access logging, and recorded set durations omit conversion time required by the documented contract.

No open issues 🎉

2 previously missed issues

In code that hasn't changed since last review

Medium severity Handler-created tasks remain excluded from parameter logging

src/​qcodes/​parameters/​parameter_access.py:283

A task created by a logging handler or filter inherits _recording=True, so its parameter accesses remain excluded from logging after dispatch returns, even while the logging block is still active. Resetting the token only restores the parent's context; list recording still receives the child's accesses. Use a thread-local reentrancy guard or shared state that is deactivated when dispatch ends, and add a regression test for a handler-created task.

Medium severity Set duration omits value-conversion time

src/​qcodes/​parameters/​parameter_base.py:1112

The recorded set duration excludes all value conversion: _from_value_to_raw_value() runs before t0, while ParameterAccess.duration explicitly promises to include conversions. This matters for existing drivers such as SR830._set_sensitivity, whose set parser queries another instrument parameter; its query time is omitted from the set's duration, whereas get-parser time is included for gets. Measure conversion time separately and include it in the access duration, keeping the existing delay timer unchanged so inter/post delays remain excluded.

2 resolved since last review

Review effort: Balanced

Address the review of the parameter access recording:

- Guard against recursive logging with a thread-local flag instead of a
  context variable. A task that a logging handler starts copied the flag
  and so was never logged, even after the dispatch had ended.
- Include the time spent converting a set value to its raw value, such as
  in a set_parser, in the duration of the access, as for gets. The delay
  timers are unchanged, so inter_delay and post_delay stay excluded.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

🔵 Needs a closer look

Human assessment is needed because subtle failure-path interactions can lose diagnostic values, omit completed writes, or allow reporting failures to alter get/set outcomes.

No open issues 🎉

Review effort: Balanced

@asmull

Copy link
Copy Markdown
Contributor

Changes all look reasonable to me

Accept any MutableSequence rather than only a plain list, as suggested in
review, for example a deque with a maxlen. The sequence that is passed in
is yielded with its own type, and a new list is still used by default.
A custom sequence's append must only store the access; this is documented,
since recording is not a way to react to an access.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merged via the queue into microsoft:main with commit 192544e Nov 2, 2026
19 checks passed
@jenshnielsen
Jens Hedegaard Nielsen (jenshnielsen) deleted the jenshnielsen/parameter_observers branch November 2, 2026 20:10
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.

3 participants