func Traces(w io.Writer, filepaths []string) error {
	var (
		fnForPID    = make(map[int]string)
		eventsForFn = make(map[string][]traceinternal.ViewerEvent)
		highestTimestamp     float64
		highestTimestampFile string
	)
	for _, fn := range filepaths {
		events, err := loadTrace(fn)
		if err != nil {
			return err
		}
		eventsForFn[fn] = events
		for _, ev := range events {
			if ev.Time > highestTimestamp {
				highestTimestamp = ev.Time
				highestTimestampFile = fn
			}
		}
		parts := strings.Split(fn, ".")
		if parts[len(parts)-1] != "trace" {
			continue
		}
		pid, err := strconv.Atoi(parts[len(parts)-2])
		if err != nil {
			return err
		}
		fnForPID[pid] = fn
	}
	t := &traceFiles{
		eventsForFn: eventsForFn,
		fnForPID:    fnForPID,
	}
	root, err := t.replacePIDWithEvents(eventsForFn[highestTimestampFile])
	if err != nil {
		return err
	}
	b, err := json.Marshal(&root)
	if err != nil {
		return err
	}
	_, err = w.Write(b)
	return err
}
Traces reads the specified chrome://tracing trace files and combines them
into a whole-system view trace file.
package combine
import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"github.com/google/chrometracing/traceinternal"
)
func loadTrace(path string) ([]traceinternal.ViewerEvent, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	b = bytes.TrimSpace(b)
	b = bytes.TrimSuffix(b, []byte{','})
	if !bytes.HasSuffix(b, []byte{']'}) {
		b = append(b, ']')
	}
	var events []traceinternal.ViewerEvent
	if err := json.Unmarshal(b, &events); err != nil {
		return nil, err
	}
	return events, nil
}
type traceFiles struct {
	eventsForFn map[string][]traceinternal.ViewerEvent
	fnForPID    map[int]string
}
func (t *traceFiles) replacePIDWithEvents(root []traceinternal.ViewerEvent) ([]traceinternal.ViewerEvent, error) {
	replaced := make([]traceinternal.ViewerEvent, 0, len(root))
	for _, ev := range root {
		replaced = append(replaced, ev)
		if !strings.HasPrefix(ev.Name, "pid:") ||
			ev.Phase != "B" {
			continue
		}
		pid, err := strconv.Atoi(strings.TrimPrefix(ev.Name, "pid:"))
		if err != nil {
			return nil, fmt.Errorf("BUG: pid: prefix followed by invalid pid: %q", ev.Name)
		}
		insertEvents := t.eventsForFn[t.fnForPID[pid]]
		for idx, iev := range insertEvents {
			iev.Time = ev.Time + iev.Time
			insertEvents[idx] = iev
		}
		insertEvents, err = t.replacePIDWithEvents(insertEvents)
		if err != nil {
			return nil, err
		}
		replaced = append(replaced, insertEvents...)
	}
	return replaced, nil
}