Skip to content

Commit bc285a1

Browse files
Add CodSpeed performance benchmarks
1 parent 427deb4 commit bc285a1

10 files changed

Lines changed: 3163 additions & 4 deletions

File tree

.github/workflows/codspeed.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: CodSpeed
2+
3+
on:
4+
push:
5+
branches: main
6+
pull_request:
7+
branches: main
8+
# `workflow_dispatch` allows CodSpeed to trigger backtest
9+
# performance analysis in order to generate initial data.
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
id-token: write
15+
16+
jobs:
17+
benchmarks:
18+
name: Run benchmarks
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
with:
25+
submodules: true
26+
27+
- name: Setup Deno
28+
uses: denoland/setup-deno@v2
29+
with:
30+
deno-version: v2.x
31+
32+
- name: Setup Node
33+
uses: actions/setup-node@v4
34+
with:
35+
node-version: 22
36+
37+
- name: Build WASM
38+
run: make
39+
40+
- name: Install dependencies
41+
run: npm install
42+
43+
- name: Run benchmarks
44+
uses: CodSpeedHQ/action@v4
45+
with:
46+
mode: simulation
47+
run: npx vitest bench --run

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# clayterm
22

3+
[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/bombshell-dev/clayterm?utm_source=badge)
4+
35
A low-level, platform-independent terminal renderer and event parser for
46
JavaScript. You can use clayterm directly, or as the foundation for your own
57
framework.

bench/input.bench.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, bench, beforeAll } from "vitest";
2+
import { createInput, type Input } from "../input.ts";
3+
4+
function bytes(...values: number[]): Uint8Array {
5+
return new Uint8Array(values);
6+
}
7+
8+
function str(s: string): Uint8Array {
9+
return new TextEncoder().encode(s);
10+
}
11+
12+
describe("input parsing", () => {
13+
let input: Input;
14+
15+
beforeAll(async () => {
16+
input = await createInput({ escLatency: 25 });
17+
});
18+
19+
bench("printable ASCII (single char)", () => {
20+
input.scan(bytes(0x61));
21+
});
22+
23+
bench("printable ASCII (short string)", () => {
24+
input.scan(str("hello world"));
25+
});
26+
27+
bench("arrow key (CSI sequence)", () => {
28+
input.scan(bytes(0x1b, 0x5b, 0x41));
29+
});
30+
31+
bench("modifier combo (Ctrl+Shift+Arrow)", () => {
32+
input.scan(bytes(0x1b, 0x5b, 0x31, 0x3b, 0x38, 0x41));
33+
});
34+
35+
bench("SGR mouse press", () => {
36+
input.scan(str("\x1b[<0;35;12M"));
37+
});
38+
39+
bench("multi-event burst (arrows + text)", () => {
40+
input.scan(
41+
bytes(0x1b, 0x5b, 0x41, 0x1b, 0x5b, 0x42, 0x68, 0x69),
42+
);
43+
});
44+
45+
bench("UTF-8 3-byte character", () => {
46+
input.scan(bytes(0xe4, 0xb8, 0xad));
47+
});
48+
49+
bench("UTF-8 4-byte emoji", () => {
50+
input.scan(bytes(0xf0, 0x9f, 0x8e, 0x89));
51+
});
52+
53+
bench("Kitty protocol (CSI u with modifiers)", () => {
54+
input.scan(str("\x1b[97;3u"));
55+
});
56+
57+
let longBurst = new Uint8Array(200);
58+
for (let i = 0; i < 200; i++) {
59+
longBurst[i] = 0x61 + (i % 26);
60+
}
61+
62+
bench("long input burst (200 bytes)", () => {
63+
input.scan(longBurst);
64+
});
65+
});

bench/ops.bench.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { describe, bench } from "vitest";
2+
import { close, fixed, grow, open, pack, rgba, text } from "../ops.ts";
3+
import type { Op } from "../ops.ts";
4+
5+
function makeBuf(size: number): ArrayBuffer {
6+
return new ArrayBuffer(size);
7+
}
8+
9+
describe("pack", () => {
10+
let simpleOps: Op[] = [
11+
open("root", {
12+
layout: { width: grow(), height: grow(), direction: "ttb" },
13+
}),
14+
text("Hello, World!"),
15+
close(),
16+
];
17+
18+
bench("simple tree (root + text)", () => {
19+
let buf = makeBuf(4096);
20+
pack(simpleOps, buf, 0);
21+
});
22+
23+
let complexOps: Op[] = [
24+
open("root", {
25+
layout: { width: grow(), height: grow(), direction: "ttb" },
26+
}),
27+
open("header", {
28+
layout: {
29+
width: grow(),
30+
height: fixed(3),
31+
padding: { left: 1, right: 1 },
32+
direction: "ltr",
33+
},
34+
bg: rgba(30, 30, 40),
35+
border: {
36+
color: rgba(100, 100, 120),
37+
bottom: 1,
38+
},
39+
}),
40+
text("Title", { color: rgba(255, 255, 255), fontSize: 1 }),
41+
close(),
42+
open("body", {
43+
layout: {
44+
width: grow(),
45+
height: grow(),
46+
direction: "ltr",
47+
gap: 1,
48+
},
49+
}),
50+
open("sidebar", {
51+
layout: {
52+
width: fixed(20),
53+
height: grow(),
54+
direction: "ttb",
55+
padding: { left: 1, right: 1, top: 1 },
56+
},
57+
bg: rgba(25, 25, 35),
58+
border: {
59+
color: rgba(60, 60, 80),
60+
right: 1,
61+
},
62+
}),
63+
text("Menu Item 1"),
64+
text("Menu Item 2"),
65+
text("Menu Item 3"),
66+
close(),
67+
open("main", {
68+
layout: {
69+
width: grow(),
70+
height: grow(),
71+
direction: "ttb",
72+
padding: { left: 2, top: 1 },
73+
},
74+
}),
75+
text("Main content area with longer text to exercise the encoder"),
76+
close(),
77+
close(),
78+
open("footer", {
79+
layout: {
80+
width: grow(),
81+
height: fixed(1),
82+
padding: { left: 1 },
83+
direction: "ltr",
84+
},
85+
bg: rgba(30, 30, 40),
86+
}),
87+
text("Status: OK"),
88+
close(),
89+
close(),
90+
];
91+
92+
bench("complex layout (header + sidebar + main + footer)", () => {
93+
let buf = makeBuf(8192);
94+
pack(complexOps, buf, 0);
95+
});
96+
97+
let listOps: Op[] = [
98+
open("root", {
99+
layout: { width: grow(), height: grow(), direction: "ttb" },
100+
}),
101+
...Array.from({ length: 50 }, (_, i) => [
102+
open(`item-${i}`, {
103+
layout: {
104+
width: grow(),
105+
height: fixed(1),
106+
padding: { left: 2 },
107+
direction: "ltr",
108+
},
109+
bg: i % 2 === 0 ? rgba(30, 30, 40) : rgba(35, 35, 45),
110+
}),
111+
text(`List item ${i}: some description text`),
112+
close(),
113+
]).flat(),
114+
close(),
115+
];
116+
117+
bench("large list (50 items)", () => {
118+
let buf = makeBuf(32768);
119+
pack(listOps, buf, 0);
120+
});
121+
});

0 commit comments

Comments
 (0)