@fuzdev/fuz_util 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +19 -12
- package/dist/async.d.ts +2 -2
- package/dist/async.d.ts.map +1 -1
- package/dist/async.js +2 -2
- package/dist/benchmark.d.ts +179 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +400 -0
- package/dist/benchmark_baseline.d.ts +195 -0
- package/dist/benchmark_baseline.d.ts.map +1 -0
- package/dist/benchmark_baseline.js +415 -0
- package/dist/benchmark_format.d.ts +92 -0
- package/dist/benchmark_format.d.ts.map +1 -0
- package/dist/benchmark_format.js +327 -0
- package/dist/benchmark_stats.d.ts +112 -0
- package/dist/benchmark_stats.d.ts.map +1 -0
- package/dist/benchmark_stats.js +336 -0
- package/dist/benchmark_types.d.ts +174 -0
- package/dist/benchmark_types.d.ts.map +1 -0
- package/dist/benchmark_types.js +1 -0
- package/dist/library_json.d.ts +3 -3
- package/dist/library_json.d.ts.map +1 -1
- package/dist/library_json.js +1 -1
- package/dist/object.js +1 -1
- package/dist/stats.d.ts +126 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +262 -0
- package/dist/time.d.ts +161 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +260 -0
- package/dist/timings.d.ts +1 -7
- package/dist/timings.d.ts.map +1 -1
- package/dist/timings.js +16 -16
- package/package.json +21 -19
- package/src/lib/async.ts +3 -3
- package/src/lib/benchmark.ts +498 -0
- package/src/lib/benchmark_baseline.ts +573 -0
- package/src/lib/benchmark_format.ts +379 -0
- package/src/lib/benchmark_stats.ts +448 -0
- package/src/lib/benchmark_types.ts +197 -0
- package/src/lib/library_json.ts +3 -3
- package/src/lib/object.ts +1 -1
- package/src/lib/stats.ts +353 -0
- package/src/lib/time.ts +314 -0
- package/src/lib/timings.ts +17 -17
- package/src/lib/types.ts +2 -2
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import type {BenchmarkResult, BenchmarkGroup} from './benchmark_types.js';
|
|
2
|
+
import {time_unit_detect_best, time_format, type TimeUnit} from './time.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculate the display width of a string in terminal columns.
|
|
6
|
+
* Emojis and other wide characters take 2 columns.
|
|
7
|
+
*/
|
|
8
|
+
const string_display_width = (str: string): number => {
|
|
9
|
+
let width = 0;
|
|
10
|
+
for (const char of str) {
|
|
11
|
+
const code = char.codePointAt(0)!;
|
|
12
|
+
// Emoji and other wide characters (rough heuristic)
|
|
13
|
+
// - Most emoji are in range 0x1F300-0x1FAFF
|
|
14
|
+
// - Some are in 0x2600-0x27BF (misc symbols)
|
|
15
|
+
// - CJK characters 0x4E00-0x9FFF also double-width but not handling here
|
|
16
|
+
if (
|
|
17
|
+
(code >= 0x1f300 && code <= 0x1faff) ||
|
|
18
|
+
(code >= 0x2600 && code <= 0x27bf) ||
|
|
19
|
+
(code >= 0x1f600 && code <= 0x1f64f) ||
|
|
20
|
+
(code >= 0x1f680 && code <= 0x1f6ff)
|
|
21
|
+
) {
|
|
22
|
+
width += 2;
|
|
23
|
+
} else {
|
|
24
|
+
width += 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return width;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pad a string to a target display width (accounting for wide characters).
|
|
32
|
+
*/
|
|
33
|
+
const pad_to_width = (
|
|
34
|
+
str: string,
|
|
35
|
+
target_width: number,
|
|
36
|
+
align: 'left' | 'right' = 'left',
|
|
37
|
+
): string => {
|
|
38
|
+
const current_width = string_display_width(str);
|
|
39
|
+
const padding = Math.max(0, target_width - current_width);
|
|
40
|
+
if (align === 'left') {
|
|
41
|
+
return str + ' '.repeat(padding);
|
|
42
|
+
} else {
|
|
43
|
+
return ' '.repeat(padding) + str;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format results as an ASCII table with percentiles, min/max, and relative performance.
|
|
49
|
+
* All times use the same unit for easy comparison.
|
|
50
|
+
* @param results - Array of benchmark results
|
|
51
|
+
* @returns Formatted table string with enhanced metrics
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* console.log(benchmark_format_table(results));
|
|
56
|
+
* // ┌────┬─────────────┬────────────┬────────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
|
|
57
|
+
* // │ │ Task Name │ ops/sec │ median(μs) │ p75 (μs) │ p90 (μs) │ p95 (μs) │ p99 (μs) │ min (μs) │ max (μs) │ vs Best │
|
|
58
|
+
* // ├────┼─────────────┼────────────┼────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
|
|
59
|
+
* // │ 🐇 │ slugify v2 │ 1,237,144 │ 0.81 │ 0.85 │ 0.89 │ 0.95 │ 1.20 │ 0.72 │ 2.45 │ baseline │
|
|
60
|
+
* // │ 🐢 │ slugify │ 261,619 │ 3.82 │ 3.95 │ 4.12 │ 4.35 │ 5.10 │ 3.21 │ 12.45 │ 4.73x │
|
|
61
|
+
* // └────┴─────────────┴────────────┴────────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* **Performance tier animals:**
|
|
65
|
+
* - 🐆 Cheetah: >1M ops/sec (extremely fast)
|
|
66
|
+
* - 🐇 Rabbit: >100K ops/sec (fast)
|
|
67
|
+
* - 🐢 Turtle: >10K ops/sec (moderate)
|
|
68
|
+
* - 🐌 Snail: <10K ops/sec (slow)
|
|
69
|
+
*/
|
|
70
|
+
export const benchmark_format_table = (results: Array<BenchmarkResult>): string => {
|
|
71
|
+
if (results.length === 0) return '(no results)';
|
|
72
|
+
|
|
73
|
+
// Detect best unit for all results
|
|
74
|
+
const mean_times = results.map((r) => r.stats.mean_ns);
|
|
75
|
+
const unit = time_unit_detect_best(mean_times);
|
|
76
|
+
const unit_str = UNIT_LABELS[unit];
|
|
77
|
+
|
|
78
|
+
// Find fastest for relative comparison
|
|
79
|
+
const fastest_ops = Math.max(...results.map((r) => r.stats.ops_per_second));
|
|
80
|
+
|
|
81
|
+
const rows: Array<Array<string>> = [];
|
|
82
|
+
|
|
83
|
+
// Header with unit
|
|
84
|
+
rows.push([
|
|
85
|
+
'',
|
|
86
|
+
'Task Name',
|
|
87
|
+
'ops/sec',
|
|
88
|
+
`median (${unit_str})`,
|
|
89
|
+
`p75 (${unit_str})`,
|
|
90
|
+
`p90 (${unit_str})`,
|
|
91
|
+
`p95 (${unit_str})`,
|
|
92
|
+
`p99 (${unit_str})`,
|
|
93
|
+
`min (${unit_str})`,
|
|
94
|
+
`max (${unit_str})`,
|
|
95
|
+
'vs Best',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// Data rows - all use same unit
|
|
99
|
+
results.forEach((r) => {
|
|
100
|
+
const tier = get_perf_tier(r.stats.ops_per_second);
|
|
101
|
+
const ops_sec = benchmark_format_number(r.stats.ops_per_second, 2);
|
|
102
|
+
const median = time_format(r.stats.median_ns, unit, 2).replace(unit_str, '').trim();
|
|
103
|
+
const p75 = time_format(r.stats.p75_ns, unit, 2).replace(unit_str, '').trim();
|
|
104
|
+
const p90 = time_format(r.stats.p90_ns, unit, 2).replace(unit_str, '').trim();
|
|
105
|
+
const p95 = time_format(r.stats.p95_ns, unit, 2).replace(unit_str, '').trim();
|
|
106
|
+
const p99 = time_format(r.stats.p99_ns, unit, 2).replace(unit_str, '').trim();
|
|
107
|
+
const min = time_format(r.stats.min_ns, unit, 2).replace(unit_str, '').trim();
|
|
108
|
+
const max = time_format(r.stats.max_ns, unit, 2).replace(unit_str, '').trim();
|
|
109
|
+
|
|
110
|
+
// Calculate relative performance
|
|
111
|
+
const ratio = fastest_ops / r.stats.ops_per_second;
|
|
112
|
+
const vs_best = ratio === 1.0 ? 'baseline' : `${ratio.toFixed(2)}x`;
|
|
113
|
+
|
|
114
|
+
rows.push([tier, r.name, ops_sec, median, p75, p90, p95, p99, min, max, vs_best]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Calculate column widths (using display width for proper emoji handling)
|
|
118
|
+
const widths = rows[0]!.map((_, col_i) => {
|
|
119
|
+
return Math.max(...rows.map((row) => string_display_width(row[col_i]!)));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Build table
|
|
123
|
+
const lines: Array<string> = [];
|
|
124
|
+
|
|
125
|
+
// Top border
|
|
126
|
+
lines.push('┌' + widths.map((w) => '─'.repeat(w + 2)).join('┬') + '┐');
|
|
127
|
+
|
|
128
|
+
// Header
|
|
129
|
+
const header = rows[0]!.map((cell, i) => ' ' + pad_to_width(cell, widths[i]!) + ' ').join('│');
|
|
130
|
+
lines.push('│' + header + '│');
|
|
131
|
+
|
|
132
|
+
// Header separator
|
|
133
|
+
lines.push('├' + widths.map((w) => '─'.repeat(w + 2)).join('┼') + '┤');
|
|
134
|
+
|
|
135
|
+
// Data rows
|
|
136
|
+
for (let i = 1; i < rows.length; i++) {
|
|
137
|
+
const row = rows[i]!.map((cell, col_i) => {
|
|
138
|
+
const width = widths[col_i]!;
|
|
139
|
+
// Left-align tier emoji and task name, right-align numbers
|
|
140
|
+
if (col_i === 0 || col_i === 1) {
|
|
141
|
+
return ' ' + pad_to_width(cell, width, 'left') + ' ';
|
|
142
|
+
} else {
|
|
143
|
+
return ' ' + pad_to_width(cell, width, 'right') + ' ';
|
|
144
|
+
}
|
|
145
|
+
}).join('│');
|
|
146
|
+
lines.push('│' + row + '│');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Bottom border
|
|
150
|
+
lines.push('└' + widths.map((w) => '─'.repeat(w + 2)).join('┴') + '┘');
|
|
151
|
+
|
|
152
|
+
return lines.join('\n');
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format results as a Markdown table with key metrics.
|
|
157
|
+
* All times use the same unit for easy comparison.
|
|
158
|
+
* @param results - Array of benchmark results
|
|
159
|
+
* @returns Formatted markdown table string
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* console.log(benchmark_format_markdown(results));
|
|
164
|
+
* // | Task Name | ops/sec | median (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best |
|
|
165
|
+
* // |------------|------------|-------------|----------|----------|----------|----------|----------|----------|----------|
|
|
166
|
+
* // | slugify v2 | 1,237,144 | 0.81 | 0.85 | 0.89 | 0.95 | 1.20 | 0.72 | 2.45 | baseline |
|
|
167
|
+
* // | slugify | 261,619 | 3.82 | 3.95 | 4.12 | 4.35 | 5.10 | 3.21 | 12.45 | 4.73x |
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
export const benchmark_format_markdown = (results: Array<BenchmarkResult>): string => {
|
|
171
|
+
if (results.length === 0) return '(no results)';
|
|
172
|
+
|
|
173
|
+
// Detect best unit for all results
|
|
174
|
+
const mean_times = results.map((r) => r.stats.mean_ns);
|
|
175
|
+
const unit = time_unit_detect_best(mean_times);
|
|
176
|
+
const unit_str = UNIT_LABELS[unit];
|
|
177
|
+
|
|
178
|
+
// Find fastest for relative comparison
|
|
179
|
+
const fastest_ops = Math.max(...results.map((r) => r.stats.ops_per_second));
|
|
180
|
+
|
|
181
|
+
const rows: Array<Array<string>> = [];
|
|
182
|
+
|
|
183
|
+
// Header with unit
|
|
184
|
+
rows.push([
|
|
185
|
+
'Task Name',
|
|
186
|
+
'ops/sec',
|
|
187
|
+
`median (${unit_str})`,
|
|
188
|
+
`p75 (${unit_str})`,
|
|
189
|
+
`p90 (${unit_str})`,
|
|
190
|
+
`p95 (${unit_str})`,
|
|
191
|
+
`p99 (${unit_str})`,
|
|
192
|
+
`min (${unit_str})`,
|
|
193
|
+
`max (${unit_str})`,
|
|
194
|
+
'vs Best',
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
// Data rows - all use same unit
|
|
198
|
+
results.forEach((r) => {
|
|
199
|
+
const ops_sec = benchmark_format_number(r.stats.ops_per_second, 2);
|
|
200
|
+
const median = time_format(r.stats.median_ns, unit, 2).replace(unit_str, '').trim();
|
|
201
|
+
const p75 = time_format(r.stats.p75_ns, unit, 2).replace(unit_str, '').trim();
|
|
202
|
+
const p90 = time_format(r.stats.p90_ns, unit, 2).replace(unit_str, '').trim();
|
|
203
|
+
const p95 = time_format(r.stats.p95_ns, unit, 2).replace(unit_str, '').trim();
|
|
204
|
+
const p99 = time_format(r.stats.p99_ns, unit, 2).replace(unit_str, '').trim();
|
|
205
|
+
const min = time_format(r.stats.min_ns, unit, 2).replace(unit_str, '').trim();
|
|
206
|
+
const max = time_format(r.stats.max_ns, unit, 2).replace(unit_str, '').trim();
|
|
207
|
+
|
|
208
|
+
// Calculate relative performance
|
|
209
|
+
const ratio = fastest_ops / r.stats.ops_per_second;
|
|
210
|
+
const vs_best = ratio === 1.0 ? 'baseline' : `${ratio.toFixed(2)}x`;
|
|
211
|
+
|
|
212
|
+
rows.push([r.name, ops_sec, median, p75, p90, p95, p99, min, max, vs_best]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Calculate column widths
|
|
216
|
+
const widths = rows[0]!.map((_, col_i) => {
|
|
217
|
+
return Math.max(...rows.map((row) => row[col_i]!.length));
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Build table
|
|
221
|
+
const lines: Array<string> = [];
|
|
222
|
+
|
|
223
|
+
// Header
|
|
224
|
+
const header = rows[0]!.map((cell, i) => cell.padEnd(widths[i]!)).join(' | ');
|
|
225
|
+
lines.push('| ' + header + ' |');
|
|
226
|
+
|
|
227
|
+
// Separator
|
|
228
|
+
const separator = widths.map((w) => '-'.repeat(w)).join(' | ');
|
|
229
|
+
lines.push('| ' + separator + ' |');
|
|
230
|
+
|
|
231
|
+
// Data rows
|
|
232
|
+
for (let i = 1; i < rows.length; i++) {
|
|
233
|
+
const row = rows[i]!.map((cell, col_i) => {
|
|
234
|
+
const width = widths[col_i]!;
|
|
235
|
+
// Right-align numbers, left-align names
|
|
236
|
+
if (col_i === 0) {
|
|
237
|
+
return cell.padEnd(width);
|
|
238
|
+
} else {
|
|
239
|
+
return cell.padStart(width);
|
|
240
|
+
}
|
|
241
|
+
}).join(' | ');
|
|
242
|
+
lines.push('| ' + row + ' |');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return lines.join('\n');
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export interface BenchmarkFormatJsonOptions {
|
|
249
|
+
/** Whether to pretty-print (default: true) */
|
|
250
|
+
pretty?: boolean;
|
|
251
|
+
/** Whether to include raw timings array (default: false, can be large) */
|
|
252
|
+
include_timings?: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Format results as JSON.
|
|
257
|
+
* @param results - Array of benchmark results
|
|
258
|
+
* @param options - Formatting options
|
|
259
|
+
* @returns JSON string
|
|
260
|
+
*
|
|
261
|
+
* @example
|
|
262
|
+
* ```ts
|
|
263
|
+
* console.log(format_json(results));
|
|
264
|
+
* console.log(format_json(results, {pretty: false}));
|
|
265
|
+
* console.log(format_json(results, {include_timings: true}));
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
export const benchmark_format_json = (
|
|
269
|
+
results: Array<BenchmarkResult>,
|
|
270
|
+
options?: BenchmarkFormatJsonOptions,
|
|
271
|
+
): string => {
|
|
272
|
+
const pretty = options?.pretty ?? true;
|
|
273
|
+
const include_timings = options?.include_timings ?? false;
|
|
274
|
+
// Flatten stats into result object for easier consumption
|
|
275
|
+
const flattened = results.map((r) => ({
|
|
276
|
+
name: r.name,
|
|
277
|
+
iterations: r.iterations,
|
|
278
|
+
total_time_ms: r.total_time_ms,
|
|
279
|
+
ops_per_second: r.stats.ops_per_second,
|
|
280
|
+
mean_ns: r.stats.mean_ns,
|
|
281
|
+
median_ns: r.stats.median_ns,
|
|
282
|
+
std_dev_ns: r.stats.std_dev_ns,
|
|
283
|
+
min_ns: r.stats.min_ns,
|
|
284
|
+
max_ns: r.stats.max_ns,
|
|
285
|
+
p75_ns: r.stats.p75_ns,
|
|
286
|
+
p90_ns: r.stats.p90_ns,
|
|
287
|
+
p95_ns: r.stats.p95_ns,
|
|
288
|
+
p99_ns: r.stats.p99_ns,
|
|
289
|
+
cv: r.stats.cv,
|
|
290
|
+
confidence_interval_ns: r.stats.confidence_interval_ns,
|
|
291
|
+
outliers: r.stats.outliers_ns.length,
|
|
292
|
+
outlier_ratio: r.stats.outlier_ratio,
|
|
293
|
+
sample_size: r.stats.sample_size,
|
|
294
|
+
raw_sample_size: r.stats.raw_sample_size,
|
|
295
|
+
failed_iterations: r.stats.failed_iterations,
|
|
296
|
+
...(include_timings ? {timings_ns: r.timings_ns} : {}),
|
|
297
|
+
}));
|
|
298
|
+
|
|
299
|
+
return pretty ? JSON.stringify(flattened, null, 2) : JSON.stringify(flattened);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format results as a grouped table with visual separators between groups.
|
|
304
|
+
* @param results - Array of benchmark results
|
|
305
|
+
* @param groups - Array of group definitions
|
|
306
|
+
* @returns Formatted table string with group separators
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```ts
|
|
310
|
+
* const groups = [
|
|
311
|
+
* { name: 'FAST PATHS', filter: (r) => r.name.includes('fast') },
|
|
312
|
+
* { name: 'SLOW PATHS', filter: (r) => r.name.includes('slow') },
|
|
313
|
+
* ];
|
|
314
|
+
* console.log(benchmark_format_table_grouped(results, groups));
|
|
315
|
+
* // 📦 FAST PATHS
|
|
316
|
+
* // ┌────┬─────────────┬────────────┬...┐
|
|
317
|
+
* // │ 🐆 │ fast test 1 │ 1,237,144 │...│
|
|
318
|
+
* // │ 🐇 │ fast test 2 │ 261,619 │...│
|
|
319
|
+
* // └────┴─────────────┴────────────┴...┘
|
|
320
|
+
* //
|
|
321
|
+
* // 📦 SLOW PATHS
|
|
322
|
+
* // ┌────┬─────────────┬────────────┬...┐
|
|
323
|
+
* // │ 🐢 │ slow test 1 │ 10,123 │...│
|
|
324
|
+
* // └────┴─────────────┴────────────┴...┘
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export const benchmark_format_table_grouped = (
|
|
328
|
+
results: Array<BenchmarkResult>,
|
|
329
|
+
groups: Array<BenchmarkGroup>,
|
|
330
|
+
): string => {
|
|
331
|
+
if (results.length === 0) return '(no results)';
|
|
332
|
+
|
|
333
|
+
const sections: Array<string> = [];
|
|
334
|
+
|
|
335
|
+
for (const group of groups) {
|
|
336
|
+
const group_results = results.filter(group.filter);
|
|
337
|
+
if (group_results.length === 0) continue;
|
|
338
|
+
|
|
339
|
+
// Add group header and table
|
|
340
|
+
const header = group.description
|
|
341
|
+
? `\n📦 ${group.name}\n ${group.description}`
|
|
342
|
+
: `\n📦 ${group.name}`;
|
|
343
|
+
sections.push(header);
|
|
344
|
+
sections.push(benchmark_format_table(group_results));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Handle ungrouped results (those that don't match any group)
|
|
348
|
+
const grouped_names = new Set(groups.flatMap((g) => results.filter(g.filter).map((r) => r.name)));
|
|
349
|
+
const ungrouped = results.filter((r) => !grouped_names.has(r.name));
|
|
350
|
+
|
|
351
|
+
if (ungrouped.length > 0) {
|
|
352
|
+
sections.push('\n📦 Other');
|
|
353
|
+
sections.push(benchmark_format_table(ungrouped));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return sections.join('\n');
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// TODO consider extracting to a general format utility module when more formatters are needed
|
|
360
|
+
/**
|
|
361
|
+
* Format a number with fixed decimal places and thousands separators.
|
|
362
|
+
*/
|
|
363
|
+
export const benchmark_format_number = (n: number, decimals: number = 2): string => {
|
|
364
|
+
if (!isFinite(n)) return String(n);
|
|
365
|
+
return n.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get performance tier symbol based on ops/sec.
|
|
370
|
+
*/
|
|
371
|
+
const get_perf_tier = (ops_per_sec: number): string => {
|
|
372
|
+
if (ops_per_sec >= 1_000_000) return '🐆'; // > 1M ops/sec (cheetah - extremely fast)
|
|
373
|
+
if (ops_per_sec >= 100_000) return '🐇'; // > 100K ops/sec (rabbit - fast)
|
|
374
|
+
if (ops_per_sec >= 10_000) return '🐢'; // > 10K ops/sec (turtle - moderate)
|
|
375
|
+
return '🐌'; // < 10K ops/sec (snail - slow)
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/** Unit labels for display (μs instead of us). */
|
|
379
|
+
const UNIT_LABELS: Record<TimeUnit, string> = {ns: 'ns', us: 'μs', ms: 'ms', s: 's'};
|