Skip to content

Commit 85aa329

Browse files
committed
Run in chunks
1 parent a28f623 commit 85aa329

File tree

12 files changed

+680
-51
lines changed

12 files changed

+680
-51
lines changed

benchmark/apps/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"dependencies": {
1212
"@benchmark/db": "workspace:^",
1313
"@benchmark/ipc": "workspace:^",
14+
"@benchmark/lib": "workspace:^",
1415
"@benchmark/types": "workspace:^",
1516
"execa": "^9.5.2",
1617
"gluegun": "^5.1.2",

benchmark/apps/cli/src/index.ts

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
updateTask,
2828
createTaskMetrics,
2929
} from "@benchmark/db"
30+
import { inChunksOf } from "@benchmark/lib"
3031
import { IpcServer, IpcClient } from "@benchmark/ipc"
3132

3233
import { __dirname, extensionDevelopmentPath, exercisesPath } from "./paths.js"
@@ -90,54 +91,30 @@ const run = async (toolbox: GluegunToolbox) => {
9091
throw new Error("No tasks found.")
9192
}
9293

93-
let currentTask = tasks[0]
94-
9594
const server = new IpcServer(run.socketPath, () => {})
9695
server.listen()
9796

9897
server.on(IpcMessageType.Connect, (clientId) => {
9998
server.send(clientId, {
10099
type: IpcMessageType.TaskEvent,
101100
origin: IpcOrigin.Server,
102-
data: { eventName: RooCodeEventName.Connect, taskId: currentTask.id },
101+
// TODO: Broacast the set of running tasks.
102+
data: { eventName: RooCodeEventName.Connect, taskId: -1 },
103103
})
104104
})
105105

106-
for (const task of tasks) {
107-
currentTask = task
108-
109-
await runExercise({ run, task, server })
110-
111-
const cmd = testCommands[task.language]
112-
const exercisePath = path.resolve(exercisesPath, task.language, task.exercise)
113-
const cwd = cmd.cwd ? path.resolve(exercisePath, cmd.cwd) : exercisePath
114-
const commands = cmd.commands.map((cs) => parseCommandString(cs))
115-
116-
let passed = true
106+
const chunks = inChunksOf(tasks, 2)
117107

118-
for (const command of commands) {
119-
const controller = new AbortController()
120-
const cancelSignal = controller.signal
121-
const timeout = setTimeout(() => controller.abort(), cmd.timeout ?? 15_000)
108+
for (const chunk of chunks) {
109+
await Promise.all(
110+
chunk.map(async (task) => {
111+
const runSucceeded = await runExercise({ run, task, server })
112+
const passed = runSucceeded ? await runUnitTest({ task }) : false
113+
await updateTask(task.id, { passed })
114+
}),
115+
)
122116

123-
try {
124-
const result = await execa({ cwd, shell: true, reject: false, cancelSignal })`${command}`
125-
// console.log('[cli#run] execa result =', { ...result, cwd, command })
126-
127-
clearTimeout(timeout)
128-
129-
if (result.failed) {
130-
passed = false
131-
break
132-
}
133-
} catch (error) {
134-
console.log("[cli#run] execa error =", error)
135-
passed = false
136-
break
137-
}
138-
}
139-
140-
await updateTask(task.id, { passed })
117+
break
141118
}
142119

143120
const result = await finishRun(run.id)
@@ -155,8 +132,8 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
155132
const prompt = fs.readFileSync(path.resolve(exercisesPath, `prompts/${language}.md`), "utf-8")
156133

157134
const dirname = path.dirname(run.socketPath)
158-
const basename = path.basename(run.socketPath, ".sock")
159-
const taskSocketPath = path.resolve(dirname, `${dirname}/${basename}-${task.id}.sock`)
135+
const taskSocketPath = path.resolve(dirname, `${dirname}/task-${task.id}.sock`)
136+
160137
await execa({
161138
env: { ROO_CODE_IPC_SOCKET_PATH: taskSocketPath },
162139
})`code -n ${path.resolve(exercisesPath, language, exercise)}`
@@ -179,7 +156,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
179156
let isTaskFinished = false
180157

181158
client.on(IpcMessageType.Disconnect, () => {
182-
console.log("disconnect")
159+
console.log(`[cli#runExercise | ${language} / ${exercise}] disconnect`)
183160
isTaskFinished = true
184161
})
185162

@@ -202,7 +179,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
202179
})
203180

