Skip to content

DDIR server: diagnostics as diag.* collections, read with peek and tail - #1009

Merged
frankmcsherry merged 2 commits into
TimelyDataflow:master-nextfrom
frankmcsherry:ddir-diagnostics
Oct 23, 2026
Merged

frankmcsherry merged 2 commits into
TimelyDataflow:master-nextfrom
frankmcsherry:ddir-diagnostics

Conversation

@frankmcsherry

@frankmcsherry frankmcsherry commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Diagnostics for the DDIR server as ordinary DDIR collections. They're read with peek and tail like any answer, instead of through one-off commands (#989's profile, #1006's sizes) or the WebSocket console. Design note: https://claude.ai/artifact/VoTk6wN3XzR3LfXYkz1w1S

How it works. With DDIR_DIAGNOSTICS=1, each worker registers timely's "timely" logger, DD's "differential/arrange" logger and rendering's "ddir/labels" logger before building any dataflow. At each tick, the feeder (interactive/src/diag.rs) folds the buffered events into per-operator state, then gives each installed diag.* source its changes at the epoch being closed. Programs import the sources the way they import generated sources such as iota:N. Each count carries a magnitude:

Source Row Count
diag.operator ((worker, op), (program, statement, name, address)) +1 when created; −1 at shutdown or when its dataflow is dropped
diag.time ((worker, op), ()) the operator's own nanoseconds scheduled, while it lives
diag.time_retired ((worker, program, statement, name), ()) at shutdown, an operator's total moves here from diag.time
diag.records ((worker, op), ()) updates held, from batch, merge and drop events

Views. interactive/server/diag.ddp is installed as program diag. Its exports are diag.time_by_statement, diag.time_by_program (live plus retired time), diag.records_by_statement and diag.operators_by_program. Every view leaves out program diag itself.

Retention stays bounded. Everything keyed by an operator is retracted when the operator, or its dataflow, is dropped. Time moves into diag.time_retired, so a program installed and dropped every epoch still adds up. That state is bounded by the number of distinct (program, statement, name) triples. Over 300 install/drop cycles, the diagnostics' own held updates stayed flat at about 1,640.

From #989: its statement labels. Lowering records which statement built each item, kept aligned through optimize, and rendering logs which operator ids each item became. Export arrangements are now labelled export <name>. #989's profile command is superseded.

Held back: #1006's heap-byte accounting walks corgi's value layout from outside corgi, so it is left out to be owned by corgi, either as a corgi heap_bytes or as sizes carried in DD's batch events. diag.trace_bytes waits for it.

Agreement with them:

  • time on the txn resolver: 361.5 ms against profile's 361.8 ms, with every statement's share within 0.3 points;
  • updates held: 52,737,336 against sizes' 52,737,206, the differences being one tick of lag;

Overhead with DDIR_DIAGNOSTICS=1; it's off by default:

  • the loggers and feeder alone cost about 4%;
  • the views program costs a fixed ~0.5 ms per tick. On the txn bench that's +27–32% at batch 1,000 (about 2 ms cycles) and +5.5% at batch 10,000.
  • On LDBC SF0.1 it's +1.5% in all.

Installing the views on demand is a possible follow-up.

What changes:

  • DDIR_DIAGNOSTICS=1 now feeds diag.* and installs diag.ddp, instead of starting the WebSocket diagnostics dataflow, and the server no longer depends on the diagnostics crate.
  • Until the console reads the diag.* traces, it loses its DDIR_DIAG_PORT listener, the live wall-clock view during a long tick, channel and message counts, and batcher stats.

Known limits:

  • Events during a tick appear at the next one.
  • Dataflow scaffolding, about 5–10% of a program's time, has no statement.
  • Names are byte lists, so text peek shows them as numbers; frames decode them.
  • Not yet included: diag.batcher, and any byte accounting (see above).

Checks:

  • cargo test -p interactive -p ddir-server, including tests/diag.rs and tests/labels.rs, on master-next fceb62f: 196 passed, none failed.
  • Clippy reports nothing on new lines.
  • All 16 LDBC CI steps pass with diagnostics off, including authored/check.py.

🤖 Generated with Claude Code

https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

… own logging at each tick

With DDIR_DIAGNOSTICS=1 the server owns timely's "timely" logger, DD's
"differential/arrange" logger and rendering's "ddir/labels" logger, and at
each tick turns the events since the last one into rows of four sources that
any program may import, the way it imports a generated source:

- diag.operator: ((worker, op), (program, statement, name, address)), +1 when
  an operator is created and -1 when it shuts down or its dataflow is dropped;
