// 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" import { PropTable, Toolbar } from "../util/widgets.js" //////////////////////////////////////////////////////////////////////////////// class TemporalStat { constructor(data, as_bytes) { this._data = data; this._as_bytes = as_bytes; } toString() { const columns = [ /* count */ {}, /* rate */ {}, /* t */ {}, {}, ]; const data = this._data; for (var key in data) { var out = columns[0]; if (key.startsWith("rate_")) out = columns[1]; else if (key.startsWith("t_p")) out = columns[3]; else if (key.startsWith("t_")) out = columns[2]; out[key] = data[key]; } var friendly = this._as_bytes ? Friendly.bytes : Friendly.sep; var content = ""; for (var i = 0; i < columns.length; ++i) { const column = columns[i]; for (var key in column) { var value = column[key]; if (i) { value = Friendly.sep(value, 2); key = key.padStart(9); content += key + ": " + value; } else content += friendly(value); content += "\r\n"; } } return content; } tag() { return "pre"; } } //////////////////////////////////////////////////////////////////////////////// export class Page extends ZenPage { async main() { this.set_title("metrics"); const metrics_section = this.add_section("metrics"); const top_toolbar = metrics_section.add_widget(Toolbar); const tb_right = top_toolbar.right(); this._refresh_label = tb_right.add("", "label"); this._pause_btn = tb_right.add("pause").on_click(() => this._toggle_pause()); this._paused = false; this._last_refresh = Date.now(); this._provider_views = []; const providers_data = await new Fetcher().resource("stats").json(); const providers = providers_data["providers"] || []; const stats_list = await Promise.all(providers.map((provider) => new Fetcher() .resource("stats", provider) .param("cidstorestats", "true") .param("cachestorestats", "true") .json() .then((stats) => ({ provider, stats })) )); for (const { provider, stats } of stats_list) { this._condense(stats); this._provider_views.push(this._render_provider(provider, stats)); } this._last_refresh = Date.now(); this._update_refresh_label(); this._timer_id = setInterval(() => this._refresh(), 5000); this._tick_id = setInterval(() => this._update_refresh_label(), 1000); document.addEventListener("visibilitychange", () => { if (document.hidden) this._pause_timer(false); else if (!this._paused) this._resume_timer(); }); } _render_provider(provider, stats) { const section = this.add_section(provider); const toolbar = section.add_widget(Toolbar); toolbar.right().add("detailed →").on_click(() => { window.location = "?page=stat&provider=" + provider; }); const table = section.add_widget(PropTable); let current_stats = stats; let current_category = undefined; const show_category = (cat) => { current_category = cat; table.clear(); table.add_object(current_stats[cat], true, 3); }; var first = undefined; for (var name in stats) { first = first || name; toolbar.left().add(name).on_click(show_category, name); } if (first) show_category(first); return { provider, set_stats: (new_stats) => { current_stats = new_stats; if (current_category && current_stats[current_category]) show_category(current_category); }, }; } async _refresh() { const updates = await Promise.all(this._provider_views.map((view) => new Fetcher() .resource("stats", view.provider) .param("cidstorestats", "true") .param("cachestorestats", "true") .json() .then((stats) => ({ view, stats })) )); for (const { view, stats } of updates) { this._condense(stats); view.set_stats(stats); } this._last_refresh = Date.now(); this._update_refresh_label(); } _update_refresh_label() { const elapsed = Math.floor((Date.now() - this._last_refresh) / 1000); this._refresh_label.inner().textContent = "refreshed " + elapsed + "s ago"; } _toggle_pause() { if (this._paused) this._resume_timer(); else this._pause_timer(true); } _pause_timer(user_paused=true) { clearInterval(this._timer_id); this._timer_id = undefined; if (user_paused) { this._paused = true; this._pause_btn.inner().textContent = "resume"; } } _resume_timer() { this._paused = false; this._pause_btn.inner().textContent = "pause"; this._timer_id = setInterval(() => this._refresh(), 5000); this._refresh(); } _condense(stats) { const impl = function(node) { for (var name in node) { const candidate = node[name]; if (!(candidate instanceof Object)) continue; if (candidate["rate_mean"] != undefined) { const as_bytes = (name.indexOf("bytes") >= 0); node[name] = new TemporalStat(candidate, as_bytes); continue; } impl(candidate); } } for (var name in stats) impl(stats[name]); } }