204181
if (!ignoreEvents.includes(eventName)) {
205-
console.log(`[cli#runExercise] taskEvent -> ${eventName}`)
182+
console.log(`[cli#runExercise | ${language} / ${exercise}] taskEvent -> ${eventName}`)
206183
}
207184

208185
// if (eventName === RooCodeEventName.Message) {
@@ -215,6 +192,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
215192

216193
if (eventName === RooCodeEventName.TaskStarted) {
217194
taskStartedAt = Date.now()
195+
await updateTask(task.id, { startedAt: new Date() })
218196
}
219197

220198
if (eventName === RooCodeEventName.TaskCompleted) {
@@ -233,7 +211,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
233211
cacheReads: totalCacheReads ?? 0,
234212
})
235213

236-
await updateTask(task.id, { taskMetricsId: taskMetrics.id })
214+
await updateTask(task.id, { taskMetricsId: taskMetrics.id, finishedAt: new Date() })
237215
isTaskFinished = true
238216
}
239217

@@ -242,7 +220,7 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
242220
}
243221
})
244222

245-
console.log(`[cli#runExercise] StartNewTask -> ${language} / ${exercise}`)
223+
console.log(`[cli#runExercise | ${language} / ${exercise}] StartNewTask (${taskSocketPath})`)
246224

