mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-25 15:35:14 +02:00
chore(dev): tweak frontend browser diagnostics
This commit is contained in:
58
.github/scripts/chrome.mts
vendored
58
.github/scripts/chrome.mts
vendored
@@ -90,53 +90,19 @@ export type TabMemory = {
|
||||
totalBytes: number;
|
||||
};
|
||||
|
||||
export type BrowserConsoleMetrics = {
|
||||
log: number;
|
||||
warn: number;
|
||||
error: number;
|
||||
info: number;
|
||||
};
|
||||
|
||||
export type BrowserDiagnostics = {
|
||||
pageErrorCount: number;
|
||||
console: BrowserConsoleMetrics;
|
||||
console: Record<'log' | 'warning' | 'error' | 'info', number>;
|
||||
};
|
||||
|
||||
export function createBrowserDiagnostics(): BrowserDiagnostics {
|
||||
return {
|
||||
pageErrorCount: 0,
|
||||
console: {
|
||||
log: 0,
|
||||
warn: 0,
|
||||
error: 0,
|
||||
info: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function recordPageError(diagnostics: BrowserDiagnostics) {
|
||||
diagnostics.pageErrorCount += 1;
|
||||
}
|
||||
|
||||
function isBrowserConsoleMetricKey(value: string): value is keyof BrowserConsoleMetrics {
|
||||
return value === 'log' || value === 'warn' || value === 'error' || value === 'info';
|
||||
}
|
||||
|
||||
export function recordConsoleMessageType(diagnostics: BrowserDiagnostics, messageType: string) {
|
||||
const metricKey = messageType === 'warning' ? 'warn' : messageType;
|
||||
if (!isBrowserConsoleMetricKey(metricKey)) return;
|
||||
diagnostics.console[metricKey] += 1;
|
||||
}
|
||||
|
||||
export function summarizeBrowserDiagnostics(samples: BrowserDiagnostics[]): BrowserDiagnostics {
|
||||
if (samples.length === 0) throw new Error('No browser diagnostic samples');
|
||||
const median = (select: (sample: BrowserDiagnostics) => number) => util.median(samples.map(select));
|
||||
|
||||
return {
|
||||
pageErrorCount: median(sample => sample.pageErrorCount),
|
||||
console: {
|
||||
log: median(sample => sample.console.log),
|
||||
warn: median(sample => sample.console.warn),
|
||||
warning: median(sample => sample.console.warning),
|
||||
error: median(sample => sample.console.error),
|
||||
info: median(sample => sample.console.info),
|
||||
},
|
||||
@@ -204,7 +170,10 @@ type PlaywrightBrowserOptions = {
|
||||
export class HeadlessChromeController {
|
||||
public networkRequests: NetworkRequest[] = [];
|
||||
public webSocketConnections: WebSocketConnection[] = [];
|
||||
private readonly diagnostics = createBrowserDiagnostics();
|
||||
private readonly diagnostics = {
|
||||
pageErrorCount: 0,
|
||||
console: {} as Record<string, number | undefined>,
|
||||
};
|
||||
private readonly browser: Browser;
|
||||
private readonly context: BrowserContext;
|
||||
public readonly page: Page;
|
||||
@@ -225,10 +194,14 @@ export class HeadlessChromeController {
|
||||
this.page.setDefaultTimeout(options.scenarioTimeoutMs);
|
||||
this.page.setDefaultNavigationTimeout(options.scenarioTimeoutMs);
|
||||
this.page.on('pageerror', () => {
|
||||
recordPageError(this.diagnostics);
|
||||
this.diagnostics.pageErrorCount++;
|
||||
});
|
||||
this.page.on('console', message => {
|
||||
recordConsoleMessageType(this.diagnostics, message.type());
|
||||
if (this.diagnostics.console[message.type()] == null) {
|
||||
this.diagnostics.console[message.type()] = 1;
|
||||
} else {
|
||||
this.diagnostics.console[message.type()]++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -441,7 +414,12 @@ export class HeadlessChromeController {
|
||||
public collectDiagnostics(): BrowserDiagnostics {
|
||||
return {
|
||||
pageErrorCount: this.diagnostics.pageErrorCount,
|
||||
console: { ...this.diagnostics.console },
|
||||
console: {
|
||||
log: this.diagnostics.console.log ?? 0,
|
||||
warning: this.diagnostics.console.warning ?? 0,
|
||||
error: this.diagnostics.console.error ?? 0,
|
||||
info: this.diagnostics.console.info ?? 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
18
.github/scripts/frontend-browser-report.mts
vendored
18
.github/scripts/frontend-browser-report.mts
vendored
@@ -172,19 +172,8 @@ function getMetric(report: BrowserMeasurement, key: string) {
|
||||
return report.performance.cdpMetrics[key];
|
||||
}
|
||||
|
||||
export function renderBrowserDiagnosticsRows(base: BrowserMetricsReport, head: BrowserMetricsReport, all = false) {
|
||||
return [
|
||||
metricRow('Page errors', base, head, summary => summary.diagnostics.pageErrorCount, sample => sample.diagnostics.pageErrorCount, util.formatNumber, 1, !all),
|
||||
metricRow('Console log', base, head, summary => summary.diagnostics.console.log, sample => sample.diagnostics.console.log, util.formatNumber, 1, !all),
|
||||
metricRow('Console warnings', base, head, summary => summary.diagnostics.console.warn, sample => sample.diagnostics.console.warn, util.formatNumber, 1, !all),
|
||||
metricRow('Console errors', base, head, summary => summary.diagnostics.console.error, sample => sample.diagnostics.console.error, util.formatNumber, 1, !all),
|
||||
metricRow('Console info', base, head, summary => summary.diagnostics.console.info, sample => sample.diagnostics.console.info, util.formatNumber, 1, !all),
|
||||
].filter(row => row != null);
|
||||
}
|
||||
|
||||
function renderSummaryTable(base: BrowserMetricsReport, head: BrowserMetricsReport, all = false) {
|
||||
const rows = [
|
||||
...renderBrowserDiagnosticsRows(base, head, all),
|
||||
//metricRow('Scenario duration', base, head, summary => summary.durationMs, sample => sample.durationMs, formatMs),
|
||||
metricRow('Requests', base, head, summary => summary.network.requestCount, sample => sample.network.requestCount, util.formatNumber, 1, !all),
|
||||
//metricRow('Failed requests', base, head, summary => summary.network.failedRequestCount, sample => sample.network.failedRequestCount, util.formatNumber),
|
||||
@@ -215,7 +204,12 @@ function renderSummaryTable(base: BrowserMetricsReport, head: BrowserMetricsRepo
|
||||
metricRow('WebSocket connections', base, head, summary => summary.network.webSocketConnectionCount, sample => sample.network.webSocketConnectionCount, util.formatNumber, 1, !all),
|
||||
metricRow('WebSocket sent', base, head, summary => summary.network.webSocketSentBytes, sample => sample.network.webSocketSentBytes, util.formatBytes, 10000, !all),
|
||||
metricRow('WebSocket received', base, head, summary => summary.network.webSocketReceivedBytes, sample => sample.network.webSocketReceivedBytes, util.formatBytes, 10000, !all),
|
||||
metricRow('Tab memory', base, head, summary => summary.performance.tabMemory.totalBytes, sample => sample.performance.tabMemory.totalBytes, util.formatBytes, 10000, !all),
|
||||
metricRow('Page errors', base, head, summary => summary.diagnostics.pageErrorCount, sample => sample.diagnostics.pageErrorCount, util.formatNumber, 1, !all),
|
||||
metricRow('Console log', base, head, summary => summary.diagnostics.console.log, sample => sample.diagnostics.console.log, util.formatNumber, 1, !all),
|
||||
metricRow('Console warnings', base, head, summary => summary.diagnostics.console.warn, sample => sample.diagnostics.console.warn, util.formatNumber, 1, !all),
|
||||
metricRow('Console errors', base, head, summary => summary.diagnostics.console.error, sample => sample.diagnostics.console.error, util.formatNumber, 1, !all),
|
||||
metricRow('Console info', base, head, summary => summary.diagnostics.console.info, sample => sample.diagnostics.console.info, util.formatNumber, 1, !all),
|
||||
metricRow('Page-attributed memory', base, head, summary => summary.performance.tabMemory.totalBytes, sample => sample.performance.tabMemory.totalBytes, util.formatBytes, 10000, !all),
|
||||
].filter(row => row != null);
|
||||
|
||||
return [
|
||||
|
||||
98
.github/scripts/frontend-browser-report.test.mts
vendored
98
.github/scripts/frontend-browser-report.test.mts
vendored
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import {
|
||||
createBrowserDiagnostics,
|
||||
recordConsoleMessageType,
|
||||
recordPageError,
|
||||
summarizeBrowserDiagnostics,
|
||||
type BrowserDiagnostics,
|
||||
} from './chrome.mts';
|
||||
import {
|
||||
renderBrowserDiagnosticsRows,
|
||||
type BrowserMeasurement,
|
||||
type BrowserMeasurementSample,
|
||||
type BrowserMetricsReport,
|
||||
} from './frontend-browser-report.mts';
|
||||
|
||||
function diagnostics(pageErrorCount: number, log: number, warn: number, error: number, info: number): BrowserDiagnostics {
|
||||
return {
|
||||
pageErrorCount,
|
||||
console: { log, warn, error, info },
|
||||
};
|
||||
}
|
||||
|
||||
function browserReport(label: 'base' | 'head', values: BrowserDiagnostics[]): BrowserMetricsReport {
|
||||
const samples = values.map((value, index) => ({
|
||||
round: index + 1,
|
||||
diagnostics: value,
|
||||
} as BrowserMeasurementSample));
|
||||
|
||||
return {
|
||||
label,
|
||||
timestamp: '2026-07-16T00:00:00.000Z',
|
||||
url: 'http://127.0.0.1:61812',
|
||||
scenario: 'test',
|
||||
sampleCount: samples.length,
|
||||
aggregation: 'median',
|
||||
summary: {
|
||||
diagnostics: summarizeBrowserDiagnostics(values),
|
||||
} as BrowserMeasurement,
|
||||
samples,
|
||||
};
|
||||
}
|
||||
|
||||
test('records page errors and only the requested console message types', () => {
|
||||
const result = createBrowserDiagnostics();
|
||||
recordPageError(result);
|
||||
recordPageError(result);
|
||||
for (const type of ['log', 'warning', 'error', 'info', 'debug', 'trace']) {
|
||||
recordConsoleMessageType(result, type);
|
||||
}
|
||||
|
||||
assert.deepEqual(result, diagnostics(2, 1, 1, 1, 1));
|
||||
});
|
||||
|
||||
test('summarizes browser diagnostics with a median for every counter', () => {
|
||||
const result = summarizeBrowserDiagnostics([
|
||||
diagnostics(0, 10, 20, 30, 40),
|
||||
diagnostics(4, 14, 24, 34, 44),
|
||||
diagnostics(2, 12, 22, 32, 42),
|
||||
diagnostics(3, 13, 23, 33, 43),
|
||||
diagnostics(1, 11, 21, 31, 41),
|
||||
]);
|
||||
|
||||
assert.deepEqual(result, diagnostics(2, 12, 22, 32, 42));
|
||||
});
|
||||
|
||||
test('renders each changed browser diagnostics metric as a separate row', () => {
|
||||
const base = browserReport('base', Array.from({ length: 5 }, () => diagnostics(0, 0, 0, 0, 0)));
|
||||
const head = browserReport('head', Array.from({ length: 5 }, () => diagnostics(1, 1, 1, 1, 1)));
|
||||
const rows = renderBrowserDiagnosticsRows(base, head).join('\n');
|
||||
|
||||
for (const label of ['Page errors', 'Console log', 'Console warnings', 'Console errors', 'Console info']) {
|
||||
assert.match(rows, new RegExp(`\\*\\*${label}\\*\\*`));
|
||||
}
|
||||
});
|
||||
|
||||
test('renders browser diagnostics deltas from paired rounds instead of summary medians', () => {
|
||||
const base = browserReport('base', [0, 9, 10, 100, 101].map(value => diagnostics(value, 0, 0, 0, 0)));
|
||||
const head = browserReport('head', [10, 100, 101, 0, 9].map(value => diagnostics(value, 0, 0, 0, 0)));
|
||||
const rows = renderBrowserDiagnosticsRows(base, head);
|
||||
|
||||
assert.equal(rows.length, 1);
|
||||
assert.match(rows[0], /\*\*Page errors\*\*/);
|
||||
assert.match(rows[0], /\\text\{\+10\}/);
|
||||
});
|
||||
|
||||
test('omits browser diagnostics rows whose paired median does not change', () => {
|
||||
const values = Array.from({ length: 5 }, () => diagnostics(2, 3, 4, 5, 6));
|
||||
const base = browserReport('base', values);
|
||||
const head = browserReport('head', values);
|
||||
|
||||
assert.deepEqual(renderBrowserDiagnosticsRows(base, head), []);
|
||||
});
|
||||
1
.github/workflows/lint.yml
vendored
1
.github/workflows/lint.yml
vendored
@@ -152,4 +152,3 @@ jobs:
|
||||
node-version-file: '.node-version'
|
||||
- run: node --test .github/scripts/frontend-js-size.test.mts
|
||||
- run: node --test .github/scripts/memory-stability-util.test.mts
|
||||
- run: node --test .github/scripts/frontend-browser-report.test.mts
|
||||
|
||||
Reference in New Issue
Block a user