Skip to content

Commit a9eb8d4

Browse files
authored
Merge pull request #17 from Mojang/frgarc/split-cores
feat: Split jobs per cores instead of hosts.
2 parents ef7cb3e + 56a9ddd commit a9eb8d4

2 files changed

Lines changed: 73 additions & 27 deletions

File tree

FASTBuildMonitorVSCode/src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ function initializeMonitorPanel(
100100
service.on("stateChanged", () => {
101101
const session = service!.getCurrentSession();
102102
if (session && !session.endTime) {
103-
const progress = session.progress.toFixed(0);
103+
const progress = session.progress.toFixed(1);
104104
statusBarItem.text = `$(sync~spin) FASTBuild ${progress}%`;
105105
} else if (session?.endTime) {
106106
const failed = session.jobs.filter(

FASTBuildMonitorVSCode/webview-ui/src/monitor_panel/components/Timeline.tsx

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,67 @@ interface TimelineProps {
1717
onTimeScaleChange: (scale: number) => void;
1818
}
1919

20-
function getWorkerNames(session: BuildSession): string[] {
21-
const seen = new Map<string, string>();
22-
for (const job of session.jobs) {
20+
interface CoreRow {
21+
hostName: string;
22+
coreIndex: number;
23+
jobs: BuildJob[];
24+
}
25+
26+
// Assign jobs to virtual "cores" per host via first-fit scheduling, so that
27+
// each row in the timeline represents a single execution slot that runs at
28+
// most one job at a time. The number of cores per host therefore equals the
29+
// peak number of concurrent jobs that ran on that host.
30+
function getCoreRows(session: BuildSession): CoreRow[] {
31+
const sortedJobs = [...session.jobs].sort((a, b) => a.startTime - b.startTime);
32+
33+
interface CoreSlot { jobs: BuildJob[]; lastEndTime: number; }
34+
const hostCores = new Map<string, CoreSlot[]>();
35+
const displayNameByKey = new Map<string, string>();
36+
37+
for (const job of sortedJobs) {
2338
const key = job.hostName.toLowerCase();
24-
if (!seen.has(key)) {
25-
seen.set(key, job.hostName);
39+
if (!displayNameByKey.has(key)) {
40+
displayNameByKey.set(key, job.hostName);
41+
}
42+
let cores = hostCores.get(key);
43+
if (!cores) {
44+
cores = [];
45+
hostCores.set(key, cores);
2646
}
47+
48+
const jobEnd = job.endTime ?? Date.now();
49+
let assigned = false;
50+
for (const core of cores) {
51+
if (core.lastEndTime <= job.startTime) {
52+
core.jobs.push(job);
53+
core.lastEndTime = jobEnd;
54+
assigned = true;
55+
break;
56+
}
57+
}
58+
if (!assigned) {
59+
cores.push({ jobs: [job], lastEndTime: jobEnd });
60+
}
61+
}
62+
63+
// Local host first, then remaining hosts alphabetically.
64+
const hostKeys = Array.from(hostCores.keys()).sort((a, b) => {
65+
const aLocal = a === 'local';
66+
const bLocal = b === 'local';
67+
if (aLocal && !bLocal) return -1;
68+
if (!aLocal && bLocal) return 1;
69+
return a.localeCompare(b);
70+
});
71+
72+
const rows: CoreRow[] = [];
73+
for (const key of hostKeys) {
74+
const cores = hostCores.get(key)!;
75+
const displayName = displayNameByKey.get(key) || key;
76+
cores.forEach((core, idx) => {
77+
rows.push({ hostName: displayName, coreIndex: idx, jobs: core.jobs });
78+
});
2779
}
28-
return Array.from(seen.values());
80+
return rows;
2981
}
3082

3183
function getMaxTime(session: BuildSession): number {
@@ -137,8 +189,8 @@ export default function Timeline({
137189

138190
const dpr = window.devicePixelRatio || 1;
139191
const containerWidth = container.clientWidth;
140-
const workerNames = session ? getWorkerNames(session) : [];
141-
const canvasHeight = Math.max(200, HEADER_HEIGHT + workerNames.length * ROW_HEIGHT + PAD * 2);
192+
const coreRows = session ? getCoreRows(session) : [];
193+
const canvasHeight = Math.max(200, HEADER_HEIGHT + coreRows.length * ROW_HEIGHT + PAD * 2);
142194

143195
canvas.style.width = containerWidth + 'px';
144196
canvas.style.height = canvasHeight + 'px';
@@ -150,7 +202,7 @@ export default function Timeline({
150202
ctx.fillStyle = '#1E1E1E';
151203
ctx.fillRect(0, 0, containerWidth, canvasHeight);
152204

153-
if (!session || workerNames.length === 0) {
205+
if (!session || coreRows.length === 0) {
154206
ctx.fillStyle = '#888';
155207
ctx.font = '16px sans-serif';
156208
ctx.textAlign = 'center';
@@ -210,9 +262,9 @@ export default function Timeline({
210262
ctx.stroke();
211263
}
212264

213-
// Worker rows
214-
for (let i = 0; i < workerNames.length; i++) {
215-
const wName = workerNames[i];
265+
// Core rows (one row per virtual CPU core per host)
266+
for (let i = 0; i < coreRows.length; i++) {
267+
const row = coreRows[i];
216268
const y = HEADER_HEIGHT + i * ROW_HEIGHT;
217269

218270
// Alternate background
@@ -229,11 +281,8 @@ export default function Timeline({
229281
ctx.lineTo(containerWidth, y + ROW_HEIGHT);
230282
ctx.stroke();
231283

232-
// Draw jobs for this worker
233-
const jobs = session.jobs.filter(
234-
j => j.hostName.toLowerCase() === wName.toLowerCase()
235-
);
236-
for (const job of jobs) {
284+
// Draw jobs scheduled on this core
285+
for (const job of row.jobs) {
237286
drawJob(ctx, job, startTime, y, containerWidth, timeScale, horizontalOffset);
238287
}
239288

@@ -245,12 +294,13 @@ export default function Timeline({
245294
}
246295
ctx.fillRect(0, y, LABEL_WIDTH, ROW_HEIGHT);
247296

248-
// Worker label
297+
// Core label
249298
ctx.fillStyle = '#CCC';
250299
ctx.font = '11px sans-serif';
251300
ctx.textAlign = 'left';
252301
ctx.textBaseline = 'middle';
253-
ctx.fillText(wName, 4, y + ROW_HEIGHT / 2, LABEL_WIDTH - 8);
302+
const label = `${row.hostName} (Core # ${row.coreIndex})`;
303+
ctx.fillText(label, 4, y + ROW_HEIGHT / 2, LABEL_WIDTH - 8);
254304
}
255305

256306
// Label column separator
@@ -301,21 +351,17 @@ export default function Timeline({
301351
const dpr = window.devicePixelRatio || 1;
302352
const mx = (e.clientX - rect.left) * dpr;
303353
const my = (e.clientY - rect.top) * dpr;
304-
const workerNames = getWorkerNames(session);
354+
const coreRows = getCoreRows(session);
305355
const startTime = session.startTime;
306356

307357
let tooltipJob: BuildJob | undefined = undefined;
308358

309-
for (let i = 0; i < workerNames.length; i++) {
359+
for (let i = 0; i < coreRows.length; i++) {
310360
const yTop = (HEADER_HEIGHT + i * ROW_HEIGHT + PAD) * dpr;
311361
const yBot = (HEADER_HEIGHT + i * ROW_HEIGHT + ROW_HEIGHT - PAD) * dpr;
312362
if (my < yTop || my > yBot) continue;
313363

314-
const wName = workerNames[i];
315-
const jobs = session.jobs.filter(
316-
j => j.hostName.toLowerCase() === wName.toLowerCase()
317-
);
318-
for (const job of jobs) {
364+
for (const job of coreRows[i].jobs) {
319365
const jobStart = (job.startTime - startTime) / 1000;
320366
const jobEnd = ((job.endTime || Date.now()) - startTime) / 1000;
321367
const x = (LABEL_WIDTH + jobStart * timeScale - horizontalOffset) * dpr;

0 commit comments

Comments
 (0)