Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/i18n-catalog-performance.ts
T

95 lines
3.5 KiB
TypeScript

#!/usr/bin/env tsx
/* eslint-disable no-console */
import { readFileSync } from "node:fs";
import path from "node:path";
import { performance } from "node:perf_hooks";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import { i18nNamespaces } from "../packages/i18n/src/index.ts";
type Measurement = {
readonly locales: readonly string[];
readonly loadMs: number;
readonly rssDeltaMb: number;
readonly sourceMb: number;
};
const baselineLocales = ["de", "en"] as const;
const fullLocales = ["de", "en", "pl", "ru", "uk", "hu"] as const;
const maxAdditionalLoadMs = 250;
const maxAdditionalRssMb = 30;
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const childArgument = process.argv.find((argument) => argument.startsWith("--measure="));
if (childArgument) {
measureChild(childArgument.slice("--measure=".length).split(","));
} else {
const baseline = medianMeasurement(baselineLocales);
const full = medianMeasurement(fullLocales);
const additionalLoadMs = round(Math.max(0, full.loadMs - baseline.loadMs));
const additionalRssMb = round(Math.max(0, full.rssDeltaMb - baseline.rssDeltaMb));
const result = {
baseline,
full,
delta: { loadMs: additionalLoadMs, rssMb: additionalRssMb },
thresholds: { loadMs: maxAdditionalLoadMs, rssMb: maxAdditionalRssMb },
};
console.log(JSON.stringify(result, null, 2));
if (additionalLoadMs > maxAdditionalLoadMs || additionalRssMb > maxAdditionalRssMb) {
console.error("FAIL i18n catalog performance: switch to locale-scoped cached loading before release.");
process.exit(1);
}
console.log("PASS i18n catalog performance");
}
function measureChild(locales: readonly string[]): never {
global.gc?.();
const rssBefore = process.memoryUsage().rss;
const startedAt = performance.now();
let sourceBytes = 0;
const resources: Record<string, Record<string, unknown>> = {};
for (const locale of locales.flatMap((candidate) => [candidate, `${candidate}-formal`])) {
resources[locale] = {};
for (const namespace of i18nNamespaces) {
const source = readFileSync(path.join(root, "apps/web/public/locales", locale, `${namespace}.json`), "utf8");
sourceBytes += Buffer.byteLength(source);
resources[locale]![namespace] = JSON.parse(source) as unknown;
}
}
const loadMs = performance.now() - startedAt;
global.gc?.();
const rssAfter = process.memoryUsage().rss;
if (Object.keys(resources).length === 0) throw new Error("No i18n resources loaded.");
const result: Measurement = {
locales,
loadMs: round(loadMs),
rssDeltaMb: round((rssAfter - rssBefore) / 1024 / 1024),
sourceMb: round(sourceBytes / 1024 / 1024),
};
process.stdout.write(JSON.stringify(result));
process.exit(0);
}
function medianMeasurement(locales: readonly string[]): Measurement {
const samples = Array.from({ length: 3 }, () => runMeasurement(locales)).sort((left, right) => left.loadMs - right.loadMs);
return samples[1]!;
}
function runMeasurement(locales: readonly string[]): Measurement {
const result = spawnSync(
process.execPath,
["--expose-gc", "--import", "tsx", fileURLToPath(import.meta.url), `--measure=${locales.join(",")}`],
{ cwd: root, encoding: "utf8" }
);
if (result.status !== 0) throw new Error(result.stderr || `i18n catalog measurement exited with ${result.status}`);
return JSON.parse(result.stdout) as Measurement;
}
function round(value: number): number {
return Math.round(value * 100) / 100;
}