aboutsummaryrefslogtreecommitdiff
path: root/src/zen/cmds/cache_cmd.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-04-20 23:52:38 +0200
committerGitHub Enterprise <[email protected]>2026-04-20 23:52:38 +0200
commit27d72af24a8de9a81500e68a0874f1430297b3bc (patch)
tree33aed09055a4d38b4b3bc6513be7567d0e53d1f8 /src/zen/cmds/cache_cmd.cpp
parentRename logging::ToStringView to ToString for consistency (#993) (diff)
downloadarchived-zen-27d72af24a8de9a81500e68a0874f1430297b3bc.tar.xz
archived-zen-27d72af24a8de9a81500e68a0874f1430297b3bc.zip
Zen CLI common server interface (#920)
Introduces a common `ZenServiceClient` RAII wrapper for zen CLI commands that interact with a zenserver instance. CLI operations (admin, builds, cache, exec, hub, info, projectstore, trace, ui, version, vfs, workspaces) automatically register sessions so they become visible in the server's session list, and forward log output to the server's session log endpoint. All session HTTP I/O (announce, remove, log batches) runs on a single background worker thread, so CLI startup and shutdown never block on server availability. ### Key changes - **`ZenServiceClient`** — new RAII class that wraps host resolution, HTTP client creation, and session lifecycle (register on connect, remove on exit). Replaces ad-hoc boilerplate across all command files that talk to a server, including the new `trace` subcommands (`start`, `stop`, `status`). - **Async session I/O** — `SessionsServiceClient` now owns a single worker thread and command queue. `Announce()`, `Remove()`, and `UpdateMetadata()` enqueue commands and return immediately. The worker creates one `HttpClient` with a 5-second total timeout, bounding any individual request. Eliminates main-thread stalls when the server is unreachable. - **Session log forwarding** — `SessionLogSink` is a thin enqueuer that posts log messages to the same worker queue (no separate thread or HTTP client). Log levels are serialized as integers; the server-side ingest handles both string and integer formats for backwards compatibility, with bounds checking on integer values. - **Build & projectstore session registration** — Long-running `builds` and projectstore cache (oplog-download) connections register sessions too, making them visible alongside regular CLI command sessions. ### Cleanup - Extract `SetupCacheSession` helper on `StorageInstance` to reduce duplication. - Remove unused `HttpClient` reference in ui command.
Diffstat (limited to 'src/zen/cmds/cache_cmd.cpp')
-rw-r--r--src/zen/cmds/cache_cmd.cpp46
1 files changed, 27 insertions, 19 deletions
diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp
index f024ea57e..b6f272ee7 100644
--- a/src/zen/cmds/cache_cmd.cpp
+++ b/src/zen/cmds/cache_cmd.cpp
@@ -2,6 +2,8 @@
#include "cache_cmd.h"
+#include "zenserviceclient.h"
+
#include <zencore/compactbinarybuilder.h>
#include <zencore/compress.h>
#include <zencore/except.h>
@@ -169,6 +171,8 @@ void
CacheDropSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "drop"});
+ HttpClient& Http = Service.Http();
if (m_NamespaceName.empty())
{
@@ -180,18 +184,16 @@ CacheDropSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
if (m_BucketName.empty())
{
- DropDescription = fmt::format("cache namespace '{}' from '{}'", m_NamespaceName, m_HostName);
+ DropDescription = fmt::format("cache namespace '{}' from '{}'", m_NamespaceName, Service.HostSpec());
Url = fmt::format("/z$/{}", m_NamespaceName);
}
else
{
- DropDescription = fmt::format("cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, m_HostName);
+ DropDescription = fmt::format("cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, Service.HostSpec());
Url = fmt::format("/z$/{}/{}", m_NamespaceName, m_BucketName);
}
ZEN_CONSOLE("Dropping {}", DropDescription);
-
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
if (HttpClient::Response Response = Http.Delete(Url))
{
ZEN_CONSOLE("{}", Response.ToText());
@@ -224,6 +226,8 @@ void
CacheInfoSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "info"});
+ HttpClient& Http = Service.Http();
std::string Url;
if (m_NamespaceName.empty())
@@ -236,7 +240,7 @@ CacheInfoSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
throw OptionParseException("'--bucketsize' requires '--namespace' and '--bucket'", m_SubOptions.help());
}
- ZEN_CONSOLE("Info on cache from '{}'", m_HostName);
+ ZEN_CONSOLE("Info on cache from '{}'", Service.HostSpec());
Url = "/z$";
}
else if (m_BucketName.empty())
@@ -246,7 +250,7 @@ CacheInfoSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
throw OptionParseException(fmt::format("'--bucketsize' requires '--namespace' and '--bucket' ('{}')", m_BucketName),
m_SubOptions.help());
}
- ZEN_CONSOLE("Info on cache namespace '{}' from '{}'", m_NamespaceName, m_HostName);
+ ZEN_CONSOLE("Info on cache namespace '{}' from '{}'", m_NamespaceName, Service.HostSpec());
Url = fmt::format("/z$/{}", m_NamespaceName);
}
else
@@ -255,7 +259,7 @@ CacheInfoSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
throw OptionParseException("'--bucketsizes' conflicts with '--bucket'", m_SubOptions.help());
}
- ZEN_CONSOLE("Info on cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, m_HostName);
+ ZEN_CONSOLE("Info on cache bucket '{}/{}' from '{}'", m_NamespaceName, m_BucketName, Service.HostSpec());
Url = fmt::format("/z$/{}/{}", m_NamespaceName, m_BucketName);
}
@@ -271,7 +275,6 @@ CacheInfoSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
const ZenContentType AcceptType = m_YAML ? ZenContentType::kYAML : ZenContentType::kJSON;
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
if (HttpClient::Response Response = Http.Get(Url, HttpClient::Accept(AcceptType), Parameters))
{
ZEN_CONSOLE("{}", Response.ToText());
@@ -294,10 +297,11 @@ void
CacheStatsSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "stats"});
+ HttpClient& Http = Service.Http();
const ZenContentType AcceptType = m_YAML ? ZenContentType::kYAML : ZenContentType::kJSON;
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
if (HttpClient::Response Response = Http.Get("/stats/z$", HttpClient::Accept(AcceptType)))
{
ZEN_CONSOLE("{}", Response.ToText());
@@ -331,6 +335,8 @@ void
CacheDetailsSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "details"});
+ HttpClient& Http = Service.Http();
if (m_CSV && m_YAML)
{
@@ -387,7 +393,6 @@ CacheDetailsSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
Url = "/z$/details$";
}
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
if (HttpClient::Response Response = Http.Get(Url, Headers, Parameters))
{
ZEN_CONSOLE("{}", Response.ToText());
@@ -429,6 +434,8 @@ void
CacheGenSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "gen"});
+ HttpClient& Http = Service.Http();
if (m_MaxSize == 0 && m_MinSize == 0)
{
@@ -486,8 +493,6 @@ CacheGenSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
std::uniform_int_distribution<uint64_t> KeyDistribution;
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
-
auto GeneratePutCacheValueRequest(
[this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) -> CbPackage {
CbPackage Package;
@@ -663,7 +668,11 @@ CacheGetSubCmd::CacheGetSubCmd() : CacheSubCmdBase("get", "Get cache values/reco
void
CacheGetSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
+ using namespace std::literals;
+
ResolveHost();
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "get"});
+ HttpClient& Http = Service.Http();
if (m_Namespace.empty())
{
@@ -695,8 +704,6 @@ CacheGetSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
}
}
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
-
if (!m_OutputPath.empty())
{
if (IsDir(m_OutputPath))
@@ -774,8 +781,8 @@ void
CacheRecordSubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
ResolveHost();
-
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "record"});
+ HttpClient& Http = Service.Http();
if (m_Path == "stop")
{
@@ -877,8 +884,6 @@ CacheReplaySubCmd::CacheReplaySubCmd() : CacheSubCmdBase("replay", "Replays a pr
void
CacheReplaySubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
{
- ResolveHost();
-
if (m_RecordingPath.empty())
{
throw OptionParseException("'--path' is required", m_SubOptions.help());
@@ -896,6 +901,9 @@ CacheReplaySubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
m_ThreadCount = Max(m_ThreadCount, 1);
+ ZenServiceClient Service({.HostSpec = m_HostName, .CommandName = "replay"});
+ m_HostName = Service.HostSpec();
+
ZEN_CONSOLE("Replay '{}' (start offset {}, stride {}) to '{}', {} threads",
m_RecordingPath,
m_Offset,
@@ -907,7 +915,7 @@ CacheReplaySubCmd::Run(const ZenCliOptions& /*GlobalOptions*/)
if (m_OnHost)
{
- HttpClient Http = CacheCommand::CreateHttpClient(m_HostName);
+ HttpClient& Http = Service.Http();
if (HttpClient::Response Response =
Http.Post("/z$/exec$/replay-recording"sv,
HttpClient::KeyValueMap{},