1
2
3
4
5 package testtrace
6
7 import (
8 "errors"
9 "fmt"
10 "internal/trace/v2"
11 "slices"
12 "strings"
13 )
14
15
16 type Validator struct {
17 lastTs trace.Time
18 gs map[trace.GoID]*goState
19 ps map[trace.ProcID]*procState
20 ms map[trace.ThreadID]*schedContext
21 ranges map[trace.ResourceID][]string
22 tasks map[trace.TaskID]string
23 seenSync bool
24 }
25
26 type schedContext struct {
27 M trace.ThreadID
28 P trace.ProcID
29 G trace.GoID
30 }
31
32 type goState struct {
33 state trace.GoState
34 binding *schedContext
35 }
36
37 type procState struct {
38 state trace.ProcState
39 binding *schedContext
40 }
41
42
43 func NewValidator() *Validator {
44 return &Validator{
45 gs: make(map[trace.GoID]*goState),
46 ps: make(map[trace.ProcID]*procState),
47 ms: make(map[trace.ThreadID]*schedContext),
48 ranges: make(map[trace.ResourceID][]string),
49 tasks: make(map[trace.TaskID]string),
50 }
51 }
52
53
54
55
56 func (v *Validator) Event(ev trace.Event) error {
57 e := new(errAccumulator)
58
59
60 if v.lastTs != 0 {
61 if ev.Time() <= v.lastTs {
62 e.Errorf("timestamp out-of-order for %+v", ev)
63 } else {
64 v.lastTs = ev.Time()
65 }
66 } else {
67 v.lastTs = ev.Time()
68 }
69
70
71 checkStack(e, ev.Stack())
72
73 switch ev.Kind() {
74 case trace.EventSync:
75
76 v.seenSync = true
77 case trace.EventMetric:
78 m := ev.Metric()
79 if !strings.Contains(m.Name, ":") {
80
81 e.Errorf("invalid metric name %q", m.Name)
82 }
83
84 if m.Value.Kind() == trace.ValueBad {
85 e.Errorf("invalid value")
86 }
87 switch m.Value.Kind() {
88 case trace.ValueUint64:
89
90 _ = m.Value.Uint64()
91 }
92 case trace.EventLabel:
93 l := ev.Label()
94
95
96 if l.Label == "" {
97 e.Errorf("invalid label %q", l.Label)
98 }
99
100
101 if l.Resource.Kind == trace.ResourceNone {
102 e.Errorf("label resource none")
103 }
104 switch l.Resource.Kind {
105 case trace.ResourceGoroutine:
106 id := l.Resource.Goroutine()
107 if _, ok := v.gs[id]; !ok {
108 e.Errorf("label for invalid goroutine %d", id)
109 }
110 case trace.ResourceProc:
111 id := l.Resource.Proc()
112 if _, ok := v.ps[id]; !ok {
113 e.Errorf("label for invalid proc %d", id)
114 }
115 case trace.ResourceThread:
116 id := l.Resource.Thread()
117 if _, ok := v.ms[id]; !ok {
118 e.Errorf("label for invalid thread %d", id)
119 }
120 }
121 case trace.EventStackSample:
122
123
124
125 case trace.EventStateTransition:
126
127
128
129
130
131
132 tr := ev.StateTransition()
133 checkStack(e, tr.Stack)
134 switch tr.Resource.Kind {
135 case trace.ResourceGoroutine:
136
137 id := tr.Resource.Goroutine()
138 old, new := tr.Goroutine()
139 if new == trace.GoUndetermined {
140 e.Errorf("transition to undetermined state for goroutine %d", id)
141 }
142 if v.seenSync && old == trace.GoUndetermined {
143 e.Errorf("undetermined goroutine %d after first global sync", id)
144 }
145 if new == trace.GoNotExist && v.hasAnyRange(trace.MakeResourceID(id)) {
146 e.Errorf("goroutine %d died with active ranges", id)
147 }
148 state, ok := v.gs[id]
149 if ok {
150 if old != state.state {
151 e.Errorf("bad old state for goroutine %d: got %s, want %s", id, old, state.state)
152 }
153 state.state = new
154 } else {
155 if old != trace.GoUndetermined && old != trace.GoNotExist {
156 e.Errorf("bad old state for unregistered goroutine %d: %s", id, old)
157 }
158 state = &goState{state: new}
159 v.gs[id] = state
160 }
161
162 if new.Executing() {
163 ctx := v.getOrCreateThread(e, ev.Thread())
164 if ctx != nil {
165 if ctx.G != trace.NoGoroutine && ctx.G != id {
166 e.Errorf("tried to run goroutine %d when one was already executing (%d) on thread %d", id, ctx.G, ev.Thread())
167 }
168 ctx.G = id
169 state.binding = ctx
170 }
171 } else if old.Executing() && !new.Executing() {
172 if tr.Stack != ev.Stack() {
173
174
175 e.Errorf("StateTransition.Stack doesn't match Event.Stack")
176 }
177 ctx := state.binding
178 if ctx != nil {
179 if ctx.G != id {
180 e.Errorf("tried to stop goroutine %d when it wasn't currently executing (currently executing %d) on thread %d", id, ctx.G, ev.Thread())
181 }
182 ctx.G = trace.NoGoroutine
183 state.binding = nil
184 } else {
185 e.Errorf("stopping goroutine %d not bound to any active context", id)
186 }
187 }
188 case trace.ResourceProc:
189
190 id := tr.Resource.Proc()
191 old, new := tr.Proc()
192 if new == trace.ProcUndetermined {
193 e.Errorf("transition to undetermined state for proc %d", id)
194 }
195 if v.seenSync && old == trace.ProcUndetermined {
196 e.Errorf("undetermined proc %d after first global sync", id)
197 }
198 if new == trace.ProcNotExist && v.hasAnyRange(trace.MakeResourceID(id)) {
199 e.Errorf("proc %d died with active ranges", id)
200 }
201 state, ok := v.ps[id]
202 if ok {
203 if old != state.state {
204 e.Errorf("bad old state for proc %d: got %s, want %s", id, old, state.state)
205 }
206 state.state = new
207 } else {
208 if old != trace.ProcUndetermined && old != trace.ProcNotExist {
209 e.Errorf("bad old state for unregistered proc %d: %s", id, old)
210 }
211 state = &procState{state: new}
212 v.ps[id] = state
213 }
214
215 if new.Executing() {
216 ctx := v.getOrCreateThread(e, ev.Thread())
217 if ctx != nil {
218 if ctx.P != trace.NoProc && ctx.P != id {
219 e.Errorf("tried to run proc %d when one was already executing (%d) on thread %d", id, ctx.P, ev.Thread())
220 }
221 ctx.P = id
222 state.binding = ctx
223 }
224 } else if old.Executing() && !new.Executing() {
225 ctx := state.binding
226 if ctx != nil {
227 if ctx.P != id {
228 e.Errorf("tried to stop proc %d when it wasn't currently executing (currently executing %d) on thread %d", id, ctx.P, ctx.M)
229 }
230 ctx.P = trace.NoProc
231 state.binding = nil
232 } else {
233 e.Errorf("stopping proc %d not bound to any active context", id)
234 }
235 }
236 }
237 case trace.EventRangeBegin, trace.EventRangeActive, trace.EventRangeEnd:
238
239 r := ev.Range()
240 switch ev.Kind() {
241 case trace.EventRangeBegin:
242 if v.hasRange(r.Scope, r.Name) {
243 e.Errorf("already active range %q on %v begun again", r.Name, r.Scope)
244 }
245 v.addRange(r.Scope, r.Name)
246 case trace.EventRangeActive:
247 if !v.hasRange(r.Scope, r.Name) {
248 v.addRange(r.Scope, r.Name)
249 }
250 case trace.EventRangeEnd:
251 if !v.hasRange(r.Scope, r.Name) {
252 e.Errorf("inactive range %q on %v ended", r.Name, r.Scope)
253 }
254 v.deleteRange(r.Scope, r.Name)
255 }
256 case trace.EventTaskBegin:
257
258 t := ev.Task()
259 if t.ID == trace.NoTask || t.ID == trace.BackgroundTask {
260
261 e.Errorf("found invalid task ID for task of type %s", t.Type)
262 }
263 if t.Parent == trace.BackgroundTask {
264
265 e.Errorf("found background task as the parent for task of type %s", t.Type)
266 }
267
268 v.tasks[t.ID] = t.Type
269 case trace.EventTaskEnd:
270
271
272
273 t := ev.Task()
274 if typ, ok := v.tasks[t.ID]; ok {
275 if t.Type != typ {
276 e.Errorf("task end type %q doesn't match task start type %q for task %d", t.Type, typ, t.ID)
277 }
278 delete(v.tasks, t.ID)
279 }
280 case trace.EventLog:
281
282
283
284
285
286 _ = ev.Log()
287 }
288 return e.Errors()
289 }
290
291 func (v *Validator) hasRange(r trace.ResourceID, name string) bool {
292 ranges, ok := v.ranges[r]
293 return ok && slices.Contains(ranges, name)
294 }
295
296 func (v *Validator) addRange(r trace.ResourceID, name string) {
297 ranges, _ := v.ranges[r]
298 ranges = append(ranges, name)
299 v.ranges[r] = ranges
300 }
301
302 func (v *Validator) hasAnyRange(r trace.ResourceID) bool {
303 ranges, ok := v.ranges[r]
304 return ok && len(ranges) != 0
305 }
306
307 func (v *Validator) deleteRange(r trace.ResourceID, name string) {
308 ranges, ok := v.ranges[r]
309 if !ok {
310 return
311 }
312 i := slices.Index(ranges, name)
313 if i < 0 {
314 return
315 }
316 v.ranges[r] = slices.Delete(ranges, i, i+1)
317 }
318
319 func (v *Validator) getOrCreateThread(e *errAccumulator, m trace.ThreadID) *schedContext {
320 if m == trace.NoThread {
321 e.Errorf("must have thread, but thread ID is none")
322 return nil
323 }
324 s, ok := v.ms[m]
325 if !ok {
326 s = &schedContext{M: m, P: trace.NoProc, G: trace.NoGoroutine}
327 v.ms[m] = s
328 return s
329 }
330 return s
331 }
332
333 func checkStack(e *errAccumulator, stk trace.Stack) {
334
335 i := 0
336 stk.Frames(func(f trace.StackFrame) bool {
337 if i == 0 {
338
339
340
341 return true
342 }
343 if f.Func == "" || f.File == "" || f.PC == 0 || f.Line == 0 {
344 e.Errorf("invalid stack frame %#v: missing information", f)
345 }
346 i++
347 return true
348 })
349 }
350
351 type errAccumulator struct {
352 errs []error
353 }
354
355 func (e *errAccumulator) Errorf(f string, args ...any) {
356 e.errs = append(e.errs, fmt.Errorf(f, args...))
357 }
358
359 func (e *errAccumulator) Errors() error {
360 return errors.Join(e.errs...)
361 }
362
View as plain text