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  }