From c49e5b15e0f86080d7d33e4e31aecfb701f8f96f Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 13 Apr 2026 14:05:03 +0200 Subject: Some minor polish from tourist branch (#949) - Replace per-type fmt::formatter specializations (StringBuilderBase, NiceBase) with a single generic formatter using a HasStringViewConversion concept - Add ThousandsNum for comma-separated integer formatting (e.g. "1,234,567") - Thread naming now accepts a sort hint for trace ordering - Fix main thread trace registration to use actual thread ID and sort first - Add ExpandEnvironmentVariables() for expanding %VAR% references in strings, with tests - Add ParseHexBytes() overload with expected byte count validation - Add Flag_BelowNormalPriority to CreateProcOptions (BELOW_NORMAL_PRIORITY_CLASS on Windows, setpriority on POSIX) - Add PrettyScroll progress bar mode that pins the status line to the bottom of the terminal using scroll regions, with signal handler cleanup for Ctrl+C/SIGTERM --- src/zencore/trace.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/zencore/trace.cpp') diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index 7c195e69f..d7084bbd1 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -6,6 +6,7 @@ # include # include # include +# include # include # define TRACE_IMPLEMENT 1 @@ -121,7 +122,7 @@ TraceInit(std::string_view ProgramName) const char* CommandLineString = ""; # endif - trace::ThreadRegister("main", /* system id */ 0, /* sort id */ 0); + trace::ThreadRegister("main", /* system id */ GetCurrentThreadId(), /* sort id */ -1); trace::DescribeSession(ProgramName, # if ZEN_BUILD_DEBUG trace::Build::Debug, -- cgit v1.2.3 From 8b842f161ee114ec9e10da1aa8efe2306836805a Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 11:03:13 +0200 Subject: added support for trace regions (#984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduces a UE-trace Region primitive in `zencore/trace.{h,cpp}` for marking named, potentially long-running intervals of work that Unreal Insights render as banners in the timeline, separately from CPU scopes. - New API: - `uint64_t TraceBeginRegion(RegionName, Category={})` / `void TraceEndRegion(RegionId)` for manual begin/end pairs. - `ScopedTraceRegion` RAII helper plus `ZEN_TRACE_REGION(name)` / `ZEN_TRACE_REGION_CAT(name, category)` macros for scope-based use. - Emits the `Misc.RegionBeginWithId` / `Misc.RegionEndWithId` trace events (paired by a `GetHifreqTimerValue()`-derived id). - Full no-op fallback under `#if !ZEN_WITH_TRACE` so callers compile in all configurations. - Annotates `GcScheduler::CollectGarbage` with `ZEN_TRACE_REGION_CAT("GcScheduler::CollectGarbage", "gc")` as a first caller — makes GC passes visible as banners in Insights without relying on the existing `ZEN_TRACE_CPU` scope alone (which doesn't render as a region). --- src/zencore/trace.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'src/zencore/trace.cpp') diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index d7084bbd1..6e2eea1c7 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -8,6 +8,7 @@ # include # include # include +# include # define TRACE_IMPLEMENT 1 # undef _WINSOCK_DEPRECATED_NO_WARNINGS @@ -25,6 +26,21 @@ # include # include +namespace { + +TRACE_EVENT_BEGIN(Misc, RegionBeginWithId, NoSync) +TRACE_EVENT_FIELD(uint64, CycleAndId) +TRACE_EVENT_FIELD(uint8[], RegionName) +TRACE_EVENT_FIELD(uint8[], Category) +TRACE_EVENT_END() + +TRACE_EVENT_BEGIN(Misc, RegionEndWithId, NoSync) +TRACE_EVENT_FIELD(uint64, Cycle) +TRACE_EVENT_FIELD(uint64, RegionId) +TRACE_EVENT_END() + +} // namespace + namespace zen { void @@ -164,6 +180,34 @@ TraceStop() return false; } +uint64_t +TraceBeginRegion(std::string_view RegionName, std::string_view Category) +{ + uint64_t RegionId = GetHifreqTimerValue(); + TRACE_LOG(Misc, RegionBeginWithId, true) << RegionBeginWithId.CycleAndId(RegionId) + << RegionBeginWithId.RegionName(reinterpret_cast(RegionName.data()), + int32_t(RegionName.size())) + << RegionBeginWithId.Category(reinterpret_cast(Category.data()), + int32_t(Category.size())); + return RegionId; +} + +void +TraceEndRegion(uint64_t RegionId) +{ + TRACE_LOG(Misc, RegionEndWithId, true) << RegionEndWithId.Cycle(GetHifreqTimerValue()) << RegionEndWithId.RegionId(RegionId); +} + +ScopedTraceRegion::ScopedTraceRegion(std::string_view RegionName, std::string_view Category) +: m_RegionId(TraceBeginRegion(RegionName, Category)) +{ +} + +ScopedTraceRegion::~ScopedTraceRegion() +{ + TraceEndRegion(m_RegionId); +} + bool GetTraceOptionsFromCommandline(TraceOptions& OutOptions) { -- cgit v1.2.3 From 8b42ed4b6e995ac0336d1abe6425cfadf239f8d2 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Wed, 22 Apr 2026 12:35:46 +0200 Subject: Zen-style trace log events (#1006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the old (not fully implemented) UE `Logging.*` sink with a typed `ZenLog.*` trace path that preserves structured fmt args end-to-end, so the zen trace analyzer (and future consumers) can re-render log messages with full formatter support. - Hook `Logger::Log` to tap `fmt::format_args` before `vformat` renders them, and emit three new events on a dedicated `ZenLogChannel`: `Category`, `MessageSpec`, `Message`. Args are serialized as `[count][descriptors][payload]` with distinct categories for bool, int, float, and string. Custom formatters fall back to a pre-rendered string. - Bool has its own wire category so `{}` renders as `true`/`false` and `{:d}` as `1`/`0`. - Zen `LogLevel` is translated to UE `ELogVerbosity` on emit so severity filtering works consistently. - Extend the zen trace analyzer to decode `ZenLog.*` via `fmt::vformat` + `dynamic_format_arg_store` — nested widths, chrono specs, etc. all work. Strings are passed as views directly from the event payload (which outlives the format call) rather than copied through a pool. - Retire the old `TraceSink` stub; the typed path supersedes it. - Switch `--trace=default` alias from `cpu,log` to `cpu,zenlog`. - Add `__int128` overloads to the arg encoder guarded by `FMT_USE_INT128` so fmt's int128 dispatch resolves unambiguously on clang/gcc. MSVC and clang-cl are unaffected. --- src/zencore/trace.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/zencore/trace.cpp') diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index 6e2eea1c7..2dec0511c 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -55,7 +55,7 @@ TraceConfigure(const TraceOptions& Options) auto ProcessTraceArg = [&](const std::string_view& Arg) { if (Arg == "default"sv) { - ProcessChannelList("cpu,log"sv); + ProcessChannelList("cpu,zenlog"sv); } else if (Arg == "memory"sv) { @@ -70,6 +70,12 @@ TraceConfigure(const TraceOptions& Options) // memtag actually traces to the memalloc channel ProcessChannelList("memalloc"sv); } + else if (Arg == "log"sv) + { + // Upstream UE trace reserves "log" for printf-style Logging.* events, which zen + // does not emit. Redirect to the zenlog channel so --trace=log does what users expect. + ProcessChannelList("zenlog"sv); + } else { // Presume that the argument is a trace channel name -- cgit v1.2.3 From acbe46d5f37af01efc3e46398e8af60e4461e68f Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 23 Apr 2026 12:39:51 +0200 Subject: trace: declare Region event name fields as AnsiString (#1012) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegionName and Category on Misc.RegionBeginWithId were declared as uint8[] — a byte array with no Field_String class flag. UE Insights' FEventData::GetString() explicitly requires Field_String and returns false otherwise, so Insights analyzers that check(GetString(...)) fire when reading zen traces. Upstream UE declares these fields as WideString; zen's source strings are std::string_view, so AnsiString is the natural fit and the wire bytes are unchanged (same Field_8 aux stream — only the schema class bit differs). Insights' FString GetString variant accepts either ANSI or WIDE, so analyzers work without change. Zen's own tourist-based analyzer in src/zen/trace/trace_model.cpp reads raw aux bytes via Array regardless of the schema tag, and its DecodeRegionName already handles both 1-byte and 2-byte widths, so it's unaffected. --- src/zencore/trace.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/zencore/trace.cpp') diff --git a/src/zencore/trace.cpp b/src/zencore/trace.cpp index 2dec0511c..d6a0b2e92 100644 --- a/src/zencore/trace.cpp +++ b/src/zencore/trace.cpp @@ -30,8 +30,8 @@ namespace { TRACE_EVENT_BEGIN(Misc, RegionBeginWithId, NoSync) TRACE_EVENT_FIELD(uint64, CycleAndId) -TRACE_EVENT_FIELD(uint8[], RegionName) -TRACE_EVENT_FIELD(uint8[], Category) +TRACE_EVENT_FIELD(UE::Trace::AnsiString, RegionName) +TRACE_EVENT_FIELD(UE::Trace::AnsiString, Category) TRACE_EVENT_END() TRACE_EVENT_BEGIN(Misc, RegionEndWithId, NoSync) @@ -191,10 +191,8 @@ TraceBeginRegion(std::string_view RegionName, std::string_view Category) { uint64_t RegionId = GetHifreqTimerValue(); TRACE_LOG(Misc, RegionBeginWithId, true) << RegionBeginWithId.CycleAndId(RegionId) - << RegionBeginWithId.RegionName(reinterpret_cast(RegionName.data()), - int32_t(RegionName.size())) - << RegionBeginWithId.Category(reinterpret_cast(Category.data()), - int32_t(Category.size())); + << RegionBeginWithId.RegionName(RegionName.data(), int32_t(RegionName.size())) + << RegionBeginWithId.Category(Category.data(), int32_t(Category.size())); return RegionId; } -- cgit v1.2.3