diff options
| author | Dan Engelbrecht <[email protected]> | 2026-04-11 15:44:36 +0200 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-04-11 15:44:36 +0200 |
| commit | 3f3d716de0b9f229092119fbfdcf5532a9cc26b2 (patch) | |
| tree | 7c97c5754e05845b065371d1518f10c95f90b58d /src | |
| parent | removed s3 test program (#942) (diff) | |
| download | zen-3f3d716de0b9f229092119fbfdcf5532a9cc26b2.tar.xz zen-3f3d716de0b9f229092119fbfdcf5532a9cc26b2.zip | |
Dashboard stats tiles no longer flicker (#943)
Diffstat (limited to 'src')
| -rw-r--r-- | src/zenserver/frontend/html/pages/builds.js | 54 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/cache.js | 213 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/page.js | 35 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/projects.js | 83 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/start.js | 1 | ||||
| -rw-r--r-- | src/zenserver/frontend/html/pages/workspaces.js | 1 |
6 files changed, 197 insertions, 190 deletions
diff --git a/src/zenserver/frontend/html/pages/builds.js b/src/zenserver/frontend/html/pages/builds.js index 6b3426378..c63d13b91 100644 --- a/src/zenserver/frontend/html/pages/builds.js +++ b/src/zenserver/frontend/html/pages/builds.js @@ -39,6 +39,7 @@ export class Page extends ZenPage _render_stats(stats) { + stats = this._merge_last_stats(stats); const grid = this._stats_grid; const safe = (obj, path) => path.split(".").reduce((a, b) => a && a[b], obj); @@ -49,39 +50,30 @@ export class Page extends ZenPage // 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 blobs = safe(stats, "store.blobs") || {}; + const metadata = safe(stats, "store.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 left = columns.tag().classify("tile-metrics"); + this._metric(left, Friendly.bytes(safe(stats, "store.size.disk") || 0), "disk", true); + 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"); - } - } + const right = columns.tag().classify("tile-metrics"); + 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/cache.js b/src/zenserver/frontend/html/pages/cache.js index c6567f0be..58e2023f9 100644 --- a/src/zenserver/frontend/html/pages/cache.js +++ b/src/zenserver/frontend/html/pages/cache.js @@ -123,49 +123,46 @@ export class Page extends ZenPage _render_stats(stats) { + stats = this._merge_last_stats(stats); const safe = (obj, path) => path.split(".").reduce((a, b) => a && a[b], obj); const grid = this._stats_grid; - this._last_stats = stats; grid.inner().innerHTML = ""; // Store I/O tile { - const store = safe(stats, "cache.store"); - if (store) - { - const tile = grid.tag().classify("card").classify("stats-tile").classify("stats-tile-detailed"); - if (this._selected_category === "store") tile.classify("stats-tile-selected"); - tile.on_click(() => this._select_category("store")); - tile.tag().classify("card-title").text("Store I/O"); - const columns = tile.tag().classify("tile-columns"); - - const left = columns.tag().classify("tile-metrics"); - const storeHits = store.hits || 0; - const storeMisses = store.misses || 0; - const storeTotal = storeHits + storeMisses; - const storeRatio = storeTotal > 0 ? ((storeHits / storeTotal) * 100).toFixed(1) + "%" : "-"; - this._metric(left, storeRatio, "store hit ratio", true); - this._metric(left, Friendly.sep(storeHits), "hits"); - this._metric(left, Friendly.sep(storeMisses), "misses"); - this._metric(left, Friendly.sep(store.writes || 0), "writes"); - this._metric(left, Friendly.sep(store.rejected_reads || 0), "rejected reads"); - this._metric(left, Friendly.sep(store.rejected_writes || 0), "rejected writes"); - - const right = columns.tag().classify("tile-metrics"); - const readRateMean = safe(store, "read.bytes.rate_mean") || 0; - const readRate1 = safe(store, "read.bytes.rate_1") || 0; - const readRate5 = safe(store, "read.bytes.rate_5") || 0; - const writeRateMean = safe(store, "write.bytes.rate_mean") || 0; - const writeRate1 = safe(store, "write.bytes.rate_1") || 0; - const writeRate5 = safe(store, "write.bytes.rate_5") || 0; - this._metric(right, Friendly.bytes(readRateMean) + "/s", "read rate (mean)", true); - this._metric(right, Friendly.bytes(readRate1) + "/s", "read rate (1m)"); - this._metric(right, Friendly.bytes(readRate5) + "/s", "read rate (5m)"); - this._metric(right, Friendly.bytes(writeRateMean) + "/s", "write rate (mean)"); - this._metric(right, Friendly.bytes(writeRate1) + "/s", "write rate (1m)"); - this._metric(right, Friendly.bytes(writeRate5) + "/s", "write rate (5m)"); - } + const store = safe(stats, "cache.store") || {}; + const tile = grid.tag().classify("card").classify("stats-tile").classify("stats-tile-detailed"); + if (this._selected_category === "store") tile.classify("stats-tile-selected"); + tile.on_click(() => this._select_category("store")); + tile.tag().classify("card-title").text("Store I/O"); + const columns = tile.tag().classify("tile-columns"); + + const left = columns.tag().classify("tile-metrics"); + const storeHits = store.hits || 0; + const storeMisses = store.misses || 0; + const storeTotal = storeHits + storeMisses; + const storeRatio = storeTotal > 0 ? ((storeHits / storeTotal) * 100).toFixed(1) + "%" : "-"; + this._metric(left, storeRatio, "store hit ratio", true); + this._metric(left, Friendly.sep(storeHits), "hits"); + this._metric(left, Friendly.sep(storeMisses), "misses"); + this._metric(left, Friendly.sep(store.writes || 0), "writes"); + this._metric(left, Friendly.sep(store.rejected_reads || 0), "rejected reads"); + this._metric(left, Friendly.sep(store.rejected_writes || 0), "rejected writes"); + + const right = columns.tag().classify("tile-metrics"); + const readRateMean = safe(store, "read.bytes.rate_mean") || 0; + const readRate1 = safe(store, "read.bytes.rate_1") || 0; + const readRate5 = safe(store, "read.bytes.rate_5") || 0; + const writeRateMean = safe(store, "write.bytes.rate_mean") || 0; + const writeRate1 = safe(store, "write.bytes.rate_1") || 0; + const writeRate5 = safe(store, "write.bytes.rate_5") || 0; + this._metric(right, Friendly.bytes(readRateMean) + "/s", "read rate (mean)", true); + this._metric(right, Friendly.bytes(readRate1) + "/s", "read rate (1m)"); + this._metric(right, Friendly.bytes(readRate5) + "/s", "read rate (5m)"); + this._metric(right, Friendly.bytes(writeRateMean) + "/s", "write rate (mean)"); + this._metric(right, Friendly.bytes(writeRate1) + "/s", "write rate (1m)"); + this._metric(right, Friendly.bytes(writeRate5) + "/s", "write rate (5m)"); } // Hit/Miss tile @@ -201,89 +198,83 @@ export class Page extends ZenPage // 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 req = safe(stats, "requests") || {}; + 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)"); - } - const badRequests = safe(stats, "cache.badrequestcount") || 0; - this._metric(left, Friendly.sep(badRequests), "bad requests"); + 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)"); + } + const badRequests = safe(stats, "cache.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"); - } - 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"); - } + 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"); } } // RPC tile { - const rpc = safe(stats, "cache.rpc"); - if (rpc) - { - const tile = grid.tag().classify("card").classify("stats-tile"); - tile.tag().classify("card-title").text("RPC"); - const columns = tile.tag().classify("tile-columns"); + const rpc = safe(stats, "cache.rpc") || {}; + const tile = grid.tag().classify("card").classify("stats-tile"); + tile.tag().classify("card-title").text("RPC"); + const columns = tile.tag().classify("tile-columns"); - const left = columns.tag().classify("tile-metrics"); - this._metric(left, Friendly.sep(rpc.count || 0), "rpc calls", true); - this._metric(left, Friendly.sep(rpc.ops || 0), "batch ops"); + const left = columns.tag().classify("tile-metrics"); + this._metric(left, Friendly.sep(rpc.count || 0), "rpc calls", true); + this._metric(left, Friendly.sep(rpc.ops || 0), "batch ops"); - const right = columns.tag().classify("tile-metrics"); - if (rpc.records) - { - this._metric(right, Friendly.sep(rpc.records.count || 0), "record calls"); - this._metric(right, Friendly.sep(rpc.records.ops || 0), "record ops"); - } - if (rpc.values) - { - this._metric(right, Friendly.sep(rpc.values.count || 0), "value calls"); - this._metric(right, Friendly.sep(rpc.values.ops || 0), "value ops"); - } - if (rpc.chunks) - { - this._metric(right, Friendly.sep(rpc.chunks.count || 0), "chunk calls"); - this._metric(right, Friendly.sep(rpc.chunks.ops || 0), "chunk ops"); - } + const right = columns.tag().classify("tile-metrics"); + if (rpc.records) + { + this._metric(right, Friendly.sep(rpc.records.count || 0), "record calls"); + this._metric(right, Friendly.sep(rpc.records.ops || 0), "record ops"); + } + if (rpc.values) + { + this._metric(right, Friendly.sep(rpc.values.count || 0), "value calls"); + this._metric(right, Friendly.sep(rpc.values.ops || 0), "value ops"); + } + if (rpc.chunks) + { + this._metric(right, Friendly.sep(rpc.chunks.count || 0), "chunk calls"); + this._metric(right, Friendly.sep(rpc.chunks.ops || 0), "chunk ops"); } } @@ -306,7 +297,7 @@ export class Page extends ZenPage this._metric(right, safe(stats, "cid.size.large") != null ? Friendly.bytes(safe(stats, "cid.size.large")) : "-", "cid large"); } - // Upstream tile (only if upstream is active) + // Upstream tile (only shown when upstream is active) { const upstream = safe(stats, "upstream"); if (upstream) diff --git a/src/zenserver/frontend/html/pages/page.js b/src/zenserver/frontend/html/pages/page.js index ff530ff8e..3653abb0e 100644 --- a/src/zenserver/frontend/html/pages/page.js +++ b/src/zenserver/frontend/html/pages/page.js @@ -6,6 +6,26 @@ import { WidgetHost } from "../util/widgets.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" +function _deep_merge_stats(base, update) +{ + const result = Object.assign({}, base); + for (const key of Object.keys(update)) + { + const bv = result[key]; + const uv = update[key]; + if (uv && typeof uv === "object" && !Array.isArray(uv) + && bv && typeof bv === "object" && !Array.isArray(bv)) + { + result[key] = _deep_merge_stats(bv, uv); + } + else + { + result[key] = uv; + } + } + return result; +} + //////////////////////////////////////////////////////////////////////////////// export class PageBase extends WidgetHost { @@ -282,10 +302,7 @@ export class ZenPage extends PageBase _render_http_requests_tile(grid, req, bad_requests = undefined) { - if (!req) - { - return; - } + req = 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"); @@ -338,6 +355,16 @@ export class ZenPage extends PageBase } } + _merge_last_stats(stats) + { + if (this._last_stats) + { + stats = _deep_merge_stats(this._last_stats, stats); + } + this._last_stats = stats; + return stats; + } + _collapsible_section(name) { const section = this.add_section(name); diff --git a/src/zenserver/frontend/html/pages/projects.js b/src/zenserver/frontend/html/pages/projects.js index e613086a9..af7c5396a 100644 --- a/src/zenserver/frontend/html/pages/projects.js +++ b/src/zenserver/frontend/html/pages/projects.js @@ -88,6 +88,7 @@ export class Page extends ZenPage _render_stats(stats) { + stats = this._merge_last_stats(stats); const safe = (obj, path) => path.split(".").reduce((a, b) => a && a[b], obj); const grid = this._stats_grid; @@ -98,54 +99,48 @@ export class Page extends ZenPage // Store Operations tile { - const store = safe(stats, "store"); - if (store) - { - const tile = grid.tag().classify("card").classify("stats-tile"); - tile.tag().classify("card-title").text("Store Operations"); - const columns = tile.tag().classify("tile-columns"); - - const left = columns.tag().classify("tile-metrics"); - const proj = store.project || {}; - this._metric(left, Friendly.sep(proj.readcount || 0), "project reads", true); - this._metric(left, Friendly.sep(proj.writecount || 0), "project writes"); - this._metric(left, Friendly.sep(proj.deletecount || 0), "project deletes"); - - const right = columns.tag().classify("tile-metrics"); - const oplog = store.oplog || {}; - this._metric(right, Friendly.sep(oplog.readcount || 0), "oplog reads", true); - this._metric(right, Friendly.sep(oplog.writecount || 0), "oplog writes"); - this._metric(right, Friendly.sep(oplog.deletecount || 0), "oplog deletes"); - } + const store = safe(stats, "store") || {}; + const tile = grid.tag().classify("card").classify("stats-tile"); + tile.tag().classify("card-title").text("Store Operations"); + const columns = tile.tag().classify("tile-columns"); + + const left = columns.tag().classify("tile-metrics"); + const proj = store.project || {}; + this._metric(left, Friendly.sep(proj.readcount || 0), "project reads", true); + this._metric(left, Friendly.sep(proj.writecount || 0), "project writes"); + this._metric(left, Friendly.sep(proj.deletecount || 0), "project deletes"); + + const right = columns.tag().classify("tile-metrics"); + const oplog = store.oplog || {}; + this._metric(right, Friendly.sep(oplog.readcount || 0), "oplog reads", true); + this._metric(right, Friendly.sep(oplog.writecount || 0), "oplog writes"); + this._metric(right, Friendly.sep(oplog.deletecount || 0), "oplog deletes"); } // Op & Chunk tile { - const store = safe(stats, "store"); - if (store) - { - const tile = grid.tag().classify("card").classify("stats-tile"); - tile.tag().classify("card-title").text("Ops & Chunks"); - const columns = tile.tag().classify("tile-columns"); - - const left = columns.tag().classify("tile-metrics"); - const op = store.op || {}; - const opTotal = (op.hitcount || 0) + (op.misscount || 0); - const opRatio = opTotal > 0 ? (((op.hitcount || 0) / opTotal) * 100).toFixed(1) + "%" : "-"; - this._metric(left, opRatio, "op hit ratio", true); - this._metric(left, Friendly.sep(op.hitcount || 0), "op hits"); - this._metric(left, Friendly.sep(op.misscount || 0), "op misses"); - this._metric(left, Friendly.sep(op.writecount || 0), "op writes"); - - const right = columns.tag().classify("tile-metrics"); - const chunk = store.chunk || {}; - const chunkTotal = (chunk.hitcount || 0) + (chunk.misscount || 0); - const chunkRatio = chunkTotal > 0 ? (((chunk.hitcount || 0) / chunkTotal) * 100).toFixed(1) + "%" : "-"; - this._metric(right, chunkRatio, "chunk hit ratio", true); - this._metric(right, Friendly.sep(chunk.hitcount || 0), "chunk hits"); - this._metric(right, Friendly.sep(chunk.misscount || 0), "chunk misses"); - this._metric(right, Friendly.sep(chunk.writecount || 0), "chunk writes"); - } + const store = safe(stats, "store") || {}; + const tile = grid.tag().classify("card").classify("stats-tile"); + tile.tag().classify("card-title").text("Ops & Chunks"); + const columns = tile.tag().classify("tile-columns"); + + const left = columns.tag().classify("tile-metrics"); + const op = store.op || {}; + const opTotal = (op.hitcount || 0) + (op.misscount || 0); + const opRatio = opTotal > 0 ? (((op.hitcount || 0) / opTotal) * 100).toFixed(1) + "%" : "-"; + this._metric(left, opRatio, "op hit ratio", true); + this._metric(left, Friendly.sep(op.hitcount || 0), "op hits"); + this._metric(left, Friendly.sep(op.misscount || 0), "op misses"); + this._metric(left, Friendly.sep(op.writecount || 0), "op writes"); + + const right = columns.tag().classify("tile-metrics"); + const chunk = store.chunk || {}; + const chunkTotal = (chunk.hitcount || 0) + (chunk.misscount || 0); + const chunkRatio = chunkTotal > 0 ? (((chunk.hitcount || 0) / chunkTotal) * 100).toFixed(1) + "%" : "-"; + this._metric(right, chunkRatio, "chunk hit ratio", true); + this._metric(right, Friendly.sep(chunk.hitcount || 0), "chunk hits"); + this._metric(right, Friendly.sep(chunk.misscount || 0), "chunk misses"); + this._metric(right, Friendly.sep(chunk.writecount || 0), "chunk writes"); } // Storage tile diff --git a/src/zenserver/frontend/html/pages/start.js b/src/zenserver/frontend/html/pages/start.js index 9a3eb6de3..d06040b2f 100644 --- a/src/zenserver/frontend/html/pages/start.js +++ b/src/zenserver/frontend/html/pages/start.js @@ -131,6 +131,7 @@ export class Page extends ZenPage _render_stats(all_stats) { + all_stats = this._merge_last_stats(all_stats); const grid = this._stats_grid; const safe_lookup = this._safe_lookup; diff --git a/src/zenserver/frontend/html/pages/workspaces.js b/src/zenserver/frontend/html/pages/workspaces.js index 2442fb35b..1668e096f 100644 --- a/src/zenserver/frontend/html/pages/workspaces.js +++ b/src/zenserver/frontend/html/pages/workspaces.js @@ -200,6 +200,7 @@ export class Page extends ZenPage _render_stats(stats) { + stats = this._merge_last_stats(stats); const grid = this._stats_grid; grid.inner().innerHTML = ""; |