Skip to content

Commit f0eca37

Browse files
committed
cmd/trace: add /userspans, /userspan pages
Change-Id: Ifbefb659a8df3b079d69679871af444b179deaeb Reviewed-on: https://go-review.googlesource.com/102599 Run-TryBot: Hyang-Ah Hana Kim <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent dc3a92e commit f0eca37

File tree

3 files changed

+240
-12
lines changed

3 files changed

+240
-12
lines changed

src/cmd/trace/annotations.go

Lines changed: 235 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import (
99
"math"
1010
"net/http"
1111
"sort"
12+
"strconv"
1213
"strings"
1314
"time"
1415
)
1516

1617
func init() {
1718
http.HandleFunc("/usertasks", httpUserTasks)
1819
http.HandleFunc("/usertask", httpUserTask)
20+
http.HandleFunc("/userspans", httpUserSpans)
21+
http.HandleFunc("/userspan", httpUserSpan)
1922
}
2023

2124
// httpUserTasks reports all tasks found in the trace.
@@ -55,6 +58,80 @@ func httpUserTasks(w http.ResponseWriter, r *http.Request) {
5558
}
5659
}
5760

61+
func httpUserSpans(w http.ResponseWriter, r *http.Request) {
62+
res, err := analyzeAnnotations()
63+
if err != nil {
64+
http.Error(w, err.Error(), http.StatusInternalServerError)
65+
return
66+
}
67+
allSpans := res.spans
68+
69+
summary := make(map[spanTypeID]spanStats)
70+
for id, spans := range allSpans {
71+
stats, ok := summary[id]
72+
if !ok {
73+
stats.spanTypeID = id
74+
}
75+
for _, s := range spans {
76+
stats.add(s)
77+
}
78+
summary[id] = stats
79+
}
80+
// Sort spans by pc and name
81+
userSpans := make([]spanStats, 0, len(summary))
82+
for _, stats := range summary {
83+
userSpans = append(userSpans, stats)
84+
}
85+
sort.Slice(userSpans, func(i, j int) bool {
86+
if userSpans[i].Type != userSpans[j].Type {
87+
return userSpans[i].Type < userSpans[j].Type
88+
}
89+
return userSpans[i].Frame.PC < userSpans[j].Frame.PC
90+
})
91+
// Emit table.
92+
err = templUserSpanTypes.Execute(w, userSpans)
93+
if err != nil {
94+
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
95+
return
96+
}
97+
}
98+
99+
func httpUserSpan(w http.ResponseWriter, r *http.Request) {
100+
filter, err := newSpanFilter(r)
101+
if err != nil {
102+
http.Error(w, err.Error(), http.StatusBadRequest)
103+
return
104+
}
105+
res, err := analyzeAnnotations()
106+
if err != nil {
107+
http.Error(w, err.Error(), http.StatusInternalServerError)
108+
return
109+
}
110+
allSpans := res.spans
111+
112+
var data []spanDesc
113+
114+
for id, spans := range allSpans {
115+
for _, s := range spans {
116+
if !filter.match(id, s) {
117+
continue
118+
}
119+
data = append(data, s)
120+
}
121+
}
122+
123+
err = templUserSpanType.Execute(w, struct {
124+
Data []spanDesc
125+
Title string
126+
}{
127+
Data: data,
128+
Title: filter.name})
129+
if err != nil {
130+
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
131+
return
132+
}
133+
}
134+
58135
// httpUserTask presents the details of the selected tasks.
59136
func httpUserTask(w http.ResponseWriter, r *http.Request) {
60137
filter, err := newTaskFilter(r)
@@ -156,8 +233,14 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) {
156233
}
157234

158235
type annotationAnalysisResult struct {
159-
tasks map[uint64]*taskDesc // tasks
160-
gcEvents []*trace.Event // GCStartevents, sorted
236+
tasks map[uint64]*taskDesc // tasks
237+
spans map[spanTypeID][]spanDesc // spans
238+
gcEvents []*trace.Event // GCStartevents, sorted
239+
}
240+
241+
type spanTypeID struct {
242+
Frame trace.Frame // top frame
243+
Type string
161244
}
162245

163246
// analyzeAnnotations analyzes user annotation events and
@@ -174,6 +257,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
174257
}
175258

176259
tasks := allTasks{}
260+
spans := map[spanTypeID][]spanDesc{}
177261
var gcEvents []*trace.Event
178262

179263
for _, ev := range events {
@@ -207,7 +291,13 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
207291
}
208292
task := tasks.task(s.TaskID)
209293
task.goroutines[goid] = struct{}{}
210-
task.spans = append(task.spans, spanDesc{UserSpanDesc: s, goid: goid})
294+
task.spans = append(task.spans, spanDesc{UserSpanDesc: s, G: goid})
295+
var frame trace.Frame
296+
if s.Start != nil {
297+
frame = *s.Start.Stk[0]
298+
}
299+
id := spanTypeID{Frame: frame, Type: s.Name}
300+
spans[id] = append(spans[id], spanDesc{UserSpanDesc: s, G: goid})
211301
}
212302
}
213303

