Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
144 lines
3.9 KiB
TypeScript
144 lines
3.9 KiB
TypeScript
import { netcattyBridge } from "../../../infrastructure/services/netcattyBridge";
|
|
|
|
export type TerminalOutputPerfMeta = {
|
|
id: string;
|
|
emittedAt: number;
|
|
chars: number;
|
|
lineFeeds: number;
|
|
};
|
|
|
|
export type TerminalOutputPerfTrace = {
|
|
id: string;
|
|
sessionId?: string;
|
|
startedAt: number;
|
|
rendererReceivedAt: number;
|
|
ingressBytes: number;
|
|
inputChars: number;
|
|
inputLineFeeds: number;
|
|
backend?: TerminalOutputPerfMeta;
|
|
};
|
|
|
|
type TerminalOutputPerfMetaCarrier = {
|
|
terminalPerf?: TerminalOutputPerfMeta;
|
|
};
|
|
|
|
const DEBUG_KEYS = [
|
|
"NETCATTY_TERMINAL_PERF_DEBUG",
|
|
"NETCATTY_TERMINAL_DEBUG",
|
|
];
|
|
const PERF_LOG_PREFIX = "[Netcatty Terminal Perf]";
|
|
const LOCAL_STORAGE_DEBUG_CACHE_TTL_MS = 1000;
|
|
|
|
let localStorageDebugCache = false;
|
|
let localStorageDebugCacheAt = 0;
|
|
|
|
const countLineFeeds = (data: string): number => {
|
|
let count = 0;
|
|
for (let index = 0; index < data.length; index += 1) {
|
|
if (data[index] === "\n") count += 1;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
const safeJson = (value: unknown): string => JSON.stringify(value, (_key, nested) => {
|
|
if (typeof nested === "bigint") return nested.toString();
|
|
if (typeof nested === "function") return "[function]";
|
|
return nested;
|
|
});
|
|
|
|
const sendRendererDiagnostic = (
|
|
message: string,
|
|
payload: Record<string, unknown>,
|
|
): void => {
|
|
try {
|
|
const logDiagnostic = netcattyBridge.get()?.logDiagnostic;
|
|
if (typeof logDiagnostic !== "function") return;
|
|
void logDiagnostic({
|
|
source: "terminal-perf",
|
|
message,
|
|
extra: payload,
|
|
}).catch(() => undefined);
|
|
} catch {
|
|
// Diagnostics must never affect terminal output.
|
|
}
|
|
};
|
|
|
|
const readLocalStorageDebugEnabled = (): boolean => {
|
|
try {
|
|
return DEBUG_KEYS.some((key) => window.localStorage?.getItem(key) === "1");
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const isLocalStorageDebugEnabled = (): boolean => {
|
|
const now = Date.now();
|
|
if (now - localStorageDebugCacheAt > LOCAL_STORAGE_DEBUG_CACHE_TTL_MS) {
|
|
localStorageDebugCache = readLocalStorageDebugEnabled();
|
|
localStorageDebugCacheAt = now;
|
|
}
|
|
return localStorageDebugCache;
|
|
};
|
|
|
|
export const isTerminalPerformanceDebugEnabled = (
|
|
meta?: TerminalOutputPerfMetaCarrier,
|
|
): boolean => Boolean(meta?.terminalPerf) || isLocalStorageDebugEnabled();
|
|
|
|
export const createTerminalOutputPerfTrace = ({
|
|
sessionId,
|
|
data,
|
|
ingressBytes,
|
|
meta,
|
|
}: {
|
|
sessionId?: string;
|
|
data: string;
|
|
ingressBytes: number;
|
|
meta?: TerminalOutputPerfMetaCarrier;
|
|
}): TerminalOutputPerfTrace | null => {
|
|
if (!isTerminalPerformanceDebugEnabled(meta)) return null;
|
|
const now = performance.now();
|
|
return {
|
|
id: meta?.terminalPerf?.id ?? `renderer-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
|
|
sessionId,
|
|
startedAt: now,
|
|
rendererReceivedAt: Date.now(),
|
|
ingressBytes,
|
|
inputChars: data.length,
|
|
inputLineFeeds: countLineFeeds(data),
|
|
backend: meta?.terminalPerf,
|
|
};
|
|
};
|
|
|
|
export const logTerminalOutputPerf = (
|
|
event: string,
|
|
trace: TerminalOutputPerfTrace | null | undefined,
|
|
details: Record<string, unknown> = {},
|
|
): void => {
|
|
if (!trace && !isLocalStorageDebugEnabled()) return;
|
|
const now = performance.now();
|
|
const backendToRendererMs = trace?.backend?.emittedAt
|
|
? trace.rendererReceivedAt - trace.backend.emittedAt
|
|
: undefined;
|
|
const payload = {
|
|
event,
|
|
id: trace?.id,
|
|
sessionId: trace?.sessionId,
|
|
at: Date.now(),
|
|
elapsedMs: trace ? Number((now - trace.startedAt).toFixed(1)) : undefined,
|
|
backendToRendererMs,
|
|
ingressBytes: trace?.ingressBytes,
|
|
inputChars: trace?.inputChars,
|
|
inputLineFeeds: trace?.inputLineFeeds,
|
|
backendChars: trace?.backend?.chars,
|
|
backendLineFeeds: trace?.backend?.lineFeeds,
|
|
...details,
|
|
};
|
|
try {
|
|
const message = `${PERF_LOG_PREFIX} ${safeJson(payload)}`;
|
|
console.info(message);
|
|
sendRendererDiagnostic(message, payload);
|
|
} catch {
|
|
// Diagnostics must never affect terminal output.
|
|
}
|
|
};
|