Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
0924326
docs: Add skills
RandomByte Apr 16, 2026
d3fa014
refactor(project): Use cacache
RandomByte Nov 24, 2025
9dd8e76
refactor(fs): Refactor Resource internals
RandomByte Nov 27, 2025
68f0740
deps: Fix depcheck issues
matz3 Jan 26, 2026
1de008e
test: Cache scenarios
d3xter666 Mar 26, 2026
0abdca4
fix: Hyperfine
d3xter666 Mar 26, 2026
23d02de
fix: Hyperfine
d3xter666 Mar 26, 2026
5bf2c27
refactor: Update branch name
d3xter666 Mar 26, 2026
b7ead81
chore: Add all cache option dependencies for benchmarking
d3xter666 Mar 27, 2026
c4bdf3b
fix: add npm rebuild after ci in benchmark runner
d3xter666 Mar 27, 2026
3ca352b
fix: use node-gyp rebuild for native addons in benchmark runner
d3xter666 Mar 27, 2026
d70663b
chore: add stream-json dependency for streaming JSON benchmarks
d3xter666 Mar 27, 2026
a5bece6
chore: add options F and G to benchmark config
d3xter666 Mar 27, 2026
0ecb331
chore: add Option H and Option I to benchmark YAML
d3xter666 Mar 30, 2026
5439147
feat: add build cache timing instrumentation
d3xter666 Mar 31, 2026
65efc7e
pref: Cache timings
d3xter666 Mar 31, 2026
9acc71b
chore: add Options J, K, L to benchmark config
d3xter666 Apr 1, 2026
4a40bd4
chore: add Options J, K, L to cache timings benchmark script
d3xter666 Apr 1, 2026
9ce5df4
chore: add median, p95, and metadata-share benchmark metrics
d3xter666 Apr 1, 2026
0e1ef91
feat: add serve timings benchmark script
d3xter666 Apr 2, 2026
d15cd75
fix: add NO_COLOR=1 to build benchmark for reliable output parsing
d3xter666 Apr 7, 2026
c6ff61d
performance: Skip dist write
d3xter666 Apr 16, 2026
3d85aff
chore: add UI5_BUILD_NO_WRITE_DEST=X to cache timings benchmark script
d3xter666 Apr 16, 2026
0300425
fix: apply benchmark env vars after sleep before build
d3xter666 Apr 16, 2026
1e8c35e
chore: increase warm benchmark runs and add 10s prepare delay
d3xter666 Apr 16, 2026
eeb8d3f
fix(fs): remove invalid private integrity assignment in Resource
d3xter666 Apr 17, 2026
ada69f3
ci: Bump deps
d3xter666 Apr 20, 2026
e972426
refactor(project): Use cacache
RandomByte Nov 24, 2025
43000d0
refactor(fs): Refactor Resource internals
RandomByte Nov 27, 2025
26adf35
deps: Fix depcheck issues
matz3 Jan 26, 2026
3a052b7
chore: Add all cache option dependencies for benchmarking
d3xter666 Mar 27, 2026
1fc72c2
perf: Use SQLite for cache metadata storage (Option C)
d3xter666 Mar 26, 2026
ae7f3cc
feat: add build cache timing instrumentation
d3xter666 Mar 31, 2026
7399b38
chore: add median/p95 and metadata-share benchmark metrics
d3xter666 Apr 1, 2026
c80ef68
performance: Skip dist write
d3xter666 Apr 16, 2026
711d7f0
chore: update package-lock.json
d3xter666 Apr 20, 2026
1b6e992
fix: Replace getIntegrity with getHash and fix CLI error handling
d3xter666 Apr 22, 2026
efeb92c
fix: Add missing getInode method to Resource
d3xter666 Apr 22, 2026
9958fe4
fix: Log BuildTimings after cache writes complete
d3xter666 Apr 23, 2026
2f6f08b
fix: Replace getIntegrity with getHash in cache code
d3xter666 Apr 23, 2026
31a0af9
refactor: Native SQLite implementation
d3xter666 Apr 24, 2026
036d691
fix: Align with feat/incremental-build-4 base branch
d3xter666 Apr 28, 2026
c075715
fix: ESLint issues
d3xter666 Apr 28, 2026
0ca7e07
revert: Align package.json
d3xter666 Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/project/lib/build/ProjectBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import composeProjectList from "./helpers/composeProjectList.js";
import BuildContext from "./helpers/BuildContext.js";
import prettyHrtime from "pretty-hrtime";
import OutputStyleEnum from "./helpers/ProjectBuilderOutputStyle.js";
import BuildTimings from "./cache/BuildTimings.js";

/**
* @public
Expand Down Expand Up @@ -357,6 +358,8 @@ class ProjectBuilder {
throw err;
} finally {
await Promise.all(pCacheWrites);
// Log BuildTimings after cache writes complete, so write timings are included
BuildTimings.logSummary();
this._deregisterCleanupSigHooks(cleanupSigHooks);
await this._executeCleanupTasks();
this.#buildIsRunning = false;
Expand Down
223 changes: 223 additions & 0 deletions packages/project/lib/build/cache/BuildTimings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {getLogger} from "@ui5/logger";

const log = getLogger("build:cache:BuildTimings");
const isEnabled = process.env.UI5_BUILD_TIMINGS === "true";

// label → {totalMs, count, minMs, maxMs}
const entries = new Map();

/**
* Start timing an operation.
* Returns a start token (timestamp) or undefined if timing is disabled.
*
* @param {string} label Operation label (e.g. "readIndexCache")
* @returns {number|undefined} Start timestamp, or undefined when disabled
*/
function start(label) {
if (!isEnabled) {
return undefined;
}
return performance.now();
}

