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/string.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'src/zencore/string.cpp') diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 358722b0b..df7d71250 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -380,6 +380,34 @@ NiceNumGeneral(uint64_t Num, std::span Buffer, NicenumFormat Format) } } +size_t +ThousandsToBuffer(uint64_t Num, std::span Buffer) +{ + // Format into a temporary buffer without separators + char Tmp[24]; + int Len = snprintf(Tmp, sizeof(Tmp), "%llu", (unsigned long long)Num); + + // Insert comma separators + int SepCount = (Len - 1) / 3; + int TotalLen = Len + SepCount; + ZEN_ASSERT(TotalLen < (int)Buffer.size()); + + int Src = Len - 1; + int Dst = TotalLen; + Buffer[Dst--] = '\0'; + + for (int i = 0; Src >= 0; i++) + { + if (i > 0 && i % 3 == 0) + { + Buffer[Dst--] = ','; + } + Buffer[Dst--] = Tmp[Src--]; + } + + return TotalLen; +} + size_t NiceNumToBuffer(uint64_t Num, std::span Buffer) { -- cgit v1.2.3 From db616a2ede14670423b7b2b28aa36c33cabb030e Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 16 Apr 2026 14:22:09 +0200 Subject: Add reduce-allocs skill and string builder infrastructure (#937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds infrastructure for reducing short-lived heap allocations, to be applied across the codebase in follow-up PRs. - **`reduce-allocs` Claude Code skill** — reviews code for unnecessary heap allocations and suggests fixes using stack-friendly patterns (`ExtendableStringBuilder`, `eastl::fixed_vector`, `TRefCounted`, etc.) - **`TransparentStringHash`** (`zencore/hashutils.h`) — enables `std::string_view` lookups on `std::string`-keyed `unordered_map` without allocating a temporary string (C++20 heterogeneous lookup via `is_transparent`) - **`AppendPaddedInt()`** and **`AppendFill()`** on `StringBuilderBase` (`zencore/string.h`) — zero-padded integer formatting and repeated-character fills without going through `fmt::format` - **`StringBuilderAppender`** output iterator adapter — allows `fmt::format_to` to write directly into a `StringBuilderBase` --- src/zencore/string.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'src/zencore/string.cpp') diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index df7d71250..44f78aa75 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -542,6 +542,40 @@ template class StringBuilderImpl; ////////////////////////////////////////////////////////////////////////// +void +StringBuilderBase::AppendPaddedInt(int64_t Value, int MinWidth) +{ + char Buf[24]; + char* End = Buf + sizeof(Buf); + char* Ptr = End; + bool Negative = Value < 0; + uint64_t Abs = Negative ? uint64_t(-Value) : uint64_t(Value); + do + { + *--Ptr = '0' + char(Abs % 10); + Abs /= 10; + } while (Abs > 0); + while ((End - Ptr) < MinWidth) + { + *--Ptr = '0'; + } + if (Negative) + { + *--Ptr = '-'; + } + AppendRange(Ptr, End); +} + +void +StringBuilderBase::AppendFill(char C, size_t Count) +{ + EnsureCapacity(Count); + std::memset(m_CurPos, C, Count); + m_CurPos += Count; +} + +////////////////////////////////////////////////////////////////////////// + void UrlDecode(std::string_view InUrl, StringBuilderBase& OutUrl) { -- cgit v1.2.3 From ed3a02bd5bbfbca337dbc0403fa4d04b955d05ec Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 21:40:02 +0200 Subject: Add CompactString utility type (#990) - Introduce `CompactString`: a move-only, heap-allocated, immutable string wrapper that stores its length in a prefix byte for cheap `Size()`/`ToView()` while keeping the object to a single pointer. - Swap the `ToString()` integer-formatting helpers in `zencore/string.cpp` to `std::to_chars`, which is ~5-10x faster and benefits every `IntNum` / `StringBuilder` / `CbJsonWriter` caller. - No in-tree users on `main` yet; the type is ready for callers that want owned-string storage with lower per-entry overhead than `std::string` (e.g. long-lived log buffers, session records). --- src/zencore/string.cpp | 90 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 4 deletions(-) (limited to 'src/zencore/string.cpp') diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 44f78aa75..2691d14b8 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -54,15 +55,21 @@ namespace zen { bool ToString(std::span Buffer, uint64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRIu64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec == std::errc{}) + { + *Ptr = '\0'; + } return true; } bool ToString(std::span Buffer, int64_t Num) { - snprintf(Buffer.data(), Buffer.size(), "%" PRId64, Num); - + auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); + if (Ec == std::errc{}) + { + *Ptr = '\0'; + } return true; } @@ -1341,6 +1348,81 @@ TEST_CASE("hidesensitivestring") CHECK_EQ(HideSensitiveString("1234567890123456789"sv), "1234XXXX..."sv); } +TEST_CASE("CompactString.default") +{ + CompactString S; + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); + CHECK(S.ToView() == std::string_view()); + CHECK(S.c_str()[0] == '\0'); +} + +TEST_CASE("CompactString.empty") +{ + CompactString S(std::string_view("")); + CHECK(S.IsEmpty()); + CHECK(S.Size() == 0); +} + +TEST_CASE("CompactString.short") +{ + CompactString S(std::string_view("hello")); + CHECK(!S.IsEmpty()); + CHECK(S.Size() == 5); + CHECK(S.ToView() == std::string_view("hello")); +} + +TEST_CASE("CompactString.sentinel_boundary") +{ + // 254 chars — largest value that fits in the prefix byte + std::string Str254(254, 'x'); + std::string_view View254(Str254); + CompactString S(View254); + CHECK(S.Size() == 254); + CHECK(S.ToView() == View254); +} + +TEST_CASE("CompactString.sentinel_exact") +{ + // 255 chars — hits the 0xFF sentinel, falls back to strlen + std::string Str255(255, 'y'); + std::string_view View255(Str255); + CompactString S(View255); + CHECK(S.Size() == 255); + CHECK(S.ToView() == View255); +} + +TEST_CASE("CompactString.long") +{ + // Well beyond the sentinel + std::string Str512(512, 'z'); + std::string_view View512(Str512); + CompactString S(View512); + CHECK(S.Size() == 512); + CHECK(S.ToView() == View512); +} + +TEST_CASE("CompactString.move") +{ + CompactString A(std::string_view("test")); + CompactString B(std::move(A)); + CHECK(A.IsEmpty()); + CHECK(B.ToView() == std::string_view("test")); + + CompactString C(std::string_view("first")); + CompactString D(std::string_view("second")); + D = std::move(C); + CHECK(C.IsEmpty()); + CHECK(D.ToView() == std::string_view("first")); +} + +TEST_CASE("CompactString.implicit_conversion") +{ + CompactString S(std::string_view("view")); + std::string_view V = S; + CHECK(V == std::string_view("view")); +} + TEST_SUITE_END(); void -- cgit v1.2.3 From 2dfb5da16b97a6c12e01977af5b5188522178a4e Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Apr 2026 21:50:41 +0200 Subject: zen trace analysis support (#945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrates the **tourist** trace analysis library and builds a full `zen trace` command suite for working with Unreal Engine `.utrace` files. ### Trace analysis library (`thirdparty/tourist/`) - Adds the tourist library as a third-party dependency with three modules: **foundation** (platform primitives, memory, scheduling), **trace** (UE Trace protocol decoding), and **analysis** (event dispatching and analyzer framework). - Cross-platform support for Windows, Linux, and macOS. ### `zen trace` CLI commands (`src/zen/cmds/`, `src/zen/trace/`) - **`zen trace analyze`** — Summarize a `.utrace` file: session metadata, thread inventory, command line + build configuration, CPU profiling scopes, timing, event rates, log messages, and (with symbols) memory allocation metrics including live-allocs dumps, callstack-keyed aggregation, and allocation churn. Optional HTML output for memory reports. - **`zen trace inspect`** — Dump the event schema (declared types, fields, sizes) from a trace file. - **`zen trace trim`** — Extract a time-window from a trace into a new `.utrace` file. - **`zen trace serve`** — Launch a local HTTP server hosting an interactive trace viewer; opens in the default browser. ### Symbolication (`src/zen/trace/symbol_resolver.*`, `thirdparty/raw_pdb/`) - Pluggable resolver with multiple backends: `pdb` (in-tree raw_pdb), `dbghelp` (Windows), `llvm-symbolizer` (all platforms), `atos` (macOS). An `auto` backend picks the best available tool per platform. - Microsoft Symbol Server support: downloads PDBs on demand using a redirect-aware HTTP client. - Local PDB cache keyed by image GUID preserves symbols across binary recompilation. - Callstack trimming heuristic strips UE internal noise from reports. - Binary analysis cache (`.ucache_z`) avoids re-resolving the same trace. ### Interactive trace viewer (`src/zen/frontend/html/`, `src/zen/trace/trace_viewer_service.*`) - Timeline: scope-level detail, horizontal zoom/pan, vertical scrolling, viewport-driven loading with pre-computed LOD for responsive navigation of large traces. - Thread grouping (collapsible sidebar sections) synthesized from name suffixes, natural sort order, visual distinction between lane threads and OS threads. - Bookmark and region annotations; region categories with per-category toggles; bookmark marker toggle in the toolbar. - Filterable Logs tab showing captured `UE_LOG` output. - Stats tab with per-scope aggregate statistics. - Memory tab with interactive allocation analysis and an allocation size histogram. - CsvProfiler event parsing and chart UI. ### Other in-branch supporting changes - **Cross-platform browser launcher** (`browser_launcher.{h,cpp}`) used by `trace serve`. - **`ReciprocalU64`** fast 64-bit integer division (zencore/intmath) for trace analyzers. - **`parallelsort`** cross-platform parallel sort helper (zenutil). - Frontend zip build rule so the viewer's HTML assets are bundled into `zen.exe`. - `/Zo` flag for better optimized debug info on Windows release builds. - `trace-tests.cpp` in the `zen-test` harness (harness itself landed on main via #985). --- src/zencore/string.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/zencore/string.cpp') diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index 2691d14b8..34519b83b 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -56,20 +56,22 @@ bool ToString(std::span Buffer, uint64_t Num) { auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); - if (Ec == std::errc{}) + if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size()) { - *Ptr = '\0'; + return false; } + *Ptr = '\0'; return true; } bool ToString(std::span Buffer, int64_t Num) { auto [Ptr, Ec] = std::to_chars(Buffer.data(), Buffer.data() + Buffer.size(), Num); - if (Ec == std::errc{}) + if (Ec != std::errc{} || Ptr == Buffer.data() + Buffer.size()) { - *Ptr = '\0'; + return false; } + *Ptr = '\0'; return true; } -- cgit v1.2.3