package trace
import (
)
const maxEventsPerLog = 100
type 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"},
}
func ( http.ResponseWriter, *http.Request, bool) {
:= time.Now()
:= &struct {
[]string
[]bucket
[][]int
string
int
eventLogs
bool
}{
: buckets,
}
. = make([]string, 0, len(families))
famMu.RLock()
for := range families {
. = append(., )
}
famMu.RUnlock()
sort.Strings(.)
. = make([][]int, len(.))
for , := range . {
:= getEventFamily()
.[] = make([]int, len(.))
for , := range . {
.[][] = .Count(, .MaxErrAge)
}
}
if != nil {
var bool
., ., = parseEventsArgs()
if ! {
} else {
. = getEventFamily(.).Copy(, buckets[.].MaxErrAge)
}
if . != nil {
defer ..Free()
sort.Sort(.)
}
if , := strconv.ParseBool(.FormValue("exp")); == nil {
. =
}
}
famMu.RLock()
defer famMu.RUnlock()
if := eventsTmpl().Execute(, ); != nil {
log.Printf("net/trace: Failed executing template: %v", )
}
}
func ( *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
}
type EventLog interface {
Printf(format string, a ...interface{})
Errorf(format string, a ...interface{})
Finish()
}
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()
}
var (
famMu sync.RWMutex
families = make(map[string]*eventFamily)
)
func ( string) *eventFamily {
famMu.Lock()
defer famMu.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
func ( eventLogs) () {
for , := range {
.unref()
}
}
func ( eventLogs) () int { return len() }
func ( eventLogs) (, int) bool { return [].Start.After([].Start) }
func ( eventLogs) (, int) { [], [] = [], [] }
type logEntry struct {
When time.Time
Elapsed time.Duration
NewDay bool
What string
IsErr bool
}
func ( logEntry) () string {
if .NewDay {
return .When.Format("2006/01/02 15:04:05.000000")
}
return .When.Format("15:04:05.000000")
}
type eventLog struct {
Family string
Title string
Start time.Time
stack []uintptr
mu sync.RWMutex
events []logEntry
LastErrorTime time.Time
discarded int
refs int32
}
func ( *eventLog) () {
.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 {
return true
}
.mu.RLock()
defer .mu.RUnlock()
return .Sub(.LastErrorTime) <
}
func ( *eventLog) ( time.Time) (time.Duration, bool) {
if len(.events) == 0 {
return .Sub(.Start), false
}
:= .events[len(.events)-1].When
return .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)
if len(.events) < maxEventsPerLog {
.events = append(.events, )
} else {
if .discarded == 0 {
.discarded = 2
} else {
.discarded++
}
.events[0].What = fmt.Sprintf("(%d events discarded)", .discarded)
.events[0].When = .events[1].When
copy(.events[1:], .events[2:])
.events[maxEventsPerLog-1] =
}
if .IsErr {
.LastErrorTime = .When
}
.mu.Unlock()
}
func ( *eventLog) () {
atomic.AddInt32(&.refs, 1)
}
func ( *eventLog) () {
if atomic.AddInt32(&.refs, -1) == 0 {
freeEventLog()
}
}
func ( *eventLog) () string {
return .Start.Format("2006/01/02 15:04:05.000000")
}
func ( *eventLog) () string {
:= time.Since(.Start)
return fmt.Sprintf("%.6f", .Seconds())
}
func ( *eventLog) () string {
:= new(bytes.Buffer)
:= tabwriter.NewWriter(, 1, 8, 1, '\t', 0)
printStackRecord(, .stack)
.Flush()
return .String()
}
func ( io.Writer, []uintptr) {
for , := range {
:= runtime.FuncForPC()
if == nil {
continue
}
, := .FileLine()
:= .Name()
if strings.HasPrefix(, "runtime.") {
continue
}
fmt.Fprintf(, "# %s\t%s:%d\n", , , )
}
}
func ( *eventLog) () []logEntry {
.mu.RLock()
defer .mu.RUnlock()
return .events
}
var freeEventLogs = make(chan *eventLog, 1000)
func () *eventLog {
select {
case := <-freeEventLogs:
return
default:
return new(eventLog)
}
}
func ( *eventLog) {
.reset()
select {
case freeEventLogs <- :
default:
}
}
var eventsTmplCache *template.Template
var eventsTmplOnce sync.Once
func () *template.Template {
eventsTmplOnce.Do(func() {
eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{
"elapsed": elapsed,
"trimSpace": strings.TrimSpace,
}).Parse(eventsHTML))
})
return eventsTmplCache
}
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}}
</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>
`