Skip to content

Commit 61c0e23

Browse files
committed
fix(bench): parse CPU time across day boundaries
1 parent 1be2f60 commit 61c0e23

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

bench/routing/measurement.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,51 @@ describe('benchmark CPU window', () => {
6060
}
6161
})
6262
})
63+
64+
// ps emits minutes, hours, and a day prefix as the accumulated CPU time grows.
65+
// Drive the reported percentage through the sampler, including boundary changes.
66+
describe('cumulative CPU time formats', () => {
67+
it.each([
68+
['minute boundary', '00:59.90', '01:00.10', 20],
69+
['hour boundary', '59:59.90', '01:00:00.10', 20],
70+
['first day boundary', '23:59:59.90', '1-00:00:00.10', 20],
71+
['later day boundary', '1-23:59:59.90', '2-00:00:00.10', 20],
72+
['day and hours', '2-03:00:00', '2-03:00:01', 100],
73+
['missing sample', '', '00:00.10', null],
74+
['malformed sample', '00:00oops', '00:00.10', null],
75+
] as const)('%s', async (name, before, after, expected) => {
76+
let wall = 0
77+
let sample: string = before
78+
const spawn = spyOn(Bun, 'spawn').mockImplementation(() => ({
79+
stdout: new Response(sample).body,
80+
}) as unknown as ReturnType<typeof Bun.spawn>)
81+
const now = spyOn(performance, 'now').mockImplementation(() => wall)
82+
try {
83+
const measured = await measureLoad({
84+
name,
85+
publishable: false,
86+
supportsFixedRate: false,
87+
isAvailable: async () => true,
88+
async run() {
89+
wall = 1000
90+
sample = after
91+
return result
92+
},
93+
}, {
94+
url: 'http://127.0.0.1:39400/bench/json',
95+
method: 'GET',
96+
headers: {},
97+
connections: 1,
98+
warmupSeconds: 0,
99+
durationSeconds: 1,
100+
}, 123)
101+
if (expected == null) expect(measured.cpuPercent).toBeNull()
102+
else expect(measured.cpuPercent).toBeCloseTo(expected, 6)
103+
expect(measured.result).toBe(result)
104+
}
105+
finally {
106+
now.mockRestore()
107+
spawn.mockRestore()
108+
}
109+
})
110+
})

bench/routing/measurement.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ async function cpuSeconds(pid: number): Promise<number | null> {
1515
const proc = Bun.spawn(['ps', '-o', 'time=', '-p', String(pid)], { stdout: 'pipe', stderr: 'ignore' })
1616
const out = (await new Response(proc.stdout).text()).trim()
1717
if (!out) return null
18-
// `[dd-]hh:]mm:ss[.ff]`
19-
const parts = out.replace('-', ':').split(':').map(Number.parseFloat)
20-
if (parts.some(n => !Number.isFinite(n))) return null
21-
return parts.reduce((total, part) => total * 60 + part, 0)
18+
// [days-][hours:]minutes:seconds[.fraction]. Days have 24 hours,
19+
// so folding every separator in base 60 inflates a day-boundary delta.
20+
const match = /^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)$/.exec(out)
21+
if (!match) return null
22+
const seconds = Number(match[1] ?? 0) * 86400
23+
+ Number(match[2] ?? 0) * 3600
24+
+ Number(match[3]) * 60 + Number(match[4])
25+
return Number.isFinite(seconds) ? seconds : null
2226
}
2327
catch {
2428
return null

0 commit comments

Comments
 (0)