From 72d91ce3da0c482445c201c5dadffa78838d827b Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:29:45 +0900 Subject: [PATCH] =?UTF-8?q?refactor(dev):=20report-backend-memory=E3=81=AE?= =?UTF-8?q?markdown=E7=94=9F=E6=88=90=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E5=88=86=E9=9B=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/scripts/backend-memory-report.mjs | 142 ++++++++++++++++++++ .github/workflows/get-backend-memory.yml | 1 + .github/workflows/report-backend-memory.yml | 129 +----------------- 3 files changed, 149 insertions(+), 123 deletions(-) create mode 100644 .github/scripts/backend-memory-report.mjs diff --git a/.github/scripts/backend-memory-report.mjs b/.github/scripts/backend-memory-report.mjs new file mode 100644 index 0000000000..d37ae98cc8 --- /dev/null +++ b/.github/scripts/backend-memory-report.mjs @@ -0,0 +1,142 @@ +import { readFile, writeFile } from 'node:fs/promises'; + +const [baseFile, headFile, outputFile] = process.argv.slice(2); + +if (baseFile == null || headFile == null || outputFile == null) { + console.error('Usage: node .github/scripts/backend-memory-report.mjs '); + process.exit(1); +} + +const numberFormatter = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 2, +}); + +const phases = [ + { + key: 'afterGc', + title: 'After GC', + }, +]; + +const metrics = [ + 'HeapUsed', + 'HeapTotal', + 'External', + 'Pss', + 'Private_Dirty', +]; + +function formatNumber(value) { + return numberFormatter.format(value); +} + +function formatMemory(valueKiB) { + return `${formatNumber(valueKiB / 1024)} MB`; +} + +function formatPercent(value) { + return `${formatNumber(value)}%`; +} + +function formatMathText(text) { + return text + .replaceAll('\\', '\\\\') + .replaceAll('{', '\\{') + .replaceAll('}', '\\}') + .replaceAll('%', '\\%'); +} + +function formatColoredDiff(text, diff) { + const color = diff > 0 ? 'orange' : 'green'; + return `$\\color{${color}}{\\text{${formatMathText(text)}}}$`; +} + +function formatDiff(baseKiB, headKiB) { + const diff = headKiB - baseKiB; + if (diff === 0) return formatMemory(0); + + const sign = diff > 0 ? '+' : '-'; + return formatColoredDiff(`${sign}${formatMemory(Math.abs(diff))}`, diff); +} + +function formatDiffPercent(baseKiB, headKiB) { + const diff = headKiB - baseKiB; + if (diff === 0) return '0%'; + if (baseKiB <= 0) return '-'; + + const sign = diff > 0 ? '+' : '-'; + return formatColoredDiff(`${sign}${formatPercent(Math.abs((diff * 100) / baseKiB))}`, diff); +} + +function getMemoryValue(report, phase, metric) { + const value = report?.[phase]?.[metric]; + return Number.isFinite(value) ? value : null; +} + +function renderTable(base, head, phase) { + const lines = [ + '| Metric | Base | Head | Δ | Δ (%) |', + '| --- | ---: | ---: | ---: | ---: |', + ]; + + for (const metric of metrics) { + const baseValue = getMemoryValue(base, phase, metric); + const headValue = getMemoryValue(head, phase, metric); + if (baseValue == null || headValue == null) continue; + + lines.push(`| ${metric} | ${formatMemory(baseValue)} | ${formatMemory(headValue)} | ${formatDiff(baseValue, headValue)} | ${formatDiffPercent(baseValue, headValue)} |`); + } + + return lines.join('\n'); +} + +function getDiffPercent(base, head, phase, metric) { + const baseValue = getMemoryValue(base, phase, metric); + const headValue = getMemoryValue(head, phase, metric); + if (baseValue == null || headValue == null || baseValue <= 0) return null; + + return ((headValue - baseValue) * 100) / baseValue; +} + +function getWarningMetric(base, head) { + for (const metric of ['Pss', 'VmRSS']) { + if (getMemoryValue(base, 'afterGc', metric) != null && getMemoryValue(head, 'afterGc', metric) != null) { + return metric; + } + } + return null; +} + +function workflowFooter() { + const repository = process.env.GITHUB_REPOSITORY; + const runId = process.env.GITHUB_RUN_ID; + if (repository == null || runId == null) { + return 'See workflow logs for details.'; + } + + return `[See workflow logs for details](https://github.com/${repository}/actions/runs/${runId})`; +} + +const base = JSON.parse(await readFile(baseFile, 'utf8')); +const head = JSON.parse(await readFile(headFile, 'utf8')); +const lines = [ + '## Backend Memory Usage Report', + '', +]; + +for (const phase of phases) { + lines.push(`### ${phase.title}`); + lines.push(renderTable(base, head, phase.key)); + lines.push(''); +} + +const warningMetric = getWarningMetric(base, head); +const warningDiffPercent = warningMetric == null ? null : getDiffPercent(base, head, 'afterGc', warningMetric); +if (warningMetric != null && warningDiffPercent != null && warningDiffPercent > 5) { + lines.push(`⚠️ **Warning**: Memory usage (${warningMetric}) has increased by more than 5%. Please verify this is not an unintended change.`); + lines.push(''); +} + +lines.push(workflowFooter()); + +await writeFile(outputFile, `${lines.join('\n')}\n`); diff --git a/.github/workflows/get-backend-memory.yml b/.github/workflows/get-backend-memory.yml index 0e53ab927d..b210fbfc5e 100644 --- a/.github/workflows/get-backend-memory.yml +++ b/.github/workflows/get-backend-memory.yml @@ -9,6 +9,7 @@ on: paths: - packages/backend/** - packages/misskey-js/** + - .github/scripts/backend-memory-report.mjs - .github/workflows/get-backend-memory.yml - .github/workflows/report-backend-memory.yml diff --git a/.github/workflows/report-backend-memory.yml b/.github/workflows/report-backend-memory.yml index fdff4484de..2795e0c1bc 100644 --- a/.github/workflows/report-backend-memory.yml +++ b/.github/workflows/report-backend-memory.yml @@ -11,9 +11,14 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} permissions: + actions: read + contents: read pull-requests: write steps: + - name: Checkout + uses: actions/checkout@v6.0.2 + - name: Download artifact uses: actions/github-script@v9 with: @@ -48,131 +53,9 @@ jobs: run: cat ./artifacts/memory-base.json - name: Output head run: cat ./artifacts/memory-head.json - - name: Compare memory usage - id: compare - run: | - BASE_MEMORY=$(cat ./artifacts/memory-base.json) - HEAD_MEMORY=$(cat ./artifacts/memory-head.json) - - variation() { - calc() { - BASE=$(echo "$BASE_MEMORY" | jq -r ".${1}.${2} // empty") - HEAD=$(echo "$HEAD_MEMORY" | jq -r ".${1}.${2} // empty") - - if [ -z "$BASE" ] || [ -z "$HEAD" ]; then - echo "null" - return - fi - - DIFF=$((HEAD - BASE)) - if [ "$BASE" -gt 0 ]; then - DIFF_PERCENT=$(awk -v diff="$DIFF" -v base="$BASE" 'BEGIN { printf "%.2f", (diff * 100) / base }') - else - DIFF_PERCENT=0.00 - fi - - # Convert KB to MB for readability - BASE_MB=$(awk -v value="$BASE" 'BEGIN { printf "%.2f", value / 1024 }') - HEAD_MB=$(awk -v value="$HEAD" 'BEGIN { printf "%.2f", value / 1024 }') - DIFF_MB=$(awk -v value="$DIFF" 'BEGIN { printf "%.2f", value / 1024 }') - - JSON=$(jq -c -n \ - --argjson base "$BASE_MB" \ - --argjson head "$HEAD_MB" \ - --argjson diff "$DIFF_MB" \ - --argjson diff_percent "$DIFF_PERCENT" \ - '{base: $base, head: $head, diff: $diff, diff_percent: $diff_percent}') - - echo "$JSON" - } - - JSON=$(jq -c -n \ - --argjson HeapUsed "$(calc $1 HeapUsed)" \ - --argjson HeapTotal "$(calc $1 HeapTotal)" \ - --argjson External "$(calc $1 External)" \ - --argjson ArrayBuffers "$(calc $1 ArrayBuffers)" \ - --argjson Pss "$(calc $1 Pss)" \ - --argjson Private_Dirty "$(calc $1 Private_Dirty)" \ - --argjson Private_Clean "$(calc $1 Private_Clean)" \ - --argjson Shared_Dirty "$(calc $1 Shared_Dirty)" \ - --argjson Shared_Clean "$(calc $1 Shared_Clean)" \ - --argjson VmRSS "$(calc $1 VmRSS)" \ - --argjson VmHWM "$(calc $1 VmHWM)" \ - --argjson VmSize "$(calc $1 VmSize)" \ - --argjson VmData "$(calc $1 VmData)" \ - '{HeapUsed: $HeapUsed, HeapTotal: $HeapTotal, External: $External, ArrayBuffers: $ArrayBuffers, Pss: $Pss, Private_Dirty: $Private_Dirty, Private_Clean: $Private_Clean, Shared_Dirty: $Shared_Dirty, Shared_Clean: $Shared_Clean, VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize, VmData: $VmData}') - - echo "$JSON" - } - - JSON=$(jq -c -n \ - --argjson beforeGc "$(variation beforeGc)" \ - --argjson afterGc "$(variation afterGc)" \ - --argjson afterRequest "$(variation afterRequest)" \ - '{beforeGc: $beforeGc, afterGc: $afterGc, afterRequest: $afterRequest}') - - echo "res=$JSON" >> "$GITHUB_OUTPUT" - id: build-comment name: Build memory comment - env: - RES: ${{ steps.compare.outputs.res }} - run: | - HEADER="## Backend Memory Usage Report" - FOOTER="[See workflow logs for details](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})" - - echo "$HEADER" > ./output.md - echo >> ./output.md - - table() { - echo "| Metric | Base | Head | Δ | Δ (%) |" >> ./output.md - echo "|--------|------:|------:|------:|------:|" >> ./output.md - - line() { - if [ "$(echo "$RES" | jq -r ".${1}.${2} == null")" = "true" ]; then - return - fi - - METRIC=$2 - BASE=$(echo "$RES" | jq -r ".${1}.${2}.base") - HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head") - DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff") - DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent") - - if (( $(echo "$DIFF_PERCENT > 0" | bc -l) )); then - DIFF="+$DIFF" - DIFF_PERCENT="+$DIFF_PERCENT" - fi - - echo "| ${METRIC} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB | ${DIFF_PERCENT}% |" >> ./output.md - } - - line $1 HeapUsed - line $1 HeapTotal - line $1 External - line $1 Pss - line $1 Private_Dirty - } - - #echo "### Before GC" >> ./output.md - #table beforeGc - #echo >> ./output.md - - echo "### After GC" >> ./output.md - table afterGc - echo >> ./output.md - - #echo "### After Request" >> ./output.md - #table afterRequest - #echo >> ./output.md - - # Determine if this is a significant change (more than 5% increase) - WARNING_METRIC=$(echo "$RES" | jq -r 'if .afterGc.Pss != null then "Pss" elif .afterGc.VmRSS != null then "VmRSS" else empty end') - if [ -n "$WARNING_METRIC" ] && [ "$(echo "$RES" | jq -r ".afterGc.${WARNING_METRIC}.diff_percent | tonumber > 5")" = "true" ]; then - echo "⚠️ **Warning**: Memory usage (${WARNING_METRIC}) has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md - echo >> ./output.md - fi - - echo "$FOOTER" >> ./output.md + run: node .github/scripts/backend-memory-report.mjs ./artifacts/memory-base.json ./artifacts/memory-head.json ./output.md - uses: thollander/actions-comment-pull-request@v3 with: pr-number: ${{ steps.load-pr-num.outputs.pr-number }}