- diag.time: ((worker, op), ()), nanoseconds scheduled net of nested operators,
  while the operator lives;
- diag.time_retired: ((worker, program, statement, name), ()), the time of
  operators that have shut down, moved out of diag.time when they do, so a
  program installed and dropped every epoch still adds up while the state
  stays bounded by distinct (program, statement, name) triples;
- diag.records: ((worker, op), ()), the updates each arrangement holds, from
  DD's batch, merge and drop events.

Each source's count carries the magnitude. A source installed later starts
from what it describes now. Statement labels come from TimelyDataflow#989: lowering records
the statement behind each scope item, optimize keeps them aligned, and
rendering logs the operator ids each item built.

The server installs server/diag.ddp as program "diag": time by
(program, statement) and by program, updates held by (program, statement),
and live operators per program, each leaving out the diagnostics program.
This replaces the WebSocket diagnostics dataflow the server started before.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o
… DDIR_DIAGNOSTICS through

- An export that is not already an arrangement is arranged after its
  statement renders; those operators are now labelled "export <name>".
- Dropping one generated or diagnostics source can reclaim others that it
  leaves unused; the reclaiming loop skips any already gone.
- The LDBC and txn bench clients no longer force DDIR_DIAGNOSTICS=0.
- The server README describes the diag.* collections and views.

Trace byte accounting (TimelyDataflow#1006's) is left out here, to be owned by corgi.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o
@frankmcsherry
frankmcsherry merged commit e6ea7b0 into TimelyDataflow:master-next Oct 23, 2026
7 checks passed
frankmcsherry added a commit that referenced this pull request Oct 24, 2026
…e.html) (#1011)

* DDIR: remove ir::Id and stale comments

- ir::Id was unreferenced.
- lib.rs and explain referred to the retired `folded` module.
- backend/corgi.rs said CorgiBackend mirrors VecBackend, which is deleted.
- projection.rs pointed at examples/self_opt, which isn't merged.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR README: describe the architecture as it is now

The section described a `vec` row backend that is deleted, and didn't
mention shapes, projection or diagnostics. It now walks the four parts
(language, analyses, runtime, host), and says where an expression
language and a backend plug in.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR: lib.rs is just its module list

- hash_u64 and gen_field_seeded move beside the `random:` generated
  source in server.rs, their one library user; a test and an example
  name them as interactive::server::.
- load_program, a read_to_string wrapper, is inlined at its four uses.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* Server example: a generated source is reclaimed when its last importer drops

generated.txt dropped the source by hand after its consumers, which now
fails with "no program": the server reclaims it when the last importer
drops. The session and the server examples README say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR explain tests: say why SCC_ROW is a copy of scc.ddp

Its tests query result edges by one-field keys, where scc.ddp keys bare
integers and counts its result, so an include_str! would need edits
throughout. The comment now says so.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* CI: check the authored LDBC plans against the generated ones

The release job now runs authored/check.py on the fixture graph at four
workers, once each, so the 41 hand-written plans can't drift from the
generated plans as the language and runtime change.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR server: retire console.html

The browser console was last changed in August, isn't run or tested, and
has likely drifted from the protocol. A future console would read the
diag.* collections and the current protocol, so it would be rebuilt
rather than kept up to date. The server README no longer points to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR server: drop the last DDIR_DIAG_PORT settings

#1009 replaced the WebSocket diagnostics with the diag.* collections, so
the server no longer reads DDIR_DIAG_PORT. The two-session demo, the txn
bench and the multi-worker test still set it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* LDBC: remove one-off recorded results, keep the methods

Recorded results with no lineage go; anything here should be
reproducible from the scripts and their method docs.
- observations/sf1-memory-2026-10-21.{csv,json}: one profiling run.
- baselines/RESULTS.md and results-mini03-sf1.csv: one SQL comparison run.
- MEMORY.md keeps how profile_memory.py measures and how to run it; its
  table of one run's observations, and the notes on that run's plans and
  binary, go.
- UPDATES.md drops a note about one revision's SF1 run; METHOD.md no
  longer points to RESULTS.md.

Kept: the bindings (sf1-memory.json and the rest), tiny.json, METHOD.md,
and every script.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

* DDIR examples: remove the import and triangles bench programs

examples/import_bench.rs and examples/triangles_bench.rs were one-off
Rust benches over the library, built by CI but run by hand. Frank: "Those
two Rust programs can probably go." With import_bench gone, hash_u64's
only user is gen_field_seeded, so it is private to server.rs.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_019XsakwFGwtESwZ1gSYwG4o

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
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.

1 participant