1
2
3
4
5 package trace
6
7 import (
8 "bytes"
9 "fmt"
10 "html/template"
11 "io"
12 "log"
13 "net/http"
14 "runtime"
15 "sort"
16 "strconv"
17 "strings"
18 "sync"
19 "sync/atomic"
20 "text/tabwriter"
21 "time"
22 )
23
24 const maxEventsPerLog = 100
25
26 type bucket struct {
27 MaxErrAge time.Duration
28 String string
29 }
30
31 var buckets = []bucket{
32 {0, "total"},
33 {10 * time.Second, "errs<10s"},
34 {1 * time.Minute, "errs<1m"},
35 {10 * time.Minute, "errs<10m"},
36 {1 * time.Hour, "errs<1h"},
37 {10 * time.Hour, "errs<10h"},
38 {24000 * time.Hour, "errors"},
39 }
40
41
42
43
44
45 func RenderEvents(w http.ResponseWriter, req *http.Request, sensitive bool) {
46 now := time.Now()
47 data := &struct {
48 Families []string
49 Buckets []bucket
50 Counts [][]int
51
52
53 Family string
54 Bucket int
55 EventLogs eventLogs
56 Expanded bool
57 }{
58 Buckets: buckets,
59 }
60
61 data.Families = make([]string, 0, len(families))
62 famMu.RLock()
63 for name := range families {
64 data.Families = append(data.Families, name)
65 }
66 famMu.RUnlock()
67 sort.Strings(data.Families)
68
69
70 data.Counts = make([][]int, len(data.Families))
71 for i, name := range data.Families {
72
73 f := getEventFamily(name)
74 data.Counts[i] = make([]int, len(data.Buckets))
75 for j, b := range data.Buckets {
76 data.Counts[i][j] = f.Count(now, b.MaxErrAge)
77 }
78 }
79
80 if req != nil {
81 var ok bool
82 data.Family, data.Bucket, ok = parseEventsArgs(req)
83 if !ok {
84
85 } else {
86 data.EventLogs = getEventFamily(data.Family).Copy(now, buckets[data.Bucket].MaxErrAge)
87 }
88 if data.EventLogs != nil {
89 defer data.EventLogs.Free()
90 sort.Sort(data.EventLogs)
91 }
92 if exp, err := strconv.ParseBool(req.FormValue("exp")); err == nil {
93 data.Expanded = exp
94 }
95 }
96
97 famMu.RLock()
98 defer famMu.RUnlock()
99 if err := eventsTmpl().Execute(w, data); err != nil {
100 log.Printf("net/trace: Failed executing template: %v", err)
101 }
102 }
103
104 func parseEventsArgs(req *http.Request) (fam string, b int, ok bool) {
105 fam, bStr := req.FormValue("fam"), req.FormValue("b")
106 if fam == "" || bStr == "" {
107 return "", 0, false
108 }
109 b, err := strconv.Atoi(bStr)
110 if err != nil || b < 0 || b >= len(buckets) {
111 return "", 0, false
112 }
113 return fam, b, true
114 }
115
116
117 type EventLog interface {
118
119
120 Printf(format string, a ...interface{})
121
122
123 Errorf(format string, a ...interface{})
124
125
126
127 Finish()
128 }
129
130
131
132 func NewEventLog(family, title string) EventLog {
133 el := newEventLog()
134 el.ref()
135 el.Family, el.Title = family, title
136 el.Start = time.Now()
137 el.events = make([]logEntry, 0, maxEventsPerLog)
138 el.stack = make([]uintptr, 32)
139 n := runtime.Callers(2, el.stack)
140 el.stack = el.stack[:n]
141
142 getEventFamily(family).add(el)
143 return el
144 }
145
146 func (el *eventLog) Finish() {
147 getEventFamily(el.Family).remove(el)
148 el.unref()
149 }
150
151 var (
152 famMu sync.RWMutex
153 families = make(map[string]*eventFamily)
154 )
155
156 func getEventFamily(fam string) *eventFamily {
157 famMu.Lock()
158 defer famMu.Unlock()
159 f := families[fam]
160 if f == nil {
161 f = &eventFamily{}
162 families[fam] = f
163 }
164 return f
165 }
166
167 type eventFamily struct {
168 mu sync.RWMutex
169 eventLogs eventLogs
170 }
171
172 func (f *eventFamily) add(el *eventLog) {
173 f.mu.Lock()
174 f.eventLogs = append(f.eventLogs, el)
175 f.mu.Unlock()
176 }
177
178 func (f *eventFamily) remove(el *eventLog) {
179 f.mu.Lock()
180 defer f.mu.Unlock()
181 for i, el0 := range f.eventLogs {
182 if el == el0 {
183 copy(f.eventLogs[i:], f.eventLogs[i+1:])
184 f.eventLogs = f.eventLogs[:len(f.eventLogs)-1]
185 return
186 }
187 }
188 }
189
190 func (f *eventFamily) Count(now time.Time, maxErrAge time.Duration) (n int) {
191 f.mu.RLock()
192 defer f.mu.RUnlock()
193 for _, el := range f.eventLogs {
194 if el.hasRecentError(now, maxErrAge) {
195 n++
196 }
197 }
198 return
199 }
200
201 func (f *eventFamily) Copy(now time.Time, maxErrAge time.Duration) (els eventLogs) {
202 f.mu.RLock()
203 defer f.mu.RUnlock()
204 els = make(eventLogs, 0, len(f.eventLogs))
205 for _, el := range f.eventLogs {
206 if el.hasRecentError(now, maxErrAge) {
207 el.ref()
208 els = append(els, el)
209 }
210 }
211 return
212 }
213
214 type eventLogs []*eventLog
215
216
217 func (els eventLogs) Free() {
218 for _, el := range els {
219 el.unref()
220 }
221 }
222
223
224 func (els eventLogs) Len() int { return len(els) }
225 func (els eventLogs) Less(i, j int) bool { return els[i].Start.After(els[j].Start) }
226 func (els eventLogs) Swap(i, j int) { els[i], els[j] = els[j], els[i] }
227
228
229 type logEntry struct {
230 When time.Time
231 Elapsed time.Duration
232 NewDay bool
233 What string
234 IsErr bool
235 }
236
237
238
239 func (e logEntry) WhenString() string {
240 if e.NewDay {
241 return e.When.Format("2006/01/02 15:04:05.000000")
242 }
243 return e.When.Format("15:04:05.000000")
244 }
245
246
247 type eventLog struct {
248
249 Family string
250
251
252 Title string
253
254
255 Start time.Time
256
257
258 stack []uintptr
259
260
261
262
263
264 mu sync.RWMutex
265 events []logEntry
266 LastErrorTime time.Time
267 discarded int
268
269 refs int32
270 }
271
272 func (el *eventLog) reset() {
273
274 el.Family = ""
275 el.Title = ""
276 el.Start = time.Time{}
277 el.stack = nil
278 el.events = nil
279 el.LastErrorTime = time.Time{}
280 el.discarded = 0
281 el.refs = 0
282 }
283
284 func (el *eventLog) hasRecentError(now time.Time, maxErrAge time.Duration) bool {
285 if maxErrAge == 0 {
286 return true
287 }
288 el.mu.RLock()
289 defer el.mu.RUnlock()
290 return now.Sub(el.LastErrorTime) < maxErrAge
291 }
292
293
294
295
296 func (el *eventLog) delta(t time.Time) (time.Duration, bool) {
297 if len(el.events) == 0 {
298 return t.Sub(el.Start), false
299 }
300 prev := el.events[len(el.events)-1].When
301 return t.Sub(prev), prev.Day() != t.Day()
302
303 }
304
305 func (el *eventLog) Printf(format string, a ...interface{}) {
306 el.printf(false, format, a...)
307 }
308
309 func (el *eventLog) Errorf(format string, a ...interface{}) {
310 el.printf(true, format, a...)
311 }
312
313 func (el *eventLog) printf(isErr bool, format string, a ...interface{}) {
314 e := logEntry{When: time.Now(), IsErr: isErr, What: fmt.Sprintf(format, a...)}
315 el.mu.Lock()
316 e.Elapsed, e.NewDay = el.delta(e.When)
317 if len(el.events) < maxEventsPerLog {
318 el.events = append(el.events, e)
319 } else {
320
321 if el.discarded == 0 {
322
323
324
325 el.discarded = 2
326 } else {
327 el.discarded++
328 }
329
330
331 el.events[0].What = fmt.Sprintf("(%d events discarded)", el.discarded)
332
333
334 el.events[0].When = el.events[1].When
335 copy(el.events[1:], el.events[2:])
336 el.events[maxEventsPerLog-1] = e
337 }
338 if e.IsErr {
339 el.LastErrorTime = e.When
340 }
341 el.mu.Unlock()
342 }
343
344 func (el *eventLog) ref() {
345 atomic.AddInt32(&el.refs, 1)
346 }
347
348 func (el *eventLog) unref() {
349 if atomic.AddInt32(&el.refs, -1) == 0 {
350 freeEventLog(el)
351 }
352 }
353
354 func (el *eventLog) When() string {
355 return el.Start.Format("2006/01/02 15:04:05.000000")
356 }
357
358 func (el *eventLog) ElapsedTime() string {
359 elapsed := time.Since(el.Start)
360 return fmt.Sprintf("%.6f", elapsed.Seconds())
361 }
362
363 func (el *eventLog) Stack() string {
364 buf := new(bytes.Buffer)
365 tw := tabwriter.NewWriter(buf, 1, 8, 1, '\t', 0)
366 printStackRecord(tw, el.stack)
367 tw.Flush()
368 return buf.String()
369 }
370
371
372
373
374 func printStackRecord(w io.Writer, stk []uintptr) {
375 for _, pc := range stk {
376 f := runtime.FuncForPC(pc)
377 if f == nil {
378 continue
379 }
380 file, line := f.FileLine(pc)
381 name := f.Name()
382
383 if strings.HasPrefix(name, "runtime.") {
384 continue
385 }
386 fmt.Fprintf(w, "# %s\t%s:%d\n", name, file, line)
387 }
388 }
389
390 func (el *eventLog) Events() []logEntry {
391 el.mu.RLock()
392 defer el.mu.RUnlock()
393 return el.events
394 }
395
396
397 var freeEventLogs = make(chan *eventLog, 1000)
398
399
400 func newEventLog() *eventLog {
401 select {
402 case el := <-freeEventLogs:
403 return el
404 default:
405 return new(eventLog)
406 }
407 }
408
409
410
411 func freeEventLog(el *eventLog) {
412 el.reset()
413 select {
414 case freeEventLogs <- el:
415 default:
416 }
417 }
418
419 var eventsTmplCache *template.Template
420 var eventsTmplOnce sync.Once
421
422 func eventsTmpl() *template.Template {
423 eventsTmplOnce.Do(func() {
424 eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{
425 "elapsed": elapsed,
426 "trimSpace": strings.TrimSpace,
427 }).Parse(eventsHTML))
428 })
429 return eventsTmplCache
430 }
431
432 const eventsHTML = `
433 <html>
434 <head>
435 <title>events</title>
436 </head>
437 <style type="text/css">
438 body {
439 font-family: sans-serif;
440 }
441 table#req-status td.family {
442 padding-right: 2em;
443 }
444 table#req-status td.active {
445 padding-right: 1em;
446 }
447 table#req-status td.empty {
448 color: #aaa;
449 }
450 table#reqs {
451 margin-top: 1em;
452 }
453 table#reqs tr.first {
454 {{if $.Expanded}}font-weight: bold;{{end}}
455 }
456 table#reqs td {
457 font-family: monospace;
458 }
459 table#reqs td.when {
460 text-align: right;
461 white-space: nowrap;
462 }
463 table#reqs td.elapsed {
464 padding: 0 0.5em;
465 text-align: right;
466 white-space: pre;
467 width: 10em;
468 }
469 address {
470 font-size: smaller;
471 margin-top: 5em;
472 }
473 </style>
474 <body>
475
476 <h1>/debug/events</h1>
477
478 <table id="req-status">
479 {{range $i, $fam := .Families}}
480 <tr>
481 <td class="family">{{$fam}}</td>
482
483 {{range $j, $bucket := $.Buckets}}
484 {{$n := index $.Counts $i $j}}
485 <td class="{{if not $bucket.MaxErrAge}}active{{end}}{{if not $n}}empty{{end}}">
486 {{if $n}}<a href="?fam={{$fam}}&b={{$j}}{{if $.Expanded}}&exp=1{{end}}">{{end}}
487 [{{$n}} {{$bucket.String}}]
488 {{if $n}}</a>{{end}}
489 </td>
490 {{end}}
491
492 </tr>{{end}}
493 </table>
494
495 {{if $.EventLogs}}
496 <hr />
497 <h3>Family: {{$.Family}}</h3>
498
499 {{if $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}">{{end}}
500 [Summary]{{if $.Expanded}}</a>{{end}}
501
502 {{if not $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}&exp=1">{{end}}
503 [Expanded]{{if not $.Expanded}}</a>{{end}}
504
505 <table id="reqs">
506 <tr><th>When</th><th>Elapsed</th></tr>
507 {{range $el := $.EventLogs}}
508 <tr class="first">
509 <td class="when">{{$el.When}}</td>
510 <td class="elapsed">{{$el.ElapsedTime}}</td>
511 <td>{{$el.Title}}
512 </tr>
513 {{if $.Expanded}}
514 <tr>
515 <td class="when"></td>
516 <td class="elapsed"></td>
517 <td><pre>{{$el.Stack|trimSpace}}</pre></td>
518 </tr>
519 {{range $el.Events}}
520 <tr>
521 <td class="when">{{.WhenString}}</td>
522 <td class="elapsed">{{elapsed .Elapsed}}</td>
523 <td>.{{if .IsErr}}E{{else}}.{{end}}. {{.What}}</td>
524 </tr>
525 {{end}}
526 {{end}}
527 {{end}}
528 </table>
529 {{end}}
530 </body>
531 </html>
532 `
533
View as plain text