Skip to content

Commit 3b93b35

Browse files
Copilothotlong
andcommitted
feat: migrate all console.log to structured pino logger across 154 source files, fix 48 test files
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 6ac12f8 commit 3b93b35

203 files changed

Lines changed: 28125 additions & 26870 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/ai/src/benchmark.ts

Lines changed: 122 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -5,156 +5,159 @@
55
* of HotCRM's critical paths: hooks, actions, workflows, and AI predictions.
66
*
77
* Usage:
8-
* const bench = new Benchmark('lead_scoring');
9-
* const result = await bench.run(() => scoreLead(lead), { iterations: 100 });
10-
* console.log(result.summary());
8+
* const bench = new Benchmark('lead_scoring');
9+
* const result = await bench.run(() => scoreLead(lead), { iterations: 100 });
10+
* logger.info(result.summary());
1111
*/
1212

13+
import { createLogger } from '@hotcrm/core';
14+
const logger = createLogger('ai:benchmark');
15+
1316
// ---------------------------------------------------------------------------
1417
// Types
1518
// ---------------------------------------------------------------------------
1619

1720
export interface BenchmarkOptions {
18-
/** Number of iterations (default: 100) */
19-
iterations?: number;
20-
/** Warm-up iterations before measurement (default: 5) */
21-
warmup?: number;
22-
/** Timeout per iteration in ms (default: 30000) */
23-
timeout?: number;
21+
/** Number of iterations (default: 100) */
22+
iterations?: number;
23+
/** Warm-up iterations before measurement (default: 5) */
24+
warmup?: number;
25+
/** Timeout per iteration in ms (default: 30000) */
26+
timeout?: number;
2427
}
2528

2629
export interface BenchmarkResult {
27-
/** Benchmark name */
28-
name: string;
29-
/** Total iterations executed */
30-
iterations: number;
31-
/** Total elapsed time in ms */
32-
totalMs: number;
33-
/** Average time per iteration in ms */
34-
meanMs: number;
35-
/** Median time per iteration in ms */
36-
medianMs: number;
37-
/** 95th percentile latency in ms */
38-
p95Ms: number;
39-
/** 99th percentile latency in ms */
40-
p99Ms: number;
41-
/** Minimum iteration time in ms */
42-
minMs: number;
43-
/** Maximum iteration time in ms */
44-
maxMs: number;
45-
/** Operations per second */
46-
opsPerSecond: number;
47-
/** Standard deviation in ms */
48-
stdDevMs: number;
49-
/** Individual iteration timings in ms */
50-
timings: number[];
30+
/** Benchmark name */
31+
name: string;
32+
/** Total iterations executed */
33+
iterations: number;
34+
/** Total elapsed time in ms */
35+
totalMs: number;
36+
/** Average time per iteration in ms */
37+
meanMs: number;
38+
/** Median time per iteration in ms */
39+
medianMs: number;
40+
/** 95th percentile latency in ms */
41+
p95Ms: number;
42+
/** 99th percentile latency in ms */
43+
p99Ms: number;
44+
/** Minimum iteration time in ms */
45+
minMs: number;
46+
/** Maximum iteration time in ms */
47+
maxMs: number;
48+
/** Operations per second */
49+
opsPerSecond: number;
50+
/** Standard deviation in ms */
51+
stdDevMs: number;
52+
/** Individual iteration timings in ms */
53+
timings: number[];
5154
}
5255

5356
// ---------------------------------------------------------------------------
5457
// Helpers
5558
// ---------------------------------------------------------------------------
5659

5760
function percentile(sorted: number[], p: number): number {
58-
if (sorted.length === 0) return 0;
59-
const idx = Math.ceil((p / 100) * sorted.length) - 1;
60-
return sorted[Math.max(0, idx)];
61+
if (sorted.length === 0) return 0;
62+
const idx = Math.ceil((p / 100) * sorted.length) - 1;
63+
return sorted[Math.max(0, idx)];
6164
}
6265