/**
* Record the elapsed time for a previously started operation.
* No-op when timing is disabled or if startTime is undefined.
*
* @param {string} label Operation label (must match the label passed to start())
* @param {number|undefined} startTime The value returned by start()
*/
function end(label, startTime) {
if (startTime === undefined) {
return;
}
const elapsed = performance.now() - startTime;
let entry = entries.get(label);
if (!entry) {
entry = {totalMs: 0, count: 0, minMs: Infinity, maxMs: -Infinity};
entries.set(label, entry);
}
entry.totalMs += elapsed;
entry.count++;
if (elapsed < entry.minMs) {
entry.minMs = elapsed;
}
if (elapsed > entry.maxMs) {
entry.maxMs = elapsed;
}
}

/**
* Format a millisecond value for display.
* Values >= 1000ms are shown as seconds (e.g. "1.234s").
* Values < 1000ms are shown with ms suffix (e.g. "12.34ms").
* Values < 0.01ms are shown as "<0.01ms".
*
* @param {number} ms Duration in milliseconds
* @returns {string} Formatted duration string
*/
function formatMs(ms) {
if (ms >= 1000) {
return `${(ms / 1000).toFixed(3)}s`;
}
if (ms < 0.01) {
return "<0.01ms";
}
return `${ms.toFixed(2)}ms`;
}

/**
* Right-pad a string to the given width.
*
* @param {string} str Input string
* @param {number} width Target width
* @returns {string} Padded string
*/
function pad(str, width) {
return str.padEnd(width);
}

/**
* Left-pad a string to the given width.
*
* @param {string} str Input string
* @param {number} width Target width
* @returns {string} Padded string
*/
function lpad(str, width) {
return str.padStart(width);
}

/**
* Log the timing summary to the build log.
* Groups entries into Reads, Writes, and Resource I/O categories.
* Only logs when timing is enabled and there are recorded entries.
*/
function logSummary() {
if (!isEnabled || entries.size === 0) {
return;
}

const COL_OP = 28;
const COL_NUM = 10;

const header =
pad("Operation", COL_OP) +
lpad("Calls", COL_NUM) +
lpad("Total", COL_NUM) +
lpad("Avg", COL_NUM) +
lpad("Min", COL_NUM) +
lpad("Max", COL_NUM);

const separator = "─".repeat(header.length);

const lines = [
"",
"Build Cache Timings",
separator,
header,
separator,
];

const readLabels = [
"readBuildManifest",
"readIndexCache",
"readTaskMetadata",
"readResultMetadata",
"readStageCache",
];
const writeLabels = [
"writeBuildManifest",
"writeIndexCache",
"writeTaskMetadata",
"writeResultMetadata",
"writeStageCache",
];
const resourceLabels = [
"getResourcePathForStage",
"writeStageResource",
];

let grandTotalMs = 0;
let grandTotalCalls = 0;

function addSection(title, labels) {
let sectionTotal = 0;
let sectionCalls = 0;
const sectionLines = [];

for (const label of labels) {
const entry = entries.get(label);
if (!entry || entry.count === 0) {
sectionLines.push(
pad(` ${label}`, COL_OP) +
lpad("0", COL_NUM) +
lpad("--", COL_NUM) +
lpad("--", COL_NUM) +
lpad("--", COL_NUM) +
lpad("--", COL_NUM)
);
continue;
}
const avg = entry.totalMs / entry.count;
sectionLines.push(
pad(` ${label}`, COL_OP) +
lpad(String(entry.count), COL_NUM) +
lpad(formatMs(entry.totalMs), COL_NUM) +
lpad(formatMs(avg), COL_NUM) +
lpad(formatMs(entry.minMs), COL_NUM) +
lpad(formatMs(entry.maxMs), COL_NUM)
);
sectionTotal += entry.totalMs;
sectionCalls += entry.count;
}

lines.push(`${title}`);
lines.push(...sectionLines);

if (sectionCalls > 0) {
lines.push(
pad(` Subtotal`, COL_OP) +
lpad(String(sectionCalls), COL_NUM) +
lpad(formatMs(sectionTotal), COL_NUM) +
lpad("", COL_NUM) +
lpad("", COL_NUM) +
lpad("", COL_NUM)
);
}
lines.push("");

grandTotalMs += sectionTotal;
grandTotalCalls += sectionCalls;
}

addSection("Reads:", readLabels);
addSection("Writes:", writeLabels);
addSection("Resource I/O:", resourceLabels);

// Include any extra labels not categorised above
const knownLabels = new Set([...readLabels, ...writeLabels, ...resourceLabels]);
const extraLabels = [...entries.keys()].filter((l) => !knownLabels.has(l));
if (extraLabels.length) {
addSection("Other:", extraLabels);
}

lines.push(separator);
lines.push(
pad("TOTAL", COL_OP) +
lpad(String(grandTotalCalls), COL_NUM) +
lpad(formatMs(grandTotalMs), COL_NUM)
);
lines.push(separator);

log.info(lines.join("\n"));
}

/**
* Reset all recorded timings.
*/
function reset() {
entries.clear();
}

export default {start, end, logSummary, reset, isEnabled};
Loading