12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103 |
- // 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 trace implements tracing of requests and long-lived objects.
- It exports HTTP interfaces on /debug/requests and /debug/events.
- A trace.Trace provides tracing for short-lived objects, usually requests.
- A request handler might be implemented like this:
- func fooHandler(w http.ResponseWriter, req *http.Request) {
- tr := trace.New("mypkg.Foo", req.URL.Path)
- defer tr.Finish()
- ...
- tr.LazyPrintf("some event %q happened", str)
- ...
- if err := somethingImportant(); err != nil {
- tr.LazyPrintf("somethingImportant failed: %v", err)
- tr.SetError()
- }
- }
- The /debug/requests HTTP endpoint organizes the traces by family,
- errors, and duration. It also provides histogram of request duration
- for each family.
- A trace.EventLog provides tracing for long-lived objects, such as RPC
- connections.
- // A Fetcher fetches URL paths for a single domain.
- type Fetcher struct {
- domain string
- events trace.EventLog
- }
- func NewFetcher(domain string) *Fetcher {
- return &Fetcher{
- domain,
- trace.NewEventLog("mypkg.Fetcher", domain),
- }
- }
- func (f *Fetcher) Fetch(path string) (string, error) {
- resp, err := http.Get("http://" + f.domain + "/" + path)
- if err != nil {
- f.events.Errorf("Get(%q) = %v", path, err)
- return "", err
- }
- f.events.Printf("Get(%q) = %s", path, resp.Status)
- ...
- }
- func (f *Fetcher) Close() error {
- f.events.Finish()
- return nil
- }
- The /debug/events HTTP endpoint organizes the event logs by family and
- by time since the last error. The expanded view displays recent log
- entries and the log's call stack.
- */
- package trace // import "golang.org/x/net/trace"
- import (
- "bytes"
- "fmt"
- "html/template"
- "io"
- "log"
- "net"
- "net/http"
- "runtime"
- "sort"
- "strconv"
- "sync"
- "sync/atomic"
- "time"
- "golang.org/x/net/internal/timeseries"
- )
- // DebugUseAfterFinish controls whether to debug uses of Trace values after finishing.
- // FOR DEBUGGING ONLY. This will slow down the program.
- var DebugUseAfterFinish = false
- // AuthRequest determines whether a specific request is permitted to load the
- // /debug/requests or /debug/events pages.
- //
- // It returns two bools; the first indicates whether the page may be viewed at all,
- // and the second indicates whether sensitive events will be shown.
- //
- // AuthRequest may be replaced by a program to customize its authorization requirements.
- //
- // The default AuthRequest function returns (true, true) if and only if the request
- // comes from localhost/127.0.0.1/[::1].
- var AuthRequest = func(req *http.Request) (any, sensitive bool) {
- // RemoteAddr is commonly in the form "IP" or "IP:port".
- // If it is in the form "IP:port", split off the port.
- host, _, err := net.SplitHostPort(req.RemoteAddr)
- if err != nil {
- host = req.RemoteAddr
- }
- switch host {
- case "localhost", "127.0.0.1", "::1":
- return true, true
- default:
- return false, false
- }
- }
- func init() {
- // TODO(jbd): Serve Traces from /debug/traces in the future?
- // There is no requirement for a request to be present to have traces.
- http.HandleFunc("/debug/requests", Traces)
- http.HandleFunc("/debug/events", Events)
- }
- // Traces responds with traces from the program.
- // The package initialization registers it in http.DefaultServeMux
- // at /debug/requests.
- //
- // It performs authorization by running AuthRequest.
- func Traces(w http.ResponseWriter, req *http.Request) {
- any, sensitive := AuthRequest(req)
- if !any {
- http.Error(w, "not allowed", http.StatusUnauthorized)
- return
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- Render(w, req, sensitive)
- }
- // Events responds with a page of events collected by EventLogs.
- // The package initialization registers it in http.DefaultServeMux
- // at /debug/events.
- //
- // It performs authorization by running AuthRequest.
- func Events(w http.ResponseWriter, req *http.Request) {
- any, sensitive := AuthRequest(req)
- if !any {
- http.Error(w, "not allowed", http.StatusUnauthorized)
- return
- }
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- RenderEvents(w, req, sensitive)
- }
- // Render renders the HTML page typically served at /debug/requests.
- // It does not do any auth checking. The request may be nil.
- //
- // Most users will use the Traces handler.
- func Render(w io.Writer, req *http.Request, sensitive bool) {
- data := &struct {
- Families []string
- ActiveTraceCount map[string]int
- CompletedTraces map[string]*family
- // Set when a bucket has been selected.
- Traces traceList
- Family string
- Bucket int
- Expanded bool
- Traced bool
- Active bool
- ShowSensitive bool // whether to show sensitive events
- Histogram template.HTML
- HistogramWindow string // e.g. "last minute", "last hour", "all time"
- // If non-zero, the set of traces is a partial set,
- // and this is the total number.
- Total int
- }{
- CompletedTraces: completedTraces,
- }
- data.ShowSensitive = sensitive
- if req != nil {
- // Allow show_sensitive=0 to force hiding of sensitive data for testing.
- // This only goes one way; you can't use show_sensitive=1 to see things.
- if req.FormValue("show_sensitive") == "0" {
- data.ShowSensitive = false
- }
- if exp, err := strconv.ParseBool(req.FormValue("exp")); err == nil {
- data.Expanded = exp
- }
- if exp, err := strconv.ParseBool(req.FormValue("rtraced")); err == nil {
- data.Traced = exp
- }
- }
- completedMu.RLock()
- data.Families = make([]string, 0, len(completedTraces))
- for fam := range completedTraces {
- data.Families = append(data.Families, fam)
- }
- completedMu.RUnlock()
- sort.Strings(data.Families)
- // We are careful here to minimize the time spent locking activeMu,
- // since that lock is required every time an RPC starts and finishes.
- data.ActiveTraceCount = make(map[string]int, len(data.Families))
- activeMu.RLock()
- for fam, s := range activeTraces {
- data.ActiveTraceCount[fam] = s.Len()
- }
- activeMu.RUnlock()
- var ok bool
- data.Family, data.Bucket, ok = parseArgs(req)
- switch {
- case !ok:
- // No-op
- case data.Bucket == -1:
- data.Active = true
- n := data.ActiveTraceCount[data.Family]
- data.Traces = getActiveTraces(data.Family)
- if len(data.Traces) < n {
- data.Total = n
- }
- case data.Bucket < bucketsPerFamily:
- if b := lookupBucket(data.Family, data.Bucket); b != nil {
- data.Traces = b.Copy(data.Traced)
- }
- default:
- if f := getFamily(data.Family, false); f != nil {
- var obs timeseries.Observable
- f.LatencyMu.RLock()
- switch o := data.Bucket - bucketsPerFamily; o {
- case 0:
- obs = f.Latency.Minute()
- data.HistogramWindow = "last minute"
- case 1:
- obs = f.Latency.Hour()
- data.HistogramWindow = "last hour"
- case 2:
- obs = f.Latency.Total()
- data.HistogramWindow = "all time"
- }
- f.LatencyMu.RUnlock()
- if obs != nil {
- data.Histogram = obs.(*histogram).html()
- }
- }
- }
- if data.Traces != nil {
- defer data.Traces.Free()
- sort.Sort(data.Traces)
- }
- completedMu.RLock()
- defer completedMu.RUnlock()
- if err := pageTmpl().ExecuteTemplate(w, "Page", data); err != nil {
- log.Printf("net/trace: Failed executing template: %v", err)
- }
- }
- func parseArgs(req *http.Request) (fam string, b int, ok bool) {
- if req == nil {
- return "", 0, false
- }
- fam, bStr := req.FormValue("fam"), req.FormValue("b")
- if fam == "" || bStr == "" {
- return "", 0, false
- }
- b, err := strconv.Atoi(bStr)
- if err != nil || b < -1 {
- return "", 0, false
- }
- return fam, b, true
- }
- func lookupBucket(fam string, b int) *traceBucket {
- f := getFamily(fam, false)
- if f == nil || b < 0 || b >= len(f.Buckets) {
- return nil
- }
- return f.Buckets[b]
- }
- type contextKeyT string
- var contextKey = contextKeyT("golang.org/x/net/trace.Trace")
- // Trace represents an active request.
- type Trace interface {
- // LazyLog adds x to the event log. It will be evaluated each time the
- // /debug/requests page is rendered. Any memory referenced by x will be
- // pinned until the trace is finished and later discarded.
- LazyLog(x fmt.Stringer, sensitive bool)
- // LazyPrintf evaluates its arguments with fmt.Sprintf each time the
- // /debug/requests page is rendered. Any memory referenced by a will be
- // pinned until the trace is finished and later discarded.
- LazyPrintf(format string, a ...interface{})
- // SetError declares that this trace resulted in an error.
- SetError()
- // SetRecycler sets a recycler for the trace.
- // f will be called for each event passed to LazyLog at a time when
- // it is no longer required, whether while the trace is still active
- // and the event is discarded, or when a completed trace is discarded.
- SetRecycler(f func(interface{}))
- // SetTraceInfo sets the trace info for the trace.
- // This is currently unused.
- SetTraceInfo(traceID, spanID uint64)
- // SetMaxEvents sets the maximum number of events that will be stored
- // in the trace. This has no effect if any events have already been
- // added to the trace.
- SetMaxEvents(m int)
- // Finish declares that this trace is complete.
- // The trace should not be used after calling this method.
- Finish()
- }
- type lazySprintf struct {
- format string
- a []interface{}
- }
- func (l *lazySprintf) String() string {
- return fmt.Sprintf(l.format, l.a...)
- }
- // New returns a new Trace with the specified family and title.
- func New(family, title string) Trace {
- tr := newTrace()
- tr.ref()
- tr.Family, tr.Title = family, title
- tr.Start = time.Now()
- tr.maxEvents = maxEventsPerTrace
- tr.events = tr.eventsBuf[:0]
- activeMu.RLock()
- s := activeTraces[tr.Family]
- activeMu.RUnlock()
- if s == nil {
- activeMu.Lock()
- s = activeTraces[tr.Family] // check again
- if s == nil {
- s = new(traceSet)
- activeTraces[tr.Family] = s
- }
- activeMu.Unlock()
- }
- s.Add(tr)
- // Trigger allocation of the completed trace structure for this family.
- // This will cause the family to be present in the request page during
- // the first trace of this family. We don't care about the return value,
- // nor is there any need for this to run inline, so we execute it in its
- // own goroutine, but only if the family isn't allocated yet.
- completedMu.RLock()
- if _, ok := completedTraces[tr.Family]; !ok {
- go allocFamily(tr.Family)
- }
- completedMu.RUnlock()
- return tr
- }
- func (tr *trace) Finish() {
- elapsed := time.Now().Sub(tr.Start)
- tr.mu.Lock()
- tr.Elapsed = elapsed
- tr.mu.Unlock()
- if DebugUseAfterFinish {
- buf := make([]byte, 4<<10) // 4 KB should be enough
- n := runtime.Stack(buf, false)
- tr.finishStack = buf[:n]
- }
- activeMu.RLock()
- m := activeTraces[tr.Family]
- activeMu.RUnlock()
- m.Remove(tr)
- f := getFamily(tr.Family, true)
- tr.mu.RLock() // protects tr fields in Cond.match calls
- for _, b := range f.Buckets {
- if b.Cond.match(tr) {
- b.Add(tr)
- }
- }
- tr.mu.RUnlock()
- // Add a sample of elapsed time as microseconds to the family's timeseries
- h := new(histogram)
- h.addMeasurement(elapsed.Nanoseconds() / 1e3)
- f.LatencyMu.Lock()
- f.Latency.Add(h)
- f.LatencyMu.Unlock()
- tr.unref() // matches ref in New
- }
- const (
- bucketsPerFamily = 9
- tracesPerBucket = 10
- maxActiveTraces = 20 // Maximum number of active traces to show.
- maxEventsPerTrace = 10
- numHistogramBuckets = 38
- )
- var (
- // The active traces.
- activeMu sync.RWMutex
- activeTraces = make(map[string]*traceSet) // family -> traces
- // Families of completed traces.
- completedMu sync.RWMutex
- completedTraces = make(map[string]*family) // family -> traces
- )
- type traceSet struct {
- mu sync.RWMutex
- m map[*trace]bool
- // We could avoid the entire map scan in FirstN by having a slice of all the traces
- // ordered by start time, and an index into that from the trace struct, with a periodic
- // repack of the slice after enough traces finish; we could also use a skip list or similar.
- // However, that would shift some of the expense from /debug/requests time to RPC time,
- // which is probably the wrong trade-off.
- }
- func (ts *traceSet) Len() int {
- ts.mu.RLock()
- defer ts.mu.RUnlock()
- return len(ts.m)
- }
- func (ts *traceSet) Add(tr *trace) {
- ts.mu.Lock()
- if ts.m == nil {
- ts.m = make(map[*trace]bool)
- }
- ts.m[tr] = true
- ts.mu.Unlock()
- }
- func (ts *traceSet) Remove(tr *trace) {
- ts.mu.Lock()
- delete(ts.m, tr)
- ts.mu.Unlock()
- }
- // FirstN returns the first n traces ordered by time.
- func (ts *traceSet) FirstN(n int) traceList {
- ts.mu.RLock()
- defer ts.mu.RUnlock()
- if n > len(ts.m) {
- n = len(ts.m)
- }
- trl := make(traceList, 0, n)
- // Fast path for when no selectivity is needed.
- if n == len(ts.m) {
- for tr := range ts.m {
- tr.ref()
- trl = append(trl, tr)
- }
- sort.Sort(trl)
- return trl
- }
- // Pick the oldest n traces.
- // This is inefficient. See the comment in the traceSet struct.
- for tr := range ts.m {
- // Put the first n traces into trl in the order they occur.
- // When we have n, sort trl, and thereafter maintain its order.
- if len(trl) < n {
- tr.ref()
- trl = append(trl, tr)
- if len(trl) == n {
- // This is guaranteed to happen exactly once during this loop.
- sort.Sort(trl)
- }
- continue
- }
- if tr.Start.After(trl[n-1].Start) {
- continue
- }
- // Find where to insert this one.
- tr.ref()
- i := sort.Search(n, func(i int) bool { return trl[i].Start.After(tr.Start) })
- trl[n-1].unref()
- copy(trl[i+1:], trl[i:])
- trl[i] = tr
- }
- return trl
- }
- func getActiveTraces(fam string) traceList {
- activeMu.RLock()
- s := activeTraces[fam]
- activeMu.RUnlock()
- if s == nil {
- return nil
- }
- return s.FirstN(maxActiveTraces)
- }
- func getFamily(fam string, allocNew bool) *family {
- completedMu.RLock()
- f := completedTraces[fam]
- completedMu.RUnlock()
- if f == nil && allocNew {
- f = allocFamily(fam)
- }
- return f
- }
- func allocFamily(fam string) *family {
- completedMu.Lock()
- defer completedMu.Unlock()
- f := completedTraces[fam]
- if f == nil {
- f = newFamily()
- completedTraces[fam] = f
- }
- return f
- }
- // family represents a set of trace buckets and associated latency information.
- type family struct {
- // traces may occur in multiple buckets.
- Buckets [bucketsPerFamily]*traceBucket
- // latency time series
- LatencyMu sync.RWMutex
- Latency *timeseries.MinuteHourSeries
- }
- func newFamily() *family {
- return &family{
- Buckets: [bucketsPerFamily]*traceBucket{
- {Cond: minCond(0)},
- {Cond: minCond(50 * time.Millisecond)},
- {Cond: minCond(100 * time.Millisecond)},
- {Cond: minCond(200 * time.Millisecond)},
- {Cond: minCond(500 * time.Millisecond)},
- {Cond: minCond(1 * time.Second)},
- {Cond: minCond(10 * time.Second)},
- {Cond: minCond(100 * time.Second)},
- {Cond: errorCond{}},
- },
- Latency: timeseries.NewMinuteHourSeries(func() timeseries.Observable { return new(histogram) }),
- }
- }
- // traceBucket represents a size-capped bucket of historic traces,
- // along with a condition for a trace to belong to the bucket.
- type traceBucket struct {
- Cond cond
- // Ring buffer implementation of a fixed-size FIFO queue.
- mu sync.RWMutex
- buf [tracesPerBucket]*trace
- start int // < tracesPerBucket
- length int // <= tracesPerBucket
- }
- func (b *traceBucket) Add(tr *trace) {
- b.mu.Lock()
- defer b.mu.Unlock()
- i := b.start + b.length
- if i >= tracesPerBucket {
- i -= tracesPerBucket
- }
- if b.length == tracesPerBucket {
- // "Remove" an element from the bucket.
- b.buf[i].unref()
- b.start++
- if b.start == tracesPerBucket {
- b.start = 0
- }
- }
- b.buf[i] = tr
- if b.length < tracesPerBucket {
- b.length++
- }
- tr.ref()
- }
- // Copy returns a copy of the traces in the bucket.
- // If tracedOnly is true, only the traces with trace information will be returned.
- // The logs will be ref'd before returning; the caller should call
- // the Free method when it is done with them.
- // TODO(dsymonds): keep track of traced requests in separate buckets.
- func (b *traceBucket) Copy(tracedOnly bool) traceList {
- b.mu.RLock()
- defer b.mu.RUnlock()
- trl := make(traceList, 0, b.length)
- for i, x := 0, b.start; i < b.length; i++ {
- tr := b.buf[x]
- if !tracedOnly || tr.spanID != 0 {
- tr.ref()
- trl = append(trl, tr)
- }
- x++
- if x == b.length {
- x = 0
- }
- }
- return trl
- }
- func (b *traceBucket) Empty() bool {
- b.mu.RLock()
- defer b.mu.RUnlock()
- return b.length == 0
- }
- // cond represents a condition on a trace.
- type cond interface {
- match(t *trace) bool
- String() string
- }
- type minCond time.Duration
- func (m minCond) match(t *trace) bool { return t.Elapsed >= time.Duration(m) }
- func (m minCond) String() string { return fmt.Sprintf("≥%gs", time.Duration(m).Seconds()) }
- type errorCond struct{}
- func (e errorCond) match(t *trace) bool { return t.IsError }
- func (e errorCond) String() string { return "errors" }
- type traceList []*trace
- // Free calls unref on each element of the list.
- func (trl traceList) Free() {
- for _, t := range trl {
- t.unref()
- }
- }
- // traceList may be sorted in reverse chronological order.
- func (trl traceList) Len() int { return len(trl) }
- func (trl traceList) Less(i, j int) bool { return trl[i].Start.After(trl[j].Start) }
- func (trl traceList) Swap(i, j int) { trl[i], trl[j] = trl[j], trl[i] }
- // An event is a timestamped log entry in a trace.
- type event struct {
- When time.Time
- Elapsed time.Duration // since previous event in trace
- NewDay bool // whether this event is on a different day to the previous event
- Recyclable bool // whether this event was passed via LazyLog
- Sensitive bool // whether this event contains sensitive information
- What interface{} // string or fmt.Stringer
- }
- // WhenString returns a string representation of the elapsed time of the event.
- // It will include the date if midnight was crossed.
- func (e event) WhenString() string {
- if e.NewDay {
- return e.When.Format("2006/01/02 15:04:05.000000")
- }
- return e.When.Format("15:04:05.000000")
- }
- // discarded represents a number of discarded events.
- // It is stored as *discarded to make it easier to update in-place.
- type discarded int
- func (d *discarded) String() string {
- return fmt.Sprintf("(%d events discarded)", int(*d))
- }
- // trace represents an active or complete request,
- // either sent or received by this program.
- type trace struct {
- // Family is the top-level grouping of traces to which this belongs.
- Family string
- // Title is the title of this trace.
- Title string
- // Start time of the this trace.
- Start time.Time
- mu sync.RWMutex
- events []event // Append-only sequence of events (modulo discards).
- maxEvents int
- recycler func(interface{})
- IsError bool // Whether this trace resulted in an error.
- Elapsed time.Duration // Elapsed time for this trace, zero while active.
- traceID uint64 // Trace information if non-zero.
- spanID uint64
- refs int32 // how many buckets this is in
- disc discarded // scratch space to avoid allocation
- finishStack []byte // where finish was called, if DebugUseAfterFinish is set
- eventsBuf [4]event // preallocated buffer in case we only log a few events
- }
- func (tr *trace) reset() {
- // Clear all but the mutex. Mutexes may not be copied, even when unlocked.
- tr.Family = ""
- tr.Title = ""
- tr.Start = time.Time{}
- tr.mu.Lock()
- tr.Elapsed = 0
- tr.traceID = 0
- tr.spanID = 0
- tr.IsError = false
- tr.maxEvents = 0
- tr.events = nil
- tr.recycler = nil
- tr.mu.Unlock()
- tr.refs = 0
- tr.disc = 0
- tr.finishStack = nil
- for i := range tr.eventsBuf {
- tr.eventsBuf[i] = event{}
- }
- }
- // delta returns the elapsed time since the last event or the trace start,
- // and whether it spans midnight.
- // L >= tr.mu
- func (tr *trace) delta(t time.Time) (time.Duration, bool) {
- if len(tr.events) == 0 {
- return t.Sub(tr.Start), false
- }
- prev := tr.events[len(tr.events)-1].When
- return t.Sub(prev), prev.Day() != t.Day()
- }
- func (tr *trace) addEvent(x interface{}, recyclable, sensitive bool) {
- if DebugUseAfterFinish && tr.finishStack != nil {
- buf := make([]byte, 4<<10) // 4 KB should be enough
- n := runtime.Stack(buf, false)
- log.Printf("net/trace: trace used after finish:\nFinished at:\n%s\nUsed at:\n%s", tr.finishStack, buf[:n])
- }
- /*
- NOTE TO DEBUGGERS
- If you are here because your program panicked in this code,
- it is almost definitely the fault of code using this package,
- and very unlikely to be the fault of this code.
- The most likely scenario is that some code elsewhere is using
- a trace.Trace after its Finish method is called.
- You can temporarily set the DebugUseAfterFinish var
- to help discover where that is; do not leave that var set,
- since it makes this package much less efficient.
- */
- e := event{When: time.Now(), What: x, Recyclable: recyclable, Sensitive: sensitive}
- tr.mu.Lock()
- e.Elapsed, e.NewDay = tr.delta(e.When)
- if len(tr.events) < tr.maxEvents {
- tr.events = append(tr.events, e)
- } else {
- // Discard the middle events.
- di := int((tr.maxEvents - 1) / 2)
- if d, ok := tr.events[di].What.(*discarded); ok {
- (*d)++
- } else {
- // disc starts at two to count for the event it is replacing,
- // plus the next one that we are about to drop.
- tr.disc = 2
- if tr.recycler != nil && tr.events[di].Recyclable {
- go tr.recycler(tr.events[di].What)
- }
- tr.events[di].What = &tr.disc
- }
- // The timestamp of the discarded meta-event should be
- // the time of the last event it is representing.
- tr.events[di].When = tr.events[di+1].When
- if tr.recycler != nil && tr.events[di+1].Recyclable {
- go tr.recycler(tr.events[di+1].What)
- }
- copy(tr.events[di+1:], tr.events[di+2:])
- tr.events[tr.maxEvents-1] = e
- }
- tr.mu.Unlock()
- }
- func (tr *trace) LazyLog(x fmt.Stringer, sensitive bool) {
- tr.addEvent(x, true, sensitive)
- }
- func (tr *trace) LazyPrintf(format string, a ...interface{}) {
- tr.addEvent(&lazySprintf{format, a}, false, false)
- }
- func (tr *trace) SetError() {
- tr.mu.Lock()
- tr.IsError = true
- tr.mu.Unlock()
- }
- func (tr *trace) SetRecycler(f func(interface{})) {
- tr.mu.Lock()
- tr.recycler = f
- tr.mu.Unlock()
- }
- func (tr *trace) SetTraceInfo(traceID, spanID uint64) {
- tr.mu.Lock()
- tr.traceID, tr.spanID = traceID, spanID
- tr.mu.Unlock()
- }
- func (tr *trace) SetMaxEvents(m int) {
- tr.mu.Lock()
- // Always keep at least three events: first, discarded count, last.
- if len(tr.events) == 0 && m > 3 {
- tr.maxEvents = m
- }
- tr.mu.Unlock()
- }
- func (tr *trace) ref() {
- atomic.AddInt32(&tr.refs, 1)
- }
- func (tr *trace) unref() {
- if atomic.AddInt32(&tr.refs, -1) == 0 {
- tr.mu.RLock()
- if tr.recycler != nil {
- // freeTrace clears tr, so we hold tr.recycler and tr.events here.
- go func(f func(interface{}), es []event) {
- for _, e := range es {
- if e.Recyclable {
- f(e.What)
- }
- }
- }(tr.recycler, tr.events)
- }
- tr.mu.RUnlock()
- freeTrace(tr)
- }
- }
- func (tr *trace) When() string {
- return tr.Start.Format("2006/01/02 15:04:05.000000")
- }
- func (tr *trace) ElapsedTime() string {
- tr.mu.RLock()
- t := tr.Elapsed
- tr.mu.RUnlock()
- if t == 0 {
- // Active trace.
- t = time.Since(tr.Start)
- }
- return fmt.Sprintf("%.6f", t.Seconds())
- }
- func (tr *trace) Events() []event {
- tr.mu.RLock()
- defer tr.mu.RUnlock()
- return tr.events
- }
- var traceFreeList = make(chan *trace, 1000) // TODO(dsymonds): Use sync.Pool?
- // newTrace returns a trace ready to use.
- func newTrace() *trace {
- select {
- case tr := <-traceFreeList:
- return tr
- default:
- return new(trace)
- }
- }
- // freeTrace adds tr to traceFreeList if there's room.
- // This is non-blocking.
- func freeTrace(tr *trace) {
- if DebugUseAfterFinish {
- return // never reuse
- }
- tr.reset()
- select {
- case traceFreeList <- tr:
- default:
- }
- }
- func elapsed(d time.Duration) string {
- b := []byte(fmt.Sprintf("%.6f", d.Seconds()))
- // For subsecond durations, blank all zeros before decimal point,
- // and all zeros between the decimal point and the first non-zero digit.
- if d < time.Second {
- dot := bytes.IndexByte(b, '.')
- for i := 0; i < dot; i++ {
- b[i] = ' '
- }
- for i := dot + 1; i < len(b); i++ {
- if b[i] == '0' {
- b[i] = ' '
- } else {
- break
- }
- }
- }
- return string(b)
- }
- var pageTmplCache *template.Template
- var pageTmplOnce sync.Once
- func pageTmpl() *template.Template {
- pageTmplOnce.Do(func() {
- pageTmplCache = template.Must(template.New("Page").Funcs(template.FuncMap{
- "elapsed": elapsed,
- "add": func(a, b int) int { return a + b },
- }).Parse(pageHTML))
- })
- return pageTmplCache
- }
- const pageHTML = `
- {{template "Prolog" .}}
- {{template "StatusTable" .}}
- {{template "Epilog" .}}
- {{define "Prolog"}}
- <html>
- <head>
- <title>/debug/requests</title>
- <style type="text/css">
- body {
- font-family: sans-serif;
- }
- table#tr-status td.family {
- padding-right: 2em;
- }
- table#tr-status td.active {
- padding-right: 1em;
- }
- table#tr-status td.latency-first {
- padding-left: 1em;
- }
- table#tr-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>
- </head>
- <body>
- <h1>/debug/requests</h1>
- {{end}} {{/* end of Prolog */}}
- {{define "StatusTable"}}
- <table id="tr-status">
- {{range $fam := .Families}}
- <tr>
- <td class="family">{{$fam}}</td>
- {{$n := index $.ActiveTraceCount $fam}}
- <td class="active {{if not $n}}empty{{end}}">
- {{if $n}}<a href="?fam={{$fam}}&b=-1{{if $.Expanded}}&exp=1{{end}}">{{end}}
- [{{$n}} active]
- {{if $n}}</a>{{end}}
- </td>
- {{$f := index $.CompletedTraces $fam}}
- {{range $i, $b := $f.Buckets}}
- {{$empty := $b.Empty}}
- <td {{if $empty}}class="empty"{{end}}>
- {{if not $empty}}<a href="?fam={{$fam}}&b={{$i}}{{if $.Expanded}}&exp=1{{end}}">{{end}}
- [{{.Cond}}]
- {{if not $empty}}</a>{{end}}
- </td>
- {{end}}
- {{$nb := len $f.Buckets}}
- <td class="latency-first">
- <a href="?fam={{$fam}}&b={{$nb}}">[minute]</a>
- </td>
- <td>
- <a href="?fam={{$fam}}&b={{add $nb 1}}">[hour]</a>
- </td>
- <td>
- <a href="?fam={{$fam}}&b={{add $nb 2}}">[total]</a>
- </td>
- </tr>
- {{end}}
- </table>
- {{end}} {{/* end of StatusTable */}}
- {{define "Epilog"}}
- {{if $.Traces}}
- <hr />
- <h3>Family: {{$.Family}}</h3>
- {{if or $.Expanded $.Traced}}
- <a href="?fam={{$.Family}}&b={{$.Bucket}}">[Normal/Summary]</a>
- {{else}}
- [Normal/Summary]
- {{end}}
- {{if or (not $.Expanded) $.Traced}}
- <a href="?fam={{$.Family}}&b={{$.Bucket}}&exp=1">[Normal/Expanded]</a>
- {{else}}
- [Normal/Expanded]
- {{end}}
- {{if not $.Active}}
- {{if or $.Expanded (not $.Traced)}}
- <a href="?fam={{$.Family}}&b={{$.Bucket}}&rtraced=1">[Traced/Summary]</a>
- {{else}}
- [Traced/Summary]
- {{end}}
- {{if or (not $.Expanded) (not $.Traced)}}
- <a href="?fam={{$.Family}}&b={{$.Bucket}}&exp=1&rtraced=1">[Traced/Expanded]</a>
- {{else}}
- [Traced/Expanded]
- {{end}}
- {{end}}
- {{if $.Total}}
- <p><em>Showing <b>{{len $.Traces}}</b> of <b>{{$.Total}}</b> traces.</em></p>
- {{end}}
- <table id="reqs">
- <caption>
- {{if $.Active}}Active{{else}}Completed{{end}} Requests
- </caption>
- <tr><th>When</th><th>Elapsed (s)</th></tr>
- {{range $tr := $.Traces}}
- <tr class="first">
- <td class="when">{{$tr.When}}</td>
- <td class="elapsed">{{$tr.ElapsedTime}}</td>
- <td>{{$tr.Title}}</td>
- {{/* TODO: include traceID/spanID */}}
- </tr>
- {{if $.Expanded}}
- {{range $tr.Events}}
- <tr>
- <td class="when">{{.WhenString}}</td>
- <td class="elapsed">{{elapsed .Elapsed}}</td>
- <td>{{if or $.ShowSensitive (not .Sensitive)}}... {{.What}}{{else}}<em>[redacted]</em>{{end}}</td>
- </tr>
- {{end}}
- {{end}}
- {{end}}
- </table>
- {{end}} {{/* if $.Traces */}}
- {{if $.Histogram}}
- <h4>Latency (µs) of {{$.Family}} over {{$.HistogramWindow}}</h4>
- {{$.Histogram}}
- {{end}} {{/* if $.Histogram */}}
- </body>
- </html>
- {{end}} {{/* end of Epilog */}}
- `
|