static collectProfileEvents(traceEvents) {
    /** @type {Map<string, CpuProfile>} */
    const profiles = new Map();
    for (const event of traceEvents) {
      if (event.name !== 'Profile' && event.name !== 'ProfileChunk') continue;
      if (typeof event.id !== 'string') continue;

      // `Profile` or `ProfileChunk` can partially define these across multiple events.
      // We'll fallback to empty values and worry about validation in the `synthesizeTraceEvents` phase.
      const cpuProfileArg = event.args.data?.cpuProfile || {};
      const timeDeltas = event.args.data?.timeDeltas || cpuProfileArg.timeDeltas;
      let profile = profiles.get(event.id);

      if (event.name === 'Profile') {
        profile = {
          id: event.id,
          pid: event.pid,
          tid: event.tid,
          startTime: event.args.data?.startTime || event.ts,
          nodes: cpuProfileArg.nodes || [],
          samples: cpuProfileArg.samples || [],
          timeDeltas: timeDeltas || [],
        };
      } else {
        if (!profile) continue;
        profile.nodes.push(...(cpuProfileArg.nodes || []));
        profile.samples.push(...(cpuProfileArg.samples || []));
        profile.timeDeltas.push(...(timeDeltas || []));
      }

      profiles.set(profile.id, profile);
    }

    return Array.from(profiles.values());
  }
Merges the data of all the `ProfileChunk` trace events into a single CpuProfile object for consumption
by `synthesizeTraceEvents()`.

@param {Array<LH.TraceEvent>} traceEvents
@return {Array<CpuProfile>}