Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
ab73b8a
[skip ci] Update CHANGELOG.md (prepend template)
github-actions[bot] Jun 22, 2026
6d89d47
fix(dev): tweak frontend-bundle-report
syuilo Jun 22, 2026
7a3e034
Merge branch 'develop' of https://github.com/misskey-dev/misskey into…
syuilo Jun 22, 2026
fff87f6
enhance(dev): tweak Frontend Chunk Report
syuilo Jun 22, 2026
5d8c31b
fix(dev): tweak frontend-bundle-report-comment
syuilo Jun 22, 2026
f5806a0
enhance(dev): tweak Frontend Chunk Report
syuilo Jun 22, 2026
d54b948
enhance(dev): tweak Frontend Chunk Report
syuilo Jun 22, 2026
6d11f57
enhance(dev): improve backend memory usage comparison workflow (#17591)
syuilo Jun 23, 2026
09b761e
enhance(dev): tweak report-backend-memory
syuilo Jun 23, 2026
72d91ce
refactor(dev): report-backend-memoryのmarkdown生成ロジックを分離
syuilo Jun 23, 2026
c899aaf
enhance(dev): tweak report-backend-memory
syuilo Jun 23, 2026
cb1d1d6
enhance(dev): tweak report-backend-memory
syuilo Jun 23, 2026
6e4380f
enhance(dev): tweak report-backend-memory
syuilo Jun 23, 2026
544c422
chore(dev): refactor
syuilo Jun 23, 2026
8186742
refactor(dev): unify frontend-bundle-visualizer-report.mjs and fronte…
syuilo Jun 23, 2026
4bacb1b
chore(dev): fix typo
syuilo Jun 23, 2026
05e00e4
refactor(dev): refactor frontend-js-size.mjs
syuilo Jun 23, 2026
2c814ec
chore(dev): tweak frontend-js-size.mjs
syuilo Jun 23, 2026
f703413
chore(dev): tweak frontend-js-size.mjs
syuilo Jun 23, 2026
f6cfe15
chore(dev): tweak frontend-js-size.mjs
syuilo Jun 23, 2026
e77184f
chore(ci): simplify FFmpeg installation in workflows (#17612)
samunohito Jun 24, 2026
3e1a090
fix(dev): tweak frontend-js-size.mjs
syuilo Jun 24, 2026
a27b155
Merge branch 'develop' of https://github.com/misskey-dev/misskey into…
syuilo Jun 24, 2026
e12f97b
enhance(dev): improve Frontend Bundle Report
syuilo Jun 24, 2026
1173550
chore(dev): refactor frontend bundle report
syuilo Jun 24, 2026
59ae380
enhance(dev): improve backend memory usage report (#17608)
syuilo Jun 24, 2026
69ae0c1
chore(dev): tweak frontend-js-size.mjs
syuilo Jun 24, 2026
bfad097
Merge branch 'develop' of https://github.com/misskey-dev/misskey into…
syuilo Jun 24, 2026
4a056bc
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
5ab352d
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
8dfa900
(test) enhance(dev): improve backend-memory-report
syuilo Jun 24, 2026
83319d1
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
351f878
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
7b2790e
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
fdc2f79
chore(dev): tweak backend-memory-report
syuilo Jun 24, 2026
d008103
chore(dev): tweak frontend-js-size
syuilo Jun 24, 2026
079ec86
chore(dev): tweak frontend-js-size
syuilo Jun 24, 2026
453f38b
chore(dev): tweak backend-memory-report
syuilo Jun 25, 2026
0904855
chore(dev): tweak backend-memory-report
syuilo Jun 25, 2026
2116213
chore(dev): tweak backend-memory-report
syuilo Jun 25, 2026
4d6ad90
chore(dev): tweak frontend-js-size
syuilo Jun 25, 2026
1f4978b
Update frontend-bundle-report.yml
syuilo Jun 25, 2026
e90ef7e
[ci skip] chore(dev): tweak frontend-js-size
syuilo Jun 25, 2026
1c4bcd9
refactor(dev): refactor of backend memory comparison workflow (#17619)
syuilo Jun 25, 2026
faecacc
fix(dev): follow up of 1c4bcd9b32414e826fecdeda89f769d9f383b9b6
syuilo Jun 25, 2026
8a479bd
fix(dev): follow up of 1c4bcd9b32414e826fecdeda89f769d9f383b9b6
syuilo Jun 25, 2026
9785962
fix(dev): follow up of 1c4bcd9b32414e826fecdeda89f769d9f383b9b6
syuilo Jun 25, 2026
aad6ce8
fix(dev): follow up of 1c4bcd9b32414e826fecdeda89f769d9f383b9b6
syuilo Jun 26, 2026
73dca04
chore(dev): refactor workflow js
syuilo Jun 26, 2026
93fd491
refactor(dev): extract heap snapshot logic
syuilo Jun 26, 2026
5cbdbd1
chore(dev): tweak backend-memory-report
syuilo Jun 26, 2026
08ca482
chore(dev): tweak some workflows
syuilo Jun 26, 2026
2747fd0
chore(dev): tweak some workflows
syuilo Jun 26, 2026
a1f995c
fix(dev): fix measure-memory
syuilo Jun 26, 2026
669889f
chore: センシティブ判定を外部サービス扱いにする (#17570)
samunohito Jun 26, 2026
61f734d
Bump version to 2026.6.1-alpha.0
github-actions[bot] Jun 26, 2026
038a2d0
refactor
syuilo Jun 26, 2026
9b32c6f
Merge branch 'develop' of https://github.com/misskey-dev/misskey into…
syuilo Jun 26, 2026
1068c64
feat: コントロールパネルから二要素認証を解除できるように (#17614)
kakkokari-gtyih Jun 26, 2026
8af2579
enhance(frontend): drop pref migration (#17379)
syuilo Jun 26, 2026
3cd772e
Merge branch 'master' into develop
syuilo Jun 26, 2026
a7503c2
chore(deps): update pnpm to v11.8.0 [security] (#17625)
renovate[bot] Jun 27, 2026
8fc3403
chore(dev): clean up unnecessary logs
syuilo Jun 27, 2026
a107cae
chore(dev): clean up unnecessary logs
syuilo Jun 27, 2026
d1cac26
fix(dev): fix typo of report-api-diff.yml
syuilo Jun 27, 2026
1faeb9b
refactor(dev): use actions/download-artifact
syuilo Jun 27, 2026
228beff
chore(dev): tweak backend-memory-report
syuilo Jun 27, 2026
dbbc5fc
chore(dev): clean up unnecessary step
syuilo Jun 27, 2026
e9715aa
enhance(dev): make heap snapshot downloadable
syuilo Jun 27, 2026
cec23e5
enhance(dev): improve heap snapshot analysis (#17628)
syuilo Jun 27, 2026
529c4d4
chore(dev): tweak heap snapshot table
syuilo Jun 27, 2026
554339a
deps: update dependencies (#17631)
kakkokari-gtyih Jun 27, 2026
42ff280
Bump version to 2026.6.1-alpha.1
github-actions[bot] Jun 27, 2026
797dec7
Update CHANGELOG.md
kakkokari-gtyih Jun 27, 2026
ba3fb4a
Merge branch 'master' into develop
kakkokari-gtyih Jun 27, 2026
4f993ce
enhance: Set default Node.js version to v26 (#17623)
kakkokari-gtyih Jun 27, 2026
ffe65ca
Update CHANGELOG.md
kakkokari-gtyih Jun 27, 2026
7544ebf
Update CHANGELOG.md
kakkokari-gtyih Jun 27, 2026
1220f05
Update CHANGELOG.md
kakkokari-gtyih Jun 27, 2026
6193c35
fix: stats API の型に reactionCount が定義されていない問題を修正 (#17634)
kakkokari-gtyih Jun 27, 2026
62f8589
Update CONTRIBUTING.md
syuilo Jun 28, 2026
4daa1ff
chore(deps): 未使用依存パッケージを削除 (#17641)
kakkokari-gtyih Jun 29, 2026
bf88122
chore(deps): update [github actions] update dependencies [ci skip] (#…
renovate[bot] Jun 30, 2026
7e29f04
fix(chat): don't send message while the IME is composing (#17646)
greymoth-jp Jul 1, 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
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "22.15.0"
"version": "22.23.1"
},
"ghcr.io/devcontainers-extra/features/pnpm:2": {
"version": "10.10.0"
Expand Down
3 changes: 0 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,3 @@ updates:
typescript-eslint:
patterns:
- "@typescript-eslint/*"
tensorflow:
patterns:
- "@tensorflow/*"
2 changes: 1 addition & 1 deletion .github/min.node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.15.0
22.22.2
3 changes: 1 addition & 2 deletions .github/scripts/backend-js-footprint.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ const [repoDirArg, outputFileArg] = process.argv.slice(2);
const STARTUP_TIMEOUT = util.readIntegerEnv('MK_JS_FOOTPRINT_STARTUP_TIMEOUT_MS', 120000, 1);
const SETTLE_TIME = util.readIntegerEnv('MK_JS_FOOTPRINT_SETTLE_TIME_MS', 10000, 0);
const REQUEST_COUNT = util.readIntegerEnv('MK_JS_FOOTPRINT_REQUEST_COUNT', 10, 0);
const MAX_TABLE_ITEMS = util.readIntegerEnv('MK_JS_FOOTPRINT_MAX_ITEMS', 20, 1);

const repoDir = resolve(repoDirArg);
const outputFile = resolve(outputFileArg);
Expand Down Expand Up @@ -389,7 +388,7 @@ function summarizeRecords(records, phase) {
stringLiteralKiB: bytesToKiB(totals.stringLiteralBytes),
},
packages: [...packages.values()].toSorted((a, b) => b.sourceBytes - a.sourceBytes),
modules: modules.toSorted((a, b) => b.sourceBytes - a.sourceBytes).slice(0, MAX_TABLE_ITEMS),
modules: modules.toSorted((a, b) => b.sourceBytes - a.sourceBytes),
};
}

Expand Down
26 changes: 15 additions & 11 deletions .github/scripts/backend-memory-report.mts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const metrics = [

function formatMemoryMb(valueKiB: number | null | undefined) {
if (valueKiB == null) return '-';
return `${util.formatNumber(valueKiB / 1024)} MB`;
return `${util.formatNumber(valueKiB / 1000)} MB`;
}

function getMemoryValue(report: MemoryReport, phase: typeof memoryReportPhases[number]['key'], metric: typeof metrics[number]) {
Expand All @@ -79,8 +79,8 @@ function renderMainTableForPhase(base: MemoryReport, head: MemoryReport, phase:
'| --- | ---: | ---: | ---: | ---: | ---: | ---: |',
];

function formatDeltaMemory(diffKiB: number) {
return util.formatColoredDelta(formatMemoryMb(Math.abs(diffKiB)), diffKiB);
function formatDeltaMemory(deltaKiB: number) {
return util.formatColoredDelta(deltaKiB, v => formatMemoryMb(v), 100); // 0.1 MB threshold
}

for (const metric of metrics) {
Expand All @@ -91,7 +91,7 @@ function renderMainTableForPhase(base: MemoryReport, head: MemoryReport, phase:
const headSpread = getSampleSpread(head, phase, metric);
const summary = util.pairedDeltaSummary(base.samples, head.samples, (sample) => getMemoryValueFromSample(sample, phase, metric));
const percent = summary.median * 100 / baseValue;
const deltaMedian = summary == null ? '-' : `${formatDeltaMemory(summary.median)}<br>${util.formatDeltaPercent(percent).replaceAll('\\%', '\\\\%')}`;
const deltaMedian = summary == null ? '-' : `${formatDeltaMemory(summary.median)}<br>${util.formatDeltaPercent(percent, 0.1).replaceAll('\\%', '\\\\%')}`;

lines.push(`| **${metric}** | ${formatMemoryMb(baseValue)} <br> ± ${formatMemoryMb(baseSpread)} | ${formatMemoryMb(headValue)} <br> ± ${formatMemoryMb(headSpread)} | ${deltaMedian} | ${summary?.mad == null ? '-' : formatMemoryMb(summary.mad)} | ${summary == null ? '-' : formatDeltaMemory(summary.min)} | ${summary == null ? '-' : formatDeltaMemory(summary.max)} |`);
}
Expand Down Expand Up @@ -144,7 +144,7 @@ function renderHeapSnapshotSection(base: MemoryReport, head: MemoryReport) {
];

for (const graph of [
heapSnapshotUtil.renderHeapSnapshotSankey(baseHeapSnapshotReport, 'Base'),
//heapSnapshotUtil.renderHeapSnapshotSankey(baseHeapSnapshotReport, 'Base'),
heapSnapshotUtil.renderHeapSnapshotSankey(headHeapSnapshotReport, 'Head'),
]) {
if (graph == null) continue;
Expand Down Expand Up @@ -183,7 +183,7 @@ function renderJsFootprintMetricTable(base: RuntimeLoadedJsFootprintReport, head
const headValue = getJsFootprintValue(head, 'afterRequest', key);
if (baseValue == null || headValue == null) continue;

lines.push(`| **${title}** | ${formatter(baseValue)} | ${formatter(headValue)} | ${util.formatColoredDelta(formatter(headValue - baseValue), headValue - baseValue)} | ${util.calcAndFormatDeltaPercent(baseValue, headValue).replaceAll('\\%', '\\\\%')} |`);
lines.push(`| **${title}** | ${formatter(baseValue)} | ${formatter(headValue)} | ${util.formatColoredDelta(headValue - baseValue, v => formatter(v))} | ${util.calcAndFormatDeltaPercent(baseValue, headValue).replaceAll('\\%', '\\\\%')} |`);
}

return lines.join('\n');
Expand Down Expand Up @@ -278,7 +278,7 @@ function renderLargestPackageIncreases(base: RuntimeLoadedJsFootprintReport, hea
];

for (const packageSummary of increases) {
lines.push(`| ${packageDisplayName(packageSummary)} | ${util.formatBytes(packageSummary.baseSourceBytes)} | ${util.formatBytes(packageSummary.sourceBytes)} | ${util.formatColoredDelta(util.formatBytes(packageSummary.sourceBytes - packageSummary.baseSourceBytes), packageSummary.sourceBytes - packageSummary.baseSourceBytes)} | ${util.formatColoredDelta(util.formatNumber(packageSummary.modules - packageSummary.baseModules), packageSummary.modules - packageSummary.baseModules)} |`);
lines.push(`| ${packageDisplayName(packageSummary)} | ${util.formatBytes(packageSummary.baseSourceBytes)} | ${util.formatBytes(packageSummary.sourceBytes)} | ${util.formatColoredDelta(packageSummary.sourceBytes - packageSummary.baseSourceBytes, v => util.formatBytes(v))} | ${util.formatColoredDelta(packageSummary.modules - packageSummary.baseModules, v => util.formatNumber(v))} |`);
}

return lines.join('\n');
Expand Down Expand Up @@ -374,6 +374,10 @@ if (heapSnapshotSection != null) {
lines.push('');
}

const artifactUrl = process.env.MK_MEMORY_HEAP_SNAPSHOT_ARTIFACT_URL_HEAD!.trim();
lines.push(`[Download representative V8 heap snapshot (head)](${artifactUrl})`);
lines.push('');

const jsFootprintSection = renderJsFootprintSection(baseJsFootprint, headJsFootprint);
if (jsFootprintSection != null) {
lines.push(jsFootprintSection);
Expand Down Expand Up @@ -402,15 +406,15 @@ function isBeyondSampleNoise(base: MemoryReport, head: MemoryReport, phase: type
const headValue = getMemoryValue(head, phase, metric);
if (baseValue == null || headValue == null) return false;

const diff = headValue - baseValue;
if (diff <= 0) return false;
const delta = headValue - baseValue;
if (delta <= 0) return false;

const baseSpread = getSampleSpread(base, phase, metric);
const headSpread = getSampleSpread(head, phase, metric);
if (baseSpread == null || headSpread == null) return true;

const combinedSpread = Math.hypot(baseSpread, headSpread);
return diff > combinedSpread * 3;
return delta > combinedSpread * 3;
}

const warningMetric = getWarningMetric(base, head);
Expand All @@ -420,6 +424,6 @@ if (warningMetric != null && warningDiffPercent != null && warningDiffPercent >
lines.push('');
}

lines.push(`[See workflow logs for details](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})`);
//lines.push(`[See workflow logs for details](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID})`);

await writeFile(outputFile, `${lines.join('\n')}\n`);
16 changes: 8 additions & 8 deletions .github/scripts/frontend-js-size.mts
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,13 @@ function renderVisualizerSummaryTable(before: ReturnType<typeof collectVisualize
`<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>`,
`<tr>`,
`<th><b>Δ</b></th>`,
...summary.map((key) => `<td>${util.calcAndFormatDeltaNumber(before.summary[key], after.summary[key])}</td>`),
...metrics.map((key) => `<td>${util.calcAndFormatDeltaBytes(before.metrics[key], after.metrics[key])}</td>`),
...summary.map((key) => `<td>${util.calcAndFormatDeltaNumber(before.summary[key], after.summary[key], 0)}</td>`),
...metrics.map((key) => `<td>${util.calcAndFormatDeltaBytes(before.metrics[key], after.metrics[key], 1000)}</td>`),
`</tr>`,
`<tr>`,
`<th><b>Δ (%)</b></th>`,
...summary.map((key) => `<td>${util.calcAndFormatDeltaPercent(before.summary[key], after.summary[key])}</td>`),
...metrics.map((key) => `<td>${util.calcAndFormatDeltaPercent(before.metrics[key], after.metrics[key])}</td>`),
...summary.map((key) => `<td>${util.calcAndFormatDeltaPercent(before.summary[key], after.summary[key], 0.1)}</td>`),
...metrics.map((key) => `<td>${util.calcAndFormatDeltaPercent(before.metrics[key], after.metrics[key], 0.1)}</td>`),
`</tr>`,
`</tbody>`,
`</table>`,
Expand Down Expand Up @@ -357,16 +357,16 @@ function chunkMarkdownTable(rows: ReturnType<typeof getChunkComparisonRows>, tot
'| --- | ---: | ---: | ---: | ---: |',
];
if (total != null) {
lines.push(`| (total) | ${util.formatBytes(total.beforeSize)} | ${util.formatBytes(total.afterSize)} | ${util.calcAndFormatDeltaBytes(total.beforeSize, total.afterSize)} | ${util.calcAndFormatDeltaPercent(total.beforeSize, total.afterSize).replaceAll('\\%', '\\\\%')} |`);
lines.push(`| (total) | ${util.formatBytes(total.beforeSize)} | ${util.formatBytes(total.afterSize)} | ${util.calcAndFormatDeltaBytes(total.beforeSize, total.afterSize, 1000)} | ${util.calcAndFormatDeltaPercent(total.beforeSize, total.afterSize, 0.1).replaceAll('\\%', '\\\\%')} |`);
lines.push('| | | | | |');
}
for (const row of rows) {
if (row.changeType === 'added') {
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize)} | $\\color{orange}{\\text{(+)}}$ |`);
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize, 1000)} | $\\color{orange}{\\text{( + )}}$ |`);
} else if (row.changeType === 'removed') {
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize)} | $\\color{green}{\\text{(-)}}$ |`);
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize, 1000)} | $\\color{green}{\\text{( - )}}$ |`);
} else {
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize)} | ${util.calcAndFormatDeltaPercent(row.beforeSize, row.afterSize).replaceAll('\\%', '\\\\%')} |`);
lines.push(`| <details><summary>\`${escapeCell(row.name)}\`</summary> \`${escapeCell(row.chunkFile)}\` </details> | ${util.formatBytes(row.beforeSize)} | ${util.formatBytes(row.afterSize)} | ${util.calcAndFormatDeltaBytes(row.beforeSize, row.afterSize, 1000)} | ${util.calcAndFormatDeltaPercent(row.beforeSize, row.afterSize, 0.1).replaceAll('\\%', '\\\\%')} |`);
}
}
return lines.join('\n');
Expand Down
8 changes: 4 additions & 4 deletions .github/scripts/heap-snapshot-util.mts
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,20 @@ export function renderHeapSnapshotTable(base: HeapSnapshotReport, head: HeapSnap
const percent = summary.median * 100 / baseValue;

if (category === 'total') {
const deltaMedian = `${util.formatDeltaBytes(summary.median)}<br>${util.formatDeltaPercent(percent).replaceAll('\\%', '\\\\%')}`;
const deltaMedian = `${util.formatDeltaBytes(summary.median, 100000)}<br>${util.formatDeltaPercent(percent, 0.1).replaceAll('\\%', '\\\\%')}`;
const baseText = `${util.formatBytes(baseValue)} <br> ± ${util.formatBytes(baseSpread)}`;
const headText = `${util.formatBytes(headValue)} <br> ± ${util.formatBytes(headSpread)}`;
const metricText = `$\\color{${heapSnapshotCategory[category].color}}{\\rule{8pt}{8pt}}$ **${heapSnapshotCategory[category].label}**`;
lines.push(`| ${metricText} | ${baseText} | ${headText} | ${deltaMedian} | ${util.formatBytes(summary.mad)} | ${util.formatDeltaBytes(summary.min)} | ${util.formatDeltaBytes(summary.max)} |`);
lines.push(`| ${metricText} | ${baseText} | ${headText} | ${deltaMedian} | ${util.formatBytes(summary.mad)} | ${util.formatDeltaBytes(summary.min, 100000)} | ${util.formatDeltaBytes(summary.max, 100000)} |`);
lines.push('| | | | | | | |');
} else {
const deltaMedian = util.formatDeltaBytes(summary.median);
const deltaMedian = util.formatDeltaBytes(summary.median, 100000);
const baseText = util.formatBytes(baseValue);
const headText = util.formatBytes(headValue);
const basePercent = util.formatPercent((baseValue * 100) / baseTotal);
const headPercent = util.formatPercent((headValue * 100) / headTotal);
const metricText = `<details><summary>$\\color{${heapSnapshotCategory[category].color}}{\\rule{8pt}{8pt}}$ **${heapSnapshotCategory[category].label}**</summary>${basePercent} → ${headPercent}</details>`;
lines.push(`| ${metricText} | ${baseText} | ${headText} | ${deltaMedian} | ${util.formatBytes(summary.mad)} | ${util.formatDeltaBytes(summary.min)} | ${util.formatDeltaBytes(summary.max)} |`);
lines.push(`| ${metricText} | ${baseText} | ${headText} | ${deltaMedian} | ${util.formatBytes(summary.mad)} | ${util.formatDeltaBytes(summary.min, 100000)} | ${util.formatDeltaBytes(summary.max, 100000)} |`);
}
}

Expand Down
60 changes: 55 additions & 5 deletions .github/scripts/measure-backend-memory-comparison.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { createRequire } from 'node:module';
import { writeFile } from 'node:fs/promises';
import { copyFile, rm, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import * as util from './utility.mts';
import * as heapSnapshotUtil from './heap-snapshot-util.mts';
Expand Down Expand Up @@ -39,6 +39,8 @@ export type MemoryReport = {
const [baseDirArg, headDirArg, baseOutputArg, headOutputArg] = process.argv.slice(2);

const HEAP_SNAPSHOT_BREAKDOWN_TOP_N = util.readIntegerEnv('MK_MEMORY_HEAP_SNAPSHOT_BREAKDOWN_TOP_N', 6, 1);
const HEAD_HEAP_SNAPSHOT_WORK_DIR = resolve('head-heap-snapshots');
const HEAD_HEAP_SNAPSHOT_OUTPUT_PATH = resolve('head-heap-snapshot.heapsnapshot');

async function resetState(repoDir: string) {
const require = createRequire(join(repoDir, 'packages/backend/package.json'));
Expand Down Expand Up @@ -165,7 +167,7 @@ function summarizeSamples(samples: MemoryReport['samples']) {
return summary;
}

async function measureRepo(label: string, repoDir: string, round: number) {
async function measureRepo(label: string, repoDir: string, round: number, options: { heapSnapshotSavePath?: string } = {}) {
process.stderr.write(`[${label}] Resetting database and Redis\n`);
await resetState(repoDir);

Expand All @@ -182,6 +184,7 @@ async function measureRepo(label: string, repoDir: string, round: number) {
MK_MEMORY_SAMPLE_COUNT: '1',
} as NodeJS.ProcessEnv;
if (round <= 0) measureEnv.MK_MEMORY_HEAP_SNAPSHOT = '0';
if (options.heapSnapshotSavePath != null) measureEnv.MK_MEMORY_HEAP_SNAPSHOT_SAVE_PATH = options.heapSnapshotSavePath;

const stdout = await util.run('node', ['packages/backend/scripts/measure-memory.mts'], {
cwd: repoDir,
Expand All @@ -194,6 +197,40 @@ async function measureRepo(label: string, repoDir: string, round: number) {
return sample;
}

function headHeapSnapshotPath(round: number) {
return join(HEAD_HEAP_SNAPSHOT_WORK_DIR, `round-${round}.heapsnapshot`);
}

function selectRepresentativeHeadHeapSnapshotRound(samples: MemoryReport['samples'], summary: MemoryReport['summary']) {
const medianTotal = summary.afterGc.heapSnapshot?.categories?.total;
if (medianTotal == null || !Number.isFinite(medianTotal)) return null;

let selected: { round: number; distance: number } | null = null;
for (const sample of samples) {
const total = sample.phases.afterGc.heapSnapshot?.categories?.total;
if (total == null || !Number.isFinite(total)) continue;

const distance = Math.abs(total - medianTotal);
if (selected == null || distance < selected.distance || (distance === selected.distance && sample.round < selected.round)) {
selected = {
round: sample.round,
distance,
};
}
}

return selected?.round ?? null;
}

async function saveRepresentativeHeadHeapSnapshot(samples: MemoryReport['samples'], summary: MemoryReport['summary']) {
const round = selectRepresentativeHeadHeapSnapshotRound(samples, summary);
if (round == null) return;

await copyFile(headHeapSnapshotPath(round), HEAD_HEAP_SNAPSHOT_OUTPUT_PATH);
process.stderr.write(`Selected head heap snapshot round ${round} for artifact\n`);
await rm(HEAD_HEAP_SNAPSHOT_WORK_DIR, { recursive: true, force: true });
}

async function main() {
const baseDir = resolve(baseDirArg);
const headDir = resolve(headDirArg);
Expand All @@ -203,6 +240,9 @@ async function main() {
const warmupRounds = util.readIntegerEnv('MK_MEMORY_COMPARE_WARMUP_ROUNDS', 1, 0);
const startedAt = new Date().toISOString();

await rm(HEAD_HEAP_SNAPSHOT_WORK_DIR, { recursive: true, force: true });
await rm(HEAD_HEAP_SNAPSHOT_OUTPUT_PATH, { force: true });

const reports = {
base: {
dir: baseDir,
Expand All @@ -225,15 +265,25 @@ async function main() {
const order = round % 2 === 1 ? ['base', 'head'] as const : ['head', 'base'] as const;
process.stderr.write(`Starting measurement round ${round}/${rounds}: ${order.join(' -> ')}\n`);

for (const [orderIndex, label] of order.entries()) {
const sample = await measureRepo(label, reports[label].dir, round);
for (const label of order) {
const shouldSaveHeadHeapSnapshot = label === 'head';
const options = shouldSaveHeadHeapSnapshot
? { heapSnapshotSavePath: headHeapSnapshotPath(round) }
: {};
const sample = await measureRepo(label, reports[label].dir, round, options);
reports[label].samples.push({
...sample,
round,
});
}
}

const summaries = {
base: summarizeSamples(reports.base.samples),
head: summarizeSamples(reports.head.samples),
};
await saveRepresentativeHeadHeapSnapshot(reports.head.samples, summaries.head);

for (const label of ['base', 'head'] as const) {
const report = {
timestamp: new Date().toISOString(),
Expand All @@ -245,7 +295,7 @@ async function main() {
warmupRounds,
startedAt,
},
summary: summarizeSamples(reports[label].samples),
summary: summaries[label],
samples: reports[label].samples,
};

Expand Down
Loading
Loading