Replace Date.now() with performance.now() for duration measurements (#258)

* Replace Date.now() with performance.now() for duration measurements

- Import performance from node:perf_hooks in all affected files
- Update Timer and ThinkingTimer classes to use performance.now()
- Update activity tracking (markActivity, getIdleMs) to use performance.now()
- Update cache duration measurements to use performance.now()
- Update agent execution timing (cursor, opencode) to use performance.now()
- Update subprocess execution timing to use performance.now()
- Update API performance monitoring to use performance.now()
- Update prep phase timing to use performance.now()
- Update timer.test.ts to mock performance.now() instead of Date.now()

Benefits:
- Monotonic clock immune to system clock adjustments
- Higher precision (microsecond vs millisecond resolution)
- Purpose-built for performance measurement

Fixes #245

* fix lint.

* Round float durations to integers in logging

Preserve original behavior by rounding performance.now() float values
to integers when displaying/logging millisecond durations.

* fix lint.

---------

Co-authored-by: pullfrog[bot] <226033991+pullfrog[bot]@users.noreply.github.com>
Co-authored-by: Robin Tail <robin_tail@me.com>
This commit is contained in:
pullfrog[bot]
2026-02-12 15:26:28 +00:00
committed by pullfrog[bot]
parent a442f766aa
commit b6e6a8976c
8 changed files with 125 additions and 111 deletions
+40 -30
View File
@@ -3682,7 +3682,7 @@ var require_util2 = __commonJS({
"use strict";
var { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = require_constants2();
var { getGlobalOrigin } = require_global();
var { performance: performance2 } = __require("perf_hooks");
var { performance: performance8 } = __require("perf_hooks");
var { isBlobLike, toUSVString, ReadableStreamFrom } = require_util();
var assert3 = __require("assert");
var { isUint8Array } = __require("util/types");
@@ -3845,7 +3845,7 @@ var require_util2 = __commonJS({
}
}
function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
return performance2.now();
return performance8.now();
}
function createOpaqueTimingInfo(timingInfo) {
return {
@@ -48701,7 +48701,7 @@ var require_util11 = __commonJS({
var { redirectStatusSet, referrerPolicyTokens, badPortsSet } = require_constants8();
var { getGlobalOrigin } = require_global3();
var { collectASequenceOfCodePoints, collectAnHTTPQuotedString, removeChars, parseMIMEType } = require_data_url();
var { performance: performance2 } = __require("node:perf_hooks");
var { performance: performance8 } = __require("node:perf_hooks");
var { ReadableStreamFrom, isValidHTTPToken, normalizedMethodRecordsBase } = require_util10();
var assert3 = __require("node:assert");
var { isUint8Array } = __require("node:util/types");
@@ -48855,7 +48855,7 @@ var require_util11 = __commonJS({
};
}
function coarsenedSharedCurrentTime(crossOriginIsolatedCapability) {
return coarsenTime(performance2.now(), crossOriginIsolatedCapability);
return coarsenTime(performance8.now(), crossOriginIsolatedCapability);
}
function createOpaqueTimingInfo(timingInfo) {
return {
@@ -140533,16 +140533,18 @@ var LIFECYCLE_HOOK_TIMEOUT_MS = 12e4;
// utils/subprocess.ts
import { spawn as nodeSpawn } from "node:child_process";
import { performance as performance3 } from "node:perf_hooks";
// utils/activity.ts
import { performance as performance2 } from "node:perf_hooks";
var DEFAULT_ACTIVITY_TIMEOUT_MS = 6e4;
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
var _lastActivity = Date.now();
var _lastActivity = performance2.now();
function markActivity() {
_lastActivity = Date.now();
_lastActivity = performance2.now();
}
function getIdleMs() {
return Date.now() - _lastActivity;
return Math.round(performance2.now() - _lastActivity);
}
function wrapWrite(original, onActivity) {
const wrapped = (chunk, encodingOrCb, cb) => {
@@ -140649,7 +140651,7 @@ async function spawn2(options) {
const { cmd, args: args2, env: env2, input, timeout, cwd, stdio, onStdout, onStderr } = options;
const activityTimeoutMs = options.activityTimeout ?? DEFAULT_ACTIVITY_TIMEOUT_MS;
installSignalHandlers();
const startTime = Date.now();
const startTime = performance3.now();
let stdoutBuffer = "";
let stderrBuffer = "";
return new Promise((resolve3, reject) => {
@@ -140666,7 +140668,7 @@ async function spawn2(options) {
let activityCheckIntervalId;
let isTimedOut = false;
let isActivityTimedOut = false;
let lastActivityTime = Date.now();
let lastActivityTime = performance3.now();
if (timeout) {
timeoutId = setTimeout(() => {
isTimedOut = true;
@@ -140680,7 +140682,7 @@ async function spawn2(options) {
}
if (activityTimeoutMs > 0) {
activityCheckIntervalId = setInterval(() => {
const idleMs = Date.now() - lastActivityTime;
const idleMs = performance3.now() - lastActivityTime;
if (idleMs > activityTimeoutMs) {
isActivityTimedOut = true;
const idleSec = Math.round(idleMs / 1e3);
@@ -140691,7 +140693,7 @@ async function spawn2(options) {
}, DEFAULT_ACTIVITY_CHECK_INTERVAL_MS);
}
function updateActivity() {
lastActivityTime = Date.now();
lastActivityTime = performance3.now();
}
if (child.stdout) {
child.stdout.on("data", (data) => {
@@ -140710,7 +140712,7 @@ async function spawn2(options) {
});
}
child.on("close", (exitCode) => {
const durationMs = Date.now() - startTime;
const durationMs = performance3.now() - startTime;
untrackChild(child);
if (timeoutId) clearTimeout(timeoutId);
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
@@ -140719,7 +140721,7 @@ async function spawn2(options) {
return;
}
if (isActivityTimedOut) {
const idleSec = Math.round((Date.now() - lastActivityTime) / 1e3);
const idleSec = Math.round((performance3.now() - lastActivityTime) / 1e3);
reject(new Error(`activity timeout: no output for ${idleSec}s`));
return;
}
@@ -140731,7 +140733,7 @@ async function spawn2(options) {
});
});
child.on("error", (error49) => {
const durationMs = Date.now() - startTime;
const durationMs = performance3.now() - startTime;
untrackChild(child);
if (timeoutId) clearTimeout(timeoutId);
if (activityCheckIntervalId) clearInterval(activityCheckIntervalId);
@@ -141539,6 +141541,9 @@ function CommitInfoTool(ctx) {
});
}
// prep/index.ts
import { performance as performance4 } from "node:perf_hooks";
// prep/installNodeDependencies.ts
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
import { join as join5 } from "node:path";
@@ -142121,7 +142126,7 @@ var installPythonDependencies = {
var prepSteps = [installNodeDependencies, installPythonDependencies];
async function runPrepPhase(options) {
log.debug("\xBB starting prep phase...");
const startTime = Date.now();
const startTime = performance4.now();
const results = [];
for (const step of prepSteps) {
const shouldRun = await step.shouldRun();
@@ -142138,8 +142143,8 @@ async function runPrepPhase(options) {
log.warning(`\xBB ${step.name}: ${result.issues[0]}`);
}
}
const totalDurationMs = Date.now() - startTime;
log.debug(`\xBB prep phase completed (${totalDurationMs}ms)`);
const totalDurationMs = performance4.now() - startTime;
log.debug(`\xBB prep phase completed (${Math.round(totalDurationMs)}ms)`);
return results;
}
@@ -144306,14 +144311,15 @@ async function installFromCurl(params) {
}
// utils/timer.ts
import { performance as performance5 } from "node:perf_hooks";
var Timer = class {
initialTimestamp;
lastCheckpointTimestamp = null;
constructor() {
this.initialTimestamp = Date.now();
this.initialTimestamp = performance5.now();
}
checkpoint(name) {
const now = Date.now();
const now = performance5.now();
const duration4 = this.lastCheckpointTimestamp ? now - this.lastCheckpointTimestamp : now - this.initialTimestamp;
log.debug(`\xBB ${name}: ${duration4}ms`);
this.lastCheckpointTimestamp = now;
@@ -144330,11 +144336,11 @@ var ThinkingTimer = class {
});
lastToolResultTimestamp = null;
markToolResult() {
this.lastToolResultTimestamp = Date.now();
this.lastToolResultTimestamp = performance5.now();
log.debug(`\xBB thinking timer: markToolResult at ${this.lastToolResultTimestamp}`);
}
markToolCall() {
const now = Date.now();
const now = performance5.now();
log.debug(
`\xBB thinking timer: markToolCall at ${now}, lastToolResult=${this.lastToolResultTimestamp}`
);
@@ -144872,6 +144878,7 @@ import { spawn as spawn3 } from "node:child_process";
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync9 } from "node:fs";
import { homedir } from "node:os";
import { join as join11 } from "node:path";
import { performance as performance6 } from "node:perf_hooks";
var cursorEffortModels = {
mini: null,
// use default (auto)
@@ -144991,7 +144998,7 @@ var cursor = agent({
}
const cursorArgs = [...baseArgs, "--force", ctx.instructions.full];
log.info("\xBB running Cursor CLI...");
const startTime = Date.now();
const startTime = performance6.now();
const cliEnv = Object.fromEntries(
Object.entries(process.env).filter(([key]) => key !== "XDG_CONFIG_HOME")
);
@@ -145033,7 +145040,7 @@ var cursor = agent({
if (signal) {
log.warning(`Cursor CLI terminated by signal: ${signal}`);
}
const duration4 = ((Date.now() - startTime) / 1e3).toFixed(1);
const duration4 = ((performance6.now() - startTime) / 1e3).toFixed(1);
if (code === 0) {
log.success(`Cursor CLI completed successfully in ${duration4}s`);
resolve3({
@@ -145051,7 +145058,7 @@ var cursor = agent({
}
});
child.on("error", (error49) => {
const duration4 = ((Date.now() - startTime) / 1e3).toFixed(1);
const duration4 = ((performance6.now() - startTime) / 1e3).toFixed(1);
const errorMessage = error49.message || String(error49);
log.error(`Cursor CLI execution failed after ${duration4}s: ${errorMessage}`);
resolve3({
@@ -145370,6 +145377,7 @@ function configureGeminiSettings(ctx) {
// agents/opencode.ts
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync11 } from "node:fs";
import { join as join13 } from "node:path";
import { performance as performance7 } from "node:perf_hooks";
var PROVIDER_ERROR_PATTERNS = [
{ pattern: "429", label: "rate limited (429)" },
{ pattern: "RESOURCE_EXHAUSTED", label: "quota exhausted" },
@@ -145424,7 +145432,7 @@ var opencode = agent({
log.debug(`\xBB working directory: ${repoDir}`);
log.debug(`\xBB HOME: ${env2.HOME}`);
log.debug(`\xBB XDG_CONFIG_HOME: ${env2.XDG_CONFIG_HOME}`);
const startTime = Date.now();
const startTime = performance7.now();
let eventCount = 0;
const thinkingTimer = new ThinkingTimer();
const recentStderr = [];
@@ -145497,8 +145505,10 @@ var opencode = agent({
}
}
});
const duration4 = Date.now() - startTime;
log.info(`\xBB OpenCode CLI completed in ${duration4}ms with exit code ${result.exitCode}`);
const duration4 = performance7.now() - startTime;
log.info(
`\xBB OpenCode CLI completed in ${Math.round(duration4)}ms with exit code ${result.exitCode}`
);
if (eventCount === 0) {
const stderrContext = recentStderr.join("\n");
const diagnosis = lastProviderError ? `provider error: ${lastProviderError}` : "unknown cause (no stdout events received)";
@@ -145538,7 +145548,7 @@ ${stderrContext}`);
output: finalOutput || output
};
} catch (error49) {
const duration4 = Date.now() - startTime;
const duration4 = performance7.now() - startTime;
const errorMessage = error49 instanceof Error ? error49.message : String(error49);
const isActivityTimeout = errorMessage.includes("activity timeout");
const stderrContext = recentStderr.slice(-10).join("\n");
@@ -145693,11 +145703,11 @@ var messageHandlers4 = {
if (toolId) {
const toolStartTime = toolCallTimings.get(toolId);
if (toolStartTime) {
const toolDuration = Date.now() - toolStartTime;
const toolDuration = performance7.now() - toolStartTime;
toolCallTimings.delete(toolId);
const stepContext = currentStepId ? ` (step=${currentStepType || "unknown"})` : "";
log.debug(
`\xBB OpenCode tool_result${stepContext}: id=${toolId}, status=${status}, duration=${toolDuration}ms`
`\xBB OpenCode tool_result${stepContext}: id=${toolId}, status=${status}, duration=${Math.round(toolDuration)}ms`
);
if (output) {
log.debug(` output: ${typeof output === "string" ? output : JSON.stringify(output)}`);