6366
function stdDev(values: number[], mean: number): number {
64-
if (values.length <= 1) return 0;
65-
const squaredDiffs = values.map(v => (v - mean) ** 2);
66-
return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1));
67+
if (values.length <= 1) return 0;
68+
const squaredDiffs = values.map(v => (v - mean) ** 2);
69+
return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / (values.length - 1));
6770
}
6871

6972
// ---------------------------------------------------------------------------
7073
// Benchmark Class
7174
// ---------------------------------------------------------------------------
7275

7376
export class Benchmark {
74-
private readonly name: string;
75-
76-
constructor(name: string) {
77-
this.name = name;
78-
}
79-
80-
/**
81-
* Run a synchronous or asynchronous function and collect timing data.
82-
*/
83-
async run(
84-
fn: () => unknown | Promise<unknown>,
85-
options?: BenchmarkOptions,
86-
): Promise<BenchmarkResult> {
87-
const iterations = options?.iterations ?? 100;
88-
const warmup = options?.warmup ?? 5;
89-
const timeout = options?.timeout ?? 30_000;
90-
91-
// Warm-up phase
92-
for (let i = 0; i < warmup; i++) {
93-
await Promise.resolve(fn());
94-
}
95-
96-
// Measurement phase
97-
const timings: number[] = [];
98-
const start = performance.now();
99-
100-
for (let i = 0; i < iterations; i++) {
101-
const iterStart = performance.now();
102-
await Promise.resolve(fn());
103-
const iterEnd = performance.now();
104-
timings.push(iterEnd - iterStart);
105-
106-
if (iterEnd - start > timeout) {
107-
break;
108-
}
109-
}
110-
111-
const totalMs = performance.now() - start;
112-
const sorted = [...timings].sort((a, b) => a - b);
113-
const mean = timings.reduce((a, b) => a + b, 0) / timings.length;
114-
115-
return {
116-
name: this.name,
117-
iterations: timings.length,
118-
totalMs,
119-
meanMs: mean,
120-
medianMs: percentile(sorted, 50),
121-
p95Ms: percentile(sorted, 95),
122-
p99Ms: percentile(sorted, 99),
123-
minMs: sorted[0] ?? 0,
124-
maxMs: sorted[sorted.length - 1] ?? 0,
125-
opsPerSecond: timings.length / (totalMs / 1000),
126-
stdDevMs: stdDev(timings, mean),
127-
timings,
128-
};
129-
}
77+
private readonly name: string;
78+
79+
constructor(name: string) {
80+
this.name = name;
81+
}
82+
83+
/**
84+
* Run a synchronous or asynchronous function and collect timing data.
85+
*/
86+
async run(
87+
fn: () => unknown | Promise<unknown>,
88+
options?: BenchmarkOptions,
89+
): Promise<BenchmarkResult> {
90+
const iterations = options?.iterations ?? 100;
91+
const warmup = options?.warmup ?? 5;
92+
const timeout = options?.timeout ?? 30_000;
93+
94+
// Warm-up phase
95+
for (let i = 0; i < warmup; i++) {
96+
await Promise.resolve(fn());
97+
}
98+
99+
// Measurement phase
100+
const timings: number[] = [];
101+
const start = performance.now();
102+
103+
for (let i = 0; i < iterations; i++) {
104+
const iterStart = performance.now();
105+
await Promise.resolve(fn());
106+
const iterEnd = performance.now();
107+
timings.push(iterEnd - iterStart);
108+
109+
if (iterEnd - start > timeout) {
110+
break;
111+
}
112+
}
113+
114+
const totalMs = performance.now() - start;
115+
const sorted = [...timings].sort((a, b) => a - b);
116+
const mean = timings.reduce((a, b) => a + b, 0) / timings.length;
117+
118+
return {
119+
name: this.name,
120+
iterations: timings.length,
121+
totalMs,
122+
meanMs: mean,
123+
medianMs: percentile(sorted, 50),
124+
p95Ms: percentile(sorted, 95),
125+
p99Ms: percentile(sorted, 99),
126+
minMs: sorted[0] ?? 0,
127+
maxMs: sorted[sorted.length - 1] ?? 0,
128+
opsPerSecond: timings.length / (totalMs / 1000),
129+
stdDevMs: stdDev(timings, mean),
130+
timings,
131+
};
132+
}
130133
}
131134

132135
// ---------------------------------------------------------------------------
133136
// Suite (run multiple benchmarks)
134137
// ---------------------------------------------------------------------------
135138

136139
export interface BenchmarkSuiteEntry {
137-
name: string;
138-
fn: () => unknown | Promise<unknown>;
139-
options?: BenchmarkOptions;
140+
name: string;
141+
fn: () => unknown | Promise<unknown>;
142+
options?: BenchmarkOptions;
140143
}
141144

142145
export class BenchmarkSuite {
143-
private readonly entries: BenchmarkSuiteEntry[] = [];
144-
145-
add(name: string, fn: () => unknown | Promise<unknown>, options?: BenchmarkOptions): this {
146-
this.entries.push({ name, fn, options });
147-
return this;
148-
}
149-
150-
async run(): Promise<BenchmarkResult[]> {
151-
const results: BenchmarkResult[] = [];
152-
for (const entry of this.entries) {
153-
const bench = new Benchmark(entry.name);
154-
results.push(await bench.run(entry.fn, entry.options));
155-
}
156-
return results;
157-
}
146+
private readonly entries: BenchmarkSuiteEntry[] = [];
147+
148+
add(name: string, fn: () => unknown | Promise<unknown>, options?: BenchmarkOptions): this {
149+
this.entries.push({ name, fn, options });
150+
return this;
151+
}
152+
153+
async run(): Promise<BenchmarkResult[]> {
154+
const results: BenchmarkResult[] = [];
155+
for (const entry of this.entries) {
156+
const bench = new Benchmark(entry.name);
157+
results.push(await bench.run(entry.fn, entry.options));
158+
}
159+
return results;
160+
}
158161
}
159162

160163
// ---------------------------------------------------------------------------
@@ -165,10 +168,10 @@ export class BenchmarkSuite {
165168
* Format benchmark results as a human-readable table string.
166169
*/
167170
export function formatBenchmarkResults(results: BenchmarkResult[]): string {
168-
const header = '| Benchmark | Ops/s | Mean (ms) | Median (ms) | P95 (ms) | P99 (ms) | Std Dev |';
169-
const sep = '|-----------|-------|-----------|-------------|----------|----------|---------|';
170-
const rows = results.map(r =>
171-
`| ${r.name} | ${r.opsPerSecond.toFixed(0)} | ${r.meanMs.toFixed(2)} | ${r.medianMs.toFixed(2)} | ${r.p95Ms.toFixed(2)} | ${r.p99Ms.toFixed(2)} | ${r.stdDevMs.toFixed(2)} |`
172-
);
173-
return [header, sep, ...rows].join('\n');
171+
const header = '| Benchmark | Ops/s | Mean (ms) | Median (ms) | P95 (ms) | P99 (ms) | Std Dev |';
172+
const sep = '|-----------|-------|-----------|-------------|----------|----------|---------|';
173+
const rows = results.map(r =>
174+
`| ${r.name} | ${r.opsPerSecond.toFixed(0)} | ${r.meanMs.toFixed(2)} | ${r.medianMs.toFixed(2)} | ${r.p95Ms.toFixed(2)} | ${r.p99Ms.toFixed(2)} | ${r.stdDevMs.toFixed(2)} |`
175+
);
176+
return [header, sep, ...rows].join('\n');
174177
}

0 commit comments

Comments
 (0)