github.com/bir3/gocompiler@v0.9.2202/src/internal/trace/v2/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 package testtrace 6 7 import ( 8 "errors" 9 "fmt" 10 "github.com/bir3/gocompiler/src/internal/trace/v2" 11 "slices" 12 "strings" 13 ) 14 15 // Validator is a type used for validating a stream of trace.Events. 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 // NewValidator creates a new Validator. 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 // Event validates ev as the next event in a stream of trace.Events. 54 // 55 // Returns an error if validation fails. 56 func (v *Validator) Event(ev trace.Event) error { 57 e := new(errAccumulator) 58 59 // Validate timestamp order. 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 // Validate event stack. 71 checkStack(e, ev.Stack()) 72 73 switch ev.Kind() { 74 case trace.EventSync: 75 // Just record that we've seen a Sync at some point. 76 v.seenSync = true 77 case trace.EventMetric: 78 m := ev.Metric() 79 if !strings.Contains(m.Name, ":") { 80 // Should have a ":" as per runtime/metrics convention. 81 e.Errorf("invalid metric name %q", m.Name) 82 } 83 // Make sure the value is OK. 84 if m.Value.Kind() == trace.ValueBad { 85 e.Errorf("invalid value") 86 } 87 switch m.Value.Kind() { 88 case trace.ValueUint64: 89 // Just make sure it doesn't panic. 90 _ = m.Value.Uint64() 91 } 92 case trace.EventLabel: 93 l := ev.Label() 94 95 // Check label. 96 if l.Label == "" { 97 e.Errorf("invalid label %q", l.Label) 98 } 99 100 // Check label resource. 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 // Not much to check here. It's basically a sched context and a stack. 123 // The sched context is also not guaranteed to align with other events. 124 // We already checked the stack above. 125 case trace.EventStateTransition: 126 // Validate state transitions. 127 // 128 // TODO(mknyszek): A lot of logic is duplicated between goroutines and procs. 129 // The two are intentionally handled identically; from the perspective of the 130 // API, resources all have the same general properties. Consider making this 131 // code generic over resources and implementing validation just once. 132 tr := ev.StateTransition() 133 checkStack(e, tr.Stack) 134 switch tr.Resource.Kind { 135 case trace.ResourceGoroutine: 136 // Basic state transition validation. 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 // Validate sched context. 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 // This is a case where the transition is happening to a goroutine that is also executing, so 174 // these two stacks should always match. 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 // Basic state transition validation. 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 // Validate sched context. 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 // Validate ranges. 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 // Validate task begin. 258 t := ev.Task() 259 if t.ID == trace.NoTask || t.ID == trace.BackgroundTask { 260 // The background task should never have an event emitted for it. 261 e.Errorf("found invalid task ID for task of type %s", t.Type) 262 } 263 if t.Parent == trace.BackgroundTask { 264 // It's not possible for a task to be a subtask of the background task. 265 e.Errorf("found background task as the parent for task of type %s", t.Type) 266 } 267 // N.B. Don't check the task type. Empty string is a valid task type. 268 v.tasks[t.ID] = t.Type 269 case trace.EventTaskEnd: 270 // Validate task end. 271 // We can see a task end without a begin, so ignore a task without information. 272 // Instead, if we've seen the task begin, just make sure the task end lines up. 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 // There's really not much here to check, except that we can 282 // generate a Log. The category and message are entirely user-created, 283 // so we can't make any assumptions as to what they are. We also 284 // can't validate the task, because proving the task's existence is very 285 // much best-effort. 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 // Check for non-empty values, but we also check for crashes due to incorrect validation. 335 i := 0 336 stk.Frames(func(f trace.StackFrame) bool { 337 if i == 0 { 338 // Allow for one fully zero stack. 339 // 340 // TODO(mknyszek): Investigate why that happens. 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 }