github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/trace/v2/jsontrace.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 trace
     6  
     7  import (
     8  	"cmp"
     9  	"log"
    10  	"math"
    11  	"net/http"
    12  	"slices"
    13  	"strconv"
    14  	"time"
    15  
    16  	"github.com/go-asm/go/trace"
    17  	"github.com/go-asm/go/trace/traceviewer"
    18  	tracev2 "github.com/go-asm/go/trace/v2"
    19  )
    20  
    21  func JSONTraceHandler(parsed *parsedTrace) http.Handler {
    22  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    23  		opts := defaultGenOpts()
    24  
    25  		switch r.FormValue("view") {
    26  		case "thread":
    27  			opts.mode = traceviewer.ModeThreadOriented
    28  		}
    29  		if goids := r.FormValue("goid"); goids != "" {
    30  			// Render trace focused on a particular goroutine.
    31  
    32  			id, err := strconv.ParseUint(goids, 10, 64)
    33  			if err != nil {
    34  				log.Printf("failed to parse goid parameter %q: %v", goids, err)
    35  				return
    36  			}
    37  			goid := tracev2.GoID(id)
    38  			g, ok := parsed.summary.Goroutines[goid]
    39  			if !ok {
    40  				log.Printf("failed to find goroutine %d", goid)
    41  				return
    42  			}
    43  			opts.mode = traceviewer.ModeGoroutineOriented
    44  			if g.StartTime != 0 {
    45  				opts.startTime = g.StartTime.Sub(parsed.startTime())
    46  			} else {
    47  				opts.startTime = 0
    48  			}
    49  			if g.EndTime != 0 {
    50  				opts.endTime = g.EndTime.Sub(parsed.startTime())
    51  			} else { // The goroutine didn't end.
    52  				opts.endTime = parsed.endTime().Sub(parsed.startTime())
    53  			}
    54  			opts.focusGoroutine = goid
    55  			opts.goroutines = trace.RelatedGoroutinesV2(parsed.events, goid)
    56  		} else if taskids := r.FormValue("focustask"); taskids != "" {
    57  			taskid, err := strconv.ParseUint(taskids, 10, 64)
    58  			if err != nil {
    59  				log.Printf("failed to parse focustask parameter %q: %v", taskids, err)
    60  				return
    61  			}
    62  			task, ok := parsed.summary.Tasks[tracev2.TaskID(taskid)]
    63  			if !ok || (task.Start == nil && task.End == nil) {
    64  				log.Printf("failed to find task with id %d", taskid)
    65  				return
    66  			}
    67  			opts.setTask(parsed, task)
    68  		} else if taskids := r.FormValue("taskid"); taskids != "" {
    69  			taskid, err := strconv.ParseUint(taskids, 10, 64)
    70  			if err != nil {
    71  				log.Printf("failed to parse taskid parameter %q: %v", taskids, err)
    72  				return
    73  			}
    74  			task, ok := parsed.summary.Tasks[tracev2.TaskID(taskid)]
    75  			if !ok {
    76  				log.Printf("failed to find task with id %d", taskid)
    77  				return
    78  			}
    79  			// This mode is goroutine-oriented.
    80  			opts.mode = traceviewer.ModeGoroutineOriented
    81  			opts.setTask(parsed, task)
    82  
    83  			// Pick the goroutine to orient ourselves around by just
    84  			// trying to pick the earliest event in the task that makes
    85  			// any sense. Though, we always want the start if that's there.
    86  			var firstEv *tracev2.Event
    87  			if task.Start != nil {
    88  				firstEv = task.Start
    89  			} else {
    90  				for _, logEv := range task.Logs {
    91  					if firstEv == nil || logEv.Time() < firstEv.Time() {
    92  						firstEv = logEv
    93  					}
    94  				}
    95  				if task.End != nil && (firstEv == nil || task.End.Time() < firstEv.Time()) {
    96  					firstEv = task.End
    97  				}
    98  			}
    99  			if firstEv == nil || firstEv.Goroutine() == tracev2.NoGoroutine {
   100  				log.Printf("failed to find task with id %d", taskid)
   101  				return
   102  			}
   103  
   104  			// Set the goroutine filtering options.
   105  			goid := firstEv.Goroutine()
   106  			opts.focusGoroutine = goid
   107  			goroutines := make(map[tracev2.GoID]struct{})
   108  			for _, task := range opts.tasks {
   109  				// Find only directly involved goroutines.
   110  				for id := range task.Goroutines {
   111  					goroutines[id] = struct{}{}
   112  				}
   113  			}
   114  			opts.goroutines = goroutines
   115  		}
   116  
   117  		// Parse start and end options. Both or none must be present.
   118  		start := int64(0)
   119  		end := int64(math.MaxInt64)
   120  		if startStr, endStr := r.FormValue("start"), r.FormValue("end"); startStr != "" && endStr != "" {
   121  			var err error
   122  			start, err = strconv.ParseInt(startStr, 10, 64)
   123  			if err != nil {
   124  				log.Printf("failed to parse start parameter %q: %v", startStr, err)
   125  				return
   126  			}
   127  
   128  			end, err = strconv.ParseInt(endStr, 10, 64)
   129  			if err != nil {
   130  				log.Printf("failed to parse end parameter %q: %v", endStr, err)
   131  				return
   132  			}
   133  		}
   134  
   135  		c := traceviewer.ViewerDataTraceConsumer(w, start, end)
   136  		if err := generateTrace(parsed, opts, c); err != nil {
   137  			log.Printf("failed to generate trace: %v", err)
   138  		}
   139  	})
   140  }
   141  
   142  // traceContext is a wrapper around a traceviewer.Emitter with some additional
   143  // information that's useful to most parts of trace viewer JSON emission.
   144  type traceContext struct {
   145  	*traceviewer.Emitter
   146  	startTime tracev2.Time
   147  	endTime   tracev2.Time
   148  }
   149  
   150  // elapsed returns the elapsed time between the trace time and the start time
   151  // of the trace.
   152  func (ctx *traceContext) elapsed(now tracev2.Time) time.Duration {
   153  	return now.Sub(ctx.startTime)
   154  }
   155  
   156  type genOpts struct {
   157  	mode      traceviewer.Mode
   158  	startTime time.Duration
   159  	endTime   time.Duration
   160  
   161  	// Used if mode != 0.
   162  	focusGoroutine tracev2.GoID
   163  	goroutines     map[tracev2.GoID]struct{} // Goroutines to be displayed for goroutine-oriented or task-oriented view. goroutines[0] is the main goroutine.
   164  	tasks          []*trace.UserTaskSummary
   165  }
   166  
   167  // setTask sets a task to focus on.
   168  func (opts *genOpts) setTask(parsed *parsedTrace, task *trace.UserTaskSummary) {
   169  	opts.mode |= traceviewer.ModeTaskOriented
   170  	if task.Start != nil {
   171  		opts.startTime = task.Start.Time().Sub(parsed.startTime())
   172  	} else { // The task started before the trace did.
   173  		opts.startTime = 0
   174  	}
   175  	if task.End != nil {
   176  		opts.endTime = task.End.Time().Sub(parsed.startTime())
   177  	} else { // The task didn't end.
   178  		opts.endTime = parsed.endTime().Sub(parsed.startTime())
   179  	}
   180  	opts.tasks = task.Descendents()
   181  	slices.SortStableFunc(opts.tasks, func(a, b *trace.UserTaskSummary) int {
   182  		aStart, bStart := parsed.startTime(), parsed.startTime()
   183  		if a.Start != nil {
   184  			aStart = a.Start.Time()
   185  		}
   186  		if b.Start != nil {
   187  			bStart = b.Start.Time()
   188  		}
   189  		if a.Start != b.Start {
   190  			return cmp.Compare(aStart, bStart)
   191  		}
   192  		// Break ties with the end time.
   193  		aEnd, bEnd := parsed.endTime(), parsed.endTime()
   194  		if a.End != nil {
   195  			aEnd = a.End.Time()
   196  		}
   197  		if b.End != nil {
   198  			bEnd = b.End.Time()
   199  		}
   200  		return cmp.Compare(aEnd, bEnd)
   201  	})
   202  }
   203  
   204  func defaultGenOpts() *genOpts {
   205  	return &genOpts{
   206  		startTime: time.Duration(0),
   207  		endTime:   time.Duration(math.MaxInt64),
   208  	}
   209  }
   210  
   211  func generateTrace(parsed *parsedTrace, opts *genOpts, c traceviewer.TraceConsumer) error {
   212  	ctx := &traceContext{
   213  		Emitter:   traceviewer.NewEmitter(c, opts.startTime, opts.endTime),
   214  		startTime: parsed.events[0].Time(),
   215  		endTime:   parsed.events[len(parsed.events)-1].Time(),
   216  	}
   217  	defer ctx.Flush()
   218  
   219  	var g generator
   220  	if opts.mode&traceviewer.ModeGoroutineOriented != 0 {
   221  		g = newGoroutineGenerator(ctx, opts.focusGoroutine, opts.goroutines)
   222  	} else if opts.mode&traceviewer.ModeThreadOriented != 0 {
   223  		g = newThreadGenerator()
   224  	} else {
   225  		g = newProcGenerator()
   226  	}
   227  	runGenerator(ctx, g, parsed, opts)
   228  	return nil
   229  }