Commit c2d10243 authored by Hana Kim's avatar Hana Kim Committed by Hyang-Ah Hana Kim

runtime/trace: rename "Span" with "Region"

"Span" is a commonly used term in many distributed tracing systems
(Dapper, OpenCensus, OpenTracing, ...). They use it to refer to a
period of time, not necessarily tied into execution of underlying
processor, thread, or goroutine, unlike the "Span" of runtime/trace
package.

Since distributed tracing and go runtime execution tracing are
already similar enough to cause confusion, this CL attempts to avoid
using the same word if possible.

"Region" is being used in a certain tracing system to refer to a code
region which is pretty close to what runtime/trace.Span currently
refers to. So, replace that.
https://software.intel.com/en-us/itc-user-and-reference-guide-defining-and-recording-functions-or-regions

This CL also tweaks APIs a bit based on jbd and heschi's comments:

  NewContext -> NewTask
    and it now returns a Task object that exports End method.

  StartSpan -> StartRegion
    and it now returns a Region object that exports End method.

Also, changed WithSpan to WithRegion and it now takes func() with no
context. Another thought is to get rid of WithRegion. It is a nice
concept but in practice, it seems problematic (a lot of code churn,
and polluting stack trace). Already, the tracing concept is very low
level, and we hope this API to be used with great care.

Recommended usage will be
   defer trace.StartRegion(ctx, "someRegion").End()

Left old APIs untouched in this CL. Once the usage of them are cleaned
up, they will be removed in a separate CL.

Change-Id: I73880635e437f3aad51314331a035dd1459b9f3a
Reviewed-on: https://go-review.googlesource.com/108296
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarJBD <jbd@google.com>
parent fb017c60
...@@ -18,8 +18,8 @@ import ( ...@@ -18,8 +18,8 @@ import (
func init() { func init() {
http.HandleFunc("/usertasks", httpUserTasks) http.HandleFunc("/usertasks", httpUserTasks)
http.HandleFunc("/usertask", httpUserTask) http.HandleFunc("/usertask", httpUserTask)
http.HandleFunc("/userspans", httpUserSpans) http.HandleFunc("/userregions", httpUserRegions)
http.HandleFunc("/userspan", httpUserSpan) http.HandleFunc("/userregion", httpUserRegion)
} }
// httpUserTasks reports all tasks found in the trace. // httpUserTasks reports all tasks found in the trace.
...@@ -59,46 +59,46 @@ func httpUserTasks(w http.ResponseWriter, r *http.Request) { ...@@ -59,46 +59,46 @@ func httpUserTasks(w http.ResponseWriter, r *http.Request) {
} }
} }
func httpUserSpans(w http.ResponseWriter, r *http.Request) { func httpUserRegions(w http.ResponseWriter, r *http.Request) {
res, err := analyzeAnnotations() res, err := analyzeAnnotations()
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
allSpans := res.spans allRegions := res.regions
summary := make(map[spanTypeID]spanStats) summary := make(map[regionTypeID]regionStats)
for id, spans := range allSpans { for id, regions := range allRegions {
stats, ok := summary[id] stats, ok := summary[id]
if !ok { if !ok {
stats.spanTypeID = id stats.regionTypeID = id
} }
for _, s := range spans { for _, s := range regions {
stats.add(s) stats.add(s)
} }
summary[id] = stats summary[id] = stats
} }
// Sort spans by pc and name // Sort regions by pc and name
userSpans := make([]spanStats, 0, len(summary)) userRegions := make([]regionStats, 0, len(summary))
for _, stats := range summary { for _, stats := range summary {
userSpans = append(userSpans, stats) userRegions = append(userRegions, stats)
} }
sort.Slice(userSpans, func(i, j int) bool { sort.Slice(userRegions, func(i, j int) bool {
if userSpans[i].Type != userSpans[j].Type { if userRegions[i].Type != userRegions[j].Type {
return userSpans[i].Type < userSpans[j].Type return userRegions[i].Type < userRegions[j].Type
} }
return userSpans[i].Frame.PC < userSpans[j].Frame.PC return userRegions[i].Frame.PC < userRegions[j].Frame.PC
}) })
// Emit table. // Emit table.
err = templUserSpanTypes.Execute(w, userSpans) err = templUserRegionTypes.Execute(w, userRegions)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("failed to execute template: %v", err), http.StatusInternalServerError)
return return
} }
} }
func httpUserSpan(w http.ResponseWriter, r *http.Request) { func httpUserRegion(w http.ResponseWriter, r *http.Request) {
filter, err := newSpanFilter(r) filter, err := newRegionFilter(r)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
...@@ -108,13 +108,13 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) { ...@@ -108,13 +108,13 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
allSpans := res.spans allRegions := res.regions
var data []spanDesc var data []regionDesc
var maxTotal int64 var maxTotal int64
for id, spans := range allSpans { for id, regions := range allRegions {
for _, s := range spans { for _, s := range regions {
if !filter.match(id, s) { if !filter.match(id, s) {
continue continue
} }
...@@ -126,7 +126,7 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) { ...@@ -126,7 +126,7 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) {
} }
sortby := r.FormValue("sortby") sortby := r.FormValue("sortby")
_, ok := reflect.TypeOf(spanDesc{}).FieldByNameFunc(func(s string) bool { _, ok := reflect.TypeOf(regionDesc{}).FieldByNameFunc(func(s string) bool {
return s == sortby return s == sortby
}) })
if !ok { if !ok {
...@@ -138,9 +138,9 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) { ...@@ -138,9 +138,9 @@ func httpUserSpan(w http.ResponseWriter, r *http.Request) {
return ival > jval return ival > jval
}) })
err = templUserSpanType.Execute(w, struct { err = templUserRegionType.Execute(w, struct {
MaxTotal int64 MaxTotal int64
Data []spanDesc Data []regionDesc
Name string Name string
}{ }{
MaxTotal: maxTotal, MaxTotal: maxTotal,
...@@ -193,9 +193,9 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) { ...@@ -193,9 +193,9 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) {
if !filter.match(task) { if !filter.match(task) {
continue continue
} }
// merge events in the task.events and task.spans.Start // merge events in the task.events and task.regions.Start
rawEvents := append([]*trace.Event{}, task.events...) rawEvents := append([]*trace.Event{}, task.events...)
for _, s := range task.spans { for _, s := range task.regions {
if s.Start != nil { if s.Start != nil {
rawEvents = append(rawEvents, s.Start) rawEvents = append(rawEvents, s.Start)
} }
...@@ -255,11 +255,11 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) { ...@@ -255,11 +255,11 @@ func httpUserTask(w http.ResponseWriter, r *http.Request) {
type annotationAnalysisResult struct { type annotationAnalysisResult struct {
tasks map[uint64]*taskDesc // tasks tasks map[uint64]*taskDesc // tasks
spans map[spanTypeID][]spanDesc // spans regions map[regionTypeID][]regionDesc // regions
gcEvents []*trace.Event // GCStartevents, sorted gcEvents []*trace.Event // GCStartevents, sorted
} }
type spanTypeID struct { type regionTypeID struct {
Frame trace.Frame // top frame Frame trace.Frame // top frame
Type string Type string
} }
...@@ -278,7 +278,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { ...@@ -278,7 +278,7 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
} }
tasks := allTasks{} tasks := allTasks{}
spans := map[spanTypeID][]spanDesc{} regions := map[regionTypeID][]regionDesc{}
var gcEvents []*trace.Event var gcEvents []*trace.Event
for _, ev := range events { for _, ev := range events {
...@@ -303,38 +303,38 @@ func analyzeAnnotations() (annotationAnalysisResult, error) { ...@@ -303,38 +303,38 @@ func analyzeAnnotations() (annotationAnalysisResult, error) {
gcEvents = append(gcEvents, ev) gcEvents = append(gcEvents, ev)
} }
} }
// combine span info. // combine region info.
analyzeGoroutines(events) analyzeGoroutines(events)
for goid, stats := range gs { for goid, stats := range gs {
// gs is a global var defined in goroutines.go as a result // gs is a global var defined in goroutines.go as a result
// of analyzeGoroutines. TODO(hyangah): fix this not to depend // of analyzeGoroutines. TODO(hyangah): fix this not to depend
// on a 'global' var. // on a 'global' var.
for _, s := range stats.Spans { for _, s := range stats.Regions {
if s.TaskID != 0 { if s.TaskID != 0 {
task := tasks.task(s.TaskID) task := tasks.task(s.TaskID)
task.goroutines[goid] = struct{}{} task.goroutines[goid] = struct{}{}
task.spans = append(task.spans, spanDesc{UserSpanDesc: s, G: goid}) task.regions = append(task.regions, regionDesc{UserRegionDesc: s, G: goid})
} }
var frame trace.Frame var frame trace.Frame
if s.Start != nil { if s.Start != nil {
frame = *s.Start.Stk[0] frame = *s.Start.Stk[0]
} }
id := spanTypeID{Frame: frame, Type: s.Name} id := regionTypeID{Frame: frame, Type: s.Name}
spans[id] = append(spans[id], spanDesc{UserSpanDesc: s, G: goid}) regions[id] = append(regions[id], regionDesc{UserRegionDesc: s, G: goid})
} }
} }
// sort spans in tasks based on the timestamps. // sort regions in tasks based on the timestamps.
for _, task := range tasks { for _, task := range tasks {
sort.SliceStable(task.spans, func(i, j int) bool { sort.SliceStable(task.regions, func(i, j int) bool {
si, sj := task.spans[i].firstTimestamp(), task.spans[j].firstTimestamp() si, sj := task.regions[i].firstTimestamp(), task.regions[j].firstTimestamp()
if si != sj { if si != sj {
return si < sj return si < sj
} }
return task.spans[i].lastTimestamp() < task.spans[i].lastTimestamp() return task.regions[i].lastTimestamp() < task.regions[i].lastTimestamp()
}) })
} }
return annotationAnalysisResult{tasks: tasks, spans: spans, gcEvents: gcEvents}, nil return annotationAnalysisResult{tasks: tasks, regions: regions, gcEvents: gcEvents}, nil
} }
// taskDesc represents a task. // taskDesc represents a task.
...@@ -342,7 +342,7 @@ type taskDesc struct { ...@@ -342,7 +342,7 @@ type taskDesc struct {
name string // user-provided task name name string // user-provided task name
id uint64 // internal task id id uint64 // internal task id
events []*trace.Event // sorted based on timestamp. events []*trace.Event // sorted based on timestamp.
spans []spanDesc // associated spans, sorted based on the start timestamp and then the last timestamp. regions []regionDesc // associated regions, sorted based on the start timestamp and then the last timestamp.
goroutines map[uint64]struct{} // involved goroutines goroutines map[uint64]struct{} // involved goroutines
create *trace.Event // Task create event create *trace.Event // Task create event
...@@ -367,8 +367,8 @@ func (task *taskDesc) String() string { ...@@ -367,8 +367,8 @@ func (task *taskDesc) String() string {
fmt.Fprintf(wb, "task %d:\t%s\n", task.id, task.name) fmt.Fprintf(wb, "task %d:\t%s\n", task.id, task.name)
fmt.Fprintf(wb, "\tstart: %v end: %v complete: %t\n", task.firstTimestamp(), task.lastTimestamp(), task.complete()) fmt.Fprintf(wb, "\tstart: %v end: %v complete: %t\n", task.firstTimestamp(), task.lastTimestamp(), task.complete())
fmt.Fprintf(wb, "\t%d goroutines\n", len(task.goroutines)) fmt.Fprintf(wb, "\t%d goroutines\n", len(task.goroutines))
fmt.Fprintf(wb, "\t%d spans:\n", len(task.spans)) fmt.Fprintf(wb, "\t%d regions:\n", len(task.regions))
for _, s := range task.spans { for _, s := range task.regions {
fmt.Fprintf(wb, "\t\t%s(goid=%d)\n", s.Name, s.G) fmt.Fprintf(wb, "\t\t%s(goid=%d)\n", s.Name, s.G)
} }
if task.parent != nil { if task.parent != nil {
...@@ -382,10 +382,10 @@ func (task *taskDesc) String() string { ...@@ -382,10 +382,10 @@ func (task *taskDesc) String() string {
return wb.String() return wb.String()
} }
// spanDesc represents a span. // regionDesc represents a region.
type spanDesc struct { type regionDesc struct {
*trace.UserSpanDesc *trace.UserRegionDesc
G uint64 // id of goroutine where the span was defined G uint64 // id of goroutine where the region was defined
} }
type allTasks map[uint64]*taskDesc type allTasks map[uint64]*taskDesc
...@@ -473,8 +473,8 @@ func (task *taskDesc) duration() time.Duration { ...@@ -473,8 +473,8 @@ func (task *taskDesc) duration() time.Duration {
return time.Duration(task.lastTimestamp()-task.firstTimestamp()) * time.Nanosecond return time.Duration(task.lastTimestamp()-task.firstTimestamp()) * time.Nanosecond
} }
func (span *spanDesc) duration() time.Duration { func (region *regionDesc) duration() time.Duration {
return time.Duration(span.lastTimestamp()-span.firstTimestamp()) * time.Nanosecond return time.Duration(region.lastTimestamp()-region.firstTimestamp()) * time.Nanosecond
} }
// overlappingGCDuration returns the sum of GC period overlapping with the task's lifetime. // overlappingGCDuration returns the sum of GC period overlapping with the task's lifetime.
...@@ -493,7 +493,7 @@ func (task *taskDesc) overlappingGCDuration(evs []*trace.Event) (overlapping tim ...@@ -493,7 +493,7 @@ func (task *taskDesc) overlappingGCDuration(evs []*trace.Event) (overlapping tim
} }
// overlappingInstant returns true if the instantaneous event, ev, occurred during // overlappingInstant returns true if the instantaneous event, ev, occurred during
// any of the task's span if ev is a goroutine-local event, or overlaps with the // any of the task's region if ev is a goroutine-local event, or overlaps with the
// task's lifetime if ev is a global event. // task's lifetime if ev is a global event.
func (task *taskDesc) overlappingInstant(ev *trace.Event) bool { func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
if isUserAnnotationEvent(ev) && task.id != ev.Args[0] { if isUserAnnotationEvent(ev) && task.id != ev.Args[0] {
...@@ -510,13 +510,13 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool { ...@@ -510,13 +510,13 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
return true return true
} }
// Goroutine local event. Check whether there are spans overlapping with the event. // Goroutine local event. Check whether there are regions overlapping with the event.
goid := ev.G goid := ev.G
for _, span := range task.spans { for _, region := range task.regions {
if span.G != goid { if region.G != goid {
continue continue
} }
if span.firstTimestamp() <= ts && ts <= span.lastTimestamp() { if region.firstTimestamp() <= ts && ts <= region.lastTimestamp() {
return true return true
} }
} }
...@@ -524,7 +524,7 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool { ...@@ -524,7 +524,7 @@ func (task *taskDesc) overlappingInstant(ev *trace.Event) bool {
} }
// overlappingDuration returns whether the durational event, ev, overlaps with // overlappingDuration returns whether the durational event, ev, overlaps with
// any of the task's span if ev is a goroutine-local event, or overlaps with // any of the task's region if ev is a goroutine-local event, or overlaps with
// the task's lifetime if ev is a global event. It returns the overlapping time // the task's lifetime if ev is a global event. It returns the overlapping time
// as well. // as well.
func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool) { func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool) {
...@@ -552,21 +552,21 @@ func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool) ...@@ -552,21 +552,21 @@ func (task *taskDesc) overlappingDuration(ev *trace.Event) (time.Duration, bool)
return o, o > 0 return o, o > 0
} }
// Goroutine local event. Check whether there are spans overlapping with the event. // Goroutine local event. Check whether there are regions overlapping with the event.
var overlapping time.Duration var overlapping time.Duration
var lastSpanEnd int64 // the end of previous overlapping span var lastRegionEnd int64 // the end of previous overlapping region
for _, span := range task.spans { for _, region := range task.regions {
if span.G != goid && span.G != goid2 { if region.G != goid && region.G != goid2 {
continue continue
} }
spanStart, spanEnd := span.firstTimestamp(), span.lastTimestamp() regionStart, regionEnd := region.firstTimestamp(), region.lastTimestamp()
if spanStart < lastSpanEnd { // skip nested spans if regionStart < lastRegionEnd { // skip nested regions
continue continue
} }
if o := overlappingDuration(spanStart, spanEnd, start, end); o > 0 { if o := overlappingDuration(regionStart, regionEnd, start, end); o > 0 {
// overlapping. // overlapping.
lastSpanEnd = spanEnd lastRegionEnd = regionEnd
overlapping += o overlapping += o
} }
} }
...@@ -602,22 +602,22 @@ func (task *taskDesc) lastEvent() *trace.Event { ...@@ -602,22 +602,22 @@ func (task *taskDesc) lastEvent() *trace.Event {
return nil return nil
} }
// firstTimestamp returns the timestamp of span start event. // firstTimestamp returns the timestamp of region start event.
// If the span's start event is not present in the trace, // If the region's start event is not present in the trace,
// the first timestamp of the trace will be returned. // the first timestamp of the trace will be returned.
func (span *spanDesc) firstTimestamp() int64 { func (region *regionDesc) firstTimestamp() int64 {
if span.Start != nil { if region.Start != nil {
return span.Start.Ts return region.Start.Ts
} }
return firstTimestamp() return firstTimestamp()
} }
// lastTimestamp returns the timestamp of span end event. // lastTimestamp returns the timestamp of region end event.
// If the span's end event is not present in the trace, // If the region's end event is not present in the trace,
// the last timestamp of the trace will be returned. // the last timestamp of the trace will be returned.
func (span *spanDesc) lastTimestamp() int64 { func (region *regionDesc) lastTimestamp() int64 {
if span.End != nil { if region.End != nil {
return span.End.Ts return region.End.Ts
} }
return lastTimestamp() return lastTimestamp()
} }
...@@ -721,7 +721,7 @@ func newTaskFilter(r *http.Request) (*taskFilter, error) { ...@@ -721,7 +721,7 @@ func newTaskFilter(r *http.Request) (*taskFilter, error) {
func taskMatches(t *taskDesc, text string) bool { func taskMatches(t *taskDesc, text string) bool {
for _, ev := range t.events { for _, ev := range t.events {
switch ev.Type { switch ev.Type {
case trace.EvUserTaskCreate, trace.EvUserSpan, trace.EvUserLog: case trace.EvUserTaskCreate, trace.EvUserRegion, trace.EvUserLog:
for _, s := range ev.SArgs { for _, s := range ev.SArgs {
if strings.Contains(s, text) { if strings.Contains(s, text) {
return true return true
...@@ -732,12 +732,12 @@ func taskMatches(t *taskDesc, text string) bool { ...@@ -732,12 +732,12 @@ func taskMatches(t *taskDesc, text string) bool {
return false return false
} }
type spanFilter struct { type regionFilter struct {
name string name string
cond []func(spanTypeID, spanDesc) bool cond []func(regionTypeID, regionDesc) bool
} }
func (f *spanFilter) match(id spanTypeID, s spanDesc) bool { func (f *regionFilter) match(id regionTypeID, s regionDesc) bool {
for _, c := range f.cond { for _, c := range f.cond {
if !c(id, s) { if !c(id, s) {
return false return false
...@@ -746,42 +746,42 @@ func (f *spanFilter) match(id spanTypeID, s spanDesc) bool { ...@@ -746,42 +746,42 @@ func (f *spanFilter) match(id spanTypeID, s spanDesc) bool {
return true return true
} }
func newSpanFilter(r *http.Request) (*spanFilter, error) { func newRegionFilter(r *http.Request) (*regionFilter, error) {
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
return nil, err return nil, err
} }
var name []string var name []string
var conditions []func(spanTypeID, spanDesc) bool var conditions []func(regionTypeID, regionDesc) bool
param := r.Form param := r.Form
if typ, ok := param["type"]; ok && len(typ) > 0 { if typ, ok := param["type"]; ok && len(typ) > 0 {
name = append(name, "type="+typ[0]) name = append(name, "type="+typ[0])
conditions = append(conditions, func(id spanTypeID, s spanDesc) bool { conditions = append(conditions, func(id regionTypeID, s regionDesc) bool {
return id.Type == typ[0] return id.Type == typ[0]
}) })
} }
if pc, err := strconv.ParseUint(r.FormValue("pc"), 16, 64); err == nil { if pc, err := strconv.ParseUint(r.FormValue("pc"), 16, 64); err == nil {
name = append(name, fmt.Sprintf("pc=%x", pc)) name = append(name, fmt.Sprintf("pc=%x", pc))
conditions = append(conditions, func(id spanTypeID, s spanDesc) bool { conditions = append(conditions, func(id regionTypeID, s regionDesc) bool {
return id.Frame.PC == pc return id.Frame.PC == pc
}) })
} }
if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil { if lat, err := time.ParseDuration(r.FormValue("latmin")); err == nil {
name = append(name, fmt.Sprintf("latency >= %s", lat)) name = append(name, fmt.Sprintf("latency >= %s", lat))
conditions = append(conditions, func(_ spanTypeID, s spanDesc) bool { conditions = append(conditions, func(_ regionTypeID, s regionDesc) bool {
return s.duration() >= lat return s.duration() >= lat
}) })
} }
if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil { if lat, err := time.ParseDuration(r.FormValue("latmax")); err == nil {
name = append(name, fmt.Sprintf("latency <= %s", lat)) name = append(name, fmt.Sprintf("latency <= %s", lat))
conditions = append(conditions, func(_ spanTypeID, s spanDesc) bool { conditions = append(conditions, func(_ regionTypeID, s regionDesc) bool {
return s.duration() <= lat return s.duration() <= lat
}) })
} }
return &spanFilter{name: strings.Join(name, ","), cond: conditions}, nil return &regionFilter{name: strings.Join(name, ","), cond: conditions}, nil
} }
type durationHistogram struct { type durationHistogram struct {
...@@ -893,22 +893,22 @@ func (h *durationHistogram) String() string { ...@@ -893,22 +893,22 @@ func (h *durationHistogram) String() string {
return w.String() return w.String()
} }
type spanStats struct { type regionStats struct {
spanTypeID regionTypeID
Histogram durationHistogram Histogram durationHistogram
} }
func (s *spanStats) UserSpanURL() func(min, max time.Duration) string { func (s *regionStats) UserRegionURL() func(min, max time.Duration) string {
return func(min, max time.Duration) string { return func(min, max time.Duration) string {
return fmt.Sprintf("/userspan?type=%s&pc=%x&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), s.Frame.PC, template.URLQueryEscaper(min), template.URLQueryEscaper(max)) return fmt.Sprintf("/userregion?type=%s&pc=%x&latmin=%v&latmax=%v", template.URLQueryEscaper(s.Type), s.Frame.PC, template.URLQueryEscaper(min), template.URLQueryEscaper(max))
} }
} }
func (s *spanStats) add(span spanDesc) { func (s *regionStats) add(region regionDesc) {
s.Histogram.add(span.duration()) s.Histogram.add(region.duration())
} }
var templUserSpanTypes = template.Must(template.New("").Parse(` var templUserRegionTypes = template.Must(template.New("").Parse(`
<html> <html>
<style type="text/css"> <style type="text/css">
.histoTime { .histoTime {
...@@ -920,15 +920,15 @@ var templUserSpanTypes = template.Must(template.New("").Parse(` ...@@ -920,15 +920,15 @@ var templUserSpanTypes = template.Must(template.New("").Parse(`
<body> <body>
<table border="1" sortable="1"> <table border="1" sortable="1">
<tr> <tr>
<th>Span type</th> <th>Region type</th>
<th>Count</th> <th>Count</th>
<th>Duration distribution (complete tasks)</th> <th>Duration distribution (complete tasks)</th>
</tr> </tr>
{{range $}} {{range $}}
<tr> <tr>
<td>{{.Type}}<br>{{.Frame.Fn}}<br>{{.Frame.File}}:{{.Frame.Line}}</td> <td>{{.Type}}<br>{{.Frame.Fn}}<br>{{.Frame.File}}:{{.Frame.Line}}</td>
<td><a href="/userspan?type={{.Type}}&pc={{.Frame.PC}}">{{.Histogram.Count}}</a></td> <td><a href="/userregion?type={{.Type}}&pc={{.Frame.PC}}">{{.Histogram.Count}}</a></td>
<td>{{.Histogram.ToHTML (.UserSpanURL)}}</td> <td>{{.Histogram.ToHTML (.UserRegionURL)}}</td>
</tr> </tr>
{{end}} {{end}}
</table> </table>
...@@ -1109,15 +1109,15 @@ func describeEvent(ev *trace.Event) string { ...@@ -1109,15 +1109,15 @@ func describeEvent(ev *trace.Event) string {
return "goroutine stopped" return "goroutine stopped"
case trace.EvUserLog: case trace.EvUserLog:
return formatUserLog(ev) return formatUserLog(ev)
case trace.EvUserSpan: case trace.EvUserRegion:
if ev.Args[1] == 0 { if ev.Args[1] == 0 {
duration := "unknown" duration := "unknown"
if ev.Link != nil { if ev.Link != nil {
duration = (time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond).String() duration = (time.Duration(ev.Link.Ts-ev.Ts) * time.Nanosecond).String()
} }
return fmt.Sprintf("span %s started (duration: %v)", ev.SArgs[0], duration) return fmt.Sprintf("region %s started (duration: %v)", ev.SArgs[0], duration)
} }
return fmt.Sprintf("span %s ended", ev.SArgs[0]) return fmt.Sprintf("region %s ended", ev.SArgs[0])
case trace.EvUserTaskCreate: case trace.EvUserTaskCreate:
return fmt.Sprintf("task %v (id %d, parent %d) created", ev.SArgs[0], ev.Args[0], ev.Args[1]) return fmt.Sprintf("task %v (id %d, parent %d) created", ev.SArgs[0], ev.Args[0], ev.Args[1])
// TODO: add child task creation events into the parent task events // TODO: add child task creation events into the parent task events
...@@ -1129,13 +1129,13 @@ func describeEvent(ev *trace.Event) string { ...@@ -1129,13 +1129,13 @@ func describeEvent(ev *trace.Event) string {
func isUserAnnotationEvent(ev *trace.Event) bool { func isUserAnnotationEvent(ev *trace.Event) bool {
switch ev.Type { switch ev.Type {
case trace.EvUserLog, trace.EvUserSpan, trace.EvUserTaskCreate, trace.EvUserTaskEnd: case trace.EvUserLog, trace.EvUserRegion, trace.EvUserTaskCreate, trace.EvUserTaskEnd:
return true return true
} }
return false return false
} }
var templUserSpanType = template.Must(template.New("").Funcs(template.FuncMap{ var templUserRegionType = template.Must(template.New("").Funcs(template.FuncMap{
"prettyDuration": func(nsec int64) template.HTML { "prettyDuration": func(nsec int64) template.HTML {
d := time.Duration(nsec) * time.Nanosecond d := time.Duration(nsec) * time.Nanosecond
return template.HTML(niceDuration(d)) return template.HTML(niceDuration(d))
...@@ -1152,7 +1152,7 @@ var templUserSpanType = template.Must(template.New("").Funcs(template.FuncMap{ ...@@ -1152,7 +1152,7 @@ var templUserSpanType = template.Must(template.New("").Funcs(template.FuncMap{
} }
return template.HTML(fmt.Sprintf("%.2f%%", float64(dividened)/float64(divisor)*100)) return template.HTML(fmt.Sprintf("%.2f%%", float64(dividened)/float64(divisor)*100))
}, },
"unknownTime": func(desc spanDesc) int64 { "unknownTime": func(desc regionDesc) int64 {
sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime sum := desc.ExecTime + desc.IOTime + desc.BlockTime + desc.SyscallTime + desc.SchedWaitTime
if sum < desc.TotalTime { if sum < desc.TotalTime {
return desc.TotalTime - sum return desc.TotalTime - sum
......
...@@ -47,9 +47,9 @@ func TestOverlappingDuration(t *testing.T) { ...@@ -47,9 +47,9 @@ func TestOverlappingDuration(t *testing.T) {
// prog0 starts three goroutines. // prog0 starts three goroutines.
// //
// goroutine 1: taskless span // goroutine 1: taskless region
// goroutine 2: starts task0, do work in task0.span0, starts task1 which ends immediately. // goroutine 2: starts task0, do work in task0.region0, starts task1 which ends immediately.
// goroutine 3: do work in task0.span1 and task0.span2, ends task0 // goroutine 3: do work in task0.region1 and task0.region2, ends task0
func prog0() { func prog0() {
ctx := context.Background() ctx := context.Background()
...@@ -58,7 +58,7 @@ func prog0() { ...@@ -58,7 +58,7 @@ func prog0() {
wg.Add(1) wg.Add(1)
go func() { // goroutine 1 go func() { // goroutine 1
defer wg.Done() defer wg.Done()
trace.WithSpan(ctx, "taskless.span", func(ctx context.Context) { trace.WithRegion(ctx, "taskless.region", func() {
trace.Log(ctx, "key0", "val0") trace.Log(ctx, "key0", "val0")
}) })
}() }()
...@@ -66,29 +66,29 @@ func prog0() { ...@@ -66,29 +66,29 @@ func prog0() {
wg.Add(1) wg.Add(1)
go func() { // goroutine 2 go func() { // goroutine 2
defer wg.Done() defer wg.Done()
ctx, taskDone := trace.NewContext(ctx, "task0") ctx, task := trace.NewTask(ctx, "task0")
trace.WithSpan(ctx, "task0.span0", func(ctx context.Context) { trace.WithRegion(ctx, "task0.region0", func() {
wg.Add(1) wg.Add(1)
go func() { // goroutine 3 go func() { // goroutine 3
defer wg.Done() defer wg.Done()
defer taskDone() defer task.End()
trace.WithSpan(ctx, "task0.span1", func(ctx context.Context) { trace.WithRegion(ctx, "task0.region1", func() {
trace.WithSpan(ctx, "task0.span2", func(ctx context.Context) { trace.WithRegion(ctx, "task0.region2", func() {
trace.Log(ctx, "key2", "val2") trace.Log(ctx, "key2", "val2")
}) })
trace.Log(ctx, "key1", "val1") trace.Log(ctx, "key1", "val1")
}) })
}() }()
}) })
ctx2, taskDone2 := trace.NewContext(ctx, "task1") ctx2, task2 := trace.NewTask(ctx, "task1")
trace.Log(ctx2, "key3", "val3") trace.Log(ctx2, "key3", "val3")
taskDone2() task2.End()
}() }()
wg.Wait() wg.Wait()
} }
func TestAnalyzeAnnotations(t *testing.T) { func TestAnalyzeAnnotations(t *testing.T) {
// TODO: classify taskless spans // TODO: classify taskless regions
// Run prog0 and capture the execution trace. // Run prog0 and capture the execution trace.
if err := traceProgram(t, prog0, "TestAnalyzeAnnotations"); err != nil { if err := traceProgram(t, prog0, "TestAnalyzeAnnotations"); err != nil {
...@@ -101,17 +101,17 @@ func TestAnalyzeAnnotations(t *testing.T) { ...@@ -101,17 +101,17 @@ func TestAnalyzeAnnotations(t *testing.T) {
} }
// For prog0, we expect // For prog0, we expect
// - task with name = "task0", with three spans. // - task with name = "task0", with three regions.
// - task with name = "task1", with no span. // - task with name = "task1", with no region.
wantTasks := map[string]struct { wantTasks := map[string]struct {
complete bool complete bool
goroutines int goroutines int
spans []string regions []string
}{ }{
"task0": { "task0": {
complete: true, complete: true,
goroutines: 2, goroutines: 2,
spans: []string{"task0.span0", "", "task0.span1", "task0.span2"}, regions: []string{"task0.region0", "", "task0.region1", "task0.region2"},
}, },
"task1": { "task1": {
complete: true, complete: true,
...@@ -125,7 +125,7 @@ func TestAnalyzeAnnotations(t *testing.T) { ...@@ -125,7 +125,7 @@ func TestAnalyzeAnnotations(t *testing.T) {
t.Errorf("unexpected task: %s", task) t.Errorf("unexpected task: %s", task)
continue continue
} }
if task.complete() != want.complete || len(task.goroutines) != want.goroutines || !reflect.DeepEqual(spanNames(task), want.spans) { if task.complete() != want.complete || len(task.goroutines) != want.goroutines || !reflect.DeepEqual(regionNames(task), want.regions) {
t.Errorf("got task %v; want %+v", task, want) t.Errorf("got task %v; want %+v", task, want)
} }
...@@ -135,37 +135,37 @@ func TestAnalyzeAnnotations(t *testing.T) { ...@@ -135,37 +135,37 @@ func TestAnalyzeAnnotations(t *testing.T) {
t.Errorf("no more tasks; want %+v", wantTasks) t.Errorf("no more tasks; want %+v", wantTasks)
} }
wantSpans := []string{ wantRegions := []string{
"", // an auto-created span for the goroutine 3 "", // an auto-created region for the goroutine 3
"taskless.span", "taskless.region",
"task0.span0", "task0.region0",
"task0.span1", "task0.region1",
"task0.span2", "task0.region2",
} }
var gotSpans []string var gotRegions []string
for spanID := range res.spans { for regionID := range res.regions {
gotSpans = append(gotSpans, spanID.Type) gotRegions = append(gotRegions, regionID.Type)
} }
sort.Strings(wantSpans) sort.Strings(wantRegions)
sort.Strings(gotSpans) sort.Strings(gotRegions)
if !reflect.DeepEqual(gotSpans, wantSpans) { if !reflect.DeepEqual(gotRegions, wantRegions) {
t.Errorf("got spans %q, want spans %q", gotSpans, wantSpans) t.Errorf("got regions %q, want regions %q", gotRegions, wantRegions)
} }
} }
// prog1 creates a task hierarchy consisting of three tasks. // prog1 creates a task hierarchy consisting of three tasks.
func prog1() { func prog1() {
ctx := context.Background() ctx := context.Background()
ctx1, done1 := trace.NewContext(ctx, "task1") ctx1, task1 := trace.NewTask(ctx, "task1")
defer done1() defer task1.End()
trace.WithSpan(ctx1, "task1.span", func(ctx context.Context) { trace.WithRegion(ctx1, "task1.region", func() {
ctx2, done2 := trace.NewContext(ctx, "task2") ctx2, task2 := trace.NewTask(ctx1, "task2")
defer done2() defer task2.End()
trace.WithSpan(ctx2, "task2.span", func(ctx context.Context) { trace.WithRegion(ctx2, "task2.region", func() {
ctx3, done3 := trace.NewContext(ctx, "task3") ctx3, task3 := trace.NewTask(ctx2, "task3")
defer done3() defer task3.End()
trace.WithSpan(ctx3, "task3.span", func(ctx context.Context) { trace.WithRegion(ctx3, "task3.region", func() {
}) })
}) })
}) })
...@@ -184,27 +184,27 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) { ...@@ -184,27 +184,27 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) {
tasks := res.tasks tasks := res.tasks
// For prog0, we expect // For prog0, we expect
// - task with name = "", with taskless.span in spans. // - task with name = "", with taskless.region in regions.
// - task with name = "task0", with three spans. // - task with name = "task0", with three regions.
wantTasks := map[string]struct { wantTasks := map[string]struct {
parent string parent string
children []string children []string
spans []string regions []string
}{ }{
"task1": { "task1": {
parent: "", parent: "",
children: []string{"task2"}, children: []string{"task2"},
spans: []string{"task1.span"}, regions: []string{"task1.region"},
}, },
"task2": { "task2": {
parent: "task1", parent: "task1",
children: []string{"task3"}, children: []string{"task3"},
spans: []string{"task2.span"}, regions: []string{"task2.region"},
}, },
"task3": { "task3": {
parent: "task2", parent: "task2",
children: nil, children: nil,
spans: []string{"task3.span"}, regions: []string{"task3.region"},
}, },
} }
...@@ -218,7 +218,7 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) { ...@@ -218,7 +218,7 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) {
if parentName(task) != want.parent || if parentName(task) != want.parent ||
!reflect.DeepEqual(childrenNames(task), want.children) || !reflect.DeepEqual(childrenNames(task), want.children) ||
!reflect.DeepEqual(spanNames(task), want.spans) { !reflect.DeepEqual(regionNames(task), want.regions) {
t.Errorf("got %v; want %+v", task, want) t.Errorf("got %v; want %+v", task, want)
} }
} }
...@@ -234,10 +234,10 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) { ...@@ -234,10 +234,10 @@ func TestAnalyzeAnnotationTaskTree(t *testing.T) {
// prog2 returns the upper-bound gc time that overlaps with the first task. // prog2 returns the upper-bound gc time that overlaps with the first task.
func prog2() (gcTime time.Duration) { func prog2() (gcTime time.Duration) {
ch := make(chan bool) ch := make(chan bool)
ctx1, done := trace.NewContext(context.Background(), "taskWithGC") ctx1, task := trace.NewTask(context.Background(), "taskWithGC")
trace.WithSpan(ctx1, "taskWithGC.span1", func(ctx context.Context) { trace.WithRegion(ctx1, "taskWithGC.region1", func() {
go func() { go func() {
defer trace.StartSpan(ctx, "taskWithGC.span2")() defer trace.StartRegion(ctx1, "taskWithGC.region2").End()
<-ch <-ch
}() }()
s := time.Now() s := time.Now()
...@@ -245,13 +245,13 @@ func prog2() (gcTime time.Duration) { ...@@ -245,13 +245,13 @@ func prog2() (gcTime time.Duration) {
gcTime = time.Since(s) gcTime = time.Since(s)
close(ch) close(ch)
}) })
done() task.End()
ctx2, done2 := trace.NewContext(context.Background(), "taskWithoutGC") ctx2, task2 := trace.NewTask(context.Background(), "taskWithoutGC")
trace.WithSpan(ctx2, "taskWithoutGC.span1", func(ctx context.Context) { trace.WithRegion(ctx2, "taskWithoutGC.region1", func() {
// do nothing. // do nothing.
}) })
done2() task2.End()
return gcTime return gcTime
} }
...@@ -343,8 +343,8 @@ func traceProgram(t *testing.T, f func(), name string) error { ...@@ -343,8 +343,8 @@ func traceProgram(t *testing.T, f func(), name string) error {
return nil return nil
} }
func spanNames(task *taskDesc) (ret []string) { func regionNames(task *taskDesc) (ret []string) {
for _, s := range task.spans { for _, s := range task.regions {
ret = append(ret, s.Name) ret = append(ret, s.Name)
} }
return ret return ret
......
...@@ -200,7 +200,7 @@ var templMain = template.Must(template.New("").Parse(` ...@@ -200,7 +200,7 @@ var templMain = template.Must(template.New("").Parse(`
<a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)<br> <a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)<br>
<a href="/sched">Scheduler latency profile</a> (<a href="/sche?raw=1" download="sched.profile">⬇</a>)<br> <a href="/sched">Scheduler latency profile</a> (<a href="/sche?raw=1" download="sched.profile">⬇</a>)<br>
<a href="/usertasks">User-defined tasks</a><br> <a href="/usertasks">User-defined tasks</a><br>
<a href="/userspans">User-defined spans</a><br> <a href="/userregions">User-defined regions</a><br>
</body> </body>
</html> </html>
`)) `))
......
...@@ -42,10 +42,10 @@ func init() { ...@@ -42,10 +42,10 @@ func init() {
http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall))) http.HandleFunc("/syscall", serveSVGProfile(pprofByGoroutine(computePprofSyscall)))
http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched))) http.HandleFunc("/sched", serveSVGProfile(pprofByGoroutine(computePprofSched)))
http.HandleFunc("/spanio", serveSVGProfile(pprofBySpan(computePprofIO))) http.HandleFunc("/regionio", serveSVGProfile(pprofByRegion(computePprofIO)))
http.HandleFunc("/spanblock", serveSVGProfile(pprofBySpan(computePprofBlock))) http.HandleFunc("/regionblock", serveSVGProfile(pprofByRegion(computePprofBlock)))
http.HandleFunc("/spansyscall", serveSVGProfile(pprofBySpan(computePprofSyscall))) http.HandleFunc("/regionsyscall", serveSVGProfile(pprofByRegion(computePprofSyscall)))
http.HandleFunc("/spansched", serveSVGProfile(pprofBySpan(computePprofSched))) http.HandleFunc("/regionsched", serveSVGProfile(pprofByRegion(computePprofSched)))
} }
// Record represents one entry in pprof-like profiles. // Record represents one entry in pprof-like profiles.
...@@ -75,13 +75,13 @@ func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, []*trace.Ev ...@@ -75,13 +75,13 @@ func pprofByGoroutine(compute func(io.Writer, map[uint64][]interval, []*trace.Ev
} }
} }
func pprofBySpan(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error { func pprofByRegion(compute func(io.Writer, map[uint64][]interval, []*trace.Event) error) func(w io.Writer, r *http.Request) error {
return func(w io.Writer, r *http.Request) error { return func(w io.Writer, r *http.Request) error {
filter, err := newSpanFilter(r) filter, err := newRegionFilter(r)
if err != nil { if err != nil {
return err return err
} }
gToIntervals, err := pprofMatchingSpans(filter) gToIntervals, err := pprofMatchingRegions(filter)
if err != nil { if err != nil {
return err return err
} }
...@@ -123,9 +123,9 @@ func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]int ...@@ -123,9 +123,9 @@ func pprofMatchingGoroutines(id string, events []*trace.Event) (map[uint64][]int
return res, nil return res, nil
} }
// pprofMatchingSpans returns the time intervals of matching spans // pprofMatchingRegions returns the time intervals of matching regions
// grouped by the goroutine id. If the filter is nil, returns nil without an error. // grouped by the goroutine id. If the filter is nil, returns nil without an error.
func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) { func pprofMatchingRegions(filter *regionFilter) (map[uint64][]interval, error) {
res, err := analyzeAnnotations() res, err := analyzeAnnotations()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -135,8 +135,8 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) { ...@@ -135,8 +135,8 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) {
} }
gToIntervals := make(map[uint64][]interval) gToIntervals := make(map[uint64][]interval)
for id, spans := range res.spans { for id, regions := range res.regions {
for _, s := range spans { for _, s := range regions {
if filter.match(id, s) { if filter.match(id, s) {
gToIntervals[s.G] = append(gToIntervals[s.G], interval{begin: s.firstTimestamp(), end: s.lastTimestamp()}) gToIntervals[s.G] = append(gToIntervals[s.G], interval{begin: s.firstTimestamp(), end: s.lastTimestamp()})
} }
...@@ -144,10 +144,10 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) { ...@@ -144,10 +144,10 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) {
} }
for g, intervals := range gToIntervals { for g, intervals := range gToIntervals {
// in order to remove nested spans and // in order to remove nested regions and
// consider only the outermost spans, // consider only the outermost regions,
// first, we sort based on the start time // first, we sort based on the start time
// and then scan through to select only the outermost spans. // and then scan through to select only the outermost regions.
sort.Slice(intervals, func(i, j int) bool { sort.Slice(intervals, func(i, j int) bool {
x := intervals[i].begin x := intervals[i].begin
y := intervals[j].begin y := intervals[j].begin
...@@ -158,13 +158,13 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) { ...@@ -158,13 +158,13 @@ func pprofMatchingSpans(filter *spanFilter) (map[uint64][]interval, error) {
}) })
var lastTimestamp int64 var lastTimestamp int64
var n int var n int
// select only the outermost spans. // select only the outermost regions.
for _, i := range intervals { for _, i := range intervals {
if lastTimestamp <= i.begin { if lastTimestamp <= i.begin {
intervals[n] = i // new non-overlapping span starts. intervals[n] = i // new non-overlapping region starts.
lastTimestamp = i.end lastTimestamp = i.end
n++ n++
} // otherwise, skip because this span overlaps with a previous span. } // otherwise, skip because this region overlaps with a previous region.
} }
gToIntervals[g] = intervals[:n] gToIntervals[g] = intervals[:n]
} }
......
...@@ -405,7 +405,7 @@ type traceContext struct { ...@@ -405,7 +405,7 @@ type traceContext struct {
threadStats, prevThreadStats threadStats threadStats, prevThreadStats threadStats
gstates, prevGstates [gStateCount]int64 gstates, prevGstates [gStateCount]int64
spanID int // last emitted span id. incremented in each emitSpan call. regionID int // last emitted region id. incremented in each emitRegion call.
} }
type heapStats struct { type heapStats struct {
...@@ -738,7 +738,7 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { ...@@ -738,7 +738,7 @@ func generateTrace(params *traceParams, consumer traceConsumer) error {
} }
} }
// Display task and its spans if we are in task-oriented presentation mode. // Display task and its regions if we are in task-oriented presentation mode.
if ctx.mode&modeTaskOriented != 0 { if ctx.mode&modeTaskOriented != 0 {
taskRow := uint64(trace.GCP + 1) taskRow := uint64(trace.GCP + 1)
for _, task := range ctx.tasks { for _, task := range ctx.tasks {
...@@ -757,11 +757,11 @@ func generateTrace(params *traceParams, consumer traceConsumer) error { ...@@ -757,11 +757,11 @@ func generateTrace(params *traceParams, consumer traceConsumer) error {
} }
ctx.emit(tEnd) ctx.emit(tEnd)
// If we are in goroutine-oriented mode, we draw spans. // If we are in goroutine-oriented mode, we draw regions.
// TODO(hyangah): add this for task/P-oriented mode (i.e., focustask view) too. // TODO(hyangah): add this for task/P-oriented mode (i.e., focustask view) too.
if ctx.mode&modeGoroutineOriented != 0 { if ctx.mode&modeGoroutineOriented != 0 {
for _, s := range task.spans { for _, s := range task.regions {
ctx.emitSpan(s) ctx.emitRegion(s)
} }
} }
} }
...@@ -859,23 +859,23 @@ func (ctx *traceContext) emitSlice(ev *trace.Event, name string) *ViewerEvent { ...@@ -859,23 +859,23 @@ func (ctx *traceContext) emitSlice(ev *trace.Event, name string) *ViewerEvent {
return sl return sl
} }
func (ctx *traceContext) emitSpan(s spanDesc) { func (ctx *traceContext) emitRegion(s regionDesc) {
if s.Name == "" { if s.Name == "" {
return return
} }
ctx.spanID++ ctx.regionID++
spanID := ctx.spanID regionID := ctx.regionID
id := s.TaskID id := s.TaskID
scopeID := fmt.Sprintf("%x", id) scopeID := fmt.Sprintf("%x", id)
sl0 := &ViewerEvent{ sl0 := &ViewerEvent{
Category: "Span", Category: "Region",
Name: s.Name, Name: s.Name,
Phase: "b", Phase: "b",
Time: float64(s.firstTimestamp()) / 1e3, Time: float64(s.firstTimestamp()) / 1e3,
Tid: s.G, Tid: s.G,
ID: uint64(spanID), ID: uint64(regionID),
Scope: scopeID, Scope: scopeID,
Cname: colorDeepMagenta, Cname: colorDeepMagenta,
} }
...@@ -885,12 +885,12 @@ func (ctx *traceContext) emitSpan(s spanDesc) { ...@@ -885,12 +885,12 @@ func (ctx *traceContext) emitSpan(s spanDesc) {
ctx.emit(sl0) ctx.emit(sl0)
sl1 := &ViewerEvent{ sl1 := &ViewerEvent{
Category: "Span", Category: "Region",
Name: s.Name, Name: s.Name,
Phase: "e", Phase: "e",
Time: float64(s.lastTimestamp()) / 1e3, Time: float64(s.lastTimestamp()) / 1e3,
Tid: s.G, Tid: s.G,
ID: uint64(spanID), ID: uint64(regionID),
Scope: scopeID, Scope: scopeID,
Cname: colorDeepMagenta, Cname: colorDeepMagenta,
} }
......
...@@ -15,8 +15,8 @@ type GDesc struct { ...@@ -15,8 +15,8 @@ type GDesc struct {
StartTime int64 StartTime int64
EndTime int64 EndTime int64
// List of spans in the goroutine, sorted based on the start time. // List of regions in the goroutine, sorted based on the start time.
Spans []*UserSpanDesc Regions []*UserRegionDesc
// Statistics of execution time during the goroutine execution. // Statistics of execution time during the goroutine execution.
GExecutionStat GExecutionStat
...@@ -24,20 +24,20 @@ type GDesc struct { ...@@ -24,20 +24,20 @@ type GDesc struct {
*gdesc // private part. *gdesc // private part.
} }
// UserSpanDesc represents a span and goroutine execution stats // UserRegionDesc represents a region and goroutine execution stats
// while the span was active. // while the region was active.
type UserSpanDesc struct { type UserRegionDesc struct {
TaskID uint64 TaskID uint64
Name string Name string
// Span start event. Normally EvUserSpan start event or nil, // Region start event. Normally EvUserRegion start event or nil,
// but can be EvGoCreate event if the span is a synthetic // but can be EvGoCreate event if the region is a synthetic
// span representing task inheritance from the parent goroutine. // region representing task inheritance from the parent goroutine.
Start *Event Start *Event
// Span end event. Normally EvUserSpan end event or nil, // Region end event. Normally EvUserRegion end event or nil,
// but can be EvGoStop or EvGoEnd event if the goroutine // but can be EvGoStop or EvGoEnd event if the goroutine
// terminated without explicitely ending the span. // terminated without explicitely ending the region.
End *Event End *Event
GExecutionStat GExecutionStat
...@@ -118,7 +118,7 @@ func (g *GDesc) snapshotStat(lastTs, activeGCStartTime int64) (ret GExecutionSta ...@@ -118,7 +118,7 @@ func (g *GDesc) snapshotStat(lastTs, activeGCStartTime int64) (ret GExecutionSta
// finalize is called when processing a goroutine end event or at // finalize is called when processing a goroutine end event or at
// the end of trace processing. This finalizes the execution stat // the end of trace processing. This finalizes the execution stat
// and any active spans in the goroutine, in which case trigger is nil. // and any active regions in the goroutine, in which case trigger is nil.
func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) { func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) {
if trigger != nil { if trigger != nil {
g.EndTime = trigger.Ts g.EndTime = trigger.Ts
...@@ -126,10 +126,10 @@ func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) { ...@@ -126,10 +126,10 @@ func (g *GDesc) finalize(lastTs, activeGCStartTime int64, trigger *Event) {
finalStat := g.snapshotStat(lastTs, activeGCStartTime) finalStat := g.snapshotStat(lastTs, activeGCStartTime)
g.GExecutionStat = finalStat g.GExecutionStat = finalStat
for _, s := range g.activeSpans { for _, s := range g.activeRegions {
s.End = trigger s.End = trigger
s.GExecutionStat = finalStat.sub(s.GExecutionStat) s.GExecutionStat = finalStat.sub(s.GExecutionStat)
g.Spans = append(g.Spans, s) g.Regions = append(g.Regions, s)
} }
*(g.gdesc) = gdesc{} *(g.gdesc) = gdesc{}
} }
...@@ -144,7 +144,7 @@ type gdesc struct { ...@@ -144,7 +144,7 @@ type gdesc struct {
blockGCTime int64 blockGCTime int64
blockSchedTime int64 blockSchedTime int64
activeSpans []*UserSpanDesc // stack of active spans activeRegions []*UserRegionDesc // stack of active regions
} }
// GoroutineStats generates statistics for all goroutines in the trace. // GoroutineStats generates statistics for all goroutines in the trace.
...@@ -159,14 +159,14 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc { ...@@ -159,14 +159,14 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc {
g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)} g := &GDesc{ID: ev.Args[0], CreationTime: ev.Ts, gdesc: new(gdesc)}
g.blockSchedTime = ev.Ts g.blockSchedTime = ev.Ts
// When a goroutine is newly created, inherit the // When a goroutine is newly created, inherit the
// task of the active span. For ease handling of // task of the active region. For ease handling of
// this case, we create a fake span description with // this case, we create a fake region description with
// the task id. // the task id.
if creatorG := gs[ev.G]; creatorG != nil && len(creatorG.gdesc.activeSpans) > 0 { if creatorG := gs[ev.G]; creatorG != nil && len(creatorG.gdesc.activeRegions) > 0 {
spans := creatorG.gdesc.activeSpans regions := creatorG.gdesc.activeRegions
s := spans[len(spans)-1] s := regions[len(regions)-1]
if s.TaskID != 0 { if s.TaskID != 0 {
g.gdesc.activeSpans = []*UserSpanDesc{ g.gdesc.activeRegions = []*UserRegionDesc{
{TaskID: s.TaskID, Start: ev}, {TaskID: s.TaskID, Start: ev},
} }
} }
...@@ -263,32 +263,32 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc { ...@@ -263,32 +263,32 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc {
} }
} }
gcStartTime = 0 // indicates gc is inactive. gcStartTime = 0 // indicates gc is inactive.
case EvUserSpan: case EvUserRegion:
g := gs[ev.G] g := gs[ev.G]
switch mode := ev.Args[1]; mode { switch mode := ev.Args[1]; mode {
case 0: // span start case 0: // region start
g.activeSpans = append(g.activeSpans, &UserSpanDesc{ g.activeRegions = append(g.activeRegions, &UserRegionDesc{
Name: ev.SArgs[0], Name: ev.SArgs[0],
TaskID: ev.Args[0], TaskID: ev.Args[0],
Start: ev, Start: ev,
GExecutionStat: g.snapshotStat(lastTs, gcStartTime), GExecutionStat: g.snapshotStat(lastTs, gcStartTime),
}) })
case 1: // span end case 1: // region end
var sd *UserSpanDesc var sd *UserRegionDesc
if spanStk := g.activeSpans; len(spanStk) > 0 { if regionStk := g.activeRegions; len(regionStk) > 0 {
n := len(spanStk) n := len(regionStk)
sd = spanStk[n-1] sd = regionStk[n-1]
spanStk = spanStk[:n-1] // pop regionStk = regionStk[:n-1] // pop
g.activeSpans = spanStk g.activeRegions = regionStk
} else { } else {
sd = &UserSpanDesc{ sd = &UserRegionDesc{
Name: ev.SArgs[0], Name: ev.SArgs[0],
TaskID: ev.Args[0], TaskID: ev.Args[0],
} }
} }
sd.GExecutionStat = g.snapshotStat(lastTs, gcStartTime).sub(sd.GExecutionStat) sd.GExecutionStat = g.snapshotStat(lastTs, gcStartTime).sub(sd.GExecutionStat)
sd.End = ev sd.End = ev
g.Spans = append(g.Spans, sd) g.Regions = append(g.Regions, sd)
} }
} }
} }
...@@ -296,10 +296,10 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc { ...@@ -296,10 +296,10 @@ func GoroutineStats(events []*Event) map[uint64]*GDesc {
for _, g := range gs { for _, g := range gs {
g.finalize(lastTs, gcStartTime, nil) g.finalize(lastTs, gcStartTime, nil)
// sort based on span start time // sort based on region start time
sort.Slice(g.Spans, func(i, j int) bool { sort.Slice(g.Regions, func(i, j int) bool {
x := g.Spans[i].Start x := g.Regions[i].Start
y := g.Spans[j].Start y := g.Regions[j].Start
if x == nil { if x == nil {
return true return true
} }
......
...@@ -56,7 +56,7 @@ type Event struct { ...@@ -56,7 +56,7 @@ type Event struct {
// for GoSysExit: the next GoStart // for GoSysExit: the next GoStart
// for GCMarkAssistStart: the associated GCMarkAssistDone // for GCMarkAssistStart: the associated GCMarkAssistDone
// for UserTaskCreate: the UserTaskEnd // for UserTaskCreate: the UserTaskEnd
// for UserSpan: if the start span, the corresponding UserSpan end event // for UserRegion: if the start region, the corresponding UserRegion end event
Link *Event Link *Event
} }
...@@ -442,7 +442,7 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even ...@@ -442,7 +442,7 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
case EvUserTaskCreate: case EvUserTaskCreate:
// e.Args 0: taskID, 1:parentID, 2:nameID // e.Args 0: taskID, 1:parentID, 2:nameID
e.SArgs = []string{strings[e.Args[2]]} e.SArgs = []string{strings[e.Args[2]]}
case EvUserSpan: case EvUserRegion:
// e.Args 0: taskID, 1: mode, 2:nameID // e.Args 0: taskID, 1: mode, 2:nameID
e.SArgs = []string{strings[e.Args[2]]} e.SArgs = []string{strings[e.Args[2]]}
case EvUserLog: case EvUserLog:
...@@ -584,7 +584,7 @@ func postProcessTrace(ver int, events []*Event) error { ...@@ -584,7 +584,7 @@ func postProcessTrace(ver int, events []*Event) error {
gs := make(map[uint64]gdesc) gs := make(map[uint64]gdesc)
ps := make(map[int]pdesc) ps := make(map[int]pdesc)
tasks := make(map[uint64]*Event) // task id to task creation events tasks := make(map[uint64]*Event) // task id to task creation events
activeSpans := make(map[uint64][]*Event) // goroutine id to stack of spans activeRegions := make(map[uint64][]*Event) // goroutine id to stack of regions
gs[0] = gdesc{state: gRunning} gs[0] = gdesc{state: gRunning}
var evGC, evSTW *Event var evGC, evSTW *Event
...@@ -730,12 +730,12 @@ func postProcessTrace(ver int, events []*Event) error { ...@@ -730,12 +730,12 @@ func postProcessTrace(ver int, events []*Event) error {
g.state = gDead g.state = gDead
p.g = 0 p.g = 0
if ev.Type == EvGoEnd { // flush all active spans if ev.Type == EvGoEnd { // flush all active regions
spans := activeSpans[ev.G] regions := activeRegions[ev.G]
for _, s := range spans { for _, s := range regions {
s.Link = ev s.Link = ev
} }
delete(activeSpans, ev.G) delete(activeRegions, ev.G)
} }
case EvGoSched, EvGoPreempt: case EvGoSched, EvGoPreempt:
...@@ -811,29 +811,29 @@ func postProcessTrace(ver int, events []*Event) error { ...@@ -811,29 +811,29 @@ func postProcessTrace(ver int, events []*Event) error {
taskCreateEv.Link = ev taskCreateEv.Link = ev
delete(tasks, taskid) delete(tasks, taskid)
} }
case EvUserSpan: case EvUserRegion:
mode := ev.Args[1] mode := ev.Args[1]
spans := activeSpans[ev.G] regions := activeRegions[ev.G]
if mode == 0 { // span start if mode == 0 { // region start
activeSpans[ev.G] = append(spans, ev) // push activeRegions[ev.G] = append(regions, ev) // push
} else if mode == 1 { // span end } else if mode == 1 { // region end
n := len(spans) n := len(regions)
if n > 0 { // matching span start event is in the trace. if n > 0 { // matching region start event is in the trace.
s := spans[n-1] s := regions[n-1]
if s.Args[0] != ev.Args[0] || s.SArgs[0] != ev.SArgs[0] { // task id, span name mismatch if s.Args[0] != ev.Args[0] || s.SArgs[0] != ev.SArgs[0] { // task id, region name mismatch
return fmt.Errorf("misuse of span in goroutine %d: span end %q when the inner-most active span start event is %q", ev.G, ev, s) return fmt.Errorf("misuse of region in goroutine %d: span end %q when the inner-most active span start event is %q", ev.G, ev, s)
} }
// Link span start event with span end event // Link region start event with span end event
s.Link = ev s.Link = ev
if n > 1 { if n > 1 {
activeSpans[ev.G] = spans[:n-1] activeRegions[ev.G] = regions[:n-1]
} else { } else {
delete(activeSpans, ev.G) delete(activeRegions, ev.G)
} }
} }
} else { } else {
return fmt.Errorf("invalid user span mode: %q", ev) return fmt.Errorf("invalid user region mode: %q", ev)
} }
} }
...@@ -1056,7 +1056,7 @@ const ( ...@@ -1056,7 +1056,7 @@ const (
EvGCMarkAssistDone = 44 // GC mark assist done [timestamp] EvGCMarkAssistDone = 44 // GC mark assist done [timestamp]
EvUserTaskCreate = 45 // trace.NewContext [timestamp, internal task id, internal parent id, stack, name string] EvUserTaskCreate = 45 // trace.NewContext [timestamp, internal task id, internal parent id, stack, name string]
EvUserTaskEnd = 46 // end of task [timestamp, internal task id, stack] EvUserTaskEnd = 46 // end of task [timestamp, internal task id, stack]
EvUserSpan = 47 // trace.WithSpan [timestamp, internal task id, mode(0:start, 1:end), stack, name string] EvUserRegion = 47 // trace.WithRegion [timestamp, internal task id, mode(0:start, 1:end), stack, name string]
EvUserLog = 48 // trace.Log [timestamp, internal id, key string id, stack, value string] EvUserLog = 48 // trace.Log [timestamp, internal id, key string id, stack, value string]
EvCount = 49 EvCount = 49
) )
...@@ -1115,6 +1115,6 @@ var EventDescriptions = [EvCount]struct { ...@@ -1115,6 +1115,6 @@ var EventDescriptions = [EvCount]struct {
EvGCMarkAssistDone: {"GCMarkAssistDone", 1009, false, []string{}, nil}, EvGCMarkAssistDone: {"GCMarkAssistDone", 1009, false, []string{}, nil},
EvUserTaskCreate: {"UserTaskCreate", 1011, true, []string{"taskid", "pid", "typeid"}, []string{"name"}}, EvUserTaskCreate: {"UserTaskCreate", 1011, true, []string{"taskid", "pid", "typeid"}, []string{"name"}},
EvUserTaskEnd: {"UserTaskEnd", 1011, true, []string{"taskid"}, nil}, EvUserTaskEnd: {"UserTaskEnd", 1011, true, []string{"taskid"}, nil},
EvUserSpan: {"UserSpan", 1011, true, []string{"taskid", "mode", "typeid"}, []string{"name"}}, EvUserRegion: {"UserRegion", 1011, true, []string{"taskid", "mode", "typeid"}, []string{"name"}},
EvUserLog: {"UserLog", 1011, true, []string{"id", "keyid"}, []string{"category", "message"}}, EvUserLog: {"UserLog", 1011, true, []string{"id", "keyid"}, []string{"category", "message"}},
} }
...@@ -66,7 +66,7 @@ const ( ...@@ -66,7 +66,7 @@ const (
traceEvGCMarkAssistDone = 44 // GC mark assist done [timestamp] traceEvGCMarkAssistDone = 44 // GC mark assist done [timestamp]
traceEvUserTaskCreate = 45 // trace.NewContext [timestamp, internal task id, internal parent task id, stack, name string] traceEvUserTaskCreate = 45 // trace.NewContext [timestamp, internal task id, internal parent task id, stack, name string]
traceEvUserTaskEnd = 46 // end of a task [timestamp, internal task id, stack] traceEvUserTaskEnd = 46 // end of a task [timestamp, internal task id, stack]
traceEvUserSpan = 47 // trace.WithSpan [timestamp, internal task id, mode(0:start, 1:end), stack, name string] traceEvUserRegion = 47 // trace.WithRegion [timestamp, internal task id, mode(0:start, 1:end), stack, name string]
traceEvUserLog = 48 // trace.Log [timestamp, internal task id, key string id, stack, value string] traceEvUserLog = 48 // trace.Log [timestamp, internal task id, key string id, stack, value string]
traceEvCount = 49 traceEvCount = 49
// Byte is used but only 6 bits are available for event type. // Byte is used but only 6 bits are available for event type.
...@@ -129,7 +129,7 @@ var trace struct { ...@@ -129,7 +129,7 @@ var trace struct {
// Dictionary for traceEvString. // Dictionary for traceEvString.
// //
// TODO: central lock to access the map is not ideal. // TODO: central lock to access the map is not ideal.
// option: pre-assign ids to all user annotation span names and tags // option: pre-assign ids to all user annotation region names and tags
// option: per-P cache // option: per-P cache
// option: sync.Map like data structure // option: sync.Map like data structure
stringsLock mutex stringsLock mutex
...@@ -1171,8 +1171,8 @@ func trace_userTaskEnd(id uint64) { ...@@ -1171,8 +1171,8 @@ func trace_userTaskEnd(id uint64) {
traceEvent(traceEvUserTaskEnd, 2, id) traceEvent(traceEvUserTaskEnd, 2, id)
} }
//go:linkname trace_userSpan runtime/trace.userSpan //go:linkname trace_userRegion runtime/trace.userRegion
func trace_userSpan(id, mode uint64, name string) { func trace_userRegion(id, mode uint64, name string) {
if !trace.enabled { if !trace.enabled {
return return
} }
...@@ -1184,7 +1184,7 @@ func trace_userSpan(id, mode uint64, name string) { ...@@ -1184,7 +1184,7 @@ func trace_userSpan(id, mode uint64, name string) {
} }
nameStringID, bufp := traceString(bufp, pid, name) nameStringID, bufp := traceString(bufp, pid, name)
traceEventLocked(0, mp, pid, bufp, traceEvUserSpan, 3, id, mode, nameStringID) traceEventLocked(0, mp, pid, bufp, traceEvUserRegion, 3, id, mode, nameStringID)
traceReleaseBuffer(pid) traceReleaseBuffer(pid)
} }
......
...@@ -9,9 +9,9 @@ import ( ...@@ -9,9 +9,9 @@ import (
type traceContextKey struct{} type traceContextKey struct{}
// NewContext creates a child context with a new task instance with // NewTask creates a task instance with the type taskType and returns
// the type taskType. If the input context contains a task, the // it along with a Context that carries the task.
// new task is its subtask. // If the input context contains a task, the new task is its subtask.
// //
// The taskType is used to classify task instances. Analysis tools // The taskType is used to classify task instances. Analysis tools
// like the Go execution tracer may assume there are only a bounded // like the Go execution tracer may assume there are only a bounded
...@@ -24,21 +24,19 @@ type traceContextKey struct{} ...@@ -24,21 +24,19 @@ type traceContextKey struct{}
// If the end function is called multiple times, only the first // If the end function is called multiple times, only the first
// call is used in the latency measurement. // call is used in the latency measurement.
// //
// ctx, taskEnd := trace.NewContext(ctx, "awesome task") // ctx, task := trace.NewContext(ctx, "awesome task")
// trace.WithSpan(ctx, prepWork) // trace.WithRegion(ctx, prepWork)
// // preparation of the task // // preparation of the task
// go func() { // continue processing the task in a separate goroutine. // go func() { // continue processing the task in a separate goroutine.
// defer taskEnd() // defer task.End()
// trace.WithSpan(ctx, remainingWork) // trace.WithRegion(ctx, remainingWork)
// } // }
func NewContext(pctx context.Context, taskType string) (ctx context.Context, end func()) { func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) {
pid := fromContext(pctx).id pid := fromContext(pctx).id
id := newID() id := newID()
userTaskCreate(id, pid, taskType) userTaskCreate(id, pid, taskType)
s := &task{id: id} s := &Task{id: id}
return context.WithValue(pctx, traceContextKey{}, s), func() { return context.WithValue(pctx, traceContextKey{}, s), s
userTaskEnd(id)
}
// We allocate a new task and the end function even when // We allocate a new task and the end function even when
// the tracing is disabled because the context and the detach // the tracing is disabled because the context and the detach
...@@ -47,8 +45,8 @@ func NewContext(pctx context.Context, taskType string) (ctx context.Context, end ...@@ -47,8 +45,8 @@ func NewContext(pctx context.Context, taskType string) (ctx context.Context, end
// //
// For example, consider the following scenario: // For example, consider the following scenario:
// - trace is enabled. // - trace is enabled.
// - trace.WithSpan is called, so a new context ctx // - trace.WithRegion is called, so a new context ctx
// with a new span is created. // with a new region is created.
// - trace is disabled. // - trace is disabled.
// - trace is enabled again. // - trace is enabled again.
// - trace APIs with the ctx is called. Is the ID in the task // - trace APIs with the ctx is called. Is the ID in the task
...@@ -60,18 +58,30 @@ func NewContext(pctx context.Context, taskType string) (ctx context.Context, end ...@@ -60,18 +58,30 @@ func NewContext(pctx context.Context, taskType string) (ctx context.Context, end
// tracing round. // tracing round.
} }
func fromContext(ctx context.Context) *task { // NewContext is obsolete by NewTask. Do not use.
if s, ok := ctx.Value(traceContextKey{}).(*task); ok { func NewContext(pctx context.Context, taskType string) (ctx context.Context, endTask func()) {
ctx, t := NewTask(pctx, taskType)
return ctx, t.End
}
func fromContext(ctx context.Context) *Task {
if s, ok := ctx.Value(traceContextKey{}).(*Task); ok {
return s return s
} }
return &bgTask return &bgTask
} }
type task struct { // Task is a data type for tracing a user-defined, logical operation.
type Task struct {
id uint64 id uint64
// TODO(hyangah): record parent id? // TODO(hyangah): record parent id?
} }
// End marks the end of the operation represented by the Task.
func (t *Task) End() {
userTaskEnd(t.id)
}
var lastTaskID uint64 = 0 // task id issued last time var lastTaskID uint64 = 0 // task id issued last time
func newID() uint64 { func newID() uint64 {
...@@ -79,7 +89,7 @@ func newID() uint64 { ...@@ -79,7 +89,7 @@ func newID() uint64 {
return atomic.AddUint64(&lastTaskID, 1) return atomic.AddUint64(&lastTaskID, 1)
} }
var bgTask = task{id: uint64(0)} var bgTask = Task{id: uint64(0)}
// Log emits a one-off event with the given category and message. // Log emits a one-off event with the given category and message.
// Category can be empty and the API assumes there are only a handful of // Category can be empty and the API assumes there are only a handful of
...@@ -100,24 +110,24 @@ func Logf(ctx context.Context, category, format string, args ...interface{}) { ...@@ -100,24 +110,24 @@ func Logf(ctx context.Context, category, format string, args ...interface{}) {
} }
const ( const (
spanStartCode = uint64(0) regionStartCode = uint64(0)
spanEndCode = uint64(1) regionEndCode = uint64(1)
) )
// WithSpan starts a span associated with its calling goroutine, runs fn, // WithRegion starts a region associated with its calling goroutine, runs fn,
// and then ends the span. If the context carries a task, the span is // and then ends the region. If the context carries a task, the region is
// attached to the task. Otherwise, the span is attached to the background // associated with the task. Otherwise, the region is attached to the background
// task. // task.
// //
// The spanType is used to classify spans, so there should be only a // The regionType is used to classify regions, so there should be only a
// handful of unique span types. // handful of unique region types.
func WithSpan(ctx context.Context, spanType string, fn func(context.Context)) { func WithRegion(ctx context.Context, regionType string, fn func()) {
// NOTE: // NOTE:
// WithSpan helps avoiding misuse of the API but in practice, // WithRegion helps avoiding misuse of the API but in practice,
// this is very restrictive: // this is very restrictive:
// - Use of WithSpan makes the stack traces captured from // - Use of WithRegion makes the stack traces captured from
// span start and end are identical. // region start and end are identical.
// - Refactoring the existing code to use WithSpan is sometimes // - Refactoring the existing code to use WithRegion is sometimes
// hard and makes the code less readable. // hard and makes the code less readable.
// e.g. code block nested deep in the loop with various // e.g. code block nested deep in the loop with various
// exit point with return values // exit point with return values
...@@ -128,22 +138,46 @@ func WithSpan(ctx context.Context, spanType string, fn func(context.Context)) { ...@@ -128,22 +138,46 @@ func WithSpan(ctx context.Context, spanType string, fn func(context.Context)) {
// makes the code less readable. // makes the code less readable.
id := fromContext(ctx).id id := fromContext(ctx).id
userSpan(id, spanStartCode, spanType) userRegion(id, regionStartCode, regionType)
defer userSpan(id, spanEndCode, spanType) defer userRegion(id, regionEndCode, regionType)
fn(ctx) fn()
} }
// StartSpan starts a span and returns a function for marking the // WithSpan is obsolete by WithRegion. Do not use.
// end of the span. The span end function must be called from the func WithSpan(ctx context.Context, spanType string, fn func(ctx context.Context)) {
// same goroutine where the span was started. WithRegion(ctx, spanType, func() { fn(ctx) })
// Within each goroutine, spans must nest. That is, spans started }
// after this span must be ended before this span can be ended.
// Callers are encouraged to instead use WithSpan when possible, // StartRegion starts a region and returns a function for marking the
// since it naturally satisfies these restrictions. // end of the region. The returned Region's End function must be called
func StartSpan(ctx context.Context, spanType string) func() { // from the same goroutine where the region was started.
// Within each goroutine, regions must nest. That is, regions started
// after this region must be ended before this region can be ended.
// Recommended usage is
//
// defer trace.StartRegion(ctx, "myTracedRegion").End()
//
func StartRegion(ctx context.Context, regionType string) *Region {
id := fromContext(ctx).id id := fromContext(ctx).id
userSpan(id, spanStartCode, spanType) userRegion(id, regionStartCode, regionType)
return func() { userSpan(id, spanEndCode, spanType) } return &Region{id, regionType}
}
// StartSpan is obsolete by StartRegion. Do not use.
func StartSpan(ctx context.Context, spanType string) func() {
r := StartRegion(ctx, spanType)
return r.End
}
// Region is a region of code whose execution time interval is traced.
type Region struct {
id uint64
regionType string
}
// End marks the end of the traced code region.
func (r *Region) End() {
userRegion(r.id, regionEndCode, r.regionType)
} }
// IsEnabled returns whether tracing is enabled. // IsEnabled returns whether tracing is enabled.
...@@ -164,8 +198,8 @@ func userTaskCreate(id, parentID uint64, taskType string) ...@@ -164,8 +198,8 @@ func userTaskCreate(id, parentID uint64, taskType string)
// emits UserTaskEnd event. // emits UserTaskEnd event.
func userTaskEnd(id uint64) func userTaskEnd(id uint64)
// emits UserSpan event. // emits UserRegion event.
func userSpan(id, mode uint64, spanType string) func userRegion(id, mode uint64, regionType string)
// emits UserLog event. // emits UserLog event.
func userLog(id uint64, category, message string) func userLog(id uint64, category, message string)
...@@ -12,11 +12,11 @@ import ( ...@@ -12,11 +12,11 @@ import (
"testing" "testing"
) )
func TestUserTaskSpan(t *testing.T) { func TestUserTaskRegion(t *testing.T) {
bgctx, cancel := context.WithCancel(context.Background()) bgctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
preExistingSpanEnd := StartSpan(bgctx, "pre-existing span") preExistingRegion := StartRegion(bgctx, "pre-existing region")
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if err := Start(buf); err != nil { if err := Start(buf); err != nil {
...@@ -25,32 +25,32 @@ func TestUserTaskSpan(t *testing.T) { ...@@ -25,32 +25,32 @@ func TestUserTaskSpan(t *testing.T) {
// Beginning of traced execution // Beginning of traced execution
var wg sync.WaitGroup var wg sync.WaitGroup
ctx, end := NewContext(bgctx, "task0") // EvUserTaskCreate("task0") ctx, task := NewTask(bgctx, "task0") // EvUserTaskCreate("task0")
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
defer end() // EvUserTaskEnd("task0") defer task.End() // EvUserTaskEnd("task0")
WithSpan(ctx, "span0", func(ctx context.Context) { WithRegion(ctx, "region0", func() {
// EvUserSpanCreate("span0", start) // EvUserRegionCreate("region0", start)
WithSpan(ctx, "span1", func(ctx context.Context) { WithRegion(ctx, "region1", func() {
Log(ctx, "key0", "0123456789abcdef") // EvUserLog("task0", "key0", "0....f") Log(ctx, "key0", "0123456789abcdef") // EvUserLog("task0", "key0", "0....f")
}) })
// EvUserSpan("span0", end) // EvUserRegion("region0", end)
}) })
}() }()
wg.Wait() wg.Wait()
preExistingSpanEnd() preExistingRegion.End()
postExistingSpanEnd := StartSpan(bgctx, "post-existing span") postExistingRegion := StartRegion(bgctx, "post-existing region")
// End of traced execution // End of traced execution
Stop() Stop()
postExistingSpanEnd() postExistingRegion.End()
saveTrace(t, buf, "TestUserTaskSpan") saveTrace(t, buf, "TestUserTaskRegion")
res, err := trace.Parse(buf, "") res, err := trace.Parse(buf, "")
if err == trace.ErrTimeOrder { if err == trace.ErrTimeOrder {
// golang.org/issues/16755 // golang.org/issues/16755
...@@ -90,25 +90,25 @@ func TestUserTaskSpan(t *testing.T) { ...@@ -90,25 +90,25 @@ func TestUserTaskSpan(t *testing.T) {
if e.Link != nil && e.Link.Type != trace.EvUserTaskCreate { if e.Link != nil && e.Link.Type != trace.EvUserTaskCreate {
t.Errorf("Unexpected linked event %q->%q", e, e.Link) t.Errorf("Unexpected linked event %q->%q", e, e.Link)
} }
case trace.EvUserSpan: case trace.EvUserRegion:
taskName := tasks[e.Args[0]] taskName := tasks[e.Args[0]]
spanName := e.SArgs[0] regionName := e.SArgs[0]
got = append(got, testData{trace.EvUserSpan, []string{taskName, spanName}, []uint64{e.Args[1]}, e.Link != nil}) got = append(got, testData{trace.EvUserRegion, []string{taskName, regionName}, []uint64{e.Args[1]}, e.Link != nil})
if e.Link != nil && (e.Link.Type != trace.EvUserSpan || e.Link.SArgs[0] != spanName) { if e.Link != nil && (e.Link.Type != trace.EvUserRegion || e.Link.SArgs[0] != regionName) {
t.Errorf("Unexpected linked event %q->%q", e, e.Link) t.Errorf("Unexpected linked event %q->%q", e, e.Link)
} }
} }
} }
want := []testData{ want := []testData{
{trace.EvUserTaskCreate, []string{"task0"}, nil, true}, {trace.EvUserTaskCreate, []string{"task0"}, nil, true},
{trace.EvUserSpan, []string{"task0", "span0"}, []uint64{0}, true}, {trace.EvUserRegion, []string{"task0", "region0"}, []uint64{0}, true},
{trace.EvUserSpan, []string{"task0", "span1"}, []uint64{0}, true}, {trace.EvUserRegion, []string{"task0", "region1"}, []uint64{0}, true},
{trace.EvUserLog, []string{"task0", "key0", "0123456789abcdef"}, nil, false}, {trace.EvUserLog, []string{"task0", "key0", "0123456789abcdef"}, nil, false},
{trace.EvUserSpan, []string{"task0", "span1"}, []uint64{1}, false}, {trace.EvUserRegion, []string{"task0", "region1"}, []uint64{1}, false},
{trace.EvUserSpan, []string{"task0", "span0"}, []uint64{1}, false}, {trace.EvUserRegion, []string{"task0", "region0"}, []uint64{1}, false},
{trace.EvUserTaskEnd, []string{"task0"}, nil, false}, {trace.EvUserTaskEnd, []string{"task0"}, nil, false},
{trace.EvUserSpan, []string{"", "pre-existing span"}, []uint64{1}, false}, {trace.EvUserRegion, []string{"", "pre-existing region"}, []uint64{1}, false},
{trace.EvUserSpan, []string{"", "post-existing span"}, []uint64{0}, false}, {trace.EvUserRegion, []string{"", "post-existing region"}, []uint64{0}, false},
} }
if !reflect.DeepEqual(got, want) { if !reflect.DeepEqual(got, want) {
pretty := func(data []testData) string { pretty := func(data []testData) string {
...@@ -118,6 +118,6 @@ func TestUserTaskSpan(t *testing.T) { ...@@ -118,6 +118,6 @@ func TestUserTaskSpan(t *testing.T) {
} }
return s.String() return s.String()
} }
t.Errorf("Got user span related events\n%+v\nwant:\n%+v", pretty(got), pretty(want)) t.Errorf("Got user region related events\n%+v\nwant:\n%+v", pretty(got), pretty(want))
} }
} }
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
// Package trace provides user annotation APIs that can be used to // Package trace provides user annotation APIs that can be used to
// log interesting events during execution. // log interesting events during execution.
// //
// There are three types of user annotations: log messages, spans, // There are three types of user annotations: log messages, regions,
// and tasks. // and tasks.
// //
// Log emits a timestamped message to the execution trace along with // Log emits a timestamped message to the execution trace along with
...@@ -48,22 +48,22 @@ ...@@ -48,22 +48,22 @@
// and group goroutines using the log category and the message supplied // and group goroutines using the log category and the message supplied
// in Log. // in Log.
// //
// A span is for logging a time interval during a goroutine's execution. // A region is for logging a time interval during a goroutine's execution.
// By definition, a span starts and ends in the same goroutine. // By definition, a region starts and ends in the same goroutine.
// Spans can be nested to represent subintervals. // Regions can be nested to represent subintervals.
// For example, the following code records four spans in the execution // For example, the following code records four regions in the execution
// trace to trace the durations of sequential steps in a cappuccino making // trace to trace the durations of sequential steps in a cappuccino making
// operation. // operation.
// //
// trace.WithSpan(ctx, "makeCappuccino", func(ctx context.Context) { // trace.WithRegion(ctx, "makeCappuccino", func() {
// //
// // orderID allows to identify a specific order // // orderID allows to identify a specific order
// // among many cappuccino order span records. // // among many cappuccino order region records.
// trace.Log(ctx, "orderID", orderID) // trace.Log(ctx, "orderID", orderID)
// //
// trace.WithSpan(ctx, "steamMilk", steamMilk) // trace.WithRegion(ctx, "steamMilk", steamMilk)
// trace.WithSpan(ctx, "extractCoffee", extractCoffee) // trace.WithRegion(ctx, "extractCoffee", extractCoffee)
// trace.WithSpan(ctx, "mixMilkCoffee", mixMilkCoffee) // trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee)
// }) // })
// //
// A task is a higher-level component that aids tracing of logical // A task is a higher-level component that aids tracing of logical
...@@ -72,8 +72,8 @@ ...@@ -72,8 +72,8 @@
// working together. Since tasks can involve multiple goroutines, // working together. Since tasks can involve multiple goroutines,
// they are tracked via a context.Context object. NewContext creates // they are tracked via a context.Context object. NewContext creates
// a new task and embeds it in the returned context.Context object. // a new task and embeds it in the returned context.Context object.
// Log messages and spans are attached to the task, if any, in the // Log messages and regions are attached to the task, if any, in the
// Context passed to Log and WithSpan. // Context passed to Log and WithRegion.
// //
// For example, assume that we decided to froth milk, extract coffee, // For example, assume that we decided to froth milk, extract coffee,
// and mix milk and coffee in separate goroutines. With a task, // and mix milk and coffee in separate goroutines. With a task,
...@@ -87,18 +87,18 @@ ...@@ -87,18 +87,18 @@
// espresso := make(chan bool) // espresso := make(chan bool)
// //
// go func() { // go func() {
// trace.WithSpan(ctx, "steamMilk", steamMilk) // trace.WithRegion(ctx, "steamMilk", steamMilk)
// milk<-true // milk<-true
// })() // })()
// go func() { // go func() {
// trace.WithSpan(ctx, "extractCoffee", extractCoffee) // trace.WithRegion(ctx, "extractCoffee", extractCoffee)
// espresso<-true // espresso<-true
// })() // })()
// go func() { // go func() {
// defer taskEnd() // When assemble is done, the order is complete. // defer taskEnd() // When assemble is done, the order is complete.
// <-espresso // <-espresso
// <-milk // <-milk
// trace.WithSpan(ctx, "mixMilkCoffee", mixMilkCoffee) // trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee)
// })() // })()
// //
// The trace tool computes the latency of a task by measuring the // The trace tool computes the latency of a task by measuring the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment