1
2
3
4
5 package trace
6
7 import (
8 "bufio"
9 "bytes"
10 "cmp"
11 "encoding/binary"
12 "fmt"
13 "io"
14 "slices"
15 "strings"
16
17 "internal/trace/v2/event"
18 "internal/trace/v2/event/go122"
19 )
20
21
22
23
24
25 type generation struct {
26 gen uint64
27 batches map[ThreadID][]batch
28 cpuSamples []cpuSample
29 *evTable
30 }
31
32
33
34
35 type spilledBatch struct {
36 gen uint64
37 *batch
38 }
39
40
41
42
43
44 func readGeneration(r *bufio.Reader, spill *spilledBatch) (*generation, *spilledBatch, error) {
45 g := &generation{
46 evTable: new(evTable),
47 batches: make(map[ThreadID][]batch),
48 }
49
50 if spill != nil {
51 g.gen = spill.gen
52 if err := processBatch(g, *spill.batch); err != nil {
53 return nil, nil, err
54 }
55 spill = nil
56 }
57
58
59 for {
60 b, gen, err := readBatch(r)
61 if err == io.EOF {
62 break
63 }
64 if err != nil {
65 return nil, nil, err
66 }
67 if gen == 0 {
68
69 return nil, nil, fmt.Errorf("invalid generation number %d", gen)
70 }
71 if g.gen == 0 {
72
73 g.gen = gen
74 }
75 if gen == g.gen+1 {
76 spill = &spilledBatch{gen: gen, batch: &b}
77 break
78 }
79 if gen != g.gen {
80
81
82
83
84
85
86
87 return nil, nil, fmt.Errorf("generations out of order")
88 }
89 if err := processBatch(g, b); err != nil {
90 return nil, nil, err
91 }
92 }
93
94
95 if g.freq == 0 {
96 return nil, nil, fmt.Errorf("no frequency event found")
97 }
98
99
100
101
102
103
104
105 g.stacks.compactify()
106 g.strings.compactify()
107
108
109 if err := validateStackStrings(&g.stacks, &g.strings); err != nil {
110 return nil, nil, err
111 }
112
113
114 for i := range g.cpuSamples {
115 s := &g.cpuSamples[i]
116 s.time = g.freq.mul(timestamp(s.time))
117 }
118
119 slices.SortFunc(g.cpuSamples, func(a, b cpuSample) int {
120 return cmp.Compare(a.time, b.time)
121 })
122 return g, spill, nil
123 }
124
125
126 func processBatch(g *generation, b batch) error {
127 switch {
128 case b.isStringsBatch():
129 if err := addStrings(&g.strings, b); err != nil {
130 return err
131 }
132 case b.isStacksBatch():
133 if err := addStacks(&g.stacks, b); err != nil {
134 return err
135 }
136 case b.isCPUSamplesBatch():
137 samples, err := addCPUSamples(g.cpuSamples, b)
138 if err != nil {
139 return err
140 }
141 g.cpuSamples = samples
142 case b.isFreqBatch():
143 freq, err := parseFreq(b)
144 if err != nil {
145 return err
146 }
147 if g.freq != 0 {
148 return fmt.Errorf("found multiple frequency events")
149 }
150 g.freq = freq
151 default:
152 g.batches[b.m] = append(g.batches[b.m], b)
153 }
154 return nil
155 }
156
157
158
159 func validateStackStrings(stacks *dataTable[stackID, stack], strings *dataTable[stringID, string]) error {
160 var err error
161 stacks.forEach(func(id stackID, stk stack) bool {
162 for _, frame := range stk.frames {
163 _, ok := strings.get(frame.funcID)
164 if !ok {
165 err = fmt.Errorf("found invalid func string ID %d for stack %d", frame.funcID, id)
166 return false
167 }
168 _, ok = strings.get(frame.fileID)
169 if !ok {
170 err = fmt.Errorf("found invalid file string ID %d for stack %d", frame.fileID, id)
171 return false
172 }
173 }
174 return true
175 })
176 return err
177 }
178
179
180
181
182 func addStrings(stringTable *dataTable[stringID, string], b batch) error {
183 if !b.isStringsBatch() {
184 return fmt.Errorf("internal error: addStrings called on non-string batch")
185 }
186 r := bytes.NewReader(b.data)
187 hdr, err := r.ReadByte()
188 if err != nil || event.Type(hdr) != go122.EvStrings {
189 return fmt.Errorf("missing strings batch header")
190 }
191
192 var sb strings.Builder
193 for r.Len() != 0 {
194
195 ev, err := r.ReadByte()
196 if err != nil {
197 return err
198 }
199 if event.Type(ev) != go122.EvString {
200 return fmt.Errorf("expected string event, got %d", ev)
201 }
202
203
204 id, err := binary.ReadUvarint(r)
205 if err != nil {
206 return err
207 }
208
209
210 len, err := binary.ReadUvarint(r)
211 if err != nil {
212 return err
213 }
214 if len > go122.MaxStringSize {
215 return fmt.Errorf("invalid string size %d, maximum is %d", len, go122.MaxStringSize)
216 }
217
218
219 n, err := io.CopyN(&sb, r, int64(len))
220 if n != int64(len) {
221 return fmt.Errorf("failed to read full string: read %d but wanted %d", n, len)
222 }
223 if err != nil {
224 return fmt.Errorf("copying string data: %w", err)
225 }
226
227
228 s := sb.String()
229 sb.Reset()
230 if err := stringTable.insert(stringID(id), s); err != nil {
231 return err
232 }
233 }
234 return nil
235 }
236
237
238
239
240 func addStacks(stackTable *dataTable[stackID, stack], b batch) error {
241 if !b.isStacksBatch() {
242 return fmt.Errorf("internal error: addStacks called on non-stacks batch")
243 }
244 r := bytes.NewReader(b.data)
245 hdr, err := r.ReadByte()
246 if err != nil || event.Type(hdr) != go122.EvStacks {
247 return fmt.Errorf("missing stacks batch header")
248 }
249
250 for r.Len() != 0 {
251
252 ev, err := r.ReadByte()
253 if err != nil {
254 return err
255 }
256 if event.Type(ev) != go122.EvStack {
257 return fmt.Errorf("expected stack event, got %d", ev)
258 }
259
260
261 id, err := binary.ReadUvarint(r)
262 if err != nil {
263 return err
264 }
265
266
267 nFrames, err := binary.ReadUvarint(r)
268 if err != nil {
269 return err
270 }
271 if nFrames > go122.MaxFramesPerStack {
272 return fmt.Errorf("invalid stack size %d, maximum is %d", nFrames, go122.MaxFramesPerStack)
273 }
274
275
276 frames := make([]frame, 0, nFrames)
277 for i := uint64(0); i < nFrames; i++ {
278
279 pc, err := binary.ReadUvarint(r)
280 if err != nil {
281 return fmt.Errorf("reading frame %d's PC for stack %d: %w", i+1, id, err)
282 }
283 funcID, err := binary.ReadUvarint(r)
284 if err != nil {
285 return fmt.Errorf("reading frame %d's funcID for stack %d: %w", i+1, id, err)
286 }
287 fileID, err := binary.ReadUvarint(r)
288 if err != nil {
289 return fmt.Errorf("reading frame %d's fileID for stack %d: %w", i+1, id, err)
290 }
291 line, err := binary.ReadUvarint(r)
292 if err != nil {
293 return fmt.Errorf("reading frame %d's line for stack %d: %w", i+1, id, err)
294 }
295 frames = append(frames, frame{
296 pc: pc,
297 funcID: stringID(funcID),
298 fileID: stringID(fileID),
299 line: line,
300 })
301 }
302
303
304 if err := stackTable.insert(stackID(id), stack{frames: frames}); err != nil {
305 return err
306 }
307 }
308 return nil
309 }
310
311
312
313
314 func addCPUSamples(samples []cpuSample, b batch) ([]cpuSample, error) {
315 if !b.isCPUSamplesBatch() {
316 return nil, fmt.Errorf("internal error: addStrings called on non-string batch")
317 }
318 r := bytes.NewReader(b.data)
319 hdr, err := r.ReadByte()
320 if err != nil || event.Type(hdr) != go122.EvCPUSamples {
321 return nil, fmt.Errorf("missing CPU samples batch header")
322 }
323
324 for r.Len() != 0 {
325
326 ev, err := r.ReadByte()
327 if err != nil {
328 return nil, err
329 }
330 if event.Type(ev) != go122.EvCPUSample {
331 return nil, fmt.Errorf("expected CPU sample event, got %d", ev)
332 }
333
334
335 ts, err := binary.ReadUvarint(r)
336 if err != nil {
337 return nil, err
338 }
339
340
341 m, err := binary.ReadUvarint(r)
342 if err != nil {
343 return nil, err
344 }
345 mid := ThreadID(m)
346
347
348 p, err := binary.ReadUvarint(r)
349 if err != nil {
350 return nil, err
351 }
352 pid := ProcID(p)
353
354
355 g, err := binary.ReadUvarint(r)
356 if err != nil {
357 return nil, err
358 }
359 goid := GoID(g)
360 if g == 0 {
361 goid = NoGoroutine
362 }
363
364
365 s, err := binary.ReadUvarint(r)
366 if err != nil {
367 return nil, err
368 }
369
370
371 samples = append(samples, cpuSample{
372 schedCtx: schedCtx{
373 M: mid,
374 P: pid,
375 G: goid,
376 },
377 time: Time(ts),
378 stack: stackID(s),
379 })
380 }
381 return samples, nil
382 }
383
384
385 func parseFreq(b batch) (frequency, error) {
386 if !b.isFreqBatch() {
387 return 0, fmt.Errorf("internal error: parseFreq called on non-frequency batch")
388 }
389 r := bytes.NewReader(b.data)
390 r.ReadByte()
391
392
393 f, err := binary.ReadUvarint(r)
394 if err != nil {
395 return 0, err
396 }
397
398 return frequency(1.0 / (float64(f) / 1e9)), nil
399 }
400
View as plain text