247225
client.sendMessage({
248226
type: IpcMessageType.TaskCommand,
@@ -273,6 +251,39 @@ const runExercise = async ({ run, task, server }: { run: Run; task: Task; server
273251
}
274252
}
275253

254+
const runUnitTest = async ({ task }: { task: Task }) => {
255+
const cmd = testCommands[task.language]
256+
const exercisePath = path.resolve(exercisesPath, task.language, task.exercise)
257+
const cwd = cmd.cwd ? path.resolve(exercisePath, cmd.cwd) : exercisePath
258+
const commands = cmd.commands.map((cs) => parseCommandString(cs))
259+
260+
let passed = true
261+
262+
for (const command of commands) {
263+
const controller = new AbortController()
264+
const cancelSignal = controller.signal
265+
const timeout = setTimeout(() => controller.abort(), cmd.timeout ?? 15_000)
266+
267+
try {
268+
const result = await execa({ cwd, shell: true, reject: false, cancelSignal })`${command}`
269+
// console.log('[cli#run] execa result =', { ...result, cwd, command })
270+
271+
clearTimeout(timeout)
272+
273+
if (result.failed) {
274+
passed = false
275+
break
276+
}
277+
} catch (error) {
278+
console.log("[cli#run] execa error =", error)
279+
passed = false
280+
break
281+
}
282+
}
283+
284+
return passed
285+
}
286+
276287
const askLanguage = async (prompt: GluegunPrompt) => {
277288
const { language } = await prompt.ask<{ language: ExerciseLanguage }>({
278289
type: "select",

benchmark/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"scripts": {
66
"lint": "turbo lint --log-order grouped --output-logs new-only",
77
"check-types": "turbo check-types --log-order grouped --output-logs new-only",
8+
"test": "turbo test --log-order grouped --output-logs new-only",
89
"format": "turbo format --log-order grouped --output-logs new-only",
910
"build": "turbo build --log-order grouped --output-logs new-only",
1011
"web": "turbo dev --filter @benchmark/web --output-logs new-only --ui tui",

benchmark/packages/ipc/src/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,24 @@ export type IpcClientEvents = {
2020

2121
export class IpcClient extends EventEmitter<IpcClientEvents> {
2222
private readonly _socketPath: string
23+
private readonly _id: string
2324
private readonly _log: (...args: unknown[]) => void
24-
2525
private _isConnected = false
2626
private _clientId?: string
2727

2828
constructor(socketPath: string, log = console.log) {
2929
super()
3030

3131
this._socketPath = socketPath
32+
this._id = `benchmark-${crypto.randomBytes(6).toString("hex")}`
3233
this._log = log
3334

3435
ipc.config.silent = true
3536

36-
ipc.connectTo("benchmarkServer", this.socketPath, () => {
37-
ipc.of.benchmarkServer?.on("connect", () => this.onConnect())
38-
ipc.of.benchmarkServer?.on("disconnect", () => this.onDisconnect())
39-
ipc.of.benchmarkServer?.on("message", (data) => this.onMessage(data))
37+
ipc.connectTo(this._id, this.socketPath, () => {
38+
ipc.of[this._id]?.on("connect", () => this.onConnect())
39+
ipc.of[this._id]?.on("disconnect", () => this.onDisconnect())
40+
ipc.of[this._id]?.on("message", (data) => this.onMessage(data))
4041
})
4142
}
4243

@@ -93,12 +94,12 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
9394
}
9495

9596
public sendMessage(message: IpcMessage) {
96-
ipc.of.benchmarkServer?.emit("message", message)
97+
ipc.of[this._id]?.emit("message", message)
9798
}
9899

99100
public disconnect() {
100101
try {
101-
ipc.disconnect("benchmarkServer")
102+
ipc.disconnect(this._id)
102103
// @TODO: Should we set _disconnect here?
103104
} catch (error) {
104105
this.log("[client#disconnect] error disconnecting", error)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { config } from "@benchmark/eslint-config/base"
2+
3+
/** @type {import("eslint").Linter.Config} */
4+
export default [...config]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@benchmark/lib",
3+
"private": true,
4+
"type": "module",
5+
"exports": "./src/index.ts",
6+
"scripts": {
7+
"lint": "eslint src --ext ts --max-warnings=0",
8+
"check-types": "tsc --noEmit",
9+
"test": "vitest --globals --run",
10+
"format": "prettier --write src"
11+
},
12+
"devDependencies": {
13+
"@benchmark/eslint-config": "workspace:^",
14+
"@benchmark/typescript-config": "workspace:^",
15+
"vitest": "^3.0.9"
16+
}
17+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// npx vitest run src/__tests__/in-chunks-of.test.ts
2+
3+
import { inChunksOf } from "../in-chunks-of.js"
4+
5+
describe("inChunksOf", () => {
6+
it("should return an array of arrays", () => {
7+
const result = inChunksOf([1, 2, 3, 4, 5])
8+
expect(result).toEqual([[1, 2], [3, 4], [5]])
9+
})
10+
11+
it("should return an array of arrays with a custom chunk size", () => {
12+
const result = inChunksOf([1, 2, 3, 4, 5], 3)
13+
expect(result).toEqual([
14+
[1, 2, 3],
15+
[4, 5],
16+
])
17+
})
18+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export function inChunksOf<T>(ary: T[], perChunk = 2) {
2+
const result = ary.reduce((collect, item, index) => {
3+
const chunkIndex = Math.floor(index / perChunk)
4+
5+
if (!collect[chunkIndex]) {
6+
collect[chunkIndex] = []
7+
}
8+
9+
collect[chunkIndex].push(item)
10+
return collect
11+
}, [] as T[][])
12+
13+
return result
14+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./in-chunks-of.js"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "@benchmark/typescript-config/base.json",
3+
"compilerOptions": {
4+
"types": ["vitest/globals"]
5+
},
6+
"include": ["src"],
7+
"exclude": ["node_modules"]
8+
}

0 commit comments

Comments
 (0)