|
465 | 465 | */ |
466 | 466 | function buildWorkerSpans(events, workerIds, maxTs) { |
467 | 467 | const workerSpans = {}; |
468 | | - const openPoll = {}, openPark = {}, openUnpark = {}; |
| 468 | + const openPoll = {}, |
| 469 | + openPark = {}, |
| 470 | + openUnpark = {}; |
469 | 471 | const openPollMeta = {}; |
470 | 472 | for (const w of workerIds) { |
471 | | - workerSpans[w] = { polls: [], parks: [], actives: [], cpuSampleTimes: [] }; |
| 473 | + workerSpans[w] = { |
| 474 | + polls: [], |
| 475 | + parks: [], |
| 476 | + actives: [], |
| 477 | + cpuSampleTimes: [], |
| 478 | + }; |
472 | 479 | } |
473 | 480 |
|
474 | 481 | // Group events by worker and sort per-worker by timestamp |
475 | 482 | const perWorker = {}; |
476 | 483 | for (const e of events) { |
477 | | - if (e.eventType !== EVENT_TYPES.QueueSample && e.eventType !== EVENT_TYPES.WakeEvent) { |
| 484 | + if ( |
| 485 | + e.eventType !== EVENT_TYPES.QueueSample && |
| 486 | + e.eventType !== EVENT_TYPES.WakeEvent |
| 487 | + ) { |
478 | 488 | (perWorker[e.workerId] ??= []).push(e); |
479 | 489 | } |
480 | 490 | } |
|
486 | 496 | for (const e of wEvents) { |
487 | 497 | if (e.eventType === EVENT_TYPES.PollStart) { |
488 | 498 | openPoll[w] = e.timestamp; |
489 | | - openPollMeta[w] = { taskId: e.taskId, spawnLocId: e.spawnLocId, spawnLoc: e.spawnLoc }; |
| 499 | + openPollMeta[w] = { |
| 500 | + taskId: e.taskId, |
| 501 | + spawnLocId: e.spawnLocId, |
| 502 | + spawnLoc: e.spawnLoc, |
| 503 | + }; |
490 | 504 | } else if (e.eventType === EVENT_TYPES.PollEnd) { |
491 | 505 | if (openPoll[w] != null) { |
492 | | - const meta = openPollMeta[w] || { taskId: 0, spawnLocId: 0, spawnLoc: null }; |
| 506 | + const meta = openPollMeta[w] || { |
| 507 | + taskId: 0, |
| 508 | + spawnLocId: 0, |
| 509 | + spawnLoc: null, |
| 510 | + }; |
493 | 511 | workerSpans[w].polls.push({ |
494 | 512 | start: openPoll[w], |
495 | 513 | end: e.timestamp, |
|
504 | 522 | if (openUnpark[w] != null) { |
505 | 523 | const wallDelta = e.timestamp - openUnpark[w].timestamp; |
506 | 524 | const cpuDelta = e.cpuTime - openUnpark[w].cpuTime; |
507 | | - const ratio = wallDelta > 0 ? Math.min(cpuDelta / wallDelta, 1.0) : 1.0; |
| 525 | + const ratio = |
| 526 | + wallDelta > 0 ? Math.min(cpuDelta / wallDelta, 1.0) : 1.0; |
508 | 527 | workerSpans[w].actives.push({ |
509 | 528 | start: openUnpark[w].timestamp, |
510 | 529 | end: e.timestamp, |
|
542 | 561 | return { workerSpans, perWorker, queueSamples }; |
543 | 562 | } |
544 | 563 |
|
| 564 | + /** |
| 565 | + * Attach CPU samples to the poll spans they fall within using binary search. |
| 566 | + * Mutates workerSpans poll objects (adds .cpuSamples[], .schedSamples[]) |
| 567 | + * and sample objects (sets .spawnLoc). |
| 568 | + * @param {CpuSample[]} cpuSamples |
| 569 | + * @param {Object} workerSpans |
| 570 | + * @returns {{ pollsWithCpuSamples: number, pollsWithSchedSamples: number }} |
| 571 | + */ |
| 572 | + function attachCpuSamples(cpuSamples, workerSpans) { |
| 573 | + for (const sample of cpuSamples) { |
| 574 | + const spans = workerSpans[sample.workerId]; |
| 575 | + if (!spans) { |
| 576 | + sample.spawnLoc = null; |
| 577 | + continue; |
| 578 | + } |
| 579 | + if (sample.source !== 1) spans.cpuSampleTimes.push(sample.timestamp); |
| 580 | + const polls = spans.polls; |
| 581 | + const ts = sample.timestamp; |
| 582 | + // Binary search for rightmost poll with start <= ts |
| 583 | + let lo = 0, |
| 584 | + hi = polls.length - 1, |
| 585 | + found = false; |
| 586 | + while (lo <= hi) { |
| 587 | + const mid = (lo + hi) >> 1; |
| 588 | + if (polls[mid].start <= ts) { |
| 589 | + lo = mid + 1; |
| 590 | + } else { |
| 591 | + hi = mid - 1; |
| 592 | + } |
| 593 | + } |
| 594 | + // hi is now the index of the last poll with start <= ts |
| 595 | + if (hi >= 0 && ts <= polls[hi].end) { |
| 596 | + const poll = polls[hi]; |
| 597 | + if (sample.source === 1) { |
| 598 | + (poll.schedSamples ??= []).push(sample); |
| 599 | + } else { |
| 600 | + (poll.cpuSamples ??= []).push(sample); |
| 601 | + } |
| 602 | + sample.spawnLoc = poll.spawnLoc; |
| 603 | + found = true; |
| 604 | + } |
| 605 | + if (!found) sample.spawnLoc = null; |
| 606 | + } |
| 607 | + |
| 608 | + let pollsWithCpuSamples = 0; |
| 609 | + let pollsWithSchedSamples = 0; |
| 610 | + for (const w of Object.keys(workerSpans)) { |
| 611 | + for (const p of workerSpans[w].polls) { |
| 612 | + if (p.cpuSamples) pollsWithCpuSamples++; |
| 613 | + if (p.schedSamples) pollsWithSchedSamples++; |
| 614 | + } |
| 615 | + } |
| 616 | + return { pollsWithCpuSamples, pollsWithSchedSamples }; |
| 617 | + } |
| 618 | + |
545 | 619 | // Export for both browser and Node.js |
546 | 620 | if (typeof module !== "undefined" && module.exports) { |
547 | 621 | module.exports = { |
|
551 | 625 | symbolizeChain, |
552 | 626 | deduplicateSamples, |
553 | 627 | buildWorkerSpans, |
| 628 | + attachCpuSamples, |
554 | 629 | }; |
555 | 630 | } else { |
556 | 631 | exports.TraceParser = { |
|
560 | 635 | symbolizeChain, |
561 | 636 | deduplicateSamples, |
562 | 637 | buildWorkerSpans, |
| 638 | + attachCpuSamples, |
563 | 639 | }; |
564 | 640 | } |
565 | 641 | })(typeof exports === "undefined" ? this : exports); |
0 commit comments