enhance(dev): Improve mem report (#17119)
* wip * Update report-backend-memory.yml * Update report-backend-memory.yml * Update measure-memory.mjs * Update report-backend-memory.yml
This commit is contained in:
parent
1adcb03b93
commit
a168e7b648
|
|
@ -85,7 +85,8 @@ jobs:
|
||||||
--argjson VmRSS "$(calc $1 VmRSS)" \
|
--argjson VmRSS "$(calc $1 VmRSS)" \
|
||||||
--argjson VmHWM "$(calc $1 VmHWM)" \
|
--argjson VmHWM "$(calc $1 VmHWM)" \
|
||||||
--argjson VmSize "$(calc $1 VmSize)" \
|
--argjson VmSize "$(calc $1 VmSize)" \
|
||||||
'{VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize}')
|
--argjson VmData "$(calc $1 VmData)" \
|
||||||
|
'{VmRSS: $VmRSS, VmHWM: $VmHWM, VmSize: $VmSize, VmData: $VmData}')
|
||||||
|
|
||||||
echo "$JSON"
|
echo "$JSON"
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +94,8 @@ jobs:
|
||||||
JSON=$(jq -c -n \
|
JSON=$(jq -c -n \
|
||||||
--argjson beforeGc "$(variation beforeGc)" \
|
--argjson beforeGc "$(variation beforeGc)" \
|
||||||
--argjson afterGc "$(variation afterGc)" \
|
--argjson afterGc "$(variation afterGc)" \
|
||||||
'{beforeGc: $beforeGc, afterGc: $afterGc}')
|
--argjson afterRequest "$(variation afterRequest)" \
|
||||||
|
'{beforeGc: $beforeGc, afterGc: $afterGc, afterRequest: $afterRequest}')
|
||||||
|
|
||||||
echo "res=$JSON" >> "$GITHUB_OUTPUT"
|
echo "res=$JSON" >> "$GITHUB_OUTPUT"
|
||||||
- id: build-comment
|
- id: build-comment
|
||||||
|
|
@ -108,20 +110,37 @@ jobs:
|
||||||
echo >> ./output.md
|
echo >> ./output.md
|
||||||
|
|
||||||
table() {
|
table() {
|
||||||
|
echo "| Metric | base (MB) | head (MB) | Diff (MB) | Diff (%) |" >> ./output.md
|
||||||
|
echo "|--------|------:|------:|------:|------:|" >> ./output.md
|
||||||
|
|
||||||
line() {
|
line() {
|
||||||
|
METRIC=$2
|
||||||
BASE=$(echo "$RES" | jq -r ".${1}.${2}.base")
|
BASE=$(echo "$RES" | jq -r ".${1}.${2}.base")
|
||||||
HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head")
|
HEAD=$(echo "$RES" | jq -r ".${1}.${2}.head")
|
||||||
DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff")
|
DIFF=$(echo "$RES" | jq -r ".${1}.${2}.diff")
|
||||||
DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent")
|
DIFF_PERCENT=$(echo "$RES" | jq -r ".${1}.${2}.diff_percent")
|
||||||
|
|
||||||
echo "| ${2} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB (${DIFF_PERCENT}%) |" >> ./output.md
|
if (( $(echo "$DIFF_PERCENT > 0" | bc -l) )); then
|
||||||
|
DIFF="+$DIFF"
|
||||||
|
DIFF_PERCENT="+$DIFF_PERCENT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# highlight VmRSS
|
||||||
|
if [ "$2" = "VmRSS" ]; then
|
||||||
|
METRIC="**${METRIC}**"
|
||||||
|
BASE="**${BASE}**"
|
||||||
|
HEAD="**${HEAD}**"
|
||||||
|
DIFF="**${DIFF}**"
|
||||||
|
DIFF_PERCENT="**${DIFF_PERCENT}**"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "| ${METRIC} | ${BASE} MB | ${HEAD} MB | ${DIFF} MB | ${DIFF_PERCENT}% |" >> ./output.md
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "| Metric | base | head | Diff |" >> ./output.md
|
|
||||||
echo "|--------|------|------|------|" >> ./output.md
|
|
||||||
line $1 VmRSS
|
line $1 VmRSS
|
||||||
line $1 VmHWM
|
line $1 VmHWM
|
||||||
line $1 VmSize
|
line $1 VmSize
|
||||||
|
line $1 VmData
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "### Before GC" >> ./output.md
|
echo "### Before GC" >> ./output.md
|
||||||
|
|
@ -132,6 +151,10 @@ jobs:
|
||||||
table afterGc
|
table afterGc
|
||||||
echo >> ./output.md
|
echo >> ./output.md
|
||||||
|
|
||||||
|
echo "### After Request" >> ./output.md
|
||||||
|
table afterRequest
|
||||||
|
echo >> ./output.md
|
||||||
|
|
||||||
# Determine if this is a significant change (more than 5% increase)
|
# Determine if this is a significant change (more than 5% increase)
|
||||||
if [ "$(echo "$RES" | jq -r '.afterGc.VmRSS.diff_percent | tonumber > 5')" = "true" ]; then
|
if [ "$(echo "$RES" | jq -r '.afterGc.VmRSS.diff_percent | tonumber > 5')" = "true" ]; then
|
||||||
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
|
echo "⚠️ **Warning**: Memory usage has increased by more than 5%. Please verify this is not an unintended change." >> ./output.md
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { fork } from 'node:child_process';
|
||||||
import { setTimeout } from 'node:timers/promises';
|
import { setTimeout } from 'node:timers/promises';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
|
import * as http from 'node:http';
|
||||||
import * as fs from 'node:fs/promises';
|
import * as fs from 'node:fs/promises';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -88,6 +89,40 @@ async function measureMemory() {
|
||||||
process.stderr.write(`[server error] ${err}\n`);
|
process.stderr.write(`[server error] ${err}\n`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function triggerGc() {
|
||||||
|
const ok = new Promise((resolve) => {
|
||||||
|
serverProcess.once('message', (message) => {
|
||||||
|
if (message === 'gc ok') resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
serverProcess.send('gc');
|
||||||
|
|
||||||
|
await ok;
|
||||||
|
|
||||||
|
await setTimeout(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRequest() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const req = http.request({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 61812,
|
||||||
|
path: '/api/meta',
|
||||||
|
method: 'POST',
|
||||||
|
}, (res) => {
|
||||||
|
res.on('data', () => { });
|
||||||
|
res.on('end', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
req.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for server to be ready or timeout
|
// Wait for server to be ready or timeout
|
||||||
const startupStartTime = Date.now();
|
const startupStartTime = Date.now();
|
||||||
while (!serverReady) {
|
while (!serverReady) {
|
||||||
|
|
@ -108,18 +143,20 @@ async function measureMemory() {
|
||||||
|
|
||||||
const beforeGc = await getMemoryUsage(pid);
|
const beforeGc = await getMemoryUsage(pid);
|
||||||
|
|
||||||
serverProcess.send('gc');
|
await triggerGc();
|
||||||
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
serverProcess.once('message', (message) => {
|
|
||||||
if (message === 'gc ok') resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await setTimeout(1000);
|
|
||||||
|
|
||||||
const afterGc = await getMemoryUsage(pid);
|
const afterGc = await getMemoryUsage(pid);
|
||||||
|
|
||||||
|
// create some http requests to simulate load
|
||||||
|
const REQUEST_COUNT = 10;
|
||||||
|
await Promise.all(
|
||||||
|
Array.from({ length: REQUEST_COUNT }).map(() => createRequest()),
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerGc();
|
||||||
|
|
||||||
|
const afterRequest = await getMemoryUsage(pid);
|
||||||
|
|
||||||
// Stop the server
|
// Stop the server
|
||||||
serverProcess.kill('SIGTERM');
|
serverProcess.kill('SIGTERM');
|
||||||
|
|
||||||
|
|
@ -143,6 +180,7 @@ async function measureMemory() {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
beforeGc,
|
beforeGc,
|
||||||
afterGc,
|
afterGc,
|
||||||
|
afterRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -159,21 +197,25 @@ async function main() {
|
||||||
// Calculate averages
|
// Calculate averages
|
||||||
const beforeGc = structuredClone(keys);
|
const beforeGc = structuredClone(keys);
|
||||||
const afterGc = structuredClone(keys);
|
const afterGc = structuredClone(keys);
|
||||||
|
const afterRequest = structuredClone(keys);
|
||||||
for (const res of results) {
|
for (const res of results) {
|
||||||
for (const key of Object.keys(keys)) {
|
for (const key of Object.keys(keys)) {
|
||||||
beforeGc[key] += res.beforeGc[key];
|
beforeGc[key] += res.beforeGc[key];
|
||||||
afterGc[key] += res.afterGc[key];
|
afterGc[key] += res.afterGc[key];
|
||||||
|
afterRequest[key] += res.afterRequest[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const key of Object.keys(keys)) {
|
for (const key of Object.keys(keys)) {
|
||||||
beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
|
beforeGc[key] = Math.round(beforeGc[key] / SAMPLE_COUNT);
|
||||||
afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
|
afterGc[key] = Math.round(afterGc[key] / SAMPLE_COUNT);
|
||||||
|
afterRequest[key] = Math.round(afterRequest[key] / SAMPLE_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
beforeGc,
|
beforeGc,
|
||||||
afterGc,
|
afterGc,
|
||||||
|
afterRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output as JSON to stdout
|
// Output as JSON to stdout
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue