// 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("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 || "-"); if (info.IpAddresses && info.IpAddresses.length > 0) { this._prop(list, "ip addresses", info.IpAddresses.join(", ")); } 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`; } }