golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/trace/internal/testtrace/validation.go (about) 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 5 // Code generated by "gen.bash" from internal/trace/v2; DO NOT EDIT. 6 7 //go:build go1.21 8 9 package testtrace 10 11 import ( 12 "errors" 13 "fmt" 14 "golang.org/x/exp/trace" 15 "slices" 16 "strings" 17 ) 18 19 // Validator is a type used for validating a stream of trace.Events. 20 type Validator struct { 21 lastTs trace.Time 22 gs map[trace.GoID]*goState 23 ps map[trace.ProcID]*procState 24 ms map[trace.ThreadID]*schedContext 25 ranges map[trace.ResourceID][]string 26 tasks map[trace.TaskID]string 27 seenSync bool 28 Go121 bool 29 } 30 31 type schedContext struct { 32 M trace.ThreadID 33 P trace.ProcID 34 G trace.GoID 35 } 36 37 type goState struct { 38 state trace.GoState 39 binding *schedContext 40 } 41 42 type procState struct { 43 state trace.ProcState 44 binding *schedContext 45 } 46 47 // NewValidator creates a new Validator. 48 func NewValidator() *Validator { 49 return &Validator{ 50 gs: make(map[trace.GoID]*goState), 51 ps: make(map[trace.ProcID]*procState), 52 ms: make(map[trace.ThreadID]*schedContext), 53 ranges: make(map[trace.ResourceID][]string), 54 tasks: make(map[trace.TaskID]string), 55 } 56 } 57 58 // Event validates ev as the next event in a stream of trace.Events. 59 // 60 // Returns an error if validation fails. 61 func (v *Validator) Event(ev trace.Event) error { 62 e := new(errAccumulator) 63 64 // Validate timestamp order. 65 if v.lastTs != 0 { 66 if ev.Time() <= v.lastTs { 67 e.Errorf("timestamp out-of-order for %+v", ev) 68 } else { 69 v.lastTs = ev.Time() 70 } 71 } else { 72 v.lastTs = ev.Time() 73 } 74 75 // Validate event stack. 76 checkStack(e, ev.Stack()) 77 78 switch ev.Kind() { 79 case trace.EventSync: 80 // Just record that we've seen a Sync at some point. 81 v.seenSync = true 82 case trace.EventMetric: 83 m := ev.Metric() 84 if !strings.Contains(m.Name, ":") { 85 // Should have a ":" as per runtime/metrics convention. 86 e.Errorf("invalid metric name %q", m.Name) 87 } 88 // Make sure the value is OK. 89 if m.Value.Kind() == trace.ValueBad { 90 e.Errorf("invalid value") 91 } 92 switch m.Value.Kind() { 93 case trace.ValueUint64: 94 // Just make sure it doesn't panic. 95 _ = m.Value.Uint64() 96 } 97 case trace.EventLabel: 98 l := ev.Label() 99 100 // Check label. 101 if l.Label == "" { 102 e.Errorf("invalid label %q", l.Label) 103 } 104 105 // Check label resource. 106 if l.Resource.Kind == trace.ResourceNone { 107 e.Errorf("label resource none") 108 } 109 switch l.Resource.Kind { 110 case trace.ResourceGoroutine: 111 id := l.Resource.Goroutine() 112 if _, ok := v.gs[id]; !ok { 113 e.Errorf("label for invalid goroutine %d", id) 114 } 115 case trace.ResourceProc: 116 id := l.Resource.Proc() 117 if _, ok := v.ps[id]; !ok { 118 e.Errorf("label for invalid proc %d", id) 119 } 120 case trace.ResourceThread: 121 id := l.Resource.Thread() 122 if _, ok := v.ms[id]; !ok { 123 e.Errorf("label for invalid thread %d", id) 124 } 125 } 126 case trace.EventStackSample: 127 // Not much to check here. It's basically a sched context and a stack. 128 // The sched context is also not guaranteed to align with other events. 129 // We already checked the stack above. 130 case trace.EventStateTransition: 131 // Validate state transitions. 132 // 133 // TODO(mknyszek): A lot of logic is duplicated between goroutines and procs. 134 // The two are intentionally handled identically; from the perspective of the 135 // API, resources all have the same general properties. Consider making this 136 // code generic over resources and implementing validation just once. 137 tr := ev.StateTransition() 138 checkStack(e, tr.Stack) 139 switch tr.Resource.Kind { 140 case trace.ResourceGoroutine: 141 // Basic state transition validation. 142 id := tr.Resource.Goroutine() 143 old, new := tr.Goroutine() 144 if new == trace.GoUndetermined { 145 e.Errorf("transition to undetermined state for goroutine %d", id) 146 } 147 if v.seenSync && old == trace.GoUndetermined { 148 e.Errorf("undetermined goroutine %d after first global sync", id) 149 } 150 if new == trace.GoNotExist && v.hasAnyRange(trace.MakeResourceID(id)) { 151 e.Errorf("goroutine %d died with active ranges", id) 152 } 153 state, ok := v.gs[id] 154 if ok { 155 if old != state.state { 156 e.Errorf("bad old state for goroutine %d: got %s, want %s", id, old, state.state) 157 } 158 state.state = new 159 } else { 160 if old != trace.GoUndetermined && old != trace.GoNotExist { 161 e.Errorf("bad old state for unregistered goroutine %d: %s", id, old) 162 } 163 state = &goState{state: new} 164 v.gs[id] = state 165 } 166 // Validate sched context. 167 if new.Executing() { 168 ctx := v.getOrCreateThread(e, ev, ev.Thread()) 169 if ctx != nil { 170 if ctx.G != trace.NoGoroutine && ctx.G != id { 171 e.Errorf("tried to run goroutine %d when one was already executing (%d) on thread %d", id, ctx.G, ev.Thread()) 172 } 173 ctx.G = id 174 state.binding = ctx 175 } 176 } else if old.Executing() && !new.Executing() { 177 if tr.Stack != ev.Stack() { 178 // This is a case where the transition is happening to a goroutine that is also executing, so 179 // these two stacks should always match. 180 e.Errorf("StateTransition.Stack doesn't match Event.Stack") 181 } 182 ctx := state.binding 183 if ctx != nil { 184 if ctx.G != id { 185 e.Errorf("tried to stop goroutine %d when it wasn't currently executing (currently executing %d) on thread %d", id, ctx.G, ev.Thread()) 186 } 187 ctx.G = trace.NoGoroutine 188 state.binding = nil 189 } else { 190 e.Errorf("stopping goroutine %d not bound to any active context", id) 191 } 192 } 193 case trace.ResourceProc: 194 // Basic state transition validation. 195 id := tr.Resource.Proc() 196 old, new := tr.Proc() 197 if new == trace.ProcUndetermined { 198 e.Errorf("transition to undetermined state for proc %d", id) 199 } 200 if v.seenSync && old == trace.ProcUndetermined { 201 e.Errorf("undetermined proc %d after first global sync", id) 202 } 203 if new == trace.ProcNotExist && v.hasAnyRange(trace.MakeResourceID(id)) { 204 e.Errorf("proc %d died with active ranges", id) 205 } 206 state, ok := v.ps[id] 207 if ok { 208 if old != state.state { 209 e.Errorf("bad old state for proc %d: got %s, want %s", id, old, state.state) 210 } 211 state.state = new 212 } else { 213 if old != trace.ProcUndetermined && old != trace.ProcNotExist { 214 e.Errorf("bad old state for unregistered proc %d: %s", id, old) 215 } 216 state = &procState{state: new} 217 v.ps[id] = state 218 } 219 // Validate sched context. 220 if new.Executing() { 221 ctx := v.getOrCreateThread(e, ev, ev.Thread()) 222 if ctx != nil { 223 if ctx.P != trace.NoProc && ctx.P != id { 224 e.Errorf("tried to run proc %d when one was already executing (%d) on thread %d", id, ctx.P, ev.Thread()) 225 } 226 ctx.P = id 227 state.binding = ctx 228 } 229 } else if old.Executing() && !new.Executing() { 230 ctx := state.binding 231 if ctx != nil { 232 if ctx.P != id { 233 e.Errorf("tried to stop proc %d when it wasn't currently executing (currently executing %d) on thread %d", id, ctx.P, ctx.M) 234 } 235 ctx.P = trace.NoProc 236 state.binding = nil 237 } else { 238 e.Errorf("stopping proc %d not bound to any active context", id) 239 } 240 } 241 } 242 case trace.EventRangeBegin, trace.EventRangeActive, trace.EventRangeEnd: 243 // Validate ranges. 244 r := ev.Range() 245 switch ev.Kind() { 246 case trace.EventRangeBegin: 247 if v.hasRange(r.Scope, r.Name) { 248 e.Errorf("already active range %q on %v begun again", r.Name, r.Scope) 249 } 250 v.addRange(r.Scope, r.Name) 251 case trace.EventRangeActive: 252 if !v.hasRange(r.Scope, r.Name) { 253 v.addRange(r.Scope, r.Name) 254 } 255 case trace.EventRangeEnd: 256 if !v.hasRange(r.Scope, r.Name) { 257 e.Errorf("inactive range %q on %v ended", r.Name, r.Scope) 258 } 259 v.deleteRange(r.Scope, r.Name) 260 } 261 case trace.EventTaskBegin: 262 // Validate task begin. 263 t := ev.Task() 264 if t.ID == trace.NoTask || t.ID == trace.BackgroundTask { 265 // The background task should never have an event emitted for it. 266 e.Errorf("found invalid task ID for task of type %s", t.Type) 267 } 268 if t.Parent == trace.BackgroundTask { 269 // It's not possible for a task to be a subtask of the background task. 270 e.Errorf("found background task as the parent for task of type %s", t.Type) 271 } 272 // N.B. Don't check the task type. Empty string is a valid task type. 273 v.tasks[t.ID] = t.Type 274 case trace.EventTaskEnd: 275 // Validate task end. 276 // We can see a task end without a begin, so ignore a task without information. 277 // Instead, if we've seen the task begin, just make sure the task end lines up. 278 t := ev.Task() 279 if typ, ok := v.tasks[t.ID]; ok { 280 if t.Type != typ { 281 e.Errorf("task end type %q doesn't match task start type %q for task %d", t.Type, typ, t.ID) 282 } 283 delete(v.tasks, t.ID) 284 } 285 case trace.EventLog: 286 // There's really not much here to check, except that we can 287 // generate a Log. The category and message are entirely user-created, 288 // so we can't make any assumptions as to what they are. We also 289 // can't validate the task, because proving the task's existence is very 290 // much best-effort. 291 _ = ev.Log() 292 } 293 return e.Errors() 294 } 295 296 func (v *Validator) hasRange(r trace.ResourceID, name string) bool { 297 ranges, ok := v.ranges[r] 298 return ok && slices.Contains(ranges, name) 299 } 300 301 func (v *Validator) addRange(r trace.ResourceID, name string) { 302 ranges, _ := v.ranges[r] 303 ranges = append(ranges, name) 304 v.ranges[r] = ranges 305 } 306 307 func (v *Validator) hasAnyRange(r trace.ResourceID) bool { 308 ranges, ok := v.ranges[r] 309 return ok && len(ranges) != 0 310 } 311 312 func (v *Validator) deleteRange(r trace.ResourceID, name string) { 313 ranges, ok := v.ranges[r] 314 if !ok { 315 return 316 } 317 i := slices.Index(ranges, name) 318 if i < 0 { 319 return 320 } 321 v.ranges[r] = slices.Delete(ranges, i, i+1) 322 } 323 324 func (v *Validator) getOrCreateThread(e *errAccumulator, ev trace.Event, m trace.ThreadID) *schedContext { 325 lenient := func() bool { 326 // Be lenient about GoUndetermined -> GoSyscall transitions if they 327 // originate from an old trace. These transitions lack thread 328 // information in trace formats older than 1.22. 329 if !v.Go121 { 330 return false 331 } 332 if ev.Kind() != trace.EventStateTransition { 333 return false 334 } 335 tr := ev.StateTransition() 336 if tr.Resource.Kind != trace.ResourceGoroutine { 337 return false 338 } 339 from, to := tr.Goroutine() 340 return from == trace.GoUndetermined && to == trace.GoSyscall 341 } 342 if m == trace.NoThread && !lenient() { 343 e.Errorf("must have thread, but thread ID is none") 344 return nil 345 } 346 s, ok := v.ms[m] 347 if !ok { 348 s = &schedContext{M: m, P: trace.NoProc, G: trace.NoGoroutine} 349 v.ms[m] = s 350 return s 351 } 352 return s 353 } 354 355 func checkStack(e *errAccumulator, stk trace.Stack) { 356 // Check for non-empty values, but we also check for crashes due to incorrect validation. 357 i := 0 358 stk.Frames(func(f trace.StackFrame) bool { 359 if i == 0 { 360 // Allow for one fully zero stack. 361 // 362 // TODO(mknyszek): Investigate why that happens. 363 return true 364 } 365 if f.Func == "" || f.File == "" || f.PC == 0 || f.Line == 0 { 366 e.Errorf("invalid stack frame %#v: missing information", f) 367 } 368 i++ 369 return true 370 }) 371 } 372 373 type errAccumulator struct { 374 errs []error 375 } 376 377 func (e *errAccumulator) Errorf(f string, args ...any) { 378 e.errs = append(e.errs, fmt.Errorf(f, args...)) 379 } 380 381 func (e *errAccumulator) Errors() error { 382 return errors.Join(e.errs...) 383 }