Repository navigation
core, net, offline, ci: Tracy profiler zones behind MORPH_ENABLE_TRACY, with a nightly capture check - #861
Merged
Conversation
Yaraslaut
force-pushed
the
lane/tracy-zones
branch
from
October 3, 2026 19:48
87ba018 to
08774e9
Compare
MORPH_ENABLE_TRACY (default OFF) finds an installed Tracy, or fetches wolfpld/tracy v0.13.1 through CPM under Lightweight's package name, tag and options, so a process linking both libraries ends up with one TracyClient. MORPH_TRACY_ENABLED and Tracy::TracyClient are INTERFACE properties of morph::morph, and an installed morph finds Tracy for its consumer. A found client without TRACY_ENABLE is a configure error, since every zone would compile to nothing. include/morph/core/profiler.hpp defines MORPH_ZONE, MORPH_ZONE_TEXT, MORPH_PLOT, MORPH_THREAD_NAME and MORPH_MESSAGE, never Tracy's own names: Lightweight stubs those, and the two share translation units. With Tracy off each macro names its arguments only in decltype, so nothing is evaluated and no argument goes unused; tests/test_profiler.cpp holds both under the full warning set. The header and docs/spec/core/profiler.md state the ODR rule: every translation unit must agree on MORPH_TRACY_ENABLED; MSVC and clang-cl get a detect_mismatch link check. Tracy is added before core-cpp: core-cpp's own CORE_CPP_WITH_TRACY takes the client its parent provides, so a build with both on runs the one v0.13.1 client instead of failing to find one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yaraslaut
force-pushed
the
lane/tracy-zones
branch
from
October 3, 2026 20:38
c678952 to
5e7e733
Compare
One zone per phase, on the thread that runs it: a dispatch crosses from the caller to the model's strand to the settling thread, and a Tracy zone has to begin and end on one thread, so no zone spans a hand-off or a co_await. The phases of one call are linked by writing Context::requestId as zone text where the phase can see the call's session. Zoned: Bridge::executeVia (and its attached/creating forms), dispatchNow, the ActionCall codec, BridgeSink's settles and forward; LocalBackend's executeInto, start and finish; every model strand task (the around-task hook); ActionDispatcher's dispatch, dispatchAsync and prepareAction; the journal's outcome recording; wire::encode/decode; SocketBackend's file, receive and frame drain, and the WebSocket send queue; the offline queues' enqueue and SyncWorker's drain and per-item replay. The pool workers are named morph.pool and the IoLoop thread, which carries every socket and TimeoutScheduler deadline, morph.io. RemoteServer is not zoned here; its dispatchMessage and dispatchExecute are the next two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A tracy-capture job on the nightly workflow builds tracy-capture and tracy-csvexport at v0.13.1, builds morph_bench and morph_bench_alloc with MORPH_ENABLE_TRACY=ON, runs each under a capture and fails unless every named zone has a non-zero count: the codec, dispatcher and strand zones from RemoteServer's dispatch, and Bridge/LocalBackend/BridgeSink from the local path. Its result is reconciled into an issue like the other nightly legs. scripts/check_tracy_capture.sh fails closed: no CSV, no rows, an unexpected column layout, a named zone absent or counted zero, a program built without Tracy, and a program or capture that does not exit are all failures. scripts/test_check_tracy_capture.sh drives the assertion through each of those from synthetic CSVs and runs first in the job. MORPH_TRACY_ON_DEMAND (advanced, default ON) lets the job build the fetched client to record from the first zone; with TRACY_NO_EXIT=1 the result then does not depend on when the capture connects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteServer::dispatchMessage covers every decoded message and RemoteServer::dispatchExecute covers an execute's admission gates, both on the server strand. The admitted run is zoned separately on the model's strand (startRemote, startTaskRemote, finishRemote), so no zone spans the hand-off. Each zone carries the envelope's requestId as its text. The nightly capture now also requires the four zones morph_bench reaches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Yaraslaut
force-pushed
the
lane/tracy-zones
branch
from
October 3, 2026 21:36
5e7e733 to
834791a
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
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.
Closes #843 (Part 1 only, as rescoped at triage; Part 2 is #860).
Four commits, each droppable on its own:
MORPH_ENABLE_TRACYand theMORPH_ZONEprofiler macros. The option is OFF by default. It runsfind_package(Tracy CONFIG QUIET)first and falls back to CPMwolfpld/tracyv0.13.1 under Lightweight's CPM name (tracy) and options (TRACY_ENABLE,TRACY_ON_DEMAND).MORPH_TRACY_ENABLEDandTracy::TracyClientareINTERFACEproperties ofmorph::morph, andmorphConfig.cmakefinds Tracy for the consumer. A found client that lacksTRACY_ENABLEis a configure error. Tracy is added before core-cpp: see the core-cpp finding below.include/morph/core/profiler.hppaddsMORPH_ZONE,MORPH_ZONE_TEXT,MORPH_PLOT,MORPH_THREAD_NAMEandMORPH_MESSAGE. None of Tracy's own names is defined. With Tracy off, each stub names its arguments only indecltype, so nothing is evaluated and no argument goes unused. The ODR rule is stated in the header and in the newdocs/spec/core/profiler.md, and MSVC/clang-cl get a#pragma detect_mismatch.tests/test_profiler.cppcovers both modes.co_await. Phases are linked byContext::requestIdas zone text. The zone list is below.tracy-capturejob tonightly-slow-checks.yml(not topull_request), wired intoreconcile-issues. The job buildstracy-capture/tracy-csvexportat v0.13.1 and buildsmorph_benchandmorph_bench_allocwith Tracy on. It captures each one and asserts that named zones have non-zero counts.scripts/check_tracy_capture.shfails closed: no CSV, no rows, an unexpected header, a zone that is absent or counted zero, a binary built without Tracy (the capture never connects), or a hang all fail the job.scripts/test_check_tracy_capture.shself-tests the assertion and runs first in the job. A new advanced option,MORPH_TRACY_ON_DEMAND(default ON), lets the job record from the first zone, andTRACY_NO_EXIT=1makes the result independent of when the capture connects.RemoteServer::dispatchMessagecovers every decoded message andRemoteServer::dispatchExecutecovers an execute's admission gates; both run on the server strand. The admitted run gets its own zones on the model's strand:startRemote,startTaskRemoteandfinishRemote. Every zone carriesrequestIdas its text. The nightlymorph_benchcapture now also requiresdispatchMessage,dispatchExecute,startRemoteandfinishRemote.Zones
Bridge::executeVia/executeAttachedVia/executeCreatingViaBridge::dispatchNowrequestIdActionTraits::toJson,ActionTraits::resultFromJsonBridgeSink::settleValue,settleException,forwardLocalBackend::executeIntorequestIdLocalBackend::startLocal,startTaskLocal,finishLocalrequestIdModelStrands::task(around-task hook, every strand task)ActionDispatcher::dispatch,dispatchAsyncrequestIdActionDispatcher::prepareAction(decode + pre-handler gates)recordActionSuccess,recordActionFailure(journal)wire::encode,wire::decoderequestIdSocketBackend::fileExecuterequestIdSocketBackend::fileControl,dispatchIncomingEnvelope,drainFrames;ws::enqueueFrame(send side)InMemoryOfflineQueue::enqueue,FileOfflineQueue::enqueue,SyncWorker::drain,SyncWorker::replayRemoteServer::dispatchMessage,RemoteServer::dispatchExecute(admission)requestIdRemoteServer::startRemote,startTaskRemote,finishRemoterequestIdThread names:
morph.poolfor everyThreadPoolExecutorworker, andmorph.iofor theIoLoopthread. Since the one-owner change, that one thread carries every socket, everyTimeoutSchedulerdeadline and the probe, so the ticket's "socket I/O thread" and "TimeoutScheduler thread" are now the same thread.Finding: core-cpp pins a different Tracy (commented on #843)
core-cpp v0.5.0 has its own
CORE_CPP_WITH_TRACY, which pins Tracy v0.14.1 under the same CPM nametracy. Under morph, core-cpp fetches nothing (CORE_CPP_FETCH_DEPS OFF).-DCORE_CPP_WITH_TRACY=ONfailed configure: "Tracy is needed becauseCORE_CPP_WITH_TRACYholds, but neither the parent project nor find_package(Tracy 0.14.1) provides Tracy::TracyClient". This was measured with morph's Tracy block placed after core-cpp.MORPH_ENABLE_TRACY=ON CORE_CPP_WITH_TRACY=ON MORPH_INSTALL=OFFconfigures. core-cpp'score-cpp-baseandcore-cpp-netbuild against the single v0.13.1 client. This was measured locally.MORPH_INSTALL=ON, core-cpp's install rules refuse to export its targets, because it links a fetched Tracy it did not install. That is core-cpp's install policy; the spec documents the workaround.Verification
All of the following was measured locally on macOS 26 / arm64 with Homebrew clang 22.1.8. That is CI's
CLANG_VERSION; clang 23 fails master on unrelated-Wlifetime-safety-*.-Weverything -Werror,MORPH_BUILD_NET=ON MORPH_BUILD_LOAD_TESTS=ON: builds with zero warnings, andbuild.ninjaandCMakeCache.txtcontain no Tracy target or include.morph_tests: 1725 cases, 1724 passed, 1 failed as expected.morph_net_tests: all 210 cases passed on the first two runs. Later runs fail inSocketServer: the accept flow keeps serving after pending connections are aborted before acceptwithTcpSocket::connect: could not connect to 127.0.0.1:<port>. The same Tracy-ON binary that passed the whole suite earlier now fails that case too, so I attribute it to machine state, not to this change. That attribution is inferred, not proven.MORPH_PLOTstub drop its arguments failstest_profiler.cppwith-Wunused-variableondepthandinFlight. GCC 15-Wall -Wextra -Wunused-but-set-variable -Werroraccepts thedecltypestubs, including a local that is set and then used only by a macro.TracyClient.cppemitssprintfdeprecation warnings).morph_testspasses (1723 cases, 1 failed as expected) andmorph_net_testspasses (210 cases).tracy-capture/tracy-csvexportbuilt locally from v0.13.1, viascripts/check_tracy_capture.shonmorph_bench:morph_testsreports 1744 cases: 1743 passed, 1 failed as expected.MORPH_ZONE("ActionDispatcher::dispatch")(and its text line), rebuiltmorph_bench, and ran the same command. Exit 1:morph_benchexits 1 witherror: tracy-capture did not exit within 120s; killed it.test_check_tracy_capture.shpasses all 10 cases. With the checker's count test mutated (-gt 0changed to-ge 0), the self-test fails 3 cases.build-doc --target doc) passes, and the macros appear inglobals_defs.html. Note: Doxygen does not fail on a missing@paramfor a function-like macro, so the macro docs were checked by hand..clang-tidy) on the changed lines is clean apart from the mac-onlypoison-system-directories. The first run caughtbugprone-sizeof-containeron the originalsizeofstubs,cppcoreguidelines-macro-usage, andreadability-use-concise-preprocessor-directives; all three are fixed. That is why the stubs usedecltype.actionlintandshellcheckare clean, and clang-format 22 reports nothing on the changed files.Not verified
morph_bench_alloccapture (Bridge/LocalBackend zones) was not run successfully locally. On Apple, Tracy's client starts lazily and is never destroyed, soTRACY_NO_EXITdoes not hold a short program open, and the capture never connected. On Linux the client is a static object and waits, so the nightly job should pass, but that is inferred from Tracy's source, not run. The Bridge zones were confirmed present in that binary (strings), not in a capture.workflow_dispatchrun also runsreconcile-issues.decltypecounts as a use for MSVC's C4189 but have not checked it. Thedetect_mismatchpragma is not exercised.find_package(installed or vcpkg): not tried.tracy-csvexportdoes not report thread names.Review notes (done inline)
co_await. The send side is zoned atenqueueFrame, not inwriterFlow, and the strand zone wraps one synchronousrun().SyncWorker::drainaroundreplay) shadowsmorphProfilerZoneon purpose, under a scoped-Wshadowsuppression, so thatMORPH_ZONE_TEXTbinds to the innermost zone.MORPH_ZONEusesZoneNamedNplusstatic_assert(true)rather thanZoneScopedN, because whether Tracy's expansion includes the;varies by compiler. WithZoneScopedN, the first ON build failed with-Wextra-semi-stmt.🤖 Generated with Claude Code