mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +00:00
feat(cli): add system metrics to telemetry and expand doctor command (#110)
* feat(cli): add system metrics to telemetry and expand doctor command Enrich render telemetry with device/environment metadata (CPU, memory, OS, Docker/CI/WSL detection) following patterns from Next.js and Turborepo. Add speed_ratio (render time / composition duration), per-frame capture timing, and resource usage to render events. Expand the doctor command with CPU, memory, disk, /dev/shm, and environment checks to help debug rendering issues on user machines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): invert speed_ratio to match experiment-framework convention composition_duration / render_time — higher is better, >1 means faster than realtime. Matches magic_edit.render.speed_ratio in experiment-framework. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): wire errorMessage into render error telemetry Address review feedback — the errorMessage field was declared in the trackRenderError interface but never populated. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(cli): add render telemetry to embedded studio server Track render_complete and render_error from the studio's render API endpoint (hyperframes dev). Uses dynamic imports so telemetry is resolved at call time within the CLI package — no telemetry coupling added to @hyperframes/studio or @hyperframes/producer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ce63160907
commit
865843ba9a
@@ -0,0 +1,139 @@
|
||||
import { cpus, totalmem, platform, release } from "node:os";
|
||||
import { existsSync, readFileSync, statfsSync } from "node:fs";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// System metadata collected once per CLI session and attached to all events.
|
||||
// Follows the same patterns as Next.js, Turborepo, and Gatsby telemetry.
|
||||
// No PII — only hardware/environment characteristics useful for debugging.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Convert bytes to whole megabytes. */
|
||||
export function bytesToMb(bytes: number): number {
|
||||
return Math.trunc(bytes / (1024 * 1024));
|
||||
}
|
||||
|
||||
export interface SystemMeta {
|
||||
os_release: string;
|
||||
cpu_count: number;
|
||||
cpu_model: string | null;
|
||||
cpu_speed: number | null;
|
||||
memory_total_mb: number;
|
||||
is_docker: boolean;
|
||||
is_ci: boolean;
|
||||
ci_name: string | null;
|
||||
is_wsl: boolean;
|
||||
is_tty: boolean;
|
||||
}
|
||||
|
||||
let cached: SystemMeta | null = null;
|
||||
|
||||
/**
|
||||
* Collect system metadata. Cached after first call.
|
||||
* Only includes static values — use `freemem()` directly for volatile readings.
|
||||
*/
|
||||
export function getSystemMeta(): SystemMeta {
|
||||
if (cached) return cached;
|
||||
|
||||
const cpuInfo = cpus();
|
||||
const firstCpu = cpuInfo[0] ?? null;
|
||||
|
||||
cached = {
|
||||
os_release: release(),
|
||||
cpu_count: cpuInfo.length,
|
||||
cpu_model: firstCpu?.model?.trim() ?? null,
|
||||
cpu_speed: firstCpu?.speed ?? null,
|
||||
memory_total_mb: bytesToMb(totalmem()),
|
||||
is_docker: detectDocker(),
|
||||
is_ci: detectCI(),
|
||||
ci_name: getCIName(),
|
||||
is_wsl: detectWSL(),
|
||||
is_tty: Boolean(process.stdout?.isTTY),
|
||||
};
|
||||
return cached;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Environment detectors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function detectDocker(): boolean {
|
||||
// Standard detection: /.dockerenv file or "docker" in /proc/1/cgroup
|
||||
try {
|
||||
if (existsSync("/.dockerenv")) return true;
|
||||
if (platform() === "linux") {
|
||||
const cgroup = readFileSync("/proc/1/cgroup", "utf-8");
|
||||
if (cgroup.includes("docker") || cgroup.includes("containerd")) return true;
|
||||
}
|
||||
} catch {
|
||||
// Ignore — not in Docker
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function detectCI(): boolean {
|
||||
return (
|
||||
process.env["CI"] === "true" ||
|
||||
process.env["CI"] === "1" ||
|
||||
process.env["CONTINUOUS_INTEGRATION"] === "true" ||
|
||||
process.env["GITHUB_ACTIONS"] === "true" ||
|
||||
process.env["GITLAB_CI"] === "true" ||
|
||||
process.env["CIRCLECI"] === "true" ||
|
||||
process.env["JENKINS_URL"] != null ||
|
||||
process.env["BUILDKITE"] === "true" ||
|
||||
process.env["TRAVIS"] === "true" ||
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function getCIName(): string | null {
|
||||
if (process.env["GITHUB_ACTIONS"] === "true") return "github_actions";
|
||||
if (process.env["GITLAB_CI"] === "true") return "gitlab_ci";
|
||||
if (process.env["CIRCLECI"] === "true") return "circleci";
|
||||
if (process.env["JENKINS_URL"] != null) return "jenkins";
|
||||
if (process.env["BUILDKITE"] === "true") return "buildkite";
|
||||
if (process.env["TRAVIS"] === "true") return "travis";
|
||||
if (detectCI()) return "unknown";
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectWSL(): boolean {
|
||||
if (platform() !== "linux") return false;
|
||||
try {
|
||||
const osRelease = release().toLowerCase();
|
||||
if (osRelease.includes("microsoft") || osRelease.includes("wsl")) return true;
|
||||
const procVersion = readFileSync("/proc/version", "utf-8").toLowerCase();
|
||||
return procVersion.includes("microsoft") || procVersion.includes("wsl");
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extended hardware checks (for doctor command and detailed render events)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get /dev/shm size in MB (Linux only). Chrome uses shared memory heavily;
|
||||
* Docker's default 64MB limit causes crashes.
|
||||
*/
|
||||
export function getShmSizeMb(): number | null {
|
||||
if (platform() !== "linux") return null;
|
||||
try {
|
||||
const stats = statfsSync("/dev/shm");
|
||||
return bytesToMb(stats.bsize * stats.blocks);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available disk space in MB at a given path.
|
||||
*/
|
||||
export function getFreeDiskMb(path: string = "."): number | null {
|
||||
try {
|
||||
const stats = statfsSync(path);
|
||||
return bytesToMb(stats.bsize * stats.bavail);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user