feat: add elapsed ms in debugs

This commit is contained in:
2026-06-07 15:28:27 +09:00
parent 968de4d4a8
commit e988bcd0d2
5 changed files with 76 additions and 11 deletions

View File

@@ -136,6 +136,8 @@ describe("runDebugBrainInit", () => {
); );
expect(result.extractedFacts).toEqual(EXTRACTED_FACTS); expect(result.extractedFacts).toEqual(EXTRACTED_FACTS);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
}); });
test("B2: invokes the LLM exactly 3 times — PERSONA_INIT, PERSONA_BASE_SYSTEM_PROMPT, fact-extractor", async () => { test("B2: invokes the LLM exactly 3 times — PERSONA_INIT, PERSONA_BASE_SYSTEM_PROMPT, fact-extractor", async () => {
@@ -194,6 +196,8 @@ describe("runDebugBrainInit", () => {
expect(result.ok).toBe(false); expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected !ok"); if (result.ok) throw new Error("expected !ok");
expect(result.error).toMatch(/Brain initialization failed/); expect(result.error).toMatch(/Brain initialization failed/);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
}); });
test("B5: with no DB_PATH / BRAINDB_PATH env, runDebugBrainInit still works (no env dependency)", async () => { test("B5: with no DB_PATH / BRAINDB_PATH env, runDebugBrainInit still works (no env dependency)", async () => {
@@ -202,6 +206,9 @@ describe("runDebugBrainInit", () => {
seed: "no env", seed: "no env",
}); });
expect(result.ok).toBe(true); expect(result.ok).toBe(true);
if (!result.ok) throw new Error("expected ok");
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
}); });
}); });

View File

