diff options
Diffstat (limited to 'src/zenserver/frontend/html/pages/cache.js')
| -rw-r--r-- | src/zenserver/frontend/html/pages/cache.js | 362 |
1 files changed, 159 insertions, 203 deletions
diff --git a/src/zenserver/frontend/html/pages/cache.js b/src/zenserver/frontend/html/pages/cache.js index 3b838958a..683f7df4f 100644 --- a/src/zenserver/frontend/html/pages/cache.js +++ b/src/zenserver/frontend/html/pages/cache.js @@ -6,11 +6,13 @@ import { ZenPage } from "./page.js" import { Fetcher } from "../util/fetcher.js" import { Friendly } from "../util/friendly.js" import { Modal } from "../util/modal.js" -import { Table, Toolbar } from "../util/widgets.js" +import { Table, Toolbar, Pager, add_copy_button } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage { + generate_crumbs() {} + async main() { this.set_title("cache"); @@ -31,13 +33,17 @@ export class Page extends ZenPage this._render_stats(stats); } - this._connect_stats_ws(); + this.connect_stats_ws((all_stats) => { + const stats = all_stats["z$"]; + if (stats) + { + this._render_stats(stats); + } + }); // Cache Namespaces var section = this._collapsible_section("Cache Namespaces"); - section.tag().classify("dropall").text("drop-all").on_click(() => this.drop_all()); - var columns = [ "namespace", "dir", @@ -48,31 +54,30 @@ export class Page extends ZenPage "actions", ]; - var zcache_info = await new Fetcher().resource("/z$/").json(); this._cache_table = section.add_widget(Table, columns, Table.Flag_FitLeft|Table.Flag_PackRight|Table.Flag_AlignNumeric); - for (const namespace of zcache_info["Namespaces"] || []) - { - new Fetcher().resource(`/z$/${namespace}/`).json().then((data) => { - const row = this._cache_table.add_row( - "", - data["Configuration"]["RootDir"], - data["Buckets"].length, - data["EntryCount"], - Friendly.bytes(data["StorageSize"].DiskSize), - Friendly.bytes(data["StorageSize"].MemorySize) - ); - var cell = row.get_cell(0); - cell.tag().text(namespace).on_click(() => this.view_namespace(namespace)); - - cell = row.get_cell(-1); - const action_tb = new Toolbar(cell, true); - action_tb.left().add("view").on_click(() => this.view_namespace(namespace)); - action_tb.left().add("drop").on_click(() => this.drop_namespace(namespace)); - - row.attr("zs_name", namespace); - }); - } + this._cache_pager = new Pager(section, 25, () => this._render_cache_page(), + Pager.make_search_fn(() => this._cache_data, item => item.namespace)); + const cache_drop_link = document.createElement("span"); + cache_drop_link.className = "dropall zen_action"; + cache_drop_link.style.position = "static"; + cache_drop_link.textContent = "drop-all"; + cache_drop_link.addEventListener("click", () => this.drop_all()); + this._cache_pager.prepend(cache_drop_link); + + const loading = Pager.loading(section); + const zcache_info = await new Fetcher().resource("/z$/").json(); + const namespaces = zcache_info["Namespaces"] || []; + const results = await Promise.allSettled( + namespaces.map(ns => new Fetcher().resource(`/z$/${ns}/`).json().then(data => ({ namespace: ns, data }))) + ); + this._cache_data = results + .filter(r => r.status === "fulfilled") + .map(r => r.value) + .sort((a, b) => a.namespace.localeCompare(b.namespace)); + this._cache_pager.set_total(this._cache_data.length); + this._render_cache_page(); + loading.remove(); // Namespace detail area (inside namespaces section so it collapses together) this._namespace_host = section; @@ -87,121 +92,79 @@ export class Page extends ZenPage } } - _collapsible_section(name) + _render_cache_page() { - const section = this.add_section(name); - const container = section._parent.inner(); - const heading = container.firstElementChild; - - heading.style.cursor = "pointer"; - heading.style.userSelect = "none"; - - const indicator = document.createElement("span"); - indicator.textContent = " \u25BC"; - indicator.style.fontSize = "0.7em"; - heading.appendChild(indicator); - - let collapsed = false; - heading.addEventListener("click", (e) => { - if (e.target !== heading && e.target !== indicator) - { - return; - } - collapsed = !collapsed; - indicator.textContent = collapsed ? " \u25B6" : " \u25BC"; - let sibling = heading.nextElementSibling; - while (sibling) - { - sibling.style.display = collapsed ? "none" : ""; - sibling = sibling.nextElementSibling; - } - }); - - return section; - } - - _connect_stats_ws() - { - try + const { start, end } = this._cache_pager.page_range(); + this._cache_table.clear(start); + for (let i = start; i < end; i++) { - const proto = location.protocol === "https:" ? "wss:" : "ws:"; - const ws = new WebSocket(`${proto}//${location.host}/stats`); - - try { this._ws_paused = localStorage.getItem("zen-ws-paused") === "true"; } catch (e) { this._ws_paused = false; } - document.addEventListener("zen-ws-toggle", (e) => { - this._ws_paused = e.detail.paused; - }); + const item = this._cache_data[i]; + const data = item.data; + const row = this._cache_table.add_row( + "", + data["Configuration"]["RootDir"], + data["Buckets"].length, + data["EntryCount"], + Friendly.bytes(data["StorageSize"].DiskSize), + Friendly.bytes(data["StorageSize"].MemorySize) + ); - ws.onmessage = (ev) => { - if (this._ws_paused) - { - return; - } - try - { - const all_stats = JSON.parse(ev.data); - const stats = all_stats["z$"]; - if (stats) - { - this._render_stats(stats); - } - } - catch (e) { /* ignore parse errors */ } - }; + const cell = row.get_cell(0); + cell.tag().text(item.namespace).on_click(() => this.view_namespace(item.namespace)); + add_copy_button(cell.inner(), item.namespace); + add_copy_button(row.get_cell(1).inner(), data["Configuration"]["RootDir"]); - ws.onclose = () => { this._stats_ws = null; }; - ws.onerror = () => { ws.close(); }; + const action_cell = row.get_cell(-1); + const action_tb = new Toolbar(action_cell, true); + action_tb.left().add("view").on_click(() => this.view_namespace(item.namespace)); + action_tb.left().add("drop").on_click(() => this.drop_namespace(item.namespace)); - this._stats_ws = ws; + row.attr("zs_name", item.namespace); } - catch (e) { /* WebSocket not available */ } } _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 @@ -237,89 +200,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"); } } @@ -342,7 +299,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) @@ -673,10 +630,9 @@ export class Page extends ZenPage async drop_all() { const drop = async () => { - for (const row of this._cache_table) + for (const item of this._cache_data || []) { - const namespace = row.attr("zs_name"); - await new Fetcher().resource("z$", namespace).delete(); + await new Fetcher().resource("z$", item.namespace).delete(); } this.reload(); }; |