gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

events.go (12549B)


      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package trace
      6 
      7 import (
      8 	"bytes"
      9 	"fmt"
     10 	"html/template"
     11 	"io"
     12 	"log"
     13 	"net/http"
     14 	"runtime"
     15 	"sort"
     16 	"strconv"
     17 	"strings"
     18 	"sync"
     19 	"sync/atomic"
     20 	"text/tabwriter"
     21 	"time"
     22 )
     23 
     24 const maxEventsPerLog = 100
     25 
     26 type bucket struct {
     27 	MaxErrAge time.Duration
     28 	String    string
     29 }
     30 
     31 var buckets = []bucket{
     32 	{0, "total"},
     33 	{10 * time.Second, "errs<10s"},
     34 	{1 * time.Minute, "errs<1m"},
     35 	{10 * time.Minute, "errs<10m"},
     36 	{1 * time.Hour, "errs<1h"},
     37 	{10 * time.Hour, "errs<10h"},
     38 	{24000 * time.Hour, "errors"},
     39 }
     40 
     41 // RenderEvents renders the HTML page typically served at /debug/events.
     42 // It does not do any auth checking. The request may be nil.
     43 //
     44 // Most users will use the Events handler.
     45 func RenderEvents(w http.ResponseWriter, req *http.Request, sensitive bool) {
     46 	now := time.Now()
     47 	data := &struct {
     48 		Families []string // family names
     49 		Buckets  []bucket
     50 		Counts   [][]int // eventLog count per family/bucket
     51 
     52 		// Set when a bucket has been selected.
     53 		Family    string
     54 		Bucket    int
     55 		EventLogs eventLogs
     56 		Expanded  bool
     57 	}{
     58 		Buckets: buckets,
     59 	}
     60 
     61 	data.Families = make([]string, 0, len(families))
     62 	famMu.RLock()
     63 	for name := range families {
     64 		data.Families = append(data.Families, name)
     65 	}
     66 	famMu.RUnlock()
     67 	sort.Strings(data.Families)
     68 
     69 	// Count the number of eventLogs in each family for each error age.
     70 	data.Counts = make([][]int, len(data.Families))
     71 	for i, name := range data.Families {
     72 		// TODO(sameer): move this loop under the family lock.
     73 		f := getEventFamily(name)
     74 		data.Counts[i] = make([]int, len(data.Buckets))
     75 		for j, b := range data.Buckets {
     76 			data.Counts[i][j] = f.Count(now, b.MaxErrAge)
     77 		}
     78 	}
     79 
     80 	if req != nil {
     81 		var ok bool
     82 		data.Family, data.Bucket, ok = parseEventsArgs(req)
     83 		if !ok {
     84 			// No-op
     85 		} else {
     86 			data.EventLogs = getEventFamily(data.Family).Copy(now, buckets[data.Bucket].MaxErrAge)
     87 		}
     88 		if data.EventLogs != nil {
     89 			defer data.EventLogs.Free()
     90 			sort.Sort(data.EventLogs)
     91 		}
     92 		if exp, err := strconv.ParseBool(req.FormValue("exp")); err == nil {
     93 			data.Expanded = exp
     94 		}
     95 	}
     96 
     97 	famMu.RLock()
     98 	defer famMu.RUnlock()
     99 	if err := eventsTmpl().Execute(w, data); err != nil {
    100 		log.Printf("net/trace: Failed executing template: %v", err)
    101 	}
    102 }
    103 
    104 func parseEventsArgs(req *http.Request) (fam string, b int, ok bool) {
    105 	fam, bStr := req.FormValue("fam"), req.FormValue("b")
    106 	if fam == "" || bStr == "" {
    107 		return "", 0, false
    108 	}
    109 	b, err := strconv.Atoi(bStr)
    110 	if err != nil || b < 0 || b >= len(buckets) {
    111 		return "", 0, false
    112 	}
    113 	return fam, b, true
    114 }
    115 
    116 // An EventLog provides a log of events associated with a specific object.
    117 type EventLog interface {
    118 	// Printf formats its arguments with fmt.Sprintf and adds the
    119 	// result to the event log.
    120 	Printf(format string, a ...interface{})
    121 
    122 	// Errorf is like Printf, but it marks this event as an error.
    123 	Errorf(format string, a ...interface{})
    124 
    125 	// Finish declares that this event log is complete.
    126 	// The event log should not be used after calling this method.
    127 	Finish()
    128 }
    129 
    130 // NewEventLog returns a new EventLog with the specified family name
    131 // and title.
    132 func NewEventLog(family, title string) EventLog {
    133 	el := newEventLog()
    134 	el.ref()
    135 	el.Family, el.Title = family, title
    136 	el.Start = time.Now()
    137 	el.events = make([]logEntry, 0, maxEventsPerLog)
    138 	el.stack = make([]uintptr, 32)
    139 	n := runtime.Callers(2, el.stack)
    140 	el.stack = el.stack[:n]
    141 
    142 	getEventFamily(family).add(el)
    143 	return el
    144 }
    145 
    146 func (el *eventLog) Finish() {
    147 	getEventFamily(el.Family).remove(el)
    148 	el.unref() // matches ref in New
    149 }
    150 
    151 var (
    152 	famMu    sync.RWMutex
    153 	families = make(map[string]*eventFamily) // family name => family
    154 )
    155 
    156 func getEventFamily(fam string) *eventFamily {
    157 	famMu.Lock()
    158 	defer famMu.Unlock()
    159 	f := families[fam]
    160 	if f == nil {
    161 		f = &eventFamily{}
    162 		families[fam] = f
    163 	}
    164 	return f
    165 }
    166 
    167 type eventFamily struct {
    168 	mu        sync.RWMutex
    169 	eventLogs eventLogs
    170 }
    171 
    172 func (f *eventFamily) add(el *eventLog) {
    173 	f.mu.Lock()
    174 	f.eventLogs = append(f.eventLogs, el)
    175 	f.mu.Unlock()
    176 }
    177 
    178 func (f *eventFamily) remove(el *eventLog) {
    179 	f.mu.Lock()
    180 	defer f.mu.Unlock()
    181 	for i, el0 := range f.eventLogs {
    182 		if el == el0 {
    183 			copy(f.eventLogs[i:], f.eventLogs[i+1:])
    184 			f.eventLogs = f.eventLogs[:len(f.eventLogs)-1]
    185 			return
    186 		}
    187 	}
    188 }
    189 
    190 func (f *eventFamily) Count(now time.Time, maxErrAge time.Duration) (n int) {
    191 	f.mu.RLock()
    192 	defer f.mu.RUnlock()
    193 	for _, el := range f.eventLogs {
    194 		if el.hasRecentError(now, maxErrAge) {
    195 			n++
    196 		}
    197 	}
    198 	return
    199 }
    200 
    201 func (f *eventFamily) Copy(now time.Time, maxErrAge time.Duration) (els eventLogs) {
    202 	f.mu.RLock()
    203 	defer f.mu.RUnlock()
    204 	els = make(eventLogs, 0, len(f.eventLogs))
    205 	for _, el := range f.eventLogs {
    206 		if el.hasRecentError(now, maxErrAge) {
    207 			el.ref()
    208 			els = append(els, el)
    209 		}
    210 	}
    211 	return
    212 }
    213 
    214 type eventLogs []*eventLog
    215 
    216 // Free calls unref on each element of the list.
    217 func (els eventLogs) Free() {
    218 	for _, el := range els {
    219 		el.unref()
    220 	}
    221 }
    222 
    223 // eventLogs may be sorted in reverse chronological order.
    224 func (els eventLogs) Len() int           { return len(els) }
    225 func (els eventLogs) Less(i, j int) bool { return els[i].Start.After(els[j].Start) }
    226 func (els eventLogs) Swap(i, j int)      { els[i], els[j] = els[j], els[i] }
    227 
    228 // A logEntry is a timestamped log entry in an event log.
    229 type logEntry struct {
    230 	When    time.Time
    231 	Elapsed time.Duration // since previous event in log
    232 	NewDay  bool          // whether this event is on a different day to the previous event
    233 	What    string
    234 	IsErr   bool
    235 }
    236 
    237 // WhenString returns a string representation of the elapsed time of the event.
    238 // It will include the date if midnight was crossed.
    239 func (e logEntry) WhenString() string {
    240 	if e.NewDay {
    241 		return e.When.Format("2006/01/02 15:04:05.000000")
    242 	}
    243 	return e.When.Format("15:04:05.000000")
    244 }
    245 
    246 // An eventLog represents an active event log.
    247 type eventLog struct {
    248 	// Family is the top-level grouping of event logs to which this belongs.
    249 	Family string
    250 
    251 	// Title is the title of this event log.
    252 	Title string
    253 
    254 	// Timing information.
    255 	Start time.Time
    256 
    257 	// Call stack where this event log was created.
    258 	stack []uintptr
    259 
    260 	// Append-only sequence of events.
    261 	//
    262 	// TODO(sameer): change this to a ring buffer to avoid the array copy
    263 	// when we hit maxEventsPerLog.
    264 	mu            sync.RWMutex
    265 	events        []logEntry
    266 	LastErrorTime time.Time
    267 	discarded     int
    268 
    269 	refs int32 // how many buckets this is in
    270 }
    271 
    272 func (el *eventLog) reset() {
    273 	// Clear all but the mutex. Mutexes may not be copied, even when unlocked.
    274 	el.Family = ""
    275 	el.Title = ""
    276 	el.Start = time.Time{}
    277 	el.stack = nil
    278 	el.events = nil
    279 	el.LastErrorTime = time.Time{}
    280 	el.discarded = 0
    281 	el.refs = 0
    282 }
    283 
    284 func (el *eventLog) hasRecentError(now time.Time, maxErrAge time.Duration) bool {
    285 	if maxErrAge == 0 {
    286 		return true
    287 	}
    288 	el.mu.RLock()
    289 	defer el.mu.RUnlock()
    290 	return now.Sub(el.LastErrorTime) < maxErrAge
    291 }
    292 
    293 // delta returns the elapsed time since the last event or the log start,
    294 // and whether it spans midnight.
    295 // L >= el.mu
    296 func (el *eventLog) delta(t time.Time) (time.Duration, bool) {
    297 	if len(el.events) == 0 {
    298 		return t.Sub(el.Start), false
    299 	}
    300 	prev := el.events[len(el.events)-1].When
    301 	return t.Sub(prev), prev.Day() != t.Day()
    302 
    303 }
    304 
    305 func (el *eventLog) Printf(format string, a ...interface{}) {
    306 	el.printf(false, format, a...)
    307 }
    308 
    309 func (el *eventLog) Errorf(format string, a ...interface{}) {
    310 	el.printf(true, format, a...)
    311 }
    312 
    313 func (el *eventLog) printf(isErr bool, format string, a ...interface{}) {
    314 	e := logEntry{When: time.Now(), IsErr: isErr, What: fmt.Sprintf(format, a...)}
    315 	el.mu.Lock()
    316 	e.Elapsed, e.NewDay = el.delta(e.When)
    317 	if len(el.events) < maxEventsPerLog {
    318 		el.events = append(el.events, e)
    319 	} else {
    320 		// Discard the oldest event.
    321 		if el.discarded == 0 {
    322 			// el.discarded starts at two to count for the event it
    323 			// is replacing, plus the next one that we are about to
    324 			// drop.
    325 			el.discarded = 2
    326 		} else {
    327 			el.discarded++
    328 		}
    329 		// TODO(sameer): if this causes allocations on a critical path,
    330 		// change eventLog.What to be a fmt.Stringer, as in trace.go.
    331 		el.events[0].What = fmt.Sprintf("(%d events discarded)", el.discarded)
    332 		// The timestamp of the discarded meta-event should be
    333 		// the time of the last event it is representing.
    334 		el.events[0].When = el.events[1].When
    335 		copy(el.events[1:], el.events[2:])
    336 		el.events[maxEventsPerLog-1] = e
    337 	}
    338 	if e.IsErr {
    339 		el.LastErrorTime = e.When
    340 	}
    341 	el.mu.Unlock()
    342 }
    343 
    344 func (el *eventLog) ref() {
    345 	atomic.AddInt32(&el.refs, 1)
    346 }
    347 
    348 func (el *eventLog) unref() {
    349 	if atomic.AddInt32(&el.refs, -1) == 0 {
    350 		freeEventLog(el)
    351 	}
    352 }
    353 
    354 func (el *eventLog) When() string {
    355 	return el.Start.Format("2006/01/02 15:04:05.000000")
    356 }
    357 
    358 func (el *eventLog) ElapsedTime() string {
    359 	elapsed := time.Since(el.Start)
    360 	return fmt.Sprintf("%.6f", elapsed.Seconds())
    361 }
    362 
    363 func (el *eventLog) Stack() string {
    364 	buf := new(bytes.Buffer)
    365 	tw := tabwriter.NewWriter(buf, 1, 8, 1, '\t', 0)
    366 	printStackRecord(tw, el.stack)
    367 	tw.Flush()
    368 	return buf.String()
    369 }
    370 
    371 // printStackRecord prints the function + source line information
    372 // for a single stack trace.
    373 // Adapted from runtime/pprof/pprof.go.
    374 func printStackRecord(w io.Writer, stk []uintptr) {
    375 	for _, pc := range stk {
    376 		f := runtime.FuncForPC(pc)
    377 		if f == nil {
    378 			continue
    379 		}
    380 		file, line := f.FileLine(pc)
    381 		name := f.Name()
    382 		// Hide runtime.goexit and any runtime functions at the beginning.
    383 		if strings.HasPrefix(name, "runtime.") {
    384 			continue
    385 		}
    386 		fmt.Fprintf(w, "#   %s\t%s:%d\n", name, file, line)
    387 	}
    388 }
    389 
    390 func (el *eventLog) Events() []logEntry {
    391 	el.mu.RLock()
    392 	defer el.mu.RUnlock()
    393 	return el.events
    394 }
    395 
    396 // freeEventLogs is a freelist of *eventLog
    397 var freeEventLogs = make(chan *eventLog, 1000)
    398 
    399 // newEventLog returns a event log ready to use.
    400 func newEventLog() *eventLog {
    401 	select {
    402 	case el := <-freeEventLogs:
    403 		return el
    404 	default:
    405 		return new(eventLog)
    406 	}
    407 }
    408 
    409 // freeEventLog adds el to freeEventLogs if there's room.
    410 // This is non-blocking.
    411 func freeEventLog(el *eventLog) {
    412 	el.reset()
    413 	select {
    414 	case freeEventLogs <- el:
    415 	default:
    416 	}
    417 }
    418 
    419 var eventsTmplCache *template.Template
    420 var eventsTmplOnce sync.Once
    421 
    422 func eventsTmpl() *template.Template {
    423 	eventsTmplOnce.Do(func() {
    424 		eventsTmplCache = template.Must(template.New("events").Funcs(template.FuncMap{
    425 			"elapsed":   elapsed,
    426 			"trimSpace": strings.TrimSpace,
    427 		}).Parse(eventsHTML))
    428 	})
    429 	return eventsTmplCache
    430 }
    431 
    432 const eventsHTML = `
    433 <html>
    434 	<head>
    435 		<title>events</title>
    436 	</head>
    437 	<style type="text/css">
    438 		body {
    439 			font-family: sans-serif;
    440 		}
    441 		table#req-status td.family {
    442 			padding-right: 2em;
    443 		}
    444 		table#req-status td.active {
    445 			padding-right: 1em;
    446 		}
    447 		table#req-status td.empty {
    448 			color: #aaa;
    449 		}
    450 		table#reqs {
    451 			margin-top: 1em;
    452 		}
    453 		table#reqs tr.first {
    454 			{{if $.Expanded}}font-weight: bold;{{end}}
    455 		}
    456 		table#reqs td {
    457 			font-family: monospace;
    458 		}
    459 		table#reqs td.when {
    460 			text-align: right;
    461 			white-space: nowrap;
    462 		}
    463 		table#reqs td.elapsed {
    464 			padding: 0 0.5em;
    465 			text-align: right;
    466 			white-space: pre;
    467 			width: 10em;
    468 		}
    469 		address {
    470 			font-size: smaller;
    471 			margin-top: 5em;
    472 		}
    473 	</style>
    474 	<body>
    475 
    476 <h1>/debug/events</h1>
    477 
    478 <table id="req-status">
    479 	{{range $i, $fam := .Families}}
    480 	<tr>
    481 		<td class="family">{{$fam}}</td>
    482 
    483 	        {{range $j, $bucket := $.Buckets}}
    484 	        {{$n := index $.Counts $i $j}}
    485 		<td class="{{if not $bucket.MaxErrAge}}active{{end}}{{if not $n}}empty{{end}}">
    486 	                {{if $n}}<a href="?fam={{$fam}}&b={{$j}}{{if $.Expanded}}&exp=1{{end}}">{{end}}
    487 		        [{{$n}} {{$bucket.String}}]
    488 			{{if $n}}</a>{{end}}
    489 		</td>
    490                 {{end}}
    491 
    492 	</tr>{{end}}
    493 </table>
    494 
    495 {{if $.EventLogs}}
    496 <hr />
    497 <h3>Family: {{$.Family}}</h3>
    498 
    499 {{if $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}">{{end}}
    500 [Summary]{{if $.Expanded}}</a>{{end}}
    501 
    502 {{if not $.Expanded}}<a href="?fam={{$.Family}}&b={{$.Bucket}}&exp=1">{{end}}
    503 [Expanded]{{if not $.Expanded}}</a>{{end}}
    504 
    505 <table id="reqs">
    506 	<tr><th>When</th><th>Elapsed</th></tr>
    507 	{{range $el := $.EventLogs}}
    508 	<tr class="first">
    509 		<td class="when">{{$el.When}}</td>
    510 		<td class="elapsed">{{$el.ElapsedTime}}</td>
    511 		<td>{{$el.Title}}
    512 	</tr>
    513 	{{if $.Expanded}}
    514 	<tr>
    515 		<td class="when"></td>
    516 		<td class="elapsed"></td>
    517 		<td><pre>{{$el.Stack|trimSpace}}</pre></td>
    518 	</tr>
    519 	{{range $el.Events}}
    520 	<tr>
    521 		<td class="when">{{.WhenString}}</td>
    522 		<td class="elapsed">{{elapsed .Elapsed}}</td>
    523 		<td>.{{if .IsErr}}E{{else}}.{{end}}. {{.What}}</td>
    524 	</tr>
    525 	{{end}}
    526 	{{end}}
    527 	{{end}}
    528 </table>
    529 {{end}}
    530 	</body>
    531 </html>
    532 `