@@ -221,7 +311,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
221311
return task.spans[i].lastTimestamp() < task.spans[i].lastTimestamp()
222312
})
223313
}
224-
return annotationAnalysisResult{tasks: tasks, gcEvents: gcEvents}, nil
314+
return annotationAnalysisResult{tasks: tasks, spans: spans, gcEvents: gcEvents}, nil
225315
}
226316

227317
// taskDesc represents a task.
@@ -256,7 +346,7 @@ func (task *taskDesc) String() string {
256346
fmt.Fprintf(wb, "\t%d goroutines\n", len(task.goroutines))
257347
fmt.Fprintf(wb, "\t%d spans:\n", len(task.spans))
258348
for _, s := range task.spans {
259-
fmt.Fprintf(wb, "\t\t%s(goid=%d)\n", s.Name, s.goid)
349+
fmt.Fprintf(wb, "\t\t%s(goid=%d)\n", s.Name, s.G)
260350
}
261351
if task.parent != nil {
262352
fmt.Fprintf(wb, "\tparent: %s\n", task.parent.name)
@@ -272,7 +362,7 @@ func (task *taskDesc) String() string {
272362
// spanDesc represents a span.
273363
type spanDesc struct {
274364
*trace.UserSpanDesc
275-
goid uint64 // id of goroutine where the span was defined
365+
G uint64 // id of goroutine where the span was defined
276366
}
277367

278368
type allTasks map[uint64]*taskDesc
@@ -361,6 +451,10 @@ func (task *taskDesc) duration() time.Duration {
361451
return time.Duration(task.lastTimestamp()-task.firstTimestamp()) * time.Nanosecond
362452
}
363453

454+
func (span *spanDesc) duration() time.Duration {
455+
return time.Duration(span.lastTimestamp()-span.firstTimestamp()) * time.Nanosecond
456+
}
457+
364458
// overlappingGCDuration returns the sum of GC period overlapping with the task's lifetime.
365459
func (task *taskDesc) overlappingGCDuration(evs []*trace.Event) (overlapping time.Duration) {
366460
for _, ev := range evs {
@@ -397,7 +491,7 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
397491
// Goroutine local event. Check whether there are spans overlapping with the event.
398492
goid := ev.G
399493
for _, span := range task.spans {
400-
if span.goid != goid {
494+
if span.G != goid {
401495
continue
402496
}
403497
if span.firstTimestamp() <= ts && ts <= span.lastTimestamp() {
@@ -440,7 +534,7 @@ func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool)
440534
var overlapping time.Duration
441535
var lastSpanEnd int64 // the end of previous overlapping span
442536
for _, span := range task.spans {
443-
if span.goid != goid && span.goid != goid2 {
537+
if span.G != goid && span.G != goid2 {
444538
continue
445539
}
446540
spanStart, spanEnd := span.firstTimestamp(), span.lastTimestamp()
@@ -616,6 +710,58 @@ func taskMatches(t *taskDesc, text string) bool {
616710
return false
617711
}
618712

713+
type spanFilter struct {
714+
name string
715+
cond []func(spanTypeID, spanDesc) bool
716+
}
717+
718+
func (f *spanFilter) match(id spanTypeID, s spanDesc) bool {
719+
for _, c := range f.cond {
720+
if !c(id, s) {
721+
return false
722+
}
723+
}
724+
return true
725+
}
726+
727+
func newSpanFilter(r *http.Request) (*spanFilter, error) {
728+
if err := r.ParseForm(); err != nil {
729+
return nil, err
730+
}
731+
732+
var name []string
733+
var conditions []func(spanTypeID, spanDesc) bool
734+
735+
param := r.Form
736+
if typ, ok := param["type"]; ok && len(typ) > 0 {
737+
name = append(name, "type="+typ[0])
738+
conditions = append(conditions, func(id spanTypeID, s spanDesc) bool {
739+
return id.Type == typ[0]
740+
})
741+
}
742+
if pc, err := strconv.ParseUint(r.FormValue("pc"), 16, 64); err == nil {
743+
name = append(name, fmt.Sprintf("pc=%x", pc))
744+
conditions = append(conditions, func(id spanTypeID, s spanDesc) bool {
745+
return id.Frame.PC == pc
746+
})
747+
}
748+
749+
if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {
750+
name = append(name, fmt.Sprintf("latency >= %s", lat))
751+
conditions = append(conditions, func(_ spanTypeID, s spanDesc) bool {
752+
return s.duration() >= lat
753+
})
754+
}
755+
if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {
756+
name = append(name, fmt.Sprintf("latency <= %s", lat))
757+
conditions = append(conditions, func(_ spanTypeID, s spanDesc) bool {
758+
return s.duration() <= lat
759+
})
760+
}
761+
762+
return &spanFilter{name: strings.Join(name, ","), cond: conditions}, nil
763+
}
764+
619765
type durationHistogram struct {
620766
Count int
621767
Buckets []int
@@ -725,6 +871,49 @@ func (h *durationHistogram) String() string {
725871
return w.String()
726872
}
727873

874+
type spanStats struct {
875+
spanTypeID
876+
Histogram durationHistogram
877+
}
878+
879+
func (s *spanStats) UserSpanURL() func(min, max time.Duration) string {
880+
return func(min, max time.Duration) string {
881+
return fmt.Sprintf("/userspan?type=%s&pc=%x&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), s.Frame.PC, template.URLQueryEscaper(min), template.URLQueryEscaper(max))
882+
}
883+
}
884+
885+
func (s *spanStats) add(span spanDesc) {
886+
s.Histogram.add(span.duration())
887+
}
888+
889+
var templUserSpanTypes = template.Must(template.New("").Parse(`
890+
<html>
891+
<style type="text/css">
892+
.histoTime {
893+
width: 20%;
894+
white-space:nowrap;
895+
}
896+
897+
</style>
898+
<body>
899+
<table border="1" sortable="1">
900+
<tr>
901+
<th>Span type</th>
902+
<th>Count</th>
903+
<th>Duration distribution (complete tasks)</th>
904+
</tr>
905+
{{range $}}
906+
<tr>
907+
<td>{{.Type}}<br>{{.Frame.Fn}}<br>{{.Frame.File}}:{{.Frame.Line}}</td>
908+
<td><a href="/userspan?type={{.Type}}&pc={{.Frame.PC}}">{{.Histogram.Count}}</a></td>
909+
<td>{{.Histogram.ToHTML (.UserSpanURL)}}</td>
910+
</tr>
911+
{{end}}
912+
</table>
913+
</body>
914+
</html>
915+
`))
916+
728917
type taskStats struct {
729918
Type string
730919
Count int // Complete + incomplete tasks
@@ -923,3 +1112,41 @@ func isUserAnnotationEvent(ev *trace.Event) bool {
9231112
}
9241113
return false
9251114
}
1115+
1116+
var templUserSpanType = template.Must(template.New("").Parse(`
1117+
<html>
1118+
<body>
1119+
<h2>{{.Title}}</h2>
1120+
<table border="1" sortable="1">
1121+
<tr>
1122+
<th> Goroutine </th>
1123+
<th> Task </th>
1124+
<th> Total time, ns </th>
1125+
<th> Execution time, ns </th>
1126+
<th> Network wait time, ns </th>
1127+
<th> Sync block time, ns </th>
1128+
<th> Blocking syscall time, ns </th>
1129+
<th> Scheduler wait time, ns </th>
1130+
<th> GC sweeping time, ns </th>
1131+
<th> GC pause time, ns </th>
1132+
<th> Logs </th>
1133+
</tr>
1134+
{{range .Data}}
1135+
<tr>
1136+
<td> <a href="/trace?goid={{.G}}">{{.G}}</a> </td>
1137+
<td> <a href="/trace?taskid={{.TaskID}}">{{.TaskID}}</a> </td>
1138+
<td> {{.TotalTime}} </td>
1139+
<td> {{.ExecTime}} </td>
1140+
<td> {{.IOTime}} </td>
1141+
<td> {{.BlockTime}} </td>
1142+
<td> {{.SyscallTime}} </td>
1143+
<td> {{.SchedWaitTime}} </td>
1144+
<td> {{.SweepTime}} </td>
1145+
<td> {{.GCTime}} </td>
1146+
<td> /* TODO */ </td>
1147+
</tr>
1148+
{{end}}
1149+
</table>
1150+
</body>
1151+
</html>
1152+
`))

src/cmd/trace/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ var templMain = template.Must(template.New("").Parse(`
200200
<a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)<br>
201201
<a href="/sched">Scheduler latency profile</a> (<a href="/sche?raw=1" download="sched.profile">⬇</a>)<br>
202202
<a href="/usertasks">User-defined tasks</a><br>
203+
<a href="/userspans">User-defined spans</a><br>
203204
</body>
204205
</html>
205206
`))

src/cmd/trace/trace.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -836,8 +836,8 @@ func (ctx *traceContext) emitSpan(s spanDesc) {
836836
Name: s.Name,
837837
Phase: "b",
838838
Time: float64(s.firstTimestamp()) / 1e3,
839-
Tid: s.goid,
840-
ID: s.goid,
839+
Tid: s.G,
840+
ID: s.G,
841841
Scope: scopeID,
842842
Cname: colorDeepMagenta,
843843
}
@@ -851,8 +851,8 @@ func (ctx *traceContext) emitSpan(s spanDesc) {
851851
Name: s.Name,
852852
Phase: "e",
853853
Time: float64(s.lastTimestamp()) / 1e3,
854-
Tid: s.goid,
855-
ID: s.goid,
854+
Tid: s.G,
855+
ID: s.G,
856856
Scope: scopeID,
857857
Cname: colorDeepMagenta,
858858
}

0 commit comments

Comments
 (0)