Skip to content

Commit

Permalink
chore: Try to improve perf report (#5891)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S authored Jul 11, 2024
1 parent bd27aa6 commit 4c612ab
Show file tree
Hide file tree
Showing 6 changed files with 7,771 additions and 7,522 deletions.
14,942 changes: 7,471 additions & 7,471 deletions integration-tests/perf/perf.csv

Large diffs are not rendered by default.

32 changes: 31 additions & 1 deletion integration-tests/src/reporter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@ interface CsvRecord {
timestamp: string;
elapsedMs: number;
repo: string;
/** number of files processed */
files: number;
filesWithIssues: number;
issues: number;
errors: number;
platform: string;
usageUser: number;
usageSystem: number;
/** number of kilobytes processed */
kilobytes: number;
}
const csvHeaders = [
'timestamp',
Expand All @@ -41,6 +44,7 @@ const csvHeaders = [
'platform',
'usageUser',
'usageSystem',
'kilobytes',
] as const;

const reformatCsv = false;
Expand Down Expand Up @@ -87,6 +91,7 @@ export function getReporter(_settings: unknown, config?: Config): CSpellReporter
platform: process.platform,
usageUser: usage.user / 1000,
usageSystem: usage.system / 1000,
kilobytes: await getFileSizes([...files]),
};
await writePerfCsvRecord(csvRecord, root);
}
Expand Down Expand Up @@ -182,11 +187,36 @@ async function createCsvFile(csvUrl: URL): Promise<void> {
return fs.writeFile(csvUrl, stringifyCsv(records, { header: true, columns: [...csvHeaders] }));
}

function extractFieldFromCsv(csvRecord: CsvRecord, field: keyof CsvRecord): number | string | undefined {
if (field === 'elapsedMs') return csvRecord[field].toFixed(2);
return csvRecord[field];
}

async function writePerfCsvRecord(csvRecord: CsvRecord, root: vscodeUri.URI): Promise<void> {
const url = getPerfCsvFileUrl(root);

await createCsvFile(url);

const record = csvHeaders.map((key) => csvRecord[key]);
const record = csvHeaders.map((key) => extractFieldFromCsv(csvRecord, key));
await fs.appendFile(url, stringifyCsv([record]));
}

/**
*
* @param files - list of files to get the size of
* @returns total size of all files in kilobytes.
*/
async function getFileSizes(files: string[]): Promise<number> {
let total = 0;

for (const file of files) {
try {
const stat = await fs.stat(file);
total += stat.size;
} catch {
// ignore
}
}

return Math.ceil(total / 1024);
}
123 changes: 100 additions & 23 deletions tools/perf-chart/lib/app.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4390,18 +4390,55 @@ function calcVariance(values, mean) {
return values.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / values.length;
}

// src/text.ts
function inject(template, ...values) {
const strings = template;
const adjValues = [];
for (let i = 0; i < values.length; ++i) {
const prevLines = strings[i].split("\n");
const currLine = prevLines[prevLines.length - 1];
const padLen = padLength(currLine);
const padding = " ".repeat(padLen);
const value = `${values[i]}`;
let pad = "";
const valueLines = [];
for (const line of value.split("\n")) {
valueLines.push(pad + line);
pad = padding;
}
adjValues.push(valueLines.join("\n"));
}
return unindent(String.raw({ raw: strings }, ...adjValues));
}
function padLength(s2) {
return s2.length - s2.trimStart().length;
}
function unindent(str) {
const lines = str.split("\n");
let curPad = str.length;
for (const line of lines) {
if (!line.trim()) continue;
curPad = Math.min(curPad, padLength(line));
}
return lines.map((line) => line.slice(curPad)).join("\n");
}

