diff --git a/load-generator/scenarios/guest-login-home-catalog-nfs.spec.ts b/load-generator/scenarios/guest-login-home-catalog-nfs.spec.ts index 7f471af..e5bb5d2 100644 --- a/load-generator/scenarios/guest-login-home-catalog-nfs.spec.ts +++ b/load-generator/scenarios/guest-login-home-catalog-nfs.spec.ts @@ -73,6 +73,6 @@ for (let i = 1; i <= loops; i++) { ).toBeVisible(); }); - networkListeners.logStatsAndErrors(); + await networkListeners.logStatsAndErrors(); }); } diff --git a/load-generator/scenarios/guest-login-home-catalog.spec.ts b/load-generator/scenarios/guest-login-home-catalog.spec.ts index 066be19..847e519 100644 --- a/load-generator/scenarios/guest-login-home-catalog.spec.ts +++ b/load-generator/scenarios/guest-login-home-catalog.spec.ts @@ -58,6 +58,6 @@ for (let i = 1; i <= loops; i++) { await expect(backstage.content.getByText('Example User List')).toBeVisible(); }); - networkListeners.logStatsAndErrors(); + await networkListeners.logStatsAndErrors(); }); } diff --git a/load-generator/scenarios/network-stats.ts b/load-generator/scenarios/network-stats.ts index 06fef4f..2c9d764 100644 --- a/load-generator/scenarios/network-stats.ts +++ b/load-generator/scenarios/network-stats.ts @@ -21,28 +21,50 @@ export const addNetworkListeners = async (page: Page): Promise => { page.on('requestfinished', (req) => requestsFinished.push(req)); page.on('response', (resp) => responses.push(resp)); - const logStatsAndErrors = () => { + const logStatsAndErrors = async () => { const stats = { consoleMessages: consoleMessages.length, requests: requests.length, requestsFailed: requestsFailed.length, requestsFinished: requestsFinished.length, responses: responses.length, + responseSizeSumKb: 0, + responseSizeMaxKb: 0, + responseTimeSumSec: 0, + responseTimeAvgSec: 0, perDomain: {} as Record, perResourceType: {} as Record, perStatusCode: {} as Record, }; const pageHostname = new URL(page.url()).hostname; - responses.forEach((resp) => { + + const responseTimes: number[] = []; + + for (const resp of responses) { + const req = resp.request(); const url = new URL(resp.url()); const hostname = pageHostname === url.hostname ? 'same-domain' : url.hostname; stats.perDomain[hostname] = (stats.perDomain[hostname] || 0) + 1; - const resourceType = resp.request().resourceType(); + const resourceType = req.resourceType(); stats.perResourceType[resourceType] = (stats.perResourceType[resourceType] || 0) + 1; const statusCode = resp.status(); stats.perStatusCode[statusCode] = (stats.perStatusCode[statusCode] || 0) + 1; - }); + + const sizes = await req.sizes(); + stats.responseSizeSumKb += sizes.responseBodySize / 1024; // Convert to KB + stats.responseSizeMaxKb = Math.max(stats.responseSizeMaxKb, sizes.responseBodySize / 1024); + + const timing = await req.timing(); + if (timing.responseEnd !== -1) { + stats.responseTimeSumSec += timing.responseEnd / 1000; // Convert to seconds + responseTimes.push(timing.responseEnd / 1000); + } + } + stats.responseSizeSumKb = parseFloat(stats.responseSizeSumKb.toFixed(2)); + stats.responseSizeMaxKb = parseFloat(stats.responseSizeMaxKb.toFixed(2)); + stats.responseTimeSumSec = parseFloat(stats.responseTimeSumSec.toFixed(2)); + stats.responseTimeAvgSec = responseTimes.length > 0 ? parseFloat((stats.responseTimeSumSec / responseTimes.length).toFixed(2)) : -1; // Calculate average console.log('Stats:', stats); diff --git a/load-generator/scripts/analyse.mts b/load-generator/scripts/analyse.mts index e4fbf3a..8c4098c 100644 --- a/load-generator/scripts/analyse.mts +++ b/load-generator/scripts/analyse.mts @@ -10,6 +10,25 @@ const report: JSONReport = JSON.parse(readFileSync(filename, 'utf-8')); const stepDurations = new Map(); +function stripAnsi(s: string): string { + return s.replace(/\x1b\[[0-9;]*m/g, ''); +} + +function parseStatsObject(text: string): Record | null { + const clean = stripAnsi(text).trim(); + if (!clean.startsWith('Stats:')) return null; + const objStr = clean.slice('Stats:'.length).trim(); + let json = objStr.replace(/(?<=[\s,{])(\w+)(?=\s*:)/g, '"$1"'); + json = json.replace(/'/g, '"'); + try { + return JSON.parse(json); + } catch { + return null; + } +} + +const networkStats: Record[] = []; + for (const suite of report.suites) { for (const spec of suite.specs) { if (!spec.ok) { @@ -36,6 +55,14 @@ for (const suite of report.suites) { durations.push(result.duration); stepDurations.set('all', durations); } + if (result.stdout) { + for (const entry of result.stdout) { + if (typeof entry === 'object' && 'text' in entry) { + const stats = parseStatsObject((entry as { text: string }).text); + if (stats) networkStats.push(stats); + } + } + } } } } @@ -78,3 +105,60 @@ for (const [title, durations] of stepDurations) { } console.log(''); + +if (networkStats.length > 0) { + const topLevel = new Map(); + const nested = new Map>(); + + for (const stats of networkStats) { + for (const [key, value] of Object.entries(stats)) { + if (typeof value === 'number') { + topLevel.set(key, (topLevel.get(key) ?? 0) + value); + } else if (typeof value === 'object' && value !== null) { + const sub = nested.get(key) ?? new Map(); + for (const [subKey, subValue] of Object.entries(value as Record)) { + sub.set(subKey, (sub.get(subKey) ?? 0) + subValue); + } + nested.set(key, sub); + } + } + } + + const count = networkStats.length; + + console.log('Network Stats (based on %d runs)', count); + console.log( + 'Metric'.padEnd(25), + 'Sum'.padStart(10), + 'Avg'.padStart(10), + ); + console.log('-'.repeat(50)); + + for (const [key, sum] of topLevel) { + console.log( + key.padEnd(25), + String(sum).padStart(10), + (sum / count).toFixed(1).padStart(10), + ); + } + + for (const [category, sums] of nested) { + console.log(''); + console.log(category); + console.log( + 'Key'.padEnd(25), + 'Sum'.padStart(10), + 'Avg'.padStart(10), + ); + console.log('-'.repeat(50)); + for (const [key, sum] of sums) { + console.log( + key.padEnd(25), + String(sum).padStart(10), + (sum / count).toFixed(1).padStart(10), + ); + } + } + + console.log(''); +}