Source File
trace.go
Belonging Package
golang.org/x/net/trace
package trace // import "golang.org/x/net/trace"
import (
)
var DebugUseAfterFinish = false
const (
debugRequestsPath = "/debug/requests"
debugEventsPath = "/debug/events"
)
, , := net.SplitHostPort(.RemoteAddr)
if != nil {
= .RemoteAddr
}
switch {
case "localhost", "127.0.0.1", "::1":
return true, true
default:
return false, false
}
}
func () {
, := http.DefaultServeMux.Handler(&http.Request{URL: &url.URL{Path: debugRequestsPath}})
if == debugRequestsPath {
panic("/debug/requests is already registered. You may have two independent copies of " +
"golang.org/x/net/trace in your binary, trying to maintain separate state. This may " +
"involve a vendored copy of golang.org/x/net/trace.")
}
func ( http.ResponseWriter, *http.Request) {
, := AuthRequest()
if ! {
http.Error(, "not allowed", http.StatusUnauthorized)
return
}
.Header().Set("Content-Type", "text/html; charset=utf-8")
Render(, , )
}
func ( http.ResponseWriter, *http.Request) {
, := AuthRequest()
if ! {
http.Error(, "not allowed", http.StatusUnauthorized)
return
}
.Header().Set("Content-Type", "text/html; charset=utf-8")
RenderEvents(, , )
}
int
}{
: completedTraces,
}
. =
if .FormValue("show_sensitive") == "0" {
. = false
}
if , := strconv.ParseBool(.FormValue("exp")); == nil {
. =
}
if , := strconv.ParseBool(.FormValue("rtraced")); == nil {
. =
}
}
completedMu.RLock()
. = make([]string, 0, len(completedTraces))
for := range completedTraces {
. = append(., )
}
completedMu.RUnlock()
sort.Strings(.)
case . == -1:
. = true
:= .[.]
. = getActiveTraces(.)
if len(.) < {
. =
}
case . < bucketsPerFamily:
if := lookupBucket(., .); != nil {
. = .Copy(.)
}
default:
if := getFamily(., false); != nil {
var timeseries.Observable
.LatencyMu.RLock()
switch := . - bucketsPerFamily; {
case 0:
= .Latency.Minute()
. = "last minute"
case 1:
= .Latency.Hour()
. = "last hour"
case 2:
= .Latency.Total()
. = "all time"
}
.LatencyMu.RUnlock()
if != nil {
. = .(*histogram).html()
}
}
}
if . != nil {
defer ..Free()
sort.Sort(.)
}
completedMu.RLock()
defer completedMu.RUnlock()
if := pageTmpl().ExecuteTemplate(, "Page", ); != nil {
log.Printf("net/trace: Failed executing template: %v", )
}
}
func ( *http.Request) ( string, int, bool) {
if == nil {
return "", 0, false
}
, := .FormValue("fam"), .FormValue("b")
if == "" || == "" {
return "", 0, false
}
, := strconv.Atoi()
if != nil || < -1 {
return "", 0, false
}
return , , true
}
func ( string, int) *traceBucket {
:= getFamily(, false)
if == nil || < 0 || >= len(.Buckets) {
return nil
}
return .Buckets[]
}
type contextKeyT string
var contextKey = contextKeyT("golang.org/x/net/trace.Trace")
LazyPrintf(format string, a ...interface{})
SetError()
SetRecycler(f func(interface{}))
SetTraceInfo(traceID, spanID uint64)
SetMaxEvents(m int)
Finish()
}
type lazySprintf struct {
format string
a []interface{}
}
func ( *lazySprintf) () string {
return fmt.Sprintf(.format, .a...)
}
func (, string) Trace {
:= newTrace()
.ref()
.Family, .Title = ,
.Start = time.Now()
.maxEvents = maxEventsPerTrace
.events = .eventsBuf[:0]
activeMu.RLock()
:= activeTraces[.Family]
activeMu.RUnlock()
if == nil {
activeMu.Lock()
= activeTraces[.Family] // check again
if == nil {
= new(traceSet)
activeTraces[.Family] =
}
activeMu.Unlock()
}
.Add()
completedMu.RLock()
if , := completedTraces[.Family]; ! {
go allocFamily(.Family)
}
completedMu.RUnlock()
return
}
func ( *trace) () {
:= time.Now().Sub(.Start)
.mu.Lock()
.Elapsed =
.mu.Unlock()
if DebugUseAfterFinish {
:= make([]byte, 4<<10) // 4 KB should be enough
:= runtime.Stack(, false)
.finishStack = [:]
}
activeMu.RLock()
:= activeTraces[.Family]
activeMu.RUnlock()
.Remove()
:= getFamily(.Family, true)
.mu.RLock() // protects tr fields in Cond.match calls
for , := range .Buckets {
if .Cond.match() {
.Add()
}
}
.mu.RUnlock()
:= new(histogram)
.addMeasurement(.Nanoseconds() / 1e3)
.LatencyMu.Lock()
.Latency.Add()
.LatencyMu.Unlock()
.unref() // matches ref in New
}
const (
bucketsPerFamily = 9
tracesPerBucket = 10
maxActiveTraces = 20 // Maximum number of active traces to show.
maxEventsPerTrace = 10
numHistogramBuckets = 38
)
.ref()
:= sort.Search(, func( int) bool { return [].Start.After(.Start) })
[-1].unref()
copy([+1:], [:])
[] =
}
return
}
func ( string) traceList {
activeMu.RLock()
:= activeTraces[]
activeMu.RUnlock()
if == nil {
return nil
}
return .FirstN(maxActiveTraces)
}
func ( string, bool) *family {
completedMu.RLock()
:= completedTraces[]
completedMu.RUnlock()
if == nil && {
= allocFamily()
}
return
}
func ( string) *family {
completedMu.Lock()
defer completedMu.Unlock()
:= completedTraces[]
if == nil {
= newFamily()
completedTraces[] =
}
return
}
LatencyMu sync.RWMutex
Latency *timeseries.MinuteHourSeries
}
func () *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) }),
}
}
type traceBucket struct {
Cond cond
mu sync.RWMutex
buf [tracesPerBucket]*trace
start int // < tracesPerBucket
length int // <= tracesPerBucket
}
func ( *traceBucket) ( *trace) {
.mu.Lock()
defer .mu.Unlock()
:= .start + .length
if >= tracesPerBucket {
-= tracesPerBucket
}
func ( *traceBucket) ( bool) traceList {
.mu.RLock()
defer .mu.RUnlock()
:= make(traceList, 0, .length)
for , := 0, .start; < .length; ++ {
:= .buf[]
if ! || .spanID != 0 {
.ref()
= append(, )
}
++
if == .length {
= 0
}
}
return
}
func ( *traceBucket) () bool {
.mu.RLock()
defer .mu.RUnlock()
return .length == 0
}
type cond interface {
match(t *trace) bool
String() string
}
type minCond time.Duration
func ( minCond) ( *trace) bool { return .Elapsed >= time.Duration() }
func ( minCond) () string { return fmt.Sprintf("≥%gs", time.Duration().Seconds()) }
type errorCond struct{}
func ( errorCond) ( *trace) bool { return .IsError }
func ( errorCond) () string { return "errors" }
type traceList []*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
}
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 ( *trace) ( time.Time) (time.Duration, bool) {
if len(.events) == 0 {
return .Sub(.Start), false
}
:= .events[len(.events)-1].When
return .Sub(), .Day() != .Day()
}
func ( *trace) ( interface{}, , bool) {
if DebugUseAfterFinish && .finishStack != nil {
:= make([]byte, 4<<10) // 4 KB should be enough
:= runtime.Stack(, false)
log.Printf("net/trace: trace used after finish:\nFinished at:\n%s\nUsed at:\n%s", .finishStack, [:])
}
.events[].When = .events[+1].When
if .recycler != nil && .events[+1].Recyclable {
go .recycler(.events[+1].What)
}
copy(.events[+1:], .events[+2:])
.events[.maxEvents-1] =
}
.mu.Unlock()
}
func ( *trace) ( fmt.Stringer, bool) {
.addEvent(, true, )
}
func ( *trace) ( string, ...interface{}) {
.addEvent(&lazySprintf{, }, false, false)
}
func ( *trace) () {
.mu.Lock()
.IsError = true
.mu.Unlock()
}
func ( *trace) ( func(interface{})) {
.mu.Lock()
.recycler =
.mu.Unlock()
}
func ( *trace) (, uint64) {
.mu.Lock()
.traceID, .spanID = ,
.mu.Unlock()
}
func ( *trace) ( int) {
if < time.Second {
:= bytes.IndexByte(, '.')
for := 0; < ; ++ {
[] = ' '
}
for := + 1; < len(); ++ {
if [] == '0' {
[] = ' '
} else {
break
}
}
}
return string()
}
var pageTmplCache *template.Template
var pageTmplOnce sync.Once
func () *template.Template {
pageTmplOnce.Do(func() {
pageTmplCache = template.Must(template.New("Page").Funcs(template.FuncMap{
"elapsed": elapsed,
"add": func(, int) int { return + },
}).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 */}}
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin 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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |