(fileName, payload)
| 313 | } |
| 314 | |
| 315 | function normalizeSummaryRun(fileName, payload) { |
| 316 | const metrics = payload.metrics || {}; |
| 317 | const httpReqs = metricValues(metrics, "http_reqs"); |
| 318 | const failed = metricValues(metrics, "http_req_failed"); |
| 319 | const checksMetric = metricValues(metrics, "checks"); |
| 320 | const durationMetric = |
| 321 | metricValues(metrics, "http_req_duration{expected_response:true}") || |
| 322 | metricValues(metrics, "http_req_duration"); |
| 323 | |
| 324 | const requestRate = toNumber(httpReqs.rate); |
| 325 | const overview = { |
| 326 | requestsTotal: toNumber(httpReqs.count), |
| 327 | requestRate, |
| 328 | throughputPerMinute: requestRate !== null ? requestRate * 60 : null, |
| 329 | successRate: toNumber(failed.rate) !== null ? clamp01(1 - toNumber(failed.rate)) : null, |
| 330 | checkRate: toNumber(checksMetric.rate), |
| 331 | avgMs: toNumber(durationMetric.avg), |
| 332 | p50Ms: toNumber(durationMetric.med), |
| 333 | p95Ms: toNumber(durationMetric["p(95)"]), |
| 334 | p99Ms: toNumber(durationMetric["p(99)"]), |
| 335 | maxMs: toNumber(durationMetric.max), |
| 336 | vus: toNumber(metricValues(metrics, "vus").value), |
| 337 | vusMax: toNumber(metricValues(metrics, "vus_max").max ?? metricValues(metrics, "vus_max").value), |
| 338 | dataSentBytes: toNumber(metricValues(metrics, "data_sent").count), |
| 339 | dataReceivedBytes: toNumber(metricValues(metrics, "data_received").count), |
| 340 | }; |
| 341 | |
| 342 | const checks = (payload.root_group?.checks || []).map((check) => { |
| 343 | const passes = toNumber(check.passes) || 0; |
| 344 | const fails = toNumber(check.fails) || 0; |
| 345 | const total = passes + fails; |
| 346 | return { |
| 347 | name: check.name || "Unnamed check", |
| 348 | passes, |
| 349 | fails, |
| 350 | rate: total ? passes / total : null, |
| 351 | }; |
| 352 | }); |
| 353 | |
| 354 | const thresholds = []; |
| 355 | Object.entries(metrics).forEach(([metricName, definition]) => { |
| 356 | Object.entries(definition.thresholds || {}).forEach(([label, result]) => { |
| 357 | thresholds.push({ |
| 358 | metric: metricName, |
| 359 | label: `${metricName} ${label}`, |
| 360 | ok: Boolean(result.ok), |
| 361 | }); |
| 362 | }); |
| 363 | }); |
| 364 | |
| 365 | return finalizeRun({ |
| 366 | id: `upload:${fileName}`, |
| 367 | name: fileName.replace(/\.[^.]+$/, ""), |
| 368 | source: fileName, |
| 369 | kind: "k6_summary", |
| 370 | collectedAt: new Date().toISOString(), |
| 371 | durationMs: toNumber(payload.state?.testRunDurationMs), |
| 372 | overview, |
no test coverage detected