diff options
Diffstat (limited to 'src/zenserver/frontend/html/pages/info.js')
| -rw-r--r-- | src/zenserver/frontend/html/pages/info.js | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/zenserver/frontend/html/pages/info.js b/src/zenserver/frontend/html/pages/info.js new file mode 100644 index 000000000..f92765c78 --- /dev/null +++ b/src/zenserver/frontend/html/pages/info.js @@ -0,0 +1,261 @@ +// 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 +{ + async main() + { + this.set_title("info"); + + const [info, gc, services, version] = await Promise.all([ + new Fetcher().resource("/health/info").json(), + new Fetcher().resource("/admin/gc").json().catch(() => null), + new Fetcher().resource("/api/").json().catch(() => ({})), + new Fetcher().resource("/health/version").param("detailed", "true").text(), + ]); + + const section = this.add_section("Server Info"); + const grid = section.tag().classify("grid").classify("info-tiles"); + + // Application + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("Application"); + const list = tile.tag().classify("info-props"); + + this._prop(list, "version", version || info.BuildVersion || "-"); + this._prop(list, "http server", info.HttpServerClass || "-"); + this._prop(list, "port", info.Port || "-"); + this._prop(list, "pid", info.Pid || "-"); + this._prop(list, "dedicated", info.IsDedicated ? "yes" : "no"); + + if (info.StartTimeMs) + { + const start = new Date(info.StartTimeMs); + const elapsed = Date.now() - info.StartTimeMs; + this._prop(list, "started", start.toLocaleString()); + this._prop(list, "uptime", this._format_duration(elapsed)); + } + + this._prop(list, "data root", info.DataRoot || "-"); + this._prop(list, "log path", info.AbsLogPath || "-"); + } + + // System + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("System"); + const list = tile.tag().classify("info-props"); + + this._prop(list, "hostname", info.Hostname || "-"); + this._prop(list, "platform", info.Platform || "-"); + this._prop(list, "os", info.OS || "-"); + this._prop(list, "arch", info.Arch || "-"); + + const sys = info.System; + if (sys) + { + this._prop(list, "cpus", sys.cpu_count || "-"); + this._prop(list, "cores", sys.core_count || "-"); + this._prop(list, "logical processors", sys.lp_count || "-"); + this._prop(list, "total memory", sys.total_memory_mb ? Friendly.bytes(sys.total_memory_mb * 1048576) : "-"); + this._prop(list, "available memory", sys.avail_memory_mb ? Friendly.bytes(sys.avail_memory_mb * 1048576) : "-"); + if (sys.uptime_seconds) + { + this._prop(list, "system uptime", this._format_duration(sys.uptime_seconds * 1000)); + } + } + } + + // Runtime Configuration + if (info.RuntimeConfig) + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("Runtime Configuration"); + const list = tile.tag().classify("info-props"); + + for (const key in info.RuntimeConfig) + { + this._prop(list, key, info.RuntimeConfig[key] || "-"); + } + } + + // Build Configuration + if (info.BuildConfig) + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("Build Configuration"); + const list = tile.tag().classify("info-props"); + + for (const key in info.BuildConfig) + { + this._prop(list, key, info.BuildConfig[key] ? "yes" : "no"); + } + } + + // Services + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("Services"); + const list = tile.tag().classify("info-props"); + + const svc_list = (services.services || []).map(s => s.base_uri).sort(); + for (const uri of svc_list) + { + this._prop(list, uri, "registered"); + } + } + + // Garbage Collection + if (gc) + { + const tile = grid.tag().classify("card").classify("info-tile"); + tile.tag().classify("card-title").text("Garbage Collection"); + const list = tile.tag().classify("info-props"); + + this._prop(list, "status", gc.Status || "-"); + + if (gc.AreDiskWritesBlocked !== undefined) + { + this._prop(list, "disk writes blocked", gc.AreDiskWritesBlocked ? "yes" : "no"); + } + + if (gc.DiskSize) + { + this._prop(list, "disk size", gc.DiskSize); + this._prop(list, "disk used", gc.DiskUsed); + this._prop(list, "disk free", gc.DiskFree); + } + + const cfg = gc.Config; + if (cfg) + { + this._prop(list, "gc enabled", cfg.Enabled ? "yes" : "no"); + if (cfg.Interval) + { + this._prop(list, "interval", this._friendly_duration(cfg.Interval)); + } + if (cfg.LightweightInterval) + { + this._prop(list, "lightweight interval", this._friendly_duration(cfg.LightweightInterval)); + } + if (cfg.MaxCacheDuration) + { + this._prop(list, "max cache duration", this._friendly_duration(cfg.MaxCacheDuration)); + } + if (cfg.MaxProjectStoreDuration) + { + this._prop(list, "max project duration", this._friendly_duration(cfg.MaxProjectStoreDuration)); + } + if (cfg.MaxBuildStoreDuration) + { + this._prop(list, "max build duration", this._friendly_duration(cfg.MaxBuildStoreDuration)); + } + } + + if (gc.FullGC) + { + if (gc.FullGC.LastTime) + { + this._prop(list, "last full gc", this._friendly_timestamp(gc.FullGC.LastTime)); + } + if (gc.FullGC.TimeToNext) + { + this._prop(list, "next full gc", this._friendly_duration(gc.FullGC.TimeToNext)); + } + } + + if (gc.LightweightGC) + { + if (gc.LightweightGC.LastTime) + { + this._prop(list, "last lightweight gc", this._friendly_timestamp(gc.LightweightGC.LastTime)); + } + if (gc.LightweightGC.TimeToNext) + { + this._prop(list, "next lightweight gc", this._friendly_duration(gc.LightweightGC.TimeToNext)); + } + } + } + } + + _prop(parent, label, value) + { + const row = parent.tag().classify("info-prop"); + row.tag().classify("info-prop-label").text(label); + const val = row.tag().classify("info-prop-value"); + const str = String(value); + if (str.match(/^[A-Za-z]:[\\/]/) || str.startsWith("/")) + { + val.tag("a").text(str).attr("href", "vscode://" + str.replace(/\\/g, "/")); + } + else + { + val.text(str); + } + } + + _friendly_timestamp(value) + { + const d = new Date(value); + if (isNaN(d.getTime())) + { + return String(value); + } + return d.toLocaleString(undefined, { + year: "numeric", month: "short", day: "numeric", + hour: "2-digit", minute: "2-digit", second: "2-digit", + }); + } + + _friendly_duration(value) + { + if (typeof value === "number") + { + return this._format_duration(value); + } + + const str = String(value); + const match = str.match(/^[+-]?(?:(\d+)\.)?(\d+):(\d+):(\d+)(?:\.(\d+))?$/); + if (!match) + { + return str; + } + + const days = parseInt(match[1] || "0", 10); + const hours = parseInt(match[2], 10); + const minutes = parseInt(match[3], 10); + const seconds = parseInt(match[4], 10); + const total_seconds = days * 86400 + hours * 3600 + minutes * 60 + seconds; + + return this._format_duration(total_seconds * 1000); + } + + _format_duration(ms) + { + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) + { + return `${days}d ${hours % 24}h ${minutes % 60}m`; + } + if (hours > 0) + { + return `${hours}h ${minutes % 60}m`; + } + if (minutes > 0) + { + return `${minutes}m ${seconds % 60}s`; + } + return `${seconds}s`; + } +} |