1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package trace
6
7import (
8	"bytes"
9	"encoding/binary"
10	"fmt"
11	"io"
12
13	"internal/trace/event"
14	"internal/trace/event/go122"
15)
16
17// timestamp is an unprocessed timestamp.
18type timestamp uint64
19
20// batch represents a batch of trace events.
21// It is unparsed except for its header.
22type batch struct {
23	m    ThreadID
24	time timestamp
25	data []byte
26	exp  event.Experiment
27}
28
29func (b *batch) isStringsBatch() bool {
30	return b.exp == event.NoExperiment && len(b.data) > 0 && event.Type(b.data[0]) == go122.EvStrings
31}
32
33func (b *batch) isStacksBatch() bool {
34	return b.exp == event.NoExperiment && len(b.data) > 0 && event.Type(b.data[0]) == go122.EvStacks
35}
36
37func (b *batch) isCPUSamplesBatch() bool {
38	return b.exp == event.NoExperiment && len(b.data) > 0 && event.Type(b.data[0]) == go122.EvCPUSamples
39}
40
41func (b *batch) isFreqBatch() bool {
42	return b.exp == event.NoExperiment && len(b.data) > 0 && event.Type(b.data[0]) == go122.EvFrequency
43}
44
45// readBatch reads the next full batch from r.
46func readBatch(r interface {
47	io.Reader
48	io.ByteReader
49}) (batch, uint64, error) {
50	// Read batch header byte.
51	b, err := r.ReadByte()
52	if err != nil {
53		return batch{}, 0, err
54	}
55	if typ := event.Type(b); typ != go122.EvEventBatch && typ != go122.EvExperimentalBatch {
56		return batch{}, 0, fmt.Errorf("expected batch event, got %s", go122.EventString(typ))
57	}
58
59	// Read the experiment of we have one.
60	exp := event.NoExperiment
61	if event.Type(b) == go122.EvExperimentalBatch {
62		e, err := r.ReadByte()
63		if err != nil {
64			return batch{}, 0, err
65		}
66		exp = event.Experiment(e)
67	}
68
69	// Read the batch header: gen (generation), thread (M) ID, base timestamp
70	// for the batch.
71	gen, err := binary.ReadUvarint(r)
72	if err != nil {
73		return batch{}, gen, fmt.Errorf("error reading batch gen: %w", err)
74	}
75	m, err := binary.ReadUvarint(r)
76	if err != nil {
77		return batch{}, gen, fmt.Errorf("error reading batch M ID: %w", err)
78	}
79	ts, err := binary.ReadUvarint(r)
80	if err != nil {
81		return batch{}, gen, fmt.Errorf("error reading batch timestamp: %w", err)
82	}
83
84	// Read in the size of the batch to follow.
85	size, err := binary.ReadUvarint(r)
86	if err != nil {
87		return batch{}, gen, fmt.Errorf("error reading batch size: %w", err)
88	}
89	if size > go122.MaxBatchSize {
90		return batch{}, gen, fmt.Errorf("invalid batch size %d, maximum is %d", size, go122.MaxBatchSize)
91	}
92
93	// Copy out the batch for later processing.
94	var data bytes.Buffer
95	data.Grow(int(size))
96	n, err := io.CopyN(&data, r, int64(size))
97	if n != int64(size) {
98		return batch{}, gen, fmt.Errorf("failed to read full batch: read %d but wanted %d", n, size)
99	}
100	if err != nil {
101		return batch{}, gen, fmt.Errorf("copying batch data: %w", err)
102	}
103
104	// Return the batch.
105	return batch{
106		m:    ThreadID(m),
107		time: timestamp(ts),
108		data: data.Bytes(),
109		exp:  exp,
110	}, gen, nil
111}
112