@@ -6,6 +6,7 @@ import type { Command } from "commander";
import ora from "ora"; import ora from "ora";
import type { ExtractedFact } from "identitydb"; import type { ExtractedFact } from "identitydb";
import { Brain } from "@/brain"; import { Brain } from "@/brain";
import { formatDuration } from "@/utils/duration";
import { logger } from "@/utils/logger"; import { logger } from "@/utils/logger";
export interface BrainInitOptions { export interface BrainInitOptions {
@@ -23,8 +24,9 @@ export type BrainInitResult =
description: string; description: string;
baseSystemPrompt: string; baseSystemPrompt: string;
extractedFacts: ExtractedFact[]; extractedFacts: ExtractedFact[];
elapsedMs: number;
} }
| { ok: false; error: string }; | { ok: false; error: string; elapsedMs: number };
/** /**
* Exercise the full `Brain.create` flow (PERSONA_INIT → PERSONA_BASE_SYSTEM_PROMPT * Exercise the full `Brain.create` flow (PERSONA_INIT → PERSONA_BASE_SYSTEM_PROMPT
@@ -44,6 +46,7 @@ export type BrainInitResult =
export async function runDebugBrainInit( export async function runDebugBrainInit(
opts: BrainInitOptions, opts: BrainInitOptions,
): Promise<BrainInitResult> { ): Promise<BrainInitResult> {
const startTime = Date.now();
const braindbPath = join( const braindbPath = join(
tmpdir(), tmpdir(),
`brainbox-debug-brain-${randomUUID()}.json`, `brainbox-debug-brain-${randomUUID()}.json`,
@@ -60,7 +63,8 @@ export async function runDebugBrainInit(
}); });
if (!result) { if (!result) {
spinner.fail("Brain initialization failed"); spinner.fail("Brain initialization failed");
return { ok: false, error: "Brain initialization failed" }; const elapsedMs = Date.now() - startTime;
return { ok: false, error: "Brain initialization failed", elapsedMs };
} }
const { const {
brain, brain,
@@ -101,7 +105,10 @@ export async function runDebugBrainInit(
} }
console.log(); console.log();
logger.info("Debug run complete. Nothing was written to real disk."); const elapsedMs = Date.now() - startTime;
logger.info(
`Debug run complete in ${formatDuration(elapsedMs)}. Nothing was written to real disk.`,
);
return { return {
ok: true, ok: true,
@@ -112,11 +119,13 @@ export async function runDebugBrainInit(
description, description,
baseSystemPrompt, baseSystemPrompt,
extractedFacts: extractedFacts ?? [], extractedFacts: extractedFacts ?? [],
elapsedMs,
}; };
} catch (error) { } catch (error) {
const reason = error instanceof Error ? error.message : String(error); const reason = error instanceof Error ? error.message : String(error);
spinner.fail("Brain initialization failed"); spinner.fail("Brain initialization failed");
return { ok: false, error: reason }; const elapsedMs = Date.now() - startTime;
return { ok: false, error: reason, elapsedMs };
} finally { } finally {
try { try {
await unlink(braindbPath); await unlink(braindbPath);

View File

@@ -110,6 +110,8 @@ describe("runDebugScheduleDaily", () => {
expect(result.schedule.items).toHaveLength(48); expect(result.schedule.items).toHaveLength(48);
expect(result.availability.items.length).toBeGreaterThan(0); expect(result.availability.items.length).toBeGreaterThan(0);
expect(result.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/); expect(result.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
const dailyCall = llmCalls.find( const dailyCall = llmCalls.find(
(c) => c.options.jsonSchemaName === "daily-schedule", (c) => c.options.jsonSchemaName === "daily-schedule",
@@ -133,6 +135,8 @@ describe("runDebugScheduleDaily", () => {
expect(result.ok).toBe(false); expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected !ok"); if (result.ok) throw new Error("expected !ok");
expect(result.error).toMatch(/Daily schedule generation failed/); expect(result.error).toMatch(/Daily schedule generation failed/);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
}); });
}); });
@@ -149,6 +153,8 @@ describe("runDebugScheduleMonthly", () => {
expect(result.kind).toBe("monthly"); expect(result.kind).toBe("monthly");
expect(result.schedule.items).toHaveLength(result.daysInMonth); expect(result.schedule.items).toHaveLength(result.daysInMonth);
expect(result.monthKey).toMatch(/^\d{4}-\d{2}$/); expect(result.monthKey).toMatch(/^\d{4}-\d{2}$/);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
const call = llmCalls.find( const call = llmCalls.find(
(c) => c.options.jsonSchemaName === "monthly-schedule", (c) => c.options.jsonSchemaName === "monthly-schedule",
@@ -167,6 +173,8 @@ describe("runDebugScheduleMonthly", () => {
expect(result.ok).toBe(false); expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected !ok"); if (result.ok) throw new Error("expected !ok");
expect(result.error).toMatch(/Monthly schedule generation failed/); expect(result.error).toMatch(/Monthly schedule generation failed/);
expect(typeof result.elapsedMs).toBe("number");
expect(result.elapsedMs).toBeGreaterThanOrEqual(0);
}); });
}); });

View File

@@ -6,6 +6,7 @@ import {
type DailySchedule, type DailySchedule,
type MonthlySchedule, type MonthlySchedule,
} from "@/openrouter/schema"; } from "@/openrouter/schema";
import { formatDuration } from "@/utils/duration";
import { logger } from "@/utils/logger"; import { logger } from "@/utils/logger";
import { formatDateKey, nextMonth, pad2 } from "@/brain/schedule"; import { formatDateKey, nextMonth, pad2 } from "@/brain/schedule";
@@ -22,8 +23,9 @@ export type DailyRunResult =
tomorrow: Date; tomorrow: Date;
schedule: DailySchedule; schedule: DailySchedule;
availability: AvailabilityWindows; availability: AvailabilityWindows;
elapsedMs: number;
} }
| { ok: false; error: string }; | { ok: false; error: string; elapsedMs: number };
export type MonthlyRunResult = export type MonthlyRunResult =
| { | {
@@ -32,12 +34,14 @@ export type MonthlyRunResult =
monthKey: string; monthKey: string;
daysInMonth: number; daysInMonth: number;
schedule: MonthlySchedule; schedule: MonthlySchedule;
elapsedMs: number;
} }
| { ok: false; error: string }; | { ok: false; error: string; elapsedMs: number };
export async function runDebugScheduleDaily( export async function runDebugScheduleDaily(
opts: ScheduleOptions, opts: ScheduleOptions,
): Promise<DailyRunResult> { ): Promise<DailyRunResult> {
const startTime = Date.now();
const today = new Date(); const today = new Date();
const tomorrow = new Date( const tomorrow = new Date(
today.getFullYear(), today.getFullYear(),
@@ -54,7 +58,12 @@ export async function runDebugScheduleDaily(
const schedule = await brain.createDailySchedule(today, opts.message); const schedule = await brain.createDailySchedule(today, opts.message);
if (!schedule) { if (!schedule) {
scheduleSpinner.fail("Daily schedule generation failed"); scheduleSpinner.fail("Daily schedule generation failed");
return { ok: false, error: "Daily schedule generation failed" }; const elapsedMs = Date.now() - startTime;
return {
ok: false,
error: "Daily schedule generation failed",
elapsedMs,
};
} }
scheduleSpinner.succeed( scheduleSpinner.succeed(
`Daily schedule generated (${schedule.items.length} slots)`, `Daily schedule generated (${schedule.items.length} slots)`,
@@ -69,7 +78,12 @@ export async function runDebugScheduleDaily(
const availability = await brain.deriveAvailabilityFromSchedule(schedule); const availability = await brain.deriveAvailabilityFromSchedule(schedule);
if (!availability) { if (!availability) {
availSpinner.fail("Availability derivation failed"); availSpinner.fail("Availability derivation failed");
return { ok: false, error: "Availability derivation failed" }; const elapsedMs = Date.now() - startTime;
return {
ok: false,
error: "Availability derivation failed",
elapsedMs,
};
} }
availSpinner.succeed( availSpinner.succeed(
`Availability derived (${availability.items.length} windows)`, `Availability derived (${availability.items.length} windows)`,
@@ -78,7 +92,10 @@ export async function runDebugScheduleDaily(
printSection(`Availability — ${dateKey}`); printSection(`Availability — ${dateKey}`);
console.log(JSON.stringify(availability, null, 2)); console.log(JSON.stringify(availability, null, 2));
logger.info("Debug run complete. Nothing was written to disk."); const elapsedMs = Date.now() - startTime;
logger.info(
`Debug run complete in ${formatDuration(elapsedMs)}. Nothing was written to disk.`,
);
return { return {
ok: true, ok: true,
@@ -87,12 +104,14 @@ export async function runDebugScheduleDaily(
tomorrow, tomorrow,
schedule, schedule,
availability, availability,
elapsedMs,
}; };
} }
export async function runDebugScheduleMonthly( export async function runDebugScheduleMonthly(
opts: ScheduleOptions, opts: ScheduleOptions,
): Promise<MonthlyRunResult> { ): Promise<MonthlyRunResult> {
const startTime = Date.now();
const today = new Date(); const today = new Date();
const next = nextMonth(today); const next = nextMonth(today);
const monthKey = `${next.year}-${pad2(next.month + 1)}`; const monthKey = `${next.year}-${pad2(next.month + 1)}`;
@@ -105,7 +124,12 @@ export async function runDebugScheduleMonthly(
const schedule = await brain.createMonthlySchedule(today, opts.message); const schedule = await brain.createMonthlySchedule(today, opts.message);
if (!schedule) { if (!schedule) {
scheduleSpinner.fail("Monthly schedule generation failed"); scheduleSpinner.fail("Monthly schedule generation failed");
return { ok: false, error: "Monthly schedule generation failed" }; const elapsedMs = Date.now() - startTime;
return {
ok: false,
error: "Monthly schedule generation failed",
elapsedMs,
};
} }
scheduleSpinner.succeed( scheduleSpinner.succeed(
`Monthly schedule generated (${schedule.items.length} day summaries)`, `Monthly schedule generated (${schedule.items.length} day summaries)`,
@@ -114,8 +138,9 @@ export async function runDebugScheduleMonthly(
printSection(`Monthly Schedule — ${monthKey} (${next.daysInMonth} days)`); printSection(`Monthly Schedule — ${monthKey} (${next.daysInMonth} days)`);
console.log(JSON.stringify(schedule, null, 2)); console.log(JSON.stringify(schedule, null, 2));
const elapsedMs = Date.now() - startTime;
logger.info( logger.info(
"Debug run complete. Nothing was written to disk. (Availability applies per-day and is not generated for the monthly view.)", `Debug run complete in ${formatDuration(elapsedMs)}. Nothing was written to disk. (Availability applies per-day and is not generated for the monthly view.)`,
); );
return { return {
@@ -124,6 +149,7 @@ export async function runDebugScheduleMonthly(
monthKey, monthKey,
daysInMonth: next.daysInMonth, daysInMonth: next.daysInMonth,
schedule, schedule,
elapsedMs,
}; };
} }

15
src/utils/duration.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* Format a millisecond duration as a human-readable string.
* - < 1s: "450ms"
* - < 60s: "1.23s"
* - >= 60s: "2m 5s"
*/
export function formatDuration(ms: number): string {
if (ms < 0 || !Number.isFinite(ms)) return "0ms";
if (ms < 1000) return `${Math.round(ms)}ms`;
const seconds = ms / 1000;
if (seconds < 60) return `${seconds.toFixed(2)}s`;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = Math.floor(seconds % 60);
return `${minutes}m ${remainingSeconds}s`;
}