// src/perfChart.ts
async function perfReport(csvFile) {
const limit = changeDate(/* @__PURE__ */ new Date(), -30).getTime();
const records = (await readCsvData(csvFile)).filter((r) => r.platform === "linux" && r.timestamp >= limit);
const dailyStats = createDailyStats(records);
const data = [...groupBy(records, "repo")].sort((a, b) => a[0].localeCompare(b[0]));
const markdown = `# Performance Report
const markdown = inject`\
# Performance Report
${createPerfTable1(data)}
${createDailyPerfGraph(dailyStats)}
${createPerfTable2(data)}
${createPerfTable1(data)}
`;
${createPerfTable2(data)}
`;
return markdown;
}
async function readCsvData(csvFile) {
Expand All @@ -4424,9 +4461,10 @@ function calcStats(data) {
return { point, avg, min, max, sum, count: values.length, sd, trend };
}
function groupBy(data, key) {
const fn = typeof key === "function" ? key : (d) => d[key];
const map = /* @__PURE__ */ new Map();
for (const d of data) {
const k = d[key];
const k = fn(d);
const group = map.get(k) || [];
group.push(d);
map.set(k, group);
Expand All @@ -4435,8 +4473,10 @@ function groupBy(data, key) {
}
function changeDate(date, deltaDays) {
const d = new Date(date);
d.setUTCDate(d.getUTCDate() + deltaDays);
return d;
const n = d.setUTCHours(0);
const dd = new Date(n + deltaDays * 24 * 60 * 60 * 1e3);
dd.setUTCHours(0, 0, 0, 0);
return dd;
}
function calcAllStats(data) {
return data.map(([_, records]) => calcStats(records));
Expand All @@ -4461,30 +4501,67 @@ function createPerfTable1(data) {
) : "";
return `| ${repo.padEnd(36)} | ${p(s(point, 2), 6)} | ${sp(min)} / ${sp(avg)} / ${sp(max)} | ${sp(sd, 5, 2)} | \`${sdGraph}\` |`;
});
return `
| Repository | Elapsed | Min/Avg/Max | SD | SD Graph |
| ---------- | ------: | ----------- | --: | -------- |
${rows.join("\n")}
`;
return inject`
| Repository | Elapsed | Min/Avg/Max | SD | SD Graph |
| ---------- | ------: | ----------- | --: | -------- |
${rows.join("\n")}
`;
}
function createPerfTable2(data) {
const stats = calcAllStats(data);
const rows = data.map(([repo], i) => {
const rows = data.map(([repo, records], i) => {
const { point, count, trend, sd, avg } = stats[i];
const trendGraph = simpleHistogram(trend, avg - 2 * sd, avg + 3 * sd);
const relChange = (100 * (point - avg) / (avg || 1)).toFixed(2) + "%";
return `| ${repo.padEnd(36)} | ${p(s(point, 2), 6)} | ${p(relChange, 6)} | \`${trendGraph}\` | ${count} |`;
const lastRecord = records[records.length - 1];
const fps = lastRecord?.files ? 1e3 * lastRecord.files / lastRecord.elapsedMs : 0;
return `| ${repo.padEnd(36)} | ${p(s(point, 2), 6)} | ${p(fps.toFixed(2), 6)} | ${p(relChange, 6)} | \`${trendGraph}\` | ${count} |`;
});
return `
| Repository | Elapsed | Rel | Trend | Count |
| ---------- | ------: | ----: | ----- | ----: |
${rows.join("\n")}
return inject`
| Repository | Elapsed | Fps | Rel | Trend | Count |
| ---------- | ------: | ---: | ----: | ----- | ----: |
${rows.join("\n")}
Note:
- Elapsed time is in seconds. The trend graph shows the last 10 runs.
The SD graph shows the current run relative to the average and standard deviation.
- Rel is the relative change from the average.
`;
}
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
function createDailyPerfGraph(dailyStats) {
const bar = dailyStats.map((d) => d.fps.toFixed(2));
const lineMax = dailyStats.map((d) => d.fpsMax.toFixed(2));
const lineMin = dailyStats.map((d) => d.fpsMin.toFixed(2));
const xAxis = dailyStats.map((d) => `${monthNames[d.date.getUTCMonth()]}-${d.date.getUTCDate()}`);
return inject`
## Daily Performance
Note:
- Elapsed time is in seconds. The trend graph shows the last 10 runs.
The SD graph shows the current run relative to the average and standard deviation.
- Rel is the relative change from the average.
`;
${"```mermaid"}
xychart-beta
title Daily Performance
y-axis Files per Second
x-axis [${xAxis.join(", ")}]
bar [${bar.join(", ")}]
line [${lineMax.join(", ")}]
line [${lineMin.join(", ")}]
${"```"}
`;
}
function createDailyStats(data) {
const dailyStats = [];
const recordsByDay = groupBy(data, (r) => new Date(r.timestamp).setUTCHours(0, 0, 0, 0));
const entries = [...recordsByDay.entries()].sort((a, b) => a[0] - b[0]);
for (const [dayTs, records] of entries) {
const date = new Date(dayTs);
const files = records.reduce((sum, r) => sum + r.files, 0);
const elapsedSeconds = records.reduce((sum, r) => sum + r.elapsedMs, 0) / 1e3;
const fps = files / elapsedSeconds;
const fpsMax = Math.max(...records.map((r) => 1e3 * r.files / r.elapsedMs));
const fpsMin = Math.min(...records.map((r) => 1e3 * r.files / r.elapsedMs));
dailyStats.push({ date, files, elapsedSeconds, fps, fpsMax, fpsMin });
}
return dailyStats;
}

// src/app.ts
Expand Down
3 changes: 2 additions & 1 deletion tools/perf-chart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"build:tsc": "tsc -p .",
"clean": "shx rm -rf dist coverage \"*.tsbuildInfo\"",
"clean-build": "pnpm run clean && pnpm run build",
"test-vitest": "vitest run"
"test-vitest": "vitest run",
"watch": "tsc -p . -w"
},
"author": "Jason Dent",
"license": "MIT",
Expand Down
Loading

0 comments on commit 4c612ab

Please sign in to comment.