@@ -9,13 +9,16 @@ import (
9
9
"math"
10
10
"net/http"
11
11
"sort"
12
+ "strconv"
12
13
"strings"
13
14
"time"
14
15
)
15
16
16
17
func init () {
17
18
http .HandleFunc ("/usertasks" , httpUserTasks )
18
19
http .HandleFunc ("/usertask" , httpUserTask )
20
+ http .HandleFunc ("/userspans" , httpUserSpans )
21
+ http .HandleFunc ("/userspan" , httpUserSpan )
19
22
}
20
23
21
24
// httpUserTasks reports all tasks found in the trace.
@@ -55,6 +58,80 @@ func httpUserTasks(w http.ResponseWriter, r *http.Request) {
55
58
}
56
59
}
57
60
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
+
58
135
// httpUserTask presents the details of the selected tasks.
59
136
func httpUserTask (w http.ResponseWriter , r * http.Request ) {
60
137
filter , err := newTaskFilter (r )
@@ -156,8 +233,14 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) {
156
233
}
157
234
158
235
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
161
244
}
162
245
163
246
// analyzeAnnotations analyzes user annotation events and
@@ -174,6 +257,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
174
257
}
175
258
176
259
tasks := allTasks {}
260
+ spans := map [spanTypeID ][]spanDesc {}
177
261
var gcEvents []* trace.Event
178
262
179
263
for _ , ev := range events {
@@ -207,7 +291,13 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
207
291
}
208
292
task := tasks .task (s .TaskID )
209
293
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 })
211
301
}
212
302
}
213
303
@@ -221,7 +311,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
221
311
return task .spans [i ].lastTimestamp () < task .spans [i ].lastTimestamp ()
222
312
})
223
313
}
224
- return annotationAnalysisResult {tasks : tasks , gcEvents : gcEvents }, nil
314
+ return annotationAnalysisResult {tasks : tasks , spans : spans , gcEvents : gcEvents }, nil
225
315
}
226
316
227
317
// taskDesc represents a task.
@@ -256,7 +346,7 @@ func (task *taskDesc) String() string {
256
346
fmt .Fprintf (wb , "\t %d goroutines\n " , len (task .goroutines ))
257
347
fmt .Fprintf (wb , "\t %d spans:\n " , len (task .spans ))
258
348
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 )
260
350
}
261
351
if task .parent != nil {
262
352
fmt .Fprintf (wb , "\t parent: %s\n " , task .parent .name )
@@ -272,7 +362,7 @@ func (task *taskDesc) String() string {
272
362
// spanDesc represents a span.
273
363
type spanDesc struct {
274
364
* trace.UserSpanDesc
275
- goid uint64 // id of goroutine where the span was defined
365
+ G uint64 // id of goroutine where the span was defined
276
366
}
277
367
278
368
type allTasks map [uint64 ]* taskDesc
@@ -361,6 +451,10 @@ func (task *taskDesc) duration() time.Duration {
361
451
return time .Duration (task .lastTimestamp ()- task .firstTimestamp ()) * time .Nanosecond
362
452
}
363
453
454
+ func (span * spanDesc ) duration () time.Duration {
455
+ return time .Duration (span .lastTimestamp ()- span .firstTimestamp ()) * time .Nanosecond
456
+ }
457
+
364
458
// overlappingGCDuration returns the sum of GC period overlapping with the task's lifetime.
365
459
func (task * taskDesc ) overlappingGCDuration (evs []* trace.Event ) (overlapping time.Duration ) {
366
460
for _ , ev := range evs {
@@ -397,7 +491,7 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
397
491
// Goroutine local event. Check whether there are spans overlapping with the event.
398
492
goid := ev .G
399
493
for _ , span := range task .spans {
400
- if span .goid != goid {
494
+ if span .G != goid {
401
495
continue
402
496
}
403
497
if span .firstTimestamp () <= ts && ts <= span .lastTimestamp () {
@@ -440,7 +534,7 @@ func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool)
440
534
var overlapping time.Duration
441
535
var lastSpanEnd int64 // the end of previous overlapping span
442
536
for _ , span := range task .spans {
443
- if span .goid != goid && span .goid != goid2 {
537
+ if span .G != goid && span .G != goid2 {
444
538
continue
445
539
}
446
540
spanStart , spanEnd := span .firstTimestamp (), span .lastTimestamp ()
@@ -616,6 +710,58 @@ func taskMatches(t *taskDesc, text string) bool {
616
710
return false
617
711
}
618
712
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
+
619
765
type durationHistogram struct {
620
766
Count int
621
767
Buckets []int
@@ -725,6 +871,49 @@ func (h *durationHistogram) String() string {
725
871
return w .String ()
726
872
}
727
873
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
+
728
917
type taskStats struct {
729
918
Type string
730
919
Count int // Complete + incomplete tasks
@@ -923,3 +1112,41 @@ func isUserAnnotationEvent(ev *trace.Event) bool {
923
1112
}
924
1113
return false
925
1114
}
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
+ ` ))
0 commit comments