mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-28 22:44:35 +02:00
refactor(dev): refactor of backend memory comparison workflow (#17619)
* refactor(dev): refactor of backend memory comparison workflow * fix
This commit is contained in:
179
.github/scripts/utility.mts
vendored
Normal file
179
.github/scripts/utility.mts
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export const heapSnapshotCategories = [
|
||||
'Total',
|
||||
'Code',
|
||||
'Strings',
|
||||
'JS arrays',
|
||||
'Typed arrays',
|
||||
'System objects',
|
||||
'Other JS objects',
|
||||
'Other non-JS objects',
|
||||
] as const;
|
||||
|
||||
export function median(values: number[]) {
|
||||
const sorted = values.toSorted((a, b) => a - b);
|
||||
const center = Math.floor(sorted.length / 2);
|
||||
if (sorted.length % 2 === 1) return sorted[center];
|
||||
return Math.round((sorted[center - 1] + sorted[center]) / 2);
|
||||
}
|
||||
|
||||
export function mad(values: number[]) {
|
||||
if (values.length < 2) return null;
|
||||
|
||||
const center = median(values);
|
||||
return median(values.map(value => Math.abs(value - center)));
|
||||
}
|
||||
|
||||
export function normalizePath(filePath: string) {
|
||||
return filePath.split(path.sep).join('/');
|
||||
}
|
||||
|
||||
export async function fileExists(filePath: string) {
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fileSize(filePath: string) {
|
||||
const stat = await fs.stat(filePath);
|
||||
return stat.size;
|
||||
}
|
||||
|
||||
export async function* traverseDirectory(dir: string): AsyncGenerator<string> {
|
||||
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
yield* traverseDirectory(fullPath);
|
||||
} else if (entry.isFile()) {
|
||||
yield fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function escapeLatex(text: string) {
|
||||
return text
|
||||
.replaceAll('\\', '\\\\')
|
||||
.replaceAll('{', '\\{')
|
||||
.replaceAll('}', '\\}')
|
||||
.replaceAll('%', '\\%');
|
||||
}
|
||||
|
||||
export function formatColoredDelta(text: string, delta: number) {
|
||||
if (delta === 0) return text;
|
||||
const color = delta > 0 ? 'orange' : 'green';
|
||||
const sign = delta > 0 ? '+' : '-';
|
||||
return `$\\color{${color}}{\\text{${sign}${escapeLatex(text)}}}$`;
|
||||
}
|
||||
|
||||
const numberFormatter = new Intl.NumberFormat('en-US', {
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
|
||||
export function formatNumber(value: number) {
|
||||
return numberFormatter.format(value);
|
||||
}
|
||||
|
||||
export function formatBytes(value: number) {
|
||||
if (value === 0) return '0 B';
|
||||
const units = ['B', 'KiB', 'MiB', 'GiB'];
|
||||
let unitIndex = 0;
|
||||
let size = value;
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024;
|
||||
unitIndex += 1;
|
||||
}
|
||||
|
||||
const maximumFractionDigits = size >= 10 || unitIndex === 0 ? 0 : 1;
|
||||
return `${numberFormatter.format(Number(size.toFixed(maximumFractionDigits)))} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
export function calcAndFormatDeltaNumber(before: number, after: number) {
|
||||
if (before == null || after == null) return '-';
|
||||
const delta = after - before;
|
||||
return formatColoredDelta(formatNumber(Math.abs(delta)), delta);
|
||||
}
|
||||
|
||||
export function formatDeltaBytes(deltaBytes: number) {
|
||||
return formatColoredDelta(formatBytes(Math.abs(deltaBytes)), deltaBytes);
|
||||
}
|
||||
|
||||
export function calcAndFormatDeltaBytes(before: number, after: number) {
|
||||
if (before == null || after == null) return '-';
|
||||
const delta = after - before;
|
||||
return formatDeltaBytes(delta);
|
||||
}
|
||||
|
||||
export function formatPercent(value: number) {
|
||||
return `${formatNumber(value)}%`;
|
||||
}
|
||||
|
||||
export function formatDeltaPercent(deltaPercent: number) {
|
||||
if (deltaPercent === 0) return '0%';
|
||||
return formatColoredDelta(formatPercent(Math.abs(deltaPercent)), deltaPercent);
|
||||
}
|
||||
|
||||
export function calcAndFormatDeltaPercent(before: number, after: number) {
|
||||
if (before == null || before === 0 || after == null || after === 0) return '-';
|
||||
const delta = after - before;
|
||||
return formatDeltaPercent(delta / before * 100);
|
||||
}
|
||||
|
||||
export function commandName(command: string) {
|
||||
if (process.platform !== 'win32') return command;
|
||||
if (command === 'pnpm') return 'pnpm.cmd';
|
||||
return command;
|
||||
}
|
||||
|
||||
export function readIntegerEnv(name: string, defaultValue: number, min: number) {
|
||||
const rawValue = process.env[name];
|
||||
if (rawValue == null || rawValue === '') return defaultValue;
|
||||
if (!/^\d+$/.test(rawValue)) throw new Error(`${name} must be an integer`);
|
||||
|
||||
const value = Number(rawValue);
|
||||
if (!Number.isSafeInteger(value) || value < min) throw new Error(`${name} must be >= ${min}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function run(command: string, args: string[], options: { cwd?: string; env?: NodeJS.ProcessEnv; logStdout?: boolean } = {}) {
|
||||
return new Promise<string>((resolvePromise, reject) => {
|
||||
const child = spawn(commandName(command), args, {
|
||||
cwd: options.cwd,
|
||||
env: options.env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', data => {
|
||||
stdout += data;
|
||||
if (options.logStdout) process.stderr.write(data);
|
||||
});
|
||||
|
||||
child.stderr.on('data', data => {
|
||||
stderr += data;
|
||||
process.stderr.write(data);
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
|
||||
child.on('close', code => {
|
||||
if (code === 0) {
|
||||
resolvePromise(stdout);
|
||||
} else {
|
||||
reject(new Error(`${command} ${args.join(' ')} failed with exit code ${code}\n${stderr}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user