// Copyright Epic Games, Inc. All Rights Reserved. #include "top_cmd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include ////////////////////////////////////////////////////////////////////////// namespace zen { namespace { struct RunningInstance { uint16_t Port; uint32_t Pid; std::string SessionId; }; static std::vector CollectInstances(ZenServerState& State) { std::vector Instances; State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { StringBuilder<25> SessionSB; Entry.GetSessionId().ToString(SessionSB); Instances.push_back({Entry.EffectiveListenPort.load(), Entry.Pid.load(), std::string(SessionSB.c_str())}); }); return Instances; } static std::string FormatCurrentTime() { std::time_t Now = std::time(nullptr); std::tm Local{}; #if ZEN_PLATFORM_WINDOWS localtime_s(&Local, &Now); #else localtime_r(&Now, &Local); #endif char Buffer[20]; std::strftime(Buffer, sizeof(Buffer), "%Y-%m-%d %H:%M:%S", &Local); return Buffer; } static void PrintHorizontalRule(uint32_t Cols) { // U+2500 BOX DRAWINGS LIGHT HORIZONTAL = \xe2\x94\x80 (3 bytes per glyph) ExtendableStringBuilder<512> Line; for (uint32_t i = 0; i < Cols; ++i) { Line.Append("\xe2\x94\x80"); } printf("%s\n", Line.c_str()); } struct ServerStats { // /health/info bool InfoAvailable = false; std::string_view DataRoot; std::string_view BuildVersion; // /stats/z$ -> cache bool CacheAvailable = false; uint64_t CacheHits = 0; uint64_t CacheMisses = 0; double CacheHitRatio = 0.0; uint64_t CacheWrites = 0; uint64_t CacheDiskSize = 0; uint64_t CacheMemSize = 0; uint64_t CacheRpcCount = 0; uint64_t CacheRpcOps = 0; // /stats/prj -> project store bool PrjAvailable = false; uint64_t PrjRequestCount = 0; uint64_t PrjDiskSize = 0; uint64_t PrjMemSize = 0; uint64_t PrjOpHits = 0; uint64_t PrjOpMisses = 0; uint64_t PrjOpWrites = 0; uint64_t PrjChunkHits = 0; uint64_t PrjChunkMisses = 0; uint64_t PrjChunkWrites = 0; // Owned buffers to keep string_views alive CbObject InfoObject; CbObject CacheObject; CbObject PrjObject; }; static void FetchServerStats(HttpClient& Http, ServerStats& Stats) { // Fetch /health/info try { if (HttpClient::Response Response = Http.Get("/health/info")) { Stats.InfoObject = Response.AsObject(); Stats.DataRoot = Stats.InfoObject["DataRoot"].AsString(); Stats.BuildVersion = Stats.InfoObject["BuildVersion"].AsString(); Stats.InfoAvailable = true; } } catch (...) { } // Fetch /stats/z$ try { if (HttpClient::Response Response = Http.Get("/stats/z$")) { Stats.CacheObject = Response.AsObject(); CbObjectView CacheView = Stats.CacheObject["cache"].AsObjectView(); Stats.CacheHits = CacheView["hits"].AsUInt64(0); Stats.CacheMisses = CacheView["misses"].AsUInt64(0); Stats.CacheHitRatio = CacheView["hit_ratio"].AsDouble(0.0); Stats.CacheWrites = CacheView["writes"].AsUInt64(0); CbObjectView SizeView = CacheView["size"].AsObjectView(); Stats.CacheDiskSize = SizeView["disk"].AsUInt64(0); Stats.CacheMemSize = SizeView["memory"].AsUInt64(0); CbObjectView RpcView = CacheView["rpc"].AsObjectView(); Stats.CacheRpcCount = RpcView["count"].AsUInt64(0); Stats.CacheRpcOps = RpcView["ops"].AsUInt64(0); Stats.CacheAvailable = true; } } catch (...) { } // Fetch /stats/prj try { if (HttpClient::Response Response = Http.Get("/stats/prj")) { Stats.PrjObject = Response.AsObject(); CbObjectView StoreView = Stats.PrjObject["store"].AsObjectView(); Stats.PrjRequestCount = StoreView["requestcount"].AsUInt64(0); CbObjectView SizeView = StoreView["size"].AsObjectView(); Stats.PrjDiskSize = SizeView["disk"].AsUInt64(0); Stats.PrjMemSize = SizeView["memory"].AsUInt64(0); CbObjectView OpView = StoreView["op"].AsObjectView(); Stats.PrjOpHits = OpView["hitcount"].AsUInt64(0); Stats.PrjOpMisses = OpView["misscount"].AsUInt64(0); Stats.PrjOpWrites = OpView["writecount"].AsUInt64(0); CbObjectView ChunkView = StoreView["chunk"].AsObjectView(); Stats.PrjChunkHits = ChunkView["hitcount"].AsUInt64(0); Stats.PrjChunkMisses = ChunkView["misscount"].AsUInt64(0); Stats.PrjChunkWrites = ChunkView["writecount"].AsUInt64(0); Stats.PrjAvailable = true; } } catch (...) { } } static HttpClientSettings MakeQuickSettings() { HttpClientSettings Settings; Settings.ConnectTimeout = std::chrono::milliseconds(2000); Settings.Timeout = std::chrono::milliseconds(3000); return Settings; } } // namespace TopCommand::TopCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), ""); m_Options .add_option("", "i", "interval", "Refresh interval in milliseconds", cxxopts::value(m_IntervalMs)->default_value("1000"), ""); } TopCommand::~TopCommand() = default; void TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return; } if (m_IntervalMs < 100) { m_IntervalMs = 100; } // Gather system metrics SystemMetrics SysMetrics = GetSystemMetrics(); // Gather running instances ZenServerState State; bool HasState = State.InitializeReadOnly(); // Collect instances and determine initial target std::vector Instances; if (HasState) { Instances = CollectInstances(State); } // Resolve the host to connect to. If -u was given, use that directly. // Otherwise auto-discover from running instances. std::string ActiveHost = m_HostName; int SelectedIdx = -1; // index into Instances; -1 = explicit host or none if (ActiveHost.empty() && !Instances.empty()) { SelectedIdx = 0; ActiveHost = fmt::format("http://localhost:{}", Instances[0].Port); } else if (!ActiveHost.empty()) { ActiveHost = ResolveTargetHostSpec(ActiveHost); } // If not an interactive terminal, print a single snapshot and exit if (!IsTuiAvailable()) { printf("zen top"); if (!ActiveHost.empty()) { printf(" - %s", ActiveHost.c_str()); } printf(" - %s\n", FormatCurrentTime().c_str()); printf("System: %u CPU, %u cores, %u LPs | Memory: %llu MiB (%llu avail)\n", SysMetrics.CpuCount, SysMetrics.CoreCount, SysMetrics.LogicalProcessorCount, (unsigned long long)SysMetrics.SystemMemoryMiB, (unsigned long long)SysMetrics.AvailSystemMemoryMiB); if (!Instances.empty()) { printf("\nInstances:\n"); printf(" %5s %7s %s\n", "PORT", "PID", "SESSION"); for (const auto& Inst : Instances) { printf(" %5u %7u %s\n", Inst.Port, Inst.Pid, Inst.SessionId.c_str()); } } if (!ActiveHost.empty()) { HttpClient Http(ActiveHost, MakeQuickSettings()); ServerStats Stats; FetchServerStats(Http, Stats); if (Stats.CacheAvailable) { printf("\nCache (z$):\n"); printf(" Hits: %llu Misses: %llu Hit Ratio: %.1f%% Writes: %llu\n", (unsigned long long)Stats.CacheHits, (unsigned long long)Stats.CacheMisses, Stats.CacheHitRatio * 100.0, (unsigned long long)Stats.CacheWrites); printf(" Disk: %s Memory: %s\n", NiceBytes(Stats.CacheDiskSize).c_str(), NiceBytes(Stats.CacheMemSize).c_str()); printf(" RPC: %llu requests, %llu ops\n", (unsigned long long)Stats.CacheRpcCount, (unsigned long long)Stats.CacheRpcOps); } if (Stats.PrjAvailable) { printf("\nProject Store:\n"); printf(" Requests: %llu Disk: %s Memory: %s\n", (unsigned long long)Stats.PrjRequestCount, NiceBytes(Stats.PrjDiskSize).c_str(), NiceBytes(Stats.PrjMemSize).c_str()); printf(" Ops: %llu hits, %llu misses, %llu writes\n", (unsigned long long)Stats.PrjOpHits, (unsigned long long)Stats.PrjOpMisses, (unsigned long long)Stats.PrjOpWrites); printf(" Chunks: %llu hits, %llu misses, %llu writes\n", (unsigned long long)Stats.PrjChunkHits, (unsigned long long)Stats.PrjChunkMisses, (unsigned long long)Stats.PrjChunkWrites); } } return; } // Enter fullscreen alternate-screen mode TuiEnterAlternateScreen(); auto ExitGuard = MakeGuard([]() { TuiExitAlternateScreen(); }); // Create HTTP client for the active host std::unique_ptr Http; auto RecreateClient = [&]() { if (!ActiveHost.empty()) { Http = std::make_unique(ActiveHost, MakeQuickSettings()); } else { Http.reset(); } }; RecreateClient(); bool NeedsRedraw = true; // Main refresh loop for (;;) { if (NeedsRedraw) { uint32_t Cols = TuiConsoleColumns(); TuiCursorHome(); // Fetch fresh system metrics each iteration SysMetrics = GetSystemMetrics(); // Refresh instance list if (HasState) { if (!State.IsReadOnly()) { State.Sweep(); } Instances = CollectInstances(State); // Clamp selection if (SelectedIdx >= static_cast(Instances.size())) { SelectedIdx = static_cast(Instances.size()) - 1; } } // Fetch server stats ServerStats Stats; if (Http) { FetchServerStats(*Http, Stats); } // Header line { ExtendableStringBuilder<256> Header; Header << "zen top"; if (!ActiveHost.empty()) { Header << " - " << ActiveHost; } if (Stats.InfoAvailable && !Stats.BuildVersion.empty()) { Header << " - " << Stats.BuildVersion; } Header << " - " << FormatCurrentTime(); printf("%-*s\n", Cols, Header.c_str()); } PrintHorizontalRule(Cols); // System metrics printf("System: %u CPU, %u cores, %u LPs | Memory: %llu MiB (%llu avail)\033[K\n", SysMetrics.CpuCount, SysMetrics.CoreCount, SysMetrics.LogicalProcessorCount, (unsigned long long)SysMetrics.SystemMemoryMiB, (unsigned long long)SysMetrics.AvailSystemMemoryMiB); // Running instances printf("\033[K\n"); if (!Instances.empty()) { bool MultipleInstances = (Instances.size() > 1 && SelectedIdx >= 0); if (MultipleInstances) { printf("\033[1mInstances:\033[0m \033[2m(\xe2\x86\x91/\xe2\x86\x93 to switch)\033[0m\033[K\n"); } else { printf("\033[1mInstances:\033[0m\033[K\n"); } printf(" %5s %7s %s\033[K\n", "PORT", "PID", "SESSION"); for (int i = 0; i < static_cast(Instances.size()); ++i) { const auto& Inst = Instances[i]; bool IsSelected = (i == SelectedIdx); if (IsSelected) { // Highlight: bold + reverse video printf("\033[1;7m"); } printf(" %5u %7u %s\033[K", Inst.Port, Inst.Pid, Inst.SessionId.c_str()); if (IsSelected) { printf("\033[0m"); } printf("\n"); } } else { printf(" No Zen instances found\033[K\n"); } // Cache stats printf("\033[K\n"); if (Stats.CacheAvailable) { printf("\033[1mCache (z$):\033[0m\033[K\n"); printf(" Hits: %-10llu Misses: %-10llu Hit Ratio: %-7.1f%% Writes: %llu\033[K\n", (unsigned long long)Stats.CacheHits, (unsigned long long)Stats.CacheMisses, Stats.CacheHitRatio * 100.0, (unsigned long long)Stats.CacheWrites); printf(" Disk: %-12s Memory: %s\033[K\n", NiceBytes(Stats.CacheDiskSize).c_str(), NiceBytes(Stats.CacheMemSize).c_str()); printf(" RPC: %llu requests, %llu ops\033[K\n", (unsigned long long)Stats.CacheRpcCount, (unsigned long long)Stats.CacheRpcOps); } else if (Http) { printf("\033[1mCache (z$):\033[0m unavailable\033[K\n"); } // Project store stats printf("\033[K\n"); if (Stats.PrjAvailable) { printf("\033[1mProject Store:\033[0m\033[K\n"); printf(" Requests: %-10llu Disk: %-12s Memory: %s\033[K\n", (unsigned long long)Stats.PrjRequestCount, NiceBytes(Stats.PrjDiskSize).c_str(), NiceBytes(Stats.PrjMemSize).c_str()); printf(" Ops: %llu hits, %llu misses, %llu writes\033[K\n", (unsigned long long)Stats.PrjOpHits, (unsigned long long)Stats.PrjOpMisses, (unsigned long long)Stats.PrjOpWrites); printf(" Chunks: %llu hits, %llu misses, %llu writes\033[K\n", (unsigned long long)Stats.PrjChunkHits, (unsigned long long)Stats.PrjChunkMisses, (unsigned long long)Stats.PrjChunkWrites); } else if (Http) { printf("\033[1mProject Store:\033[0m unavailable\033[K\n"); } // Server info if (Stats.InfoAvailable && !Stats.DataRoot.empty()) { printf("\033[K\n"); printf(" Data root: %.*s\033[K\n", static_cast(Stats.DataRoot.size()), Stats.DataRoot.data()); } // Clear any stale content below TuiClearToBottom(); // Footer (print at bottom by moving cursor) { uint32_t Rows = TuiConsoleRows(); printf("\033[%u;1H", Rows); // Move to last row if (Instances.size() > 1 && SelectedIdx >= 0) { printf("\033[2mq quit | \xe2\x86\x91/\xe2\x86\x93 switch instance | F5 refresh | Refreshing every %.1fs\033[0m\033[K", static_cast(m_IntervalMs) / 1000.0); } else { printf("\033[2mq quit | F5 refresh | Refreshing every %.1fs\033[0m\033[K", static_cast(m_IntervalMs) / 1000.0); } } fflush(stdout); NeedsRedraw = false; } // Wait for input or next tick, whichever comes first. // Uses TuiWaitForInput() which blocks on the OS input handle, so keypresses // are detected immediately rather than waiting for a sleep interval to elapse. auto TickStart = std::chrono::steady_clock::now(); for (;;) { TuiInput Input = TuiPollInput(); if (Input == TuiInput::Quit) { return; } if (Input == TuiInput::Refresh) { NeedsRedraw = true; break; } if (Input == TuiInput::ArrowUp || Input == TuiInput::ArrowDown) { // Only handle arrow keys when we have auto-discovered instances to switch between if (SelectedIdx >= 0 && Instances.size() > 1) { int Count = static_cast(Instances.size()); if (Input == TuiInput::ArrowUp) { SelectedIdx = (SelectedIdx - 1 + Count) % Count; } else { SelectedIdx = (SelectedIdx + 1) % Count; } ActiveHost = fmt::format("http://localhost:{}", Instances[SelectedIdx].Port); RecreateClient(); NeedsRedraw = true; break; } } auto Now = std::chrono::steady_clock::now(); uint32_t ElapsedMs = static_cast(std::chrono::duration_cast(Now - TickStart).count()); if (ElapsedMs >= m_IntervalMs) { break; } TuiWaitForInput(m_IntervalMs - ElapsedMs); } // Schedule a full redraw for the next iteration NeedsRedraw = true; } } ////////////////////////////////////////////////////////////////////////// PsCommand::PsCommand() { } PsCommand::~PsCommand() = default; void PsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions, argc, argv); ZenServerState State; if (!State.InitializeReadOnly()) { ZEN_CONSOLE("No Zen state found"); return; } State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { ZEN_CONSOLE("Port {} : pid {}", Entry.EffectiveListenPort.load(), Entry.Pid.load()); }); } } // namespace zen