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
5package main
6
7import (
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
20func 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.
143type 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.
151func (ctx *traceContext) elapsed(now trace.Time) time.Duration {
152	return now.Sub(ctx.startTime)
153}
154
155type 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.
167func (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
203func defaultGenOpts() *genOpts {
204	return &genOpts{
205		startTime: time.Duration(0),
206		endTime:   time.Duration(math.MaxInt64),
207	}
208}
209
210func 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