Source file src/cmd/trace/jsontrace.go

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

View as plain text