| package trace | |
| import ( | |
| "context" | |
| "fmt" | |
| "sync/atomic" | |
| _ "unsafe" | |
| ) | |
| type traceContextKey struct{} | |
| // NewContext creates a child context with a new task instance with | |
| // the type taskType. If the input context contains a task, the | |
| // new task is its subtask. | |
| // | |
| // The taskType is used to classify task instances. Analysis tools | |
| // like the Go execution tracer may assume there are only a bounded | |
| // number of unique task types in the system. | |
| // | |
| // The returned end function is used to mark the task's end. | |
| // The trace tool measures task latency as the time between task creation | |
| // and when the end function is called, and provides the latency | |
| // distribution per task type. | |
| // If the end function is called multiple times, only the first | |
| // call is used in the latency measurement. | |
| // | |
| // ctx, taskEnd := trace.NewContext(ctx, "awesome task") | |
| // trace.WithSpan(ctx, prepWork) | |
| // // preparation of the task | |
| // go func() { // continue processing the task in a separate goroutine. | |
| // defer taskEnd() | |
| // trace.WithSpan(ctx, remainingWork) | |
| // } | |
| func NewContext(pctx context.Context, taskType string) (ctx context.Context, end func()) { | |
| pid := fromContext(pctx).id | |
| id := newID() | |
| userTaskCreate(id, pid, taskType) | |
| s := &task{id: id} | |
| return context.WithValue(pctx, traceContextKey{}, s), func() { | |
| userTaskEnd(id) | |
| } | |
| // We allocate a new task and the end function even when | |
| // the tracing is disabled because the context and the detach | |
| // function can be used across trace enable/disable boundaries, | |
| // which complicates the problem. | |
| // | |
| // For example, consider the following scenario: | |
| // - trace is enabled. | |
| // - trace.WithSpan is called, so a new context ctx | |
| // with a new span is created. | |
| // - trace is disabled. | |
| // - trace is enabled again. | |
| // - trace APIs with the ctx is called. Is the ID in the task | |
| // a valid one to use? | |
| // | |
| // TODO(hyangah): reduce the overhead at least when | |
| // tracing is disabled. Maybe the id can embed a tracing | |
| // round number and ignore ids generated from previous | |
| // tracing round. | |
| } | |
| func fromContext(ctx context.Context) *task { | |
| if s, ok := ctx.Value(traceContextKey{}).(*task); ok { | |
| return s | |
| } | |
| return &bgTask | |
| } | |
| type task struct { | |
| id uint64 | |
| // TODO(hyangah): record parent id? | |
| } | |
| var lastTaskID uint64 = 0 // task id issued last time | |
| func newID() uint64 { | |
| // TODO(hyangah): use per-P cache | |
| return atomic.AddUint64(&lastTaskID, 1) | |
| } | |
| var bgTask = task{id: uint64(0)} | |
| // 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 | |
| // unique categories in the system. | |
| func Log(ctx context.Context, category, message string) { | |
| id := fromContext(ctx).id | |
| userLog(id, category, message) | |
| } | |
| // Logf is like Log, but the value is formatted using the specified format spec. | |
| func Logf(ctx context.Context, category, format string, args ...interface{}) { | |
| if IsEnabled() { | |
| // Ideally this should be just Log, but that will | |
| // add one more frame in the stack trace. | |
| id := fromContext(ctx).id | |
| userLog(id, category, fmt.Sprintf(format, args...)) | |
| } | |
| } | |
| const ( | |
| spanStartCode = uint64(0) | |
| spanEndCode = uint64(1) | |
| ) | |
| // WithSpan starts a span associated with its calling goroutine, runs fn, | |
| // and then ends the span. If the context carries a task, the span is | |
| // attached to the task. Otherwise, the span is attached to the background | |
| // task. | |
| // | |
| // The spanType is used to classify spans, so there should be only a | |
| // handful of unique span types. | |
| func WithSpan(ctx context.Context, spanType string, fn func(context.Context)) { | |
| // NOTE: | |
| // WithSpan helps avoiding misuse of the API but in practice, | |
| // this is very restrictive: | |
| // - Use of WithSpan makes the stack traces captured from | |
| // span start and end are identical. | |
| // - Refactoring the existing code to use WithSpan is sometimes | |
| // hard and makes the code less readable. | |
| // e.g. code block nested deep in the loop with various | |
| // exit point with return values | |
| // - Refactoring the code to use this API with closure can | |
| // cause different GC behavior such as retaining some parameters | |
| // longer. | |
| // This causes more churns in code than I hoped, and sometimes | |
| // makes the code less readable. | |
| id := fromContext(ctx).id | |
| userSpan(id, spanStartCode, spanType) | |
| defer userSpan(id, spanEndCode, spanType) | |
| fn(ctx) | |
| } | |
| // StartSpan starts a span and returns a function for marking the | |
| // end of the span. The span end function must be called from the | |
| // same goroutine where the span was started. | |
| // 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, | |
| // since it naturally satisfies these restrictions. | |
| func StartSpan(ctx context.Context, spanType string) func() { | |
| id := fromContext(ctx).id | |
| userSpan(id, spanStartCode, spanType) | |
| return func() { userSpan(id, spanEndCode, spanType) } | |
| } | |
| // IsEnabled returns whether tracing is enabled. | |
| // The information is advisory only. The tracing status | |
| // may have changed by the time this function returns. | |
| func IsEnabled() bool { | |
| enabled := atomic.LoadInt32(&tracing.enabled) | |
| return enabled == 1 | |
| } | |
| // | |
| // Function bodies are defined in runtime/trace.go | |
| // | |
| // emits UserTaskCreate event. | |
| func userTaskCreate(id, parentID uint64, taskType string) | |
| // emits UserTaskEnd event. | |
| func userTaskEnd(id uint64) | |
| // emits UserSpan event. | |
| func userSpan(id, mode uint64, spanType string) | |
| // emits UserLog event. | |
| func userLog(id uint64, category, message string) |