// Copyright 2015 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package traceimport ()const maxEventsPerLog = 100type bucket struct { MaxErrAge time.Duration String string}var buckets = []bucket{ {0, "total"}, {10 * time.Second, "errs<10s"}, {1 * time.Minute, "errs<1m"}, {10 * time.Minute, "errs<10m"}, {1 * time.Hour, "errs<1h"}, {10 * time.Hour, "errs<10h"}, {24000 * time.Hour, "errors"},}// RenderEvents renders the HTML page typically served at /debug/events.// It does not do any auth checking. The request may be nil.//// Most users will use the Events handler.func ( http.ResponseWriter, *http.Request, bool) { := time.Now() := &struct { []string// family names []bucket [][]int// eventLog count per family/bucket// Set when a bucket has been selected.stringinteventLogsbool }{ : buckets, } . = make([]string, 0, len(families))famMu.RLock()for := rangefamilies { . = append(., ) }famMu.RUnlock()sort.Strings(.)// Count the number of eventLogs in each family for each error age. . = make([][]int, len(.))for , := range . {// TODO(sameer): move this loop under the family lock. := getEventFamily() .[] = make([]int, len(.))for , := range . { .[][] = .Count(, .MaxErrAge) } }if != nil {varbool ., ., = parseEventsArgs()if ! {// No-op } else { . = getEventFamily(.).Copy(, buckets[.].MaxErrAge) }if . != nil {defer ..Free()sort.Sort(.) }if , := strconv.ParseBool(.FormValue("exp")); == nil { . = } }famMu.RLock()deferfamMu.RUnlock()if := eventsTmpl().Execute(, ); != nil {log.Printf("net/trace: Failed executing template: %v", ) }}func parseEventsArgs( *http.Request) ( string, int, bool) { , := .FormValue("fam"), .FormValue("b")if == "" || == "" {return"", 0, false } , := strconv.Atoi()if != nil || < 0 || >= len(buckets) {return"", 0, false }return , , true}// An EventLog provides a log of events associated with a specific object.typeEventLoginterface {// Printf formats its arguments with fmt.Sprintf and adds the // result to the event log.Printf(format string, a ...interface{})// Errorf is like Printf, but it marks this event as an error.Errorf(format string, a ...interface{})// Finish declares that this event log is complete. // The event log should not be used after calling this method.Finish()}// NewEventLog returns a new EventLog with the specified family name// and title.func (, string) EventLog { := newEventLog() .ref() .Family, .Title = , .Start = time.Now() .events = make([]logEntry, 0, maxEventsPerLog) .stack = make([]uintptr, 32) := runtime.Callers(2, .stack) .stack = .stack[:]getEventFamily().add()return}func ( *eventLog) () {getEventFamily(.Family).remove() .unref() // matches ref in New}var ( famMu sync.RWMutex families = make(map[string]*eventFamily) // family name => family)func getEventFamily( string) *eventFamily {famMu.Lock()deferfamMu.Unlock() := families[]if == nil { = &eventFamily{}families[] = }return}type eventFamily struct { mu sync.RWMutex eventLogs eventLogs}func ( *eventFamily) ( *eventLog) { .mu.Lock() .eventLogs = append(.eventLogs, ) .mu.Unlock()}func ( *eventFamily) ( *eventLog) { .mu.Lock()defer .mu.Unlock()for , := range .eventLogs {if == {copy(.eventLogs[:], .eventLogs[+1:]) .eventLogs = .eventLogs[:len(.eventLogs)-1]return } }}func ( *eventFamily) ( time.Time, time.Duration) ( int) { .mu.RLock()defer .mu.RUnlock()for , := range .eventLogs {if .hasRecentError(, ) { ++ } }return}func ( *eventFamily) ( time.Time, time.Duration) ( eventLogs) { .mu.RLock()defer .mu.RUnlock() = make(eventLogs, 0, len(.eventLogs))for , := range .eventLogs {if .hasRecentError(, ) { .ref() = append(, ) } }return}type eventLogs []*eventLog// Free calls unref on each element of the list.func ( eventLogs) () {for , := range { .unref() }}// eventLogs may be sorted in reverse chronological order.func ( eventLogs) () int { returnlen() }func ( eventLogs) (, int) bool { return [].Start.After([].Start) }func ( eventLogs) (, int) { [], [] = [], [] }// A logEntry is a timestamped log entry in an event log.type logEntry struct { When time.Time Elapsed time.Duration// since previous event in log NewDay bool// whether this event is on a different day to the previous event What string IsErr bool}// WhenString returns a string representation of the elapsed time of the event.// It will include the date if midnight was crossed.func ( logEntry) () string {if .NewDay {return .When.Format("2006/01/02 15:04:05.000000") }return .When.Format("15:04:05.000000")}// An eventLog represents an active event log.type eventLog struct {// Family is the top-level grouping of event logs to which this belongs. Family string// Title is the title of this event log. Title string// Timing information. Start time.Time// Call stack where this event log was created. stack []uintptr// Append-only sequence of events. // // TODO(sameer): change this to a ring buffer to avoid the array copy // when we hit maxEventsPerLog. mu sync.RWMutex events []logEntry LastErrorTime time.Time discarded int refs int32// how many buckets this is in}func ( *eventLog) () {// Clear all but the mutex. Mutexes may not be copied, even when unlocked. .Family = "" .Title = "" .Start = time.Time{} .stack = nil .events = nil .LastErrorTime = time.Time{} .discarded = 0 .refs = 0}func ( *eventLog) ( time.Time, time.Duration) bool {if == 0 {returntrue } .mu.RLock()defer .mu.RUnlock()return .Sub(.LastErrorTime) < }// delta returns the elapsed time since the last event or the log start,// and whether it spans midnight.// L >= el.mufunc ( *eventLog) ( time.Time) (time.Duration, bool) {iflen(.events) == 0 {return .Sub(.Start), false } := .events[len(.events)-1].Whenreturn .Sub(), .Day() != .Day()}func ( *eventLog) ( string, ...interface{}) { .printf(false, , ...)}func ( *eventLog) ( string, ...interface{}) { .printf(true, , ...)}func ( *eventLog) ( bool, string, ...interface{}) { := logEntry{When: time.Now(), IsErr: , What: fmt.Sprintf(, ...)} .mu.Lock() .Elapsed, .NewDay = .delta(.When)iflen(.events) < maxEventsPerLog { .events = append(.events, ) } else {// Discard the oldest event.if .discarded == 0 {// el.discarded starts at two to count for the event it // is replacing, plus the next one that we are about to // drop. .discarded = 2 } else { .discarded++ }// TODO(sameer): if this causes allocations on a critical path, // change eventLog.What to be a fmt.Stringer, as in trace.go. .events[0].What = fmt.Sprintf("(%d events discarded)", .discarded)// The timestamp of the discarded meta-event should be // the time of the last event it is representing. .events[0].When = .events[1].Whencopy(.events[1:], .events[2:]) .events[maxEventsPerLog-1] = }if .IsErr { .LastErrorTime = .When } .mu.Unlock()}func ( *eventLog) () {atomic.AddInt32(&.refs, 1)}func ( *eventLog) () {ifatomic.AddInt32(&.refs, -1) == 0 {freeEventLog() }}func ( *eventLog) () string {return .Start.Format("2006/01/02 15:04:05.000000")}func ( *eventLog) () string { := time.Since(.Start)returnfmt.Sprintf("%.6f", .Seconds())}func ( *eventLog) () string { := new(bytes.Buffer) := tabwriter.NewWriter(, 1, 8, 1, '\t', 0)printStackRecord(, .stack) .Flush()return .String()}// printStackRecord prints the function + source line information// for a single stack trace.// Adapted from runtime/pprof/pprof.go.func printStackRecord( io.Writer, []uintptr) {for , := range { := runtime.FuncForPC()if == nil {continue } , := .FileLine() := .Name()// Hide runtime.goexit and any runtime functions at the beginning.ifstrings.HasPrefix(, "runtime.") {continue }fmt.Fprintf(, "# %s\t%s:%d\n", , , ) }}func ( *eventLog) () []logEntry { .mu.RLock()defer .mu.RUnlock()return .events}// freeEventLogs is a freelist of *eventLogvar freeEventLogs = make(chan *eventLog, 1000)// newEventLog returns a event log ready to use.func newEventLog() *eventLog {select {case := <-freeEventLogs:returndefault:returnnew(eventLog) }}// freeEventLog adds el to freeEventLogs if there's room.// This is non-blocking.func freeEventLog( *eventLog) { .reset()select {casefreeEventLogs<- :default: }}var eventsTmplCache *template.Templatevar eventsTmplOnce sync.Oncefunc eventsTmpl() *template.Template {eventsTmplOnce.Do(func() {eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{"elapsed": elapsed,"trimSpace": strings.TrimSpace, }).Parse(eventsHTML)) })returneventsTmplCache}const eventsHTML = `<html> <head> <title>events</title> </head> <style type="text/css"> body { font-family: sans-serif; } table#req-status td.family { padding-right: 2em; } table#req-status td.active { padding-right: 1em; } table#req-status td.empty { color: #aaa; } table#reqs { margin-top: 1em; } table#reqs tr.first { {{if $.Expanded}}font-weight: bold;{{end}} } table#reqs td { font-family: monospace; } table#reqs td.when { text-align: right; white-space: nowrap; } table#reqs td.elapsed { padding: 0 0.5em; text-align: right; white-space: pre; width: 10em; } address { font-size: smaller; margin-top: 5em; } </style> <body><h1>/debug/events</h1><table id="req-status"> {{range $i, $fam := .Families}} <tr> <td class="family">{{$fam}}</td> {{range $j, $bucket := $.Buckets}} {{$n := index $.Counts $i $j}} <td class="{{if not $bucket.MaxErrAge}}active{{end}}{{if not $n}}empty{{end}}"> {{if $n}}<a href="?fam={{$fam}}&b={{$j}}{{if $.Expanded}}&exp=1{{end}}">{{end}} [{{$n}} {{$bucket.String}}] {{if $n}}</a>{{end}} </td> {{end}} </tr>{{end}}</table>{{if $.EventLogs}}<hr /><h3>Family: {{$.Family}}</h3>{{if $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}">{{end}}[Summary]{{if $.Expanded}}</a>{{end}}{{if not $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}&exp=1">{{end}}[Expanded]{{if not $.Expanded}}</a>{{end}}<table id="reqs"> <tr><th>When</th><th>Elapsed</th></tr> {{range $el := $.EventLogs}} <tr class="first"> <td class="when">{{$el.When}}</td> <td class="elapsed">{{$el.ElapsedTime}}</td> <td>{{$el.Title}}</td> </tr> {{if $.Expanded}} <tr> <td class="when"></td> <td class="elapsed"></td> <td><pre>{{$el.Stack|trimSpace}}</pre></td> </tr> {{range $el.Events}} <tr> <td class="when">{{.WhenString}}</td> <td class="elapsed">{{elapsed .Elapsed}}</td> <td>.{{if .IsErr}}E{{else}}.{{end}}. {{.What}}</td> </tr> {{end}} {{end}} {{end}}</table>{{end}} </body></html>`
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.