Skip to content

Commit 5269ca4

Browse files
committed
feat: benchmark 2.0, optimizations
1 parent de0e456 commit 5269ca4

File tree

9 files changed

+654
-28
lines changed

9 files changed

+654
-28
lines changed

benchmark-core.ts

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/** biome-ignore-all lint/suspicious/noConsole: benchmarking */
2+
/** biome-ignore-all lint/style/noMagicNumbers: benchmarking */
3+
import chalk from "chalk";
4+
import { MemoryCache } from "./src/core/cache";
5+
import { getExpirationTime, isExpired } from "./src/core/expiration";
6+
import { extractKeyFromHeaders, hasApiKey } from "./src/core/extract-key";
7+
import { generateKey } from "./src/core/generate";
8+
import { hashKey } from "./src/core/hash";
9+
import { ResourceBuilder } from "./src/core/resources";
10+
import {
11+
hasAllScopes,
12+
hasAllScopesWithResources,
13+
hasAnyScope,
14+
hasAnyScopeWithResources,
15+
hasScope,
16+
hasScopeWithResources,
17+
} from "./src/core/scopes";
18+
import { validateKey } from "./src/core/validate";
19+
import type { PermissionScope } from "./src/types/permissions-types";
20+
21+
type BenchResult = {
22+
name: string;
23+
opsPerSec: number;
24+
avgNs: number;
25+
};
26+
27+
type BenchGroup = {
28+
title: string;
29+
icon: string;
30+
results: BenchResult[];
31+
};
32+
33+
const fmt = {
34+
num: (n: number): string => {
35+
if (n >= 1_000_000) {
36+
return `${(n / 1_000_000).toFixed(2)}M`;
37+
}
38+
if (n >= 1000) {
39+
return `${(n / 1000).toFixed(2)}K`;
40+
}
41+
return n.toFixed(0);
42+
},
43+
time: (ns: number): string => {
44+
if (ns >= 1_000_000) {
45+
return `${(ns / 1_000_000).toFixed(2)} ms`;
46+
}
47+
if (ns >= 1000) {
48+
return `${(ns / 1000).toFixed(2)} µs`;
49+
}
50+
return `${ns.toFixed(2)} ns`;
51+
},
52+
};
53+
54+
function bench(
55+
name: string,
56+
fn: () => void,
57+
iterations = 100_000
58+
): BenchResult {
59+
// Warmup
60+
for (let i = 0; i < 1000; i++) {
61+
fn();
62+
}
63+
64+
const times: number[] = [];
65+
for (let i = 0; i < iterations; i++) {
66+
const start = performance.now();
67+
fn();
68+
const end = performance.now();
69+
times.push((end - start) * 1_000_000); // to ns
70+
}
71+
72+
const avgNs = times.reduce((a, b) => a + b, 0) / iterations;
73+
const opsPerSec = 1_000_000_000 / avgNs;
74+
75+
return { name, opsPerSec, avgNs };
76+
}
77+
78+
function printGroup(group: BenchGroup) {
79+
console.log(`\n${group.icon} ${chalk.bold.white(group.title)}`);
80+
console.log(chalk.dim("─".repeat(80)));
81+
82+
for (const r of group.results) {
83+
const ops = chalk.cyan.bold(fmt.num(r.opsPerSec).padStart(10));
84+
const time = chalk.dim(fmt.time(r.avgNs).padStart(12));
85+
console.log(` ${ops} ops/sec ${time} ${chalk.white(r.name)}`);
86+
}
87+
}
88+
89+
function main() {
90+
console.log(chalk.bold.magenta("\n⚡ Core Performance Benchmarks\n"));
91+
92+
// Setup
93+
const key = "sk_test_abcdef1234567890ABCDEF1234567890";
94+
const keyHash = hashKey(key);
95+
const scopes: PermissionScope[] = ["read", "write", "delete"];
96+
const largeScopes: PermissionScope[] = Array.from(
97+
{ length: 50 },
98+
(_, i) => `scope_${i}` as PermissionScope
99+
);
100+
const resources = {
101+
"website:123": ["read", "write"] as PermissionScope[],
102+
"project:789": ["deploy", "manage"] as PermissionScope[],
103+
};
104+
const headers = new Headers({ authorization: `Bearer ${key}` });
105+
const plainHeaders = { "x-api-key": key };
106+
const futureDate = new Date(Date.now() + 86_400_000).toISOString();
107+
const pastDate = new Date(Date.now() - 86_400_000).toISOString();
108+
const cache = new MemoryCache();
109+
cache.set("cached-key", "value", 60);
110+
111+
const groups: BenchGroup[] = [
112+
{
113+
title: "Key Generation",
114+
icon: "🔑",
115+
results: [
116+
bench("16 chars", () => generateKey({ length: 16 })),
117+
bench("32 chars", () => generateKey({ length: 32 })),
118+
bench("64 chars", () => generateKey({ length: 64 })),
119+
bench("with prefix", () => generateKey({ prefix: "sk_", length: 32 })),
120+
],
121+
},
122+
{
123+
title: "Cryptographic Hashing",
124+
icon: "🔒",
125+
results: [
126+
bench("SHA-256", () => hashKey(key)),
127+
bench("SHA-512", () => hashKey(key, { algorithm: "sha512" })),
128+
bench("with salt", () => hashKey(key, { salt: "secret" })),
129+
bench("validate match", () => validateKey(key, keyHash)),
130+
bench("validate no match", () => validateKey("invalid", keyHash)),
131+
],
132+
},
133+
{
134+
title: "Header Extraction",
135+
icon: "📋",
136+
results: [
137+
bench("Headers object", () => extractKeyFromHeaders(headers)),
138+
bench("plain object", () => extractKeyFromHeaders(plainHeaders)),
139+
bench("Bearer token", () =>
140+
extractKeyFromHeaders({ authorization: `Bearer ${key}` })
141+
),
142+
bench("hasApiKey (present)", () => hasApiKey(headers)),
143+
bench("hasApiKey (missing)", () => hasApiKey(new Headers())),
144+
],
145+
},
146+
{
147+
title: "Expiration Checks",
148+
icon: "⏰",
149+
results: [
150+
bench("future date", () => isExpired(futureDate)),
151+
bench("past date", () => isExpired(pastDate)),
152+
bench("null check", () => isExpired(null)),
153+
bench("get expiration (valid)", () => getExpirationTime(futureDate)),
154+
bench("get expiration (null)", () => getExpirationTime(null)),
155+
],
156+
},
157+
{
158+
title: "Permission Scopes (3 scopes)",
159+
icon: "🔐",
160+
results: [
161+
bench("hasScope (found)", () => hasScope(scopes, "read")),
162+
bench("hasScope (not found)", () =>
163+
hasScope(scopes, "admin" as PermissionScope)
164+
),
165+
bench("hasAnyScope (match)", () =>
166+
hasAnyScope(scopes, ["read", "admin"] as PermissionScope[])
167+
),
168+
bench("hasAnyScope (no match)", () =>
169+
hasAnyScope(scopes, ["admin", "super"] as PermissionScope[])
170+
),
171+
bench("hasAllScopes (match)", () =>
172+
hasAllScopes(scopes, ["read", "write"])
173+
),
174+
bench("hasAllScopes (partial)", () =>
175+
hasAllScopes(scopes, ["read", "admin"] as PermissionScope[])
176+
),
177+
],
178+
},
179+
{
180+
title: "Permission Scopes (50 scopes)",
181+
icon: "🔐",
182+
results: [
183+
bench("hasScope (middle)", () =>
184+
hasScope(largeScopes, "scope_25" as PermissionScope)
185+
),
186+
bench("hasScope (last)", () =>
187+
hasScope(largeScopes, "scope_49" as PermissionScope)
188+
),
189+
],
190+
},
191+
{
192+
title: "Resource-Scoped Permissions",
193+
icon: "🌐",
194+
results: [
195+
bench("hasScopeWithResources (global)", () =>
196+
hasScopeWithResources(scopes, resources, "read")
197+
),
198+
bench("hasScopeWithResources (resource)", () =>
199+
hasScopeWithResources(scopes, resources, "deploy", {
200+
resource: "project:789",
201+
})
202+
),
203+
bench("hasAnyScopeWithResources (global)", () =>
204+
hasAnyScopeWithResources(scopes, resources, [
205+
"read",
206+
"admin",
207+
] as PermissionScope[])
208+
),
209+
bench("hasAnyScopeWithResources (resource)", () =>
210+
hasAnyScopeWithResources(
211+
[],
212+
resources,
213+
["deploy", "admin"] as PermissionScope[],
214+
{ resource: "project:789" }
215+
)
216+
),
217+
bench("hasAllScopesWithResources (global)", () =>
218+
hasAllScopesWithResources(scopes, resources, ["read", "write"])
219+
),
220+
bench("hasAllScopesWithResources (resource)", () =>
221+
hasAllScopesWithResources([], resources, ["read", "write"], {
222+
resource: "website:123",
223+
})
224+
),
225+
],
226+
},
227+
{
228+
title: "Resource Builder",
229+
icon: "🏗️",
230+
results: [
231+
bench("add single", () =>
232+
new ResourceBuilder().add("website", "123", ["read"])
233+
),
234+
bench("add 10 resources", () => {
235+
const b = new ResourceBuilder();
236+
const ids = Array.from({ length: 10 }, (_, i) => `site_${i}`);
237+
b.addMany("website", ids, ["read"]);
238+
}),
239+
bench("build (3 resources)", () =>
240+
new ResourceBuilder()
241+
.add("website", "123", ["read"])
242+
.add("project", "456", ["deploy"])
243+
.add("team", "789", ["manage"])
244+
.build()
245+
),
246+
bench("has", () =>
247+
new ResourceBuilder()
248+
.add("website", "123", ["read"])
249+
.has("website", "123")
250+
),
251+
bench("get", () =>
252+
new ResourceBuilder()
253+
.add("website", "123", ["read"])
254+
.get("website", "123")
255+
),
256+
bench("from existing", () => ResourceBuilder.from(resources)),
257+
],
258+
},
259+
{
260+
title: "Memory Cache",
261+
icon: "💾",
262+
results: [
263+
bench("set", () => cache.set("key", "value", 60)),
264+
bench("get (hit)", () => cache.get("cached-key")),
265+
bench("get (miss)", () => cache.get("missing")),
266+
bench("del", () => {
267+
cache.set("temp", "val", 60);
268+
cache.del("temp");
269+
}),
270+
],
271+
},
272+
];
273+
274+
for (const group of groups) {
275+
printGroup(group);
276+
}
277+
278+
// Summary
279+
const allResults = groups.flatMap((g) => g.results);
280+
const fastest = allResults.reduce((a, b) =>
281+
a.opsPerSec > b.opsPerSec ? a : b
282+
);
283+
const slowest = allResults.reduce((a, b) =>
284+
a.opsPerSec < b.opsPerSec ? a : b
285+
);
286+
287+
console.log(chalk.bold.yellow("\n📊 Summary"));
288+
console.log(chalk.dim("─".repeat(80)));
289+
console.log(
290+
` ${chalk.green("🏆 Fastest:")} ${chalk.white(fastest.name)} ${chalk.cyan(`(${fmt.num(fastest.opsPerSec)} ops/sec)`)}`
291+
);
292+
console.log(
293+
` ${chalk.red("🐌 Slowest:")} ${chalk.white(slowest.name)} ${chalk.cyan(`(${fmt.num(slowest.opsPerSec)} ops/sec)`)}`
294+
);
295+
console.log(
296+
` ${chalk.magenta("⚡ Ratio:")} ${chalk.white(`${(fastest.opsPerSec / slowest.opsPerSec).toFixed(1)}x faster`)}\n`
297+
);
298+
}
299+
300+
main();

0 commit comments

Comments
 (0)