feat(orchestrator): pipeline profiling as a queryable subgraph - #476
Open
Disentinel wants to merge 3 commits into
Open
feat(orchestrator): pipeline profiling as a queryable subgraph#476Disentinel wants to merge 3 commits into
Disentinel wants to merge 3 commits into
Conversation
Emit the analyze pipeline as a profile subgraph so the critical path +
bottlenecks are a GRAPH QUERY, not log-grepping. Closes the derive-pack
blind spot (previously only a "Rule pack materialized" log line).
Schema (profile: namespace, synthetic file __grafema_profile/<run_ts>):
- profile:run — one per analyze (ts, total_ms)
- profile:phase — resolve/derive/... PART_OF -> run
- profile:stage — one per resolver-cmd / derive-pack PART_OF -> phase,
PRECEDES -> next stage (the route); attrs wall_ms,
edges_produced, nodes_produced, kind, phase, order
- METRIC — reuses the existing METRIC node + OBSERVES edge
(same as per-file parse_ms)
Implementation:
- profiler.rs: retain events in memory (ProfileEvent) + iso_now()
- analyzer.rs: profile_subgraph_to_wire() builds the WireNodes/edges
- main.rs: build_profile_stages() from the event stream; commit the
subgraph at end of analyze via commit_batch. On by default; disable
with GRAFEMA_PROFILE_SUBGRAPH=0.
- scripts/profile-graph.mjs: critical path (longest PRECEDES chain by
summed wall_ms) + jams + dead stages (wall_ms high AND edges=0), read
from the graph.
- _ai/profile-subgraph.md + SKILL.md: schema + documented Datalog queries.
Profile nodes are profile:-namespaced and excluded from the analyze
node/edge summary counts (computed pre-emission), so code queries and
existing count assertions are unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The derive-query engine (executeDatalog) binds top-level node fields and matches metadata attrs by CONSTANT value, but does not enumerate a metadata value into an unbound variable. So query stage/run node IDS + PRECEDES edges via Datalog (rule form, executeDatalog) and read the numeric measures (wall_ms / edges_produced / phase / kind) off each node via getNode(), where metadata keys are spread as top-level fields. Verified end-to-end on grafema-dev: critical path + jams + dead stages print from the committed profile subgraph. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The derive-query engine matches metadata attrs by constant value only; it does not enumerate a metadata value into an unbound variable. So the pure-Datalog dead-stages query keys off attr(S,"edges_produced","0") (constant match), and numeric measures (wall_ms) are read via getNode in the helper. Document the engine binding semantics + correct the queries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Emit the
grafema analyzepipeline as a profile subgraph so the critical path + bottlenecks are a GRAPH QUERY, not log-grepping. Closes the derive-pack blind spot — derive packs were previously visible only as aRule pack materialized pack=X ms=N edges=Mlog line; now each is a graph node.Schema (
profile:namespace, synthetic file__grafema_profile/<run_ts>)profile:runts,total_msprofile:phasephaseprofile:stagewall_ms,edges_produced,nodes_produced,kind,phase,orderMETRICvalue,unit— reuses the existing METRIC node + OBSERVES edge (same as per-fileparse_ms)Edges:
profile:phase --PART_OF--> profile:run;profile:stage --PART_OF--> profile:phase;profile:stage --PRECEDES--> profile:stage(the route);METRIC --OBSERVES--> stage/phase.On by default (cheap — a few hundred nodes). Disable with
GRAFEMA_PROFILE_SUBGRAPH=0. Profile nodes areprofile:-namespaced and committed after the analyze summary counts, so code queries and existing count assertions are unaffected.Implementation
profiler.rs— retain events in memory (ProfileEvent) +iso_now().analyzer.rs—profile_subgraph_to_wire()builds the WireNodes/edges (+ unit tests).main.rs—build_profile_stages()from the event stream; commit the subgraph at end of analyze viacommit_batch.scripts/profile-graph.mjs— critical path (longestPRECEDESchain by summedwall_ms) + jams + dead stages, read from the graph._ai/profile-subgraph.md+SKILL.md— schema + documented Datalog queries + the engine binding semantics.Query (real output from a self-analyze on grafema-dev)
716928 nodes / 1532179 edges, 0 SIGSEGV, 46 stages.Pure-Datalog dead-stages (CI-gate candidate, verified on the real graph → 14 zero-edge packs):
🤖 Generated with Claude Code