aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2026-03-27 16:23:59 +0100
committerGitHub Enterprise <[email protected]>2026-03-27 16:23:59 +0100
commitaff16da9a634ff6869b0394bf936bbb45096ad54 (patch)
tree3d88cc842e6a87c3e4d07cc838a31b8dbabfce9a /src/zenserver
parentremove CPR HTTP client backend (#894) (diff)
downloadzen-aff16da9a634ff6869b0394bf936bbb45096ad54.tar.xz
zen-aff16da9a634ff6869b0394bf936bbb45096ad54.zip
dashboard improvements (#896)
- Feature: Added Workspaces dashboard page with HTTP request stats and per-workspace metrics - Feature: Added Build Storage dashboard page with service-specific HTTP request stats - Improvement: Front page now shows Hub and Object Store activity tiles; HTTP panel is fixed above the tiles grid - Improvement: HTTP stats tiles now include 5m/15m rates and p999/max latency across all service pages
Diffstat (limited to 'src/zenserver')
-rw-r--r--src/zenserver/frontend/frontend.cpp10
-rw-r--r--src/zenserver/frontend/frontend.h1
-rw-r--r--src/zenserver/frontend/html/pages/builds.js88
-rw-r--r--src/zenserver/frontend/html/pages/hub.js15
-rw-r--r--src/zenserver/frontend/html/pages/objectstore.js48
-rw-r--r--src/zenserver/frontend/html/pages/page.js72
-rw-r--r--src/zenserver/frontend/html/pages/projects.js50
-rw-r--r--src/zenserver/frontend/html/pages/start.js115
-rw-r--r--src/zenserver/frontend/html/pages/workspaces.js236
-rw-r--r--src/zenserver/frontend/html/zen.css84
-rw-r--r--src/zenserver/hub/httphubservice.cpp12
-rw-r--r--src/zenserver/hub/httphubservice.h1
-rw-r--r--src/zenserver/storage/cache/httpstructuredcache.cpp91
-rw-r--r--src/zenserver/storage/objectstore/objectstore.cpp17
-rw-r--r--src/zenserver/storage/objectstore/objectstore.h3
-rw-r--r--src/zenserver/storage/projectstore/httpprojectstore.cpp20
-rw-r--r--src/zenserver/storage/workspaces/httpworkspaces.cpp20
17 files changed, 633 insertions, 250 deletions
diff --git a/src/zenserver/frontend/frontend.cpp b/src/zenserver/frontend/frontend.cpp
index fa7b580e8..52ec5b8b3 100644
--- a/src/zenserver/frontend/frontend.cpp
+++ b/src/zenserver/frontend/frontend.cpp
@@ -239,11 +239,17 @@ HttpFrontendService::HandleRequest(zen::HttpServerRequest& Request)
void
HttpFrontendService::HandleStatsRequest(HttpServerRequest& Request)
{
+ Request.WriteResponse(HttpResponseCode::OK, CollectStats());
+}
+
+CbObject
+HttpFrontendService::CollectStats()
+{
+ ZEN_TRACE_CPU("HttpFrontendService::Stats");
CbObjectWriter Cbo;
EmitSnapshot("requests", m_HttpRequests, Cbo);
-
- Request.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ return Cbo.Save();
}
uint64_t
diff --git a/src/zenserver/frontend/frontend.h b/src/zenserver/frontend/frontend.h
index 541e6213b..e0b86f1de 100644
--- a/src/zenserver/frontend/frontend.h
+++ b/src/zenserver/frontend/frontend.h
@@ -22,6 +22,7 @@ public:
virtual void HandleRequest(HttpServerRequest& Request) override;
virtual void HandleStatusRequest(HttpServerRequest& Request) override;
virtual void HandleStatsRequest(HttpServerRequest& Request) override;
+ virtual CbObject CollectStats() override;
virtual uint64_t GetActivityCounter() override;
private:
diff --git a/src/zenserver/frontend/html/pages/builds.js b/src/zenserver/frontend/html/pages/builds.js
new file mode 100644
index 000000000..095f0bf29
--- /dev/null
+++ b/src/zenserver/frontend/html/pages/builds.js
@@ -0,0 +1,88 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+"use strict";
+
+import { ZenPage } from "./page.js"
+import { Fetcher } from "../util/fetcher.js"
+import { Friendly } from "../util/friendly.js"
+
+////////////////////////////////////////////////////////////////////////////////
+export class Page extends ZenPage
+{
+ generate_crumbs() {}
+
+ async main()
+ {
+ this.set_title("build store");
+
+ // Build Store Stats
+ const stats_section = this.add_section("Build Store Stats");
+ stats_section.tag().classify("dropall").text("raw yaml \u2192").on_click(() => {
+ window.open("/stats/builds.yaml", "_blank");
+ });
+ this._stats_grid = stats_section.tag().classify("grid").classify("stats-tiles");
+
+ const stats = await new Fetcher().resource("stats", "builds").json();
+ if (stats)
+ {
+ this._render_stats(stats);
+ }
+
+ this.connect_stats_ws((all_stats) => {
+ const s = all_stats["builds"];
+ if (s)
+ {
+ this._render_stats(s);
+ }
+ });
+ }
+
+ _render_stats(stats)
+ {
+ const grid = this._stats_grid;
+ const safe = (obj, path) => path.split(".").reduce((a, b) => a && a[b], obj);
+
+ grid.inner().innerHTML = "";
+
+ // HTTP Requests tile
+ this._render_http_requests_tile(grid, safe(stats, "requests"), safe(stats, "store.badrequestcount") || 0);
+
+ // Build Store tile
+ {
+ const blobs = safe(stats, "store.blobs");
+ const metadata = safe(stats, "store.metadata");
+ if (blobs || metadata)
+ {
+ const tile = grid.tag().classify("card").classify("stats-tile");
+ tile.tag().classify("card-title").text("Build Store");
+ const columns = tile.tag().classify("tile-columns");
+
+ const left = columns.tag().classify("tile-metrics");
+ this._metric(left, Friendly.bytes(safe(stats, "store.size.disk") || 0), "disk", true);
+ if (blobs)
+ {
+ this._metric(left, Friendly.sep(blobs.count || 0), "blobs");
+ this._metric(left, Friendly.sep(blobs.readcount || 0), "blob reads");
+ this._metric(left, Friendly.sep(blobs.writecount || 0), "blob writes");
+ const blobHitRatio = (blobs.readcount || 0) > 0
+ ? (((blobs.hitcount || 0) / blobs.readcount) * 100).toFixed(1) + "%"
+ : "-";
+ this._metric(left, blobHitRatio, "blob hit ratio");
+ }
+
+ const right = columns.tag().classify("tile-metrics");
+ if (metadata)
+ {
+ this._metric(right, Friendly.sep(metadata.count || 0), "metadata entries", true);
+ this._metric(right, Friendly.sep(metadata.readcount || 0), "meta reads");
+ this._metric(right, Friendly.sep(metadata.writecount || 0), "meta writes");
+ const metaHitRatio = (metadata.readcount || 0) > 0
+ ? (((metadata.hitcount || 0) / metadata.readcount) * 100).toFixed(1) + "%"
+ : "-";
+ this._metric(right, metaHitRatio, "meta hit ratio");
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/zenserver/frontend/html/pages/hub.js b/src/zenserver/frontend/html/pages/hub.js
index 149a5c79c..78e3a090c 100644
--- a/src/zenserver/frontend/html/pages/hub.js
+++ b/src/zenserver/frontend/html/pages/hub.js
@@ -178,7 +178,7 @@ export class Page extends ZenPage
try
{
const [stats, status] = await Promise.all([
- new Fetcher().resource("/hub/stats").json(),
+ new Fetcher().resource("stats", "hub").json(),
new Fetcher().resource("/hub/status").json(),
]);
@@ -198,6 +198,9 @@ export class Page extends ZenPage
const max = data.maxInstanceCount || 0;
const limit = data.instanceLimit || 0;
+ // HTTP Requests tile
+ this._render_http_requests_tile(grid, data.requests);
+
{
const tile = grid.tag().classify("card").classify("stats-tile");
tile.tag().classify("card-title").text("Active Modules");
@@ -611,14 +614,4 @@ export class Page extends ZenPage
await fetch(`/hub/modules/${moduleId}/${action}`, { method: "POST" });
}
- _metric(parent, value, label, hero = false)
- {
- const m = parent.tag().classify("tile-metric");
- if (hero)
- {
- m.classify("tile-metric-hero");
- }
- m.tag().classify("metric-value").text(value);
- m.tag().classify("metric-label").text(label);
- }
}
diff --git a/src/zenserver/frontend/html/pages/objectstore.js b/src/zenserver/frontend/html/pages/objectstore.js
index 69e0a91b3..6b4890614 100644
--- a/src/zenserver/frontend/html/pages/objectstore.js
+++ b/src/zenserver/frontend/html/pages/objectstore.js
@@ -30,13 +30,16 @@ export class Page extends ZenPage
{
try
{
- const data = await new Fetcher().resource("/obj/").json();
- this._render(data);
+ const [data, stats] = await Promise.all([
+ new Fetcher().resource("/obj/").json(),
+ new Fetcher().resource("stats", "obj").json().catch(() => null),
+ ]);
+ this._render(data, stats);
}
catch (e) { /* service unavailable */ }
}
- _render(data)
+ _render(data, stats)
{
const buckets = data.buckets || [];
@@ -53,32 +56,17 @@ export class Page extends ZenPage
const total_objects = buckets.reduce((sum, b) => sum + (b.object_count || 0), 0);
const total_size = buckets.reduce((sum, b) => sum + (b.size || 0), 0);
- {
- const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("Buckets");
- const body = tile.tag().classify("tile-metrics");
- this._metric(body, Friendly.sep(buckets.length), "total", true);
- }
-
- {
- const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("Objects");
- const body = tile.tag().classify("tile-metrics");
- this._metric(body, Friendly.sep(total_objects), "total", true);
- }
+ // HTTP Requests tile
+ this._render_http_requests_tile(grid, stats && stats.requests);
{
const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("Storage");
+ tile.tag().classify("card-title").text("Object Store");
const body = tile.tag().classify("tile-metrics");
- this._metric(body, Friendly.bytes(total_size), "total size", true);
- }
-
- {
- const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("Served");
- const body = tile.tag().classify("tile-metrics");
- this._metric(body, Friendly.bytes(data.total_bytes_served || 0), "total bytes served", true);
+ this._metric(body, Friendly.sep(buckets.length), "buckets", true);
+ this._metric(body, Friendly.sep(total_objects), "objects");
+ this._metric(body, Friendly.bytes(total_size), "storage");
+ this._metric(body, Friendly.bytes(data.total_bytes_served || 0), "bytes served");
}
}
@@ -219,14 +207,4 @@ export class Page extends ZenPage
}
}
- _metric(parent, value, label, hero = false)
- {
- const m = parent.tag().classify("tile-metric");
- if (hero)
- {
- m.classify("tile-metric-hero");
- }
- m.tag().classify("metric-value").text(value);
- m.tag().classify("metric-label").text(label);
- }
}
diff --git a/src/zenserver/frontend/html/pages/page.js b/src/zenserver/frontend/html/pages/page.js
index d969d651d..cf8d3e3dd 100644
--- a/src/zenserver/frontend/html/pages/page.js
+++ b/src/zenserver/frontend/html/pages/page.js
@@ -4,6 +4,7 @@
import { WidgetHost } from "../util/widgets.js"
import { Fetcher } from "../util/fetcher.js"
+import { Friendly } from "../util/friendly.js"
////////////////////////////////////////////////////////////////////////////////
export class PageBase extends WidgetHost
@@ -148,8 +149,10 @@ export class ZenPage extends PageBase
const service_dashboards = [
{ base_uri: "/sessions/", label: "Sessions", href: "/dashboard/?page=sessions" },
{ base_uri: "/z$/", label: "Cache", href: "/dashboard/?page=cache" },
+ { base_uri: "/builds/", label: "Build Store", href: "/dashboard/?page=builds" },
{ base_uri: "/prj/", label: "Projects", href: "/dashboard/?page=projects" },
{ base_uri: "/obj/", label: "Object Store", href: "/dashboard/?page=objectstore" },
+ { base_uri: "/ws/", label: "Workspaces", href: "/dashboard/?page=workspaces" },
{ base_uri: "/compute/", label: "Compute", href: "/dashboard/?page=compute" },
{ base_uri: "/orch/", label: "Orchestrator", href: "/dashboard/?page=orchestrator" },
{ base_uri: "/hub/", label: "Hub", href: "/dashboard/?page=hub" },
@@ -265,4 +268,73 @@ export class ZenPage extends PageBase
new_crumb(auto_name);
}
+
+ _metric(parent, value, label, hero = false)
+ {
+ const m = parent.tag().classify("tile-metric");
+ if (hero)
+ {
+ m.classify("tile-metric-hero");
+ }
+ m.tag().classify("metric-value").text(value);
+ m.tag().classify("metric-label").text(label);
+ }
+
+ _render_http_requests_tile(grid, req, bad_requests = undefined)
+ {
+ if (!req)
+ {
+ return;
+ }
+ const tile = grid.tag().classify("card").classify("stats-tile");
+ tile.tag().classify("card-title").text("HTTP Requests");
+ const columns = tile.tag().classify("tile-columns");
+
+ const left = columns.tag().classify("tile-metrics");
+ const reqData = req.requests || req;
+ this._metric(left, Friendly.sep(reqData.count || 0), "total requests", true);
+ if (reqData.rate_mean > 0)
+ {
+ this._metric(left, Friendly.sep(reqData.rate_mean, 1) + "/s", "req/sec (mean)");
+ }
+ if (reqData.rate_1 > 0)
+ {
+ this._metric(left, Friendly.sep(reqData.rate_1, 1) + "/s", "req/sec (1m)");
+ }
+ if (reqData.rate_5 > 0)
+ {
+ this._metric(left, Friendly.sep(reqData.rate_5, 1) + "/s", "req/sec (5m)");
+ }
+ if (reqData.rate_15 > 0)
+ {
+ this._metric(left, Friendly.sep(reqData.rate_15, 1) + "/s", "req/sec (15m)");
+ }
+ if (bad_requests !== undefined)
+ {
+ this._metric(left, Friendly.sep(bad_requests), "bad requests");
+ }
+
+ const right = columns.tag().classify("tile-metrics");
+ this._metric(right, Friendly.duration(reqData.t_avg || 0), "avg latency", true);
+ if (reqData.t_p75)
+ {
+ this._metric(right, Friendly.duration(reqData.t_p75), "p75");
+ }
+ if (reqData.t_p95)
+ {
+ this._metric(right, Friendly.duration(reqData.t_p95), "p95");
+ }
+ if (reqData.t_p99)
+ {
+ this._metric(right, Friendly.duration(reqData.t_p99), "p99");
+ }
+ if (reqData.t_p999)
+ {
+ this._metric(right, Friendly.duration(reqData.t_p999), "p999");
+ }
+ if (reqData.t_max)
+ {
+ this._metric(right, Friendly.duration(reqData.t_max), "max");
+ }
+ }
}
diff --git a/src/zenserver/frontend/html/pages/projects.js b/src/zenserver/frontend/html/pages/projects.js
index a3c0d1555..2469bf70b 100644
--- a/src/zenserver/frontend/html/pages/projects.js
+++ b/src/zenserver/frontend/html/pages/projects.js
@@ -159,44 +159,7 @@ export class Page extends ZenPage
grid.inner().innerHTML = "";
// HTTP Requests tile
- {
- const req = safe(stats, "requests");
- if (req)
- {
- const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("HTTP Requests");
- const columns = tile.tag().classify("tile-columns");
-
- const left = columns.tag().classify("tile-metrics");
- const reqData = req.requests || req;
- this._metric(left, Friendly.sep(safe(stats, "store.requestcount") || 0), "total requests", true);
- if (reqData.rate_mean > 0)
- {
- this._metric(left, Friendly.sep(reqData.rate_mean, 1) + "/s", "req/sec (mean)");
- }
- if (reqData.rate_1 > 0)
- {
- this._metric(left, Friendly.sep(reqData.rate_1, 1) + "/s", "req/sec (1m)");
- }
- const badRequests = safe(stats, "store.badrequestcount") || 0;
- this._metric(left, Friendly.sep(badRequests), "bad requests");
-
- const right = columns.tag().classify("tile-metrics");
- this._metric(right, Friendly.duration(reqData.t_avg || 0), "avg latency", true);
- if (reqData.t_p75)
- {
- this._metric(right, Friendly.duration(reqData.t_p75), "p75");
- }
- if (reqData.t_p95)
- {
- this._metric(right, Friendly.duration(reqData.t_p95), "p95");
- }
- if (reqData.t_p99)
- {
- this._metric(right, Friendly.duration(reqData.t_p99), "p99");
- }
- }
- }
+ this._render_http_requests_tile(grid, safe(stats, "requests"), safe(stats, "store.badrequestcount") || 0);
// Store Operations tile
{
@@ -268,17 +231,6 @@ export class Page extends ZenPage
}
}
- _metric(parent, value, label, hero = false)
- {
- const m = parent.tag().classify("tile-metric");
- if (hero)
- {
- m.classify("tile-metric-hero");
- }
- m.tag().classify("metric-value").text(value);
- m.tag().classify("metric-label").text(label);
- }
-
async view_project(project_id)
{
// Toggle off if already selected
diff --git a/src/zenserver/frontend/html/pages/start.js b/src/zenserver/frontend/html/pages/start.js
index df70ea2f4..e5b4d14f1 100644
--- a/src/zenserver/frontend/html/pages/start.js
+++ b/src/zenserver/frontend/html/pages/start.js
@@ -36,6 +36,15 @@ export class Page extends ZenPage
all_stats[provider] = await new Fetcher().resource("stats", provider).json();
}));
+ this._http_panel = section.tag().classify("card").classify("stats-tile").classify("stats-http-panel");
+ this._http_panel.inner().addEventListener("click", () => { window.location = "?page=metrics"; });
+ this._http_panel.tag().classify("http-title").text("HTTP");
+ const req_section = this._http_panel.tag().classify("http-section");
+ req_section.tag().classify("http-section-label").text("Requests");
+ this._http_req_metrics = req_section.tag().classify("tile-metrics");
+ const ws_section = this._http_panel.tag().classify("http-section");
+ ws_section.tag().classify("http-section-label").text("Websockets");
+ this._http_ws_metrics = ws_section.tag().classify("tile-metrics");
this._stats_grid = section.tag().classify("grid").classify("stats-tiles");
this._safe_lookup = safe_lookup;
this._render_stats(all_stats);
@@ -113,7 +122,6 @@ export class Page extends ZenPage
);
var cell = row.get_cell(0);
cell.tag().text(namespace).on_click(() => this.view_zcache(namespace));
- row.get_cell(1).tag().text(namespace);
cell = row.get_cell(-1);
const action_tb = new Toolbar(cell, true);
@@ -143,44 +151,43 @@ export class Page extends ZenPage
const grid = this._stats_grid;
const safe_lookup = this._safe_lookup;
- // Clear existing tiles
+ // Clear and repopulate service tiles grid
grid.inner().innerHTML = "";
- // HTTP tile — aggregate request stats across all providers
- {
- const tile = grid.tag().classify("card").classify("stats-tile");
- tile.tag().classify("card-title").text("HTTP");
- const columns = tile.tag().classify("tile-columns");
-
- // Left column: request stats
- const left = columns.tag().classify("tile-metrics");
-
- let total_requests = 0;
- let total_rate = 0;
- for (const p in all_stats)
- {
- total_requests += (safe_lookup(all_stats[p], "requests.count") || 0);
- total_rate += (safe_lookup(all_stats[p], "requests.rate_1") || 0);
- }
+ // HTTP panel — update metrics containers built once in main()
+ const left = this._http_req_metrics;
+ left.inner().innerHTML = "";
- this._add_tile_metric(left, Friendly.sep(total_requests), "total requests", true);
- if (total_rate > 0)
- this._add_tile_metric(left, Friendly.sep(total_rate, 1) + "/s", "req/sec (1m)");
+ let total_requests = 0;
+ let total_rate = 0;
+ for (const p in all_stats)
+ {
+ total_requests += (safe_lookup(all_stats[p], "requests.count") || 0);
+ total_rate += (safe_lookup(all_stats[p], "requests.rate_1") || 0);
+ }
- // Right column: websocket stats
- const ws = all_stats["http"] ? (all_stats["http"]["websockets"] || {}) : {};
- const right = columns.tag().classify("tile-metrics");
+ this._add_tile_metric(left, Friendly.sep(total_requests), "total requests", true);
+ if (total_rate > 0)
+ {
+ this._add_tile_metric(left, Friendly.sep(total_rate, 1) + "/s", "req/sec (1m)");
+ }
- this._add_tile_metric(right, Friendly.sep(ws.active_connections || 0), "ws connections", true);
- const ws_frames = (ws.frames_received || 0) + (ws.frames_sent || 0);
- if (ws_frames > 0)
- this._add_tile_metric(right, Friendly.sep(ws_frames), "ws frames");
- const ws_bytes = (ws.bytes_received || 0) + (ws.bytes_sent || 0);
- if (ws_bytes > 0)
- this._add_tile_metric(right, Friendly.bytes(ws_bytes), "ws traffic");
+ const right = this._http_ws_metrics;
+ right.inner().innerHTML = "";
- tile.on_click(() => { window.location = "?page=metrics"; });
+ const ws = all_stats["http"] ? (all_stats["http"]["websockets"] || {}) : {};
+ this._add_tile_metric(right, Friendly.sep(ws.active_connections || 0), "ws connections", true);
+ const ws_frames = (ws.frames_received || 0) + (ws.frames_sent || 0);
+ if (ws_frames > 0)
+ {
+ this._add_tile_metric(right, Friendly.sep(ws_frames), "ws frames");
}
+ const ws_bytes = (ws.bytes_received || 0) + (ws.bytes_sent || 0);
+ if (ws_bytes > 0)
+ {
+ this._add_tile_metric(right, Friendly.bytes(ws_bytes), "ws traffic");
+ }
+
// Cache tile (z$)
if (all_stats["z$"])
@@ -198,7 +205,7 @@ export class Page extends ZenPage
this._add_tile_metric(body, safe_lookup(s, "cache.size.disk", Friendly.bytes) || "-", "disk");
this._add_tile_metric(body, safe_lookup(s, "cache.size.memory", Friendly.bytes) || "-", "memory");
- tile.on_click(() => { window.location = "?page=stat&provider=z$"; });
+ tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=z$"; });
}
// Project Store tile (prj)
@@ -210,9 +217,9 @@ export class Page extends ZenPage
const body = tile.tag().classify("tile-metrics");
this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true);
- this._add_tile_metric(body, safe_lookup(s, "store.size.disk", Friendly.bytes) || "-", "disk");
+ this._add_tile_metric(body, safe_lookup(s, "project_count", Friendly.sep) || "-", "projects");
- tile.on_click(() => { window.location = "?page=stat&provider=prj"; });
+ tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=prj"; });
}
// Build Store tile (builds)
@@ -226,7 +233,7 @@ export class Page extends ZenPage
this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true);
this._add_tile_metric(body, safe_lookup(s, "store.size.disk", Friendly.bytes) || "-", "disk");
- tile.on_click(() => { window.location = "?page=stat&provider=builds"; });
+ tile.inner().addEventListener("click", () => { window.location = "?page=builds"; });
}
// Proxy tile
@@ -250,7 +257,37 @@ export class Page extends ZenPage
this._add_tile_metric(body, Friendly.sep(mappings.length), "mappings");
this._add_tile_metric(body, Friendly.bytes(totalBytes), "traffic");
- tile.on_click(() => { window.location = "?page=proxy"; });
+ tile.inner().addEventListener("click", () => { window.location = "?page=proxy"; });
+ }
+
+ // Hub tile
+ if (all_stats["hub"])
+ {
+ const s = all_stats["hub"];
+ const tile = grid.tag().classify("card").classify("stats-tile");
+ tile.tag().classify("card-title").text("Hub");
+ const body = tile.tag().classify("tile-metrics");
+
+ const current = safe_lookup(s, "currentInstanceCount") || 0;
+ const limit = safe_lookup(s, "instanceLimit") || safe_lookup(s, "maxInstanceCount") || 0;
+ this._add_tile_metric(body, `${current} / ${limit}`, "instances", true);
+ this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests");
+
+ tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=hub"; });
+ }
+
+ // Object Store tile (obj)
+ if (all_stats["obj"])
+ {
+ const s = all_stats["obj"];
+ const tile = grid.tag().classify("card").classify("stats-tile");
+ tile.tag().classify("card-title").text("Object Store");
+ const body = tile.tag().classify("tile-metrics");
+
+ this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true);
+ this._add_tile_metric(body, safe_lookup(s, "total_bytes_served", Friendly.bytes) || "-", "bytes served");
+
+ tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=obj"; });
}
// Workspace tile (ws)
@@ -262,9 +299,9 @@ export class Page extends ZenPage
const body = tile.tag().classify("tile-metrics");
this._add_tile_metric(body, safe_lookup(s, "requests.count", Friendly.sep) || "-", "requests", true);
- this._add_tile_metric(body, safe_lookup(s, "workspaces.filescount", Friendly.sep) || "-", "files");
+ this._add_tile_metric(body, safe_lookup(s, "workspaces", Friendly.sep) || "-", "workspaces");
- tile.on_click(() => { window.location = "?page=stat&provider=ws"; });
+ tile.inner().addEventListener("click", () => { window.location = "?page=stat&provider=ws"; });
}
}
diff --git a/src/zenserver/frontend/html/pages/workspaces.js b/src/zenserver/frontend/html/pages/workspaces.js
new file mode 100644
index 000000000..d31fd7373
--- /dev/null
+++ b/src/zenserver/frontend/html/pages/workspaces.js
@@ -0,0 +1,236 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+"use strict";
+
+import { ZenPage } from "./page.js"
+import { Fetcher } from "../util/fetcher.js"
+
+////////////////////////////////////////////////////////////////////////////////
+export class Page extends ZenPage
+{
+ async main()
+ {
+ this.set_title("workspaces");
+
+ // Workspace Service Stats
+ const stats_section = this.add_section("Workspace Service Stats");
+ this._stats_grid = stats_section.tag().classify("grid").classify("stats-tiles");
+
+ const stats = await new Fetcher().resource("stats", "ws").json().catch(() => null);
+ if (stats) { this._render_stats(stats); }
+
+ this.connect_stats_ws((all_stats) => {
+ const s = all_stats["ws"];
+ if (s) { this._render_stats(s); }
+ });
+
+ const section = this.add_section("Workspaces");
+ const host = section.tag();
+
+ // Toolbar: refresh button
+ const toolbar = host.tag().classify("module-bulk-bar");
+ this._btn_refresh = toolbar.tag("button").classify("module-bulk-btn").inner();
+ this._btn_refresh.textContent = "\u21BB Refresh";
+ this._btn_refresh.addEventListener("click", () => this._do_refresh());
+
+ // Workspace table (raw DOM — in-place row updates require stable element refs)
+ const table = document.createElement("table");
+ table.className = "module-table";
+ const thead = document.createElement("thead");
+ const hrow = document.createElement("tr");
+ for (const label of ["WORKSPACE ID", "ROOT PATH"])
+ {
+ const th = document.createElement("th");
+ th.textContent = label;
+ hrow.appendChild(th);
+ }
+ thead.appendChild(hrow);
+ table.appendChild(thead);
+ this._tbody = document.createElement("tbody");
+ table.appendChild(this._tbody);
+ host.inner().appendChild(table);
+
+ // State
+ this._expanded = new Set(); // workspace ids with shares panel open
+ this._row_cache = new Map(); // workspace id -> row refs, for in-place DOM updates
+ this._loading = false;
+
+ await this._load();
+ }
+
+ async _load()
+ {
+ if (this._loading) { return; }
+ this._loading = true;
+ this._btn_refresh.disabled = true;
+ try
+ {
+ const data = await new Fetcher().resource("/ws/").json();
+ const workspaces = data.workspaces || [];
+ this._render(workspaces);
+ }
+ catch (e) { /* service unavailable */ }
+ finally
+ {
+ this._loading = false;
+ this._btn_refresh.disabled = false;
+ }
+ }
+
+ async _do_refresh()
+ {
+ if (this._loading) { return; }
+ this._btn_refresh.disabled = true;
+ try
+ {
+ await new Fetcher().resource("/ws/refresh").text();
+ }
+ catch (e) { /* ignore */ }
+ await this._load();
+ }
+
+ _render(workspaces)
+ {
+ const ws_map = new Map(workspaces.map(w => [w.id, w]));
+
+ // Remove rows for workspaces no longer present
+ for (const [id, row] of this._row_cache)
+ {
+ if (!ws_map.has(id))
+ {
+ row.tr.remove();
+ row.detail_tr.remove();
+ this._row_cache.delete(id);
+ this._expanded.delete(id);
+ }
+ }
+
+ // Create or update rows, then reorder tbody to match response order.
+ // appendChild on an existing node moves it, so iterating in response order
+ // achieves correct ordering without touching rows already in the right position.
+ for (const ws of workspaces)
+ {
+ const id = ws.id || "";
+ const shares = ws.shares || [];
+
+ let row = this._row_cache.get(id);
+ if (row)
+ {
+ // Update in-place — preserves DOM node identity so expanded state is kept
+ row.root_path_node.nodeValue = ws.root_path || "";
+ row.detail_tr.style.display = this._expanded.has(id) ? "" : "none";
+ row.btn_expand.textContent = this._expanded.has(id) ? "\u25BE" : "\u25B8";
+ const shares_json = JSON.stringify(shares);
+ if (shares_json !== row.shares_json)
+ {
+ row.shares_json = shares_json;
+ this._render_shares(row.sh_tbody, shares);
+ }
+ }
+ else
+ {
+ // Create new workspace row
+ const tr = document.createElement("tr");
+ const detail_tr = document.createElement("tr");
+ detail_tr.className = "module-metrics-row";
+ detail_tr.style.display = this._expanded.has(id) ? "" : "none";
+
+ const btn_expand = document.createElement("button");
+ btn_expand.className = "module-expand-btn";
+ btn_expand.textContent = this._expanded.has(id) ? "\u25BE" : "\u25B8";
+ btn_expand.addEventListener("click", () => {
+ if (this._expanded.has(id))
+ {
+ this._expanded.delete(id);
+ detail_tr.style.display = "none";
+ btn_expand.textContent = "\u25B8";
+ }
+ else
+ {
+ this._expanded.add(id);
+ detail_tr.style.display = "";
+ btn_expand.textContent = "\u25BE";
+ }
+ });
+
+ const id_wrap = document.createElement("span");
+ id_wrap.className = "ws-id-wrap";
+ id_wrap.appendChild(btn_expand);
+ id_wrap.appendChild(document.createTextNode("\u00A0" + id));
+ const td_id = document.createElement("td");
+ td_id.appendChild(id_wrap);
+ tr.appendChild(td_id);
+
+ const root_path_node = document.createTextNode(ws.root_path || "");
+ const td_root = document.createElement("td");
+ td_root.appendChild(root_path_node);
+ tr.appendChild(td_root);
+
+ // Detail row: nested shares table
+ const sh_table = document.createElement("table");
+ sh_table.className = "module-table ws-share-table";
+ const sh_thead = document.createElement("thead");
+ const sh_hrow = document.createElement("tr");
+ for (const label of ["SHARE ID", "SHARE PATH", "ALIAS"])
+ {
+ const th = document.createElement("th");
+ th.textContent = label;
+ sh_hrow.appendChild(th);
+ }
+ sh_thead.appendChild(sh_hrow);
+ sh_table.appendChild(sh_thead);
+ const sh_tbody = document.createElement("tbody");
+ sh_table.appendChild(sh_tbody);
+ const detail_td = document.createElement("td");
+ detail_td.colSpan = 2;
+ detail_td.className = "ws-detail-cell";
+ detail_td.appendChild(sh_table);
+ detail_tr.appendChild(detail_td);
+
+ this._render_shares(sh_tbody, shares);
+
+ row = { tr, detail_tr, root_path_node, sh_tbody, btn_expand, shares_json: JSON.stringify(shares) };
+ this._row_cache.set(id, row);
+ }
+
+ this._tbody.appendChild(row.tr);
+ this._tbody.appendChild(row.detail_tr);
+ }
+ }
+
+ _render_stats(stats)
+ {
+ const grid = this._stats_grid;
+ grid.inner().innerHTML = "";
+
+ // HTTP Requests tile
+ this._render_http_requests_tile(grid, stats.requests);
+ }
+
+ _render_shares(sh_tbody, shares)
+ {
+ sh_tbody.innerHTML = "";
+ if (shares.length === 0)
+ {
+ const tr = document.createElement("tr");
+ const td = document.createElement("td");
+ td.colSpan = 3;
+ td.className = "ws-no-shares-cell";
+ td.textContent = "No shares";
+ tr.appendChild(td);
+ sh_tbody.appendChild(tr);
+ return;
+ }
+ for (const share of shares)
+ {
+ const tr = document.createElement("tr");
+ for (const text of [share.id || "", share.share_path || "", share.alias || ""])
+ {
+ const td = document.createElement("td");
+ td.textContent = text;
+ tr.appendChild(td);
+ }
+ sh_tbody.appendChild(tr);
+ }
+ }
+}
diff --git a/src/zenserver/frontend/html/zen.css b/src/zenserver/frontend/html/zen.css
index b4f7270fc..d9f7491ea 100644
--- a/src/zenserver/frontend/html/zen.css
+++ b/src/zenserver/frontend/html/zen.css
@@ -803,18 +803,17 @@ zen-banner + zen-nav::part(nav-bar) {
/* stats tiles -------------------------------------------------------------- */
-.stats-tiles {
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+.grid.stats-tiles {
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.stats-tile {
cursor: pointer;
- transition: border-color 0.15s, background 0.15s;
+ transition: border-color 0.15s;
}
.stats-tile:hover {
border-color: var(--theme_p0);
- background: var(--theme_p4);
}
.stats-tile-detailed {
@@ -873,6 +872,81 @@ zen-banner + zen-nav::part(nav-bar) {
font-size: 28px;
}
+/* HTTP summary panel ------------------------------------------------------- */
+
+.stats-http-panel {
+ display: grid;
+ grid-template-columns: 20% 1fr 1fr;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.http-title {
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--theme_bright);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ line-height: 1;
+}
+
+.http-section {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding: 0 24px;
+ border-left: 1px solid var(--theme_g2);
+}
+
+.http-section-label {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--theme_g1);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.stats-http-panel .tile-metrics {
+ flex-direction: row;
+ align-items: center;
+ gap: 20px;
+}
+
+/* workspaces page ---------------------------------------------------------- */
+
+.ws-id-wrap {
+ display: inline-flex;
+ align-items: center;
+ font-family: 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace;
+ font-size: 14px;
+}
+
+.ws-share-table {
+ width: 100%;
+ margin: 4px 0;
+}
+
+.ws-share-table th {
+ padding: 4px;
+}
+
+.ws-share-table td {
+ font-family: 'SF Mono', 'Cascadia Mono', Consolas, 'DejaVu Sans Mono', monospace;
+ font-size: 13px;
+ padding: 4px;
+}
+
+.ws-share-table td.ws-no-shares-cell {
+ color: var(--theme_g1);
+ font-style: italic;
+ font-family: inherit;
+ padding: 4px 8px;
+}
+
+.module-metrics-row td.ws-detail-cell {
+ padding-left: 24px;
+}
+
/* start -------------------------------------------------------------------- */
#start {
@@ -1030,7 +1104,7 @@ html:has(#map) {
.card-title {
font-size: 14px;
font-weight: 600;
- color: var(--theme_g1);
+ color: var(--theme_g0);
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
diff --git a/src/zenserver/hub/httphubservice.cpp b/src/zenserver/hub/httphubservice.cpp
index dcab50f2d..ebefcf2e3 100644
--- a/src/zenserver/hub/httphubservice.cpp
+++ b/src/zenserver/hub/httphubservice.cpp
@@ -286,11 +286,21 @@ HttpHubService::HandleStatusRequest(HttpServerRequest& Request)
void
HttpHubService::HandleStatsRequest(HttpServerRequest& Request)
{
+ Request.WriteResponse(HttpResponseCode::OK, CollectStats());
+}
+
+CbObject
+HttpHubService::CollectStats()
+{
CbObjectWriter Cbo;
EmitSnapshot("requests", m_HttpRequests, Cbo);
- Request.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ Cbo << "currentInstanceCount" << m_Hub.GetInstanceCount();
+ Cbo << "maxInstanceCount" << m_Hub.GetMaxInstanceCount();
+ Cbo << "instanceLimit" << m_Hub.GetConfig().InstanceLimit;
+
+ return Cbo.Save();
}
uint64_t
diff --git a/src/zenserver/hub/httphubservice.h b/src/zenserver/hub/httphubservice.h
index 5f940017e..1bb1c303e 100644
--- a/src/zenserver/hub/httphubservice.h
+++ b/src/zenserver/hub/httphubservice.h
@@ -29,6 +29,7 @@ public:
virtual void HandleRequest(HttpServerRequest& Request) override;
virtual void HandleStatusRequest(HttpServerRequest& Request) override;
virtual void HandleStatsRequest(HttpServerRequest& Request) override;
+ virtual CbObject CollectStats() override;
virtual uint64_t GetActivityCounter() override;
void SetNotificationEndpoint(std::string_view UpstreamNotificationEndpoint, std::string_view InstanceId);
diff --git a/src/zenserver/storage/cache/httpstructuredcache.cpp b/src/zenserver/storage/cache/httpstructuredcache.cpp
index 81e244aff..c1727270c 100644
--- a/src/zenserver/storage/cache/httpstructuredcache.cpp
+++ b/src/zenserver/storage/cache/httpstructuredcache.cpp
@@ -1843,12 +1843,6 @@ HttpStructuredCacheService::HandleStatsRequest(HttpServerRequest& Request)
bool ShowCidStoreStats = Request.GetQueryParams().GetValue("cidstorestats") == "true";
bool ShowCacheStoreStats = Request.GetQueryParams().GetValue("cachestorestats") == "true";
- if (!ShowCidStoreStats && !ShowCacheStoreStats)
- {
- Request.WriteResponse(HttpResponseCode::OK, CollectStats());
- return;
- }
-
// Full stats with optional detailed store/cid breakdowns
CbObjectWriter Cbo;
@@ -2058,54 +2052,17 @@ HttpStructuredCacheService::HandleStatsRequest(HttpServerRequest& Request)
CbObject
HttpStructuredCacheService::CollectStats()
{
+ ZEN_TRACE_CPU("HttpStructuredCacheService::Stats");
ZEN_MEMSCOPE(GetCacheHttpTag());
CbObjectWriter Cbo;
EmitSnapshot("requests", m_HttpRequests, Cbo);
- const uint64_t HitCount = m_CacheStats.HitCount;
- const uint64_t UpstreamHitCount = m_CacheStats.UpstreamHitCount;
- const uint64_t MissCount = m_CacheStats.MissCount;
- const uint64_t WriteCount = m_CacheStats.WriteCount;
- const uint64_t BadRequestCount = m_CacheStats.BadRequestCount;
- struct CidStoreStats StoreStats = m_CidStore.Stats();
- const uint64_t ChunkHitCount = StoreStats.HitCount;
- const uint64_t ChunkMissCount = StoreStats.MissCount;
- const uint64_t ChunkWriteCount = StoreStats.WriteCount;
- const uint64_t TotalCount = HitCount + MissCount;
-
- const uint64_t RpcRequests = m_CacheStats.RpcRequests;
- const uint64_t RpcRecordRequests = m_CacheStats.RpcRecordRequests;
- const uint64_t RpcRecordBatchRequests = m_CacheStats.RpcRecordBatchRequests;
- const uint64_t RpcValueRequests = m_CacheStats.RpcValueRequests;
- const uint64_t RpcValueBatchRequests = m_CacheStats.RpcValueBatchRequests;
- const uint64_t RpcChunkRequests = m_CacheStats.RpcChunkRequests;
- const uint64_t RpcChunkBatchRequests = m_CacheStats.RpcChunkBatchRequests;
-
- const CidStoreSize CidSize = m_CidStore.TotalSize();
const CacheStoreSize CacheSize = m_CacheStore.TotalSize();
Cbo.BeginObject("cache");
{
- Cbo << "badrequestcount" << BadRequestCount;
- Cbo.BeginObject("rpc");
- Cbo << "count" << RpcRequests;
- Cbo << "ops" << RpcRecordBatchRequests + RpcValueBatchRequests + RpcChunkBatchRequests;
- Cbo.BeginObject("records");
- Cbo << "count" << RpcRecordRequests;
- Cbo << "ops" << RpcRecordBatchRequests;
- Cbo.EndObject();
- Cbo.BeginObject("values");
- Cbo << "count" << RpcValueRequests;
- Cbo << "ops" << RpcValueBatchRequests;
- Cbo.EndObject();
- Cbo.BeginObject("chunks");
- Cbo << "count" << RpcChunkRequests;
- Cbo << "ops" << RpcChunkBatchRequests;
- Cbo.EndObject();
- Cbo.EndObject();
-
Cbo.BeginObject("size");
{
Cbo << "disk" << CacheSize.DiskSize;
@@ -2113,51 +2070,7 @@ HttpStructuredCacheService::CollectStats()
}
Cbo.EndObject();
- Cbo << "hits" << HitCount << "misses" << MissCount << "writes" << WriteCount;
- Cbo << "hit_ratio" << (TotalCount > 0 ? (double(HitCount) / double(TotalCount)) : 0.0);
-
- if (m_UpstreamCache.IsActive())
- {
- Cbo << "upstream_ratio" << (HitCount > 0 ? (double(UpstreamHitCount) / double(HitCount)) : 0.0);
- Cbo << "upstream_hits" << m_CacheStats.UpstreamHitCount;
- }
-
- Cbo << "cidhits" << ChunkHitCount << "cidmisses" << ChunkMissCount << "cidwrites" << ChunkWriteCount;
-
- {
- ZenCacheStore::CacheStoreStats StoreStatsData = m_CacheStore.Stats();
- Cbo.BeginObject("store");
- Cbo << "hits" << StoreStatsData.HitCount << "misses" << StoreStatsData.MissCount << "writes" << StoreStatsData.WriteCount
- << "rejected_writes" << StoreStatsData.RejectedWriteCount << "rejected_reads" << StoreStatsData.RejectedReadCount;
- const uint64_t StoreTotal = StoreStatsData.HitCount + StoreStatsData.MissCount;
- Cbo << "hit_ratio" << (StoreTotal > 0 ? (double(StoreStatsData.HitCount) / double(StoreTotal)) : 0.0);
- EmitSnapshot("read", StoreStatsData.GetOps, Cbo);
- EmitSnapshot("write", StoreStatsData.PutOps, Cbo);
- Cbo.EndObject();
- }
- }
- Cbo.EndObject();
-
- if (m_UpstreamCache.IsActive())
- {
- EmitSnapshot("upstream_gets", m_UpstreamGetRequestTiming, Cbo);
- Cbo.BeginObject("upstream");
- {
- m_UpstreamCache.GetStatus(Cbo);
- }
- Cbo.EndObject();
- }
-
- Cbo.BeginObject("cid");
- {
- Cbo.BeginObject("size");
- {
- Cbo << "tiny" << CidSize.TinySize;
- Cbo << "small" << CidSize.SmallSize;
- Cbo << "large" << CidSize.LargeSize;
- Cbo << "total" << CidSize.TotalSize;
- }
- Cbo.EndObject();
+ Cbo << "hits" << m_CacheStats.HitCount << "misses" << m_CacheStats.MissCount;
}
Cbo.EndObject();
diff --git a/src/zenserver/storage/objectstore/objectstore.cpp b/src/zenserver/storage/objectstore/objectstore.cpp
index dec0c3cee..d6516fa1a 100644
--- a/src/zenserver/storage/objectstore/objectstore.cpp
+++ b/src/zenserver/storage/objectstore/objectstore.cpp
@@ -226,7 +226,7 @@ HttpObjectStoreService::HttpObjectStoreService(HttpStatsService& StatsService, H
, m_StatusService(StatusService)
, m_Cfg(std::move(Cfg))
{
- Inititalize();
+ Initialize();
m_StatsService.RegisterHandler("obj", *this);
m_StatusService.RegisterHandler("obj", *this);
}
@@ -266,12 +266,19 @@ HttpObjectStoreService::HandleStatusRequest(HttpServerRequest& Request)
void
HttpObjectStoreService::HandleStatsRequest(HttpServerRequest& Request)
{
+ Request.WriteResponse(HttpResponseCode::OK, CollectStats());
+}
+
+CbObject
+HttpObjectStoreService::CollectStats()
+{
+ ZEN_TRACE_CPU("HttpObjectStoreService::Stats");
CbObjectWriter Cbo;
EmitSnapshot("requests", m_HttpRequests, Cbo);
Cbo << "total_bytes_served" << m_TotalBytesServed.load();
- Request.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+ return Cbo.Save();
}
uint64_t
@@ -281,12 +288,12 @@ HttpObjectStoreService::GetActivityCounter()
}
void
-HttpObjectStoreService::Inititalize()
+HttpObjectStoreService::Initialize()
{
- ZEN_TRACE_CPU("HttpObjectStoreService::Inititalize");
+ ZEN_TRACE_CPU("HttpObjectStoreService::Initialize");
namespace fs = std::filesystem;
- ZEN_LOG_INFO(LogObj, "Initialzing Object Store in '{}'", m_Cfg.RootDirectory);
+ ZEN_LOG_INFO(LogObj, "Initializing Object Store in '{}'", m_Cfg.RootDirectory);
const fs::path BucketsPath = m_Cfg.RootDirectory / "buckets";
if (!IsDir(BucketsPath))
diff --git a/src/zenserver/storage/objectstore/objectstore.h b/src/zenserver/storage/objectstore/objectstore.h
index 8a25e750b..f51254357 100644
--- a/src/zenserver/storage/objectstore/objectstore.h
+++ b/src/zenserver/storage/objectstore/objectstore.h
@@ -35,10 +35,11 @@ public:
virtual void HandleRequest(HttpServerRequest& Request) override;
virtual void HandleStatusRequest(HttpServerRequest& Request) override;
virtual void HandleStatsRequest(HttpServerRequest& Request) override;
+ virtual CbObject CollectStats() override;
virtual uint64_t GetActivityCounter() override;
private:
- void Inititalize();
+ void Initialize();
std::filesystem::path GetBucketDirectory(std::string_view BucketName);
void ListBuckets(HttpRouterRequest& Request);
void CreateBucket(HttpRouterRequest& Request);
diff --git a/src/zenserver/storage/projectstore/httpprojectstore.cpp b/src/zenserver/storage/projectstore/httpprojectstore.cpp
index 9d132a22a..a7c8c66b6 100644
--- a/src/zenserver/storage/projectstore/httpprojectstore.cpp
+++ b/src/zenserver/storage/projectstore/httpprojectstore.cpp
@@ -848,12 +848,6 @@ HttpProjectService::HandleStatusRequest(HttpServerRequest& Request)
void
HttpProjectService::HandleStatsRequest(HttpServerRequest& HttpReq)
{
- HttpReq.WriteResponse(HttpResponseCode::OK, CollectStats());
-}
-
-CbObject
-HttpProjectService::CollectStats()
-{
ZEN_TRACE_CPU("ProjectService::Stats");
const GcStorageSize StoreSize = m_ProjectStore->StorageSize();
@@ -863,6 +857,8 @@ HttpProjectService::CollectStats()
EmitSnapshot("requests", m_HttpRequests, Cbo);
+ Cbo << "project_count" << (uint64_t)m_ProjectStore->ProjectCount();
+
Cbo.BeginObject("store");
{
Cbo.BeginObject("size");
@@ -918,6 +914,18 @@ HttpProjectService::CollectStats()
}
Cbo.EndObject();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+}
+
+CbObject
+HttpProjectService::CollectStats()
+{
+ CbObjectWriter Cbo;
+ // CollectStats does not use the HandleStatsRequest implementation to get stats since it uses some heavy operations such as
+ // m_ProjectStore->StorageSize();
+ EmitSnapshot("requests", m_HttpRequests, Cbo);
+ Cbo << "project_count" << (uint64_t)m_ProjectStore->ProjectCount();
+
return Cbo.Save();
}
diff --git a/src/zenserver/storage/workspaces/httpworkspaces.cpp b/src/zenserver/storage/workspaces/httpworkspaces.cpp
index 0119912e9..12e7bae73 100644
--- a/src/zenserver/storage/workspaces/httpworkspaces.cpp
+++ b/src/zenserver/storage/workspaces/httpworkspaces.cpp
@@ -122,13 +122,6 @@ HttpWorkspacesService::HandleStatusRequest(HttpServerRequest& Request)
void
HttpWorkspacesService::HandleStatsRequest(HttpServerRequest& HttpReq)
{
- HttpReq.WriteResponse(HttpResponseCode::OK, CollectStats());
-}
-
-CbObject
-HttpWorkspacesService::CollectStats()
-{
- ZEN_TRACE_CPU("WorkspacesService::Stats");
CbObjectWriter Cbo;
EmitSnapshot("requests", m_HttpRequests, Cbo);
@@ -165,6 +158,19 @@ HttpWorkspacesService::CollectStats()
}
Cbo.EndObject();
+ HttpReq.WriteResponse(HttpResponseCode::OK, Cbo.Save());
+}
+
+CbObject
+HttpWorkspacesService::CollectStats()
+{
+ ZEN_TRACE_CPU("HttpWorkspacesService::Stats");
+ CbObjectWriter Cbo;
+
+ EmitSnapshot("requests", m_HttpRequests, Cbo);
+
+ Cbo << "workspaces" << m_Workspaces.GetWorkspaces().size();
+
return Cbo.Save();
}