1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066 |
- // 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() {
- http.HandleFunc("/debug/requests", func(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)
- })
- http.HandleFunc("/debug/events", func(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; see AuthRequest for the default auth check
- // used by the handler registered on http.DefaultServeMux.
- // req may be nil.
- 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() {
- tr.Elapsed = time.Now().Sub(tr.Start)
- 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)
- for _, b := range f.Buckets {
- if b.Cond.match(tr) {
- b.Add(tr)
- }
- }
- // Add a sample of elapsed time as microseconds to the family's timeseries
- h := new(histogram)
- h.addMeasurement(tr.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
- // Timing information.
- Start time.Time
- Elapsed time.Duration // zero while active
- // Trace information if non-zero.
- traceID uint64
- spanID uint64
- // Whether this trace resulted in an error.
- IsError bool
- // Append-only sequence of events (modulo discards).
- mu sync.RWMutex
- events []event
- maxEvents int
- refs int32 // how many buckets this is in
- recycler func(interface{})
- 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.Elapsed = 0
- tr.traceID = 0
- tr.spanID = 0
- tr.IsError = false
- tr.maxEvents = 0
- tr.events = nil
- tr.refs = 0
- tr.recycler = nil
- 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.IsError = true }
- func (tr *trace) SetRecycler(f func(interface{})) {
- tr.recycler = f
- }
- func (tr *trace) SetTraceInfo(traceID, spanID uint64) {
- tr.traceID, tr.spanID = traceID, spanID
- }
- func (tr *trace) SetMaxEvents(m int) {
- // Always keep at least three events: first, discarded count, last.
- if len(tr.events) == 0 && m > 3 {
- tr.maxEvents = m
- }
- }
- func (tr *trace) ref() {
- atomic.AddInt32(&tr.refs, 1)
- }
- func (tr *trace) unref() {
- if atomic.AddInt32(&tr.refs, -1) == 0 {
- 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)
- }
- freeTrace(tr)
- }
- }
- func (tr *trace) When() string {
- return tr.Start.Format("2006/01/02 15:04:05.000000")
- }
- func (tr *trace) ElapsedTime() string {
- t := tr.Elapsed
- 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 */}}
- `
|