123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /*
- Copyright 2015 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package trace
- import (
- "bytes"
- "context"
- "fmt"
- "math/rand"
- "time"
- "k8s.io/klog/v2"
- )
- var klogV = func(lvl klog.Level) bool {
- return klog.V(lvl).Enabled()
- }
- // Field is a key value pair that provides additional details about the trace.
- type Field struct {
- Key string
- Value interface{}
- }
- func (f Field) format() string {
- return fmt.Sprintf("%s:%v", f.Key, f.Value)
- }
- func writeFields(b *bytes.Buffer, l []Field) {
- for i, f := range l {
- b.WriteString(f.format())
- if i < len(l)-1 {
- b.WriteString(",")
- }
- }
- }
- func writeTraceItemSummary(b *bytes.Buffer, msg string, totalTime time.Duration, startTime time.Time, fields []Field) {
- b.WriteString(fmt.Sprintf("%q ", msg))
- if len(fields) > 0 {
- writeFields(b, fields)
- b.WriteString(" ")
- }
- b.WriteString(fmt.Sprintf("%vms (%v)", durationToMilliseconds(totalTime), startTime.Format("15:04:00.000")))
- }
- func durationToMilliseconds(timeDuration time.Duration) int64 {
- return timeDuration.Nanoseconds() / 1e6
- }
- type traceItem interface {
- // time returns when the trace was recorded as completed.
- time() time.Time
- // writeItem outputs the traceItem to the buffer. If stepThreshold is non-nil, only output the
- // traceItem if its the duration exceeds the stepThreshold.
- // Each line of output is prefixed by formatter to visually indent nested items.
- writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration)
- }
- type traceStep struct {
- stepTime time.Time
- msg string
- fields []Field
- }
- func (s traceStep) time() time.Time {
- return s.stepTime
- }
- func (s traceStep) writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration) {
- stepDuration := s.stepTime.Sub(startTime)
- if stepThreshold == nil || *stepThreshold == 0 || stepDuration >= *stepThreshold || klogV(4) {
- b.WriteString(fmt.Sprintf("%s---", formatter))
- writeTraceItemSummary(b, s.msg, stepDuration, s.stepTime, s.fields)
- }
- }
- // Trace keeps track of a set of "steps" and allows us to log a specific
- // step if it took longer than its share of the total allowed time
- type Trace struct {
- name string
- fields []Field
- threshold *time.Duration
- startTime time.Time
- endTime *time.Time
- traceItems []traceItem
- parentTrace *Trace
- }
- func (t *Trace) time() time.Time {
- if t.endTime != nil {
- return *t.endTime
- }
- return t.startTime // if the trace is incomplete, don't assume an end time
- }
- func (t *Trace) writeItem(b *bytes.Buffer, formatter string, startTime time.Time, stepThreshold *time.Duration) {
- if t.durationIsWithinThreshold() || klogV(4) {
- b.WriteString(fmt.Sprintf("%v[", formatter))
- writeTraceItemSummary(b, t.name, t.TotalTime(), t.startTime, t.fields)
- if st := t.calculateStepThreshold(); st != nil {
- stepThreshold = st
- }
- t.writeTraceSteps(b, formatter+" ", stepThreshold)
- b.WriteString("]")
- return
- }
- // If the trace should not be written, still check for nested traces that should be written
- for _, s := range t.traceItems {
- if nestedTrace, ok := s.(*Trace); ok {
- nestedTrace.writeItem(b, formatter, startTime, stepThreshold)
- }
- }
- }
- // New creates a Trace with the specified name. The name identifies the operation to be traced. The
- // Fields add key value pairs to provide additional details about the trace, such as operation inputs.
- func New(name string, fields ...Field) *Trace {
- return &Trace{name: name, startTime: time.Now(), fields: fields}
- }
- // Step adds a new step with a specific message. Call this at the end of an execution step to record
- // how long it took. The Fields add key value pairs to provide additional details about the trace
- // step.
- func (t *Trace) Step(msg string, fields ...Field) {
- if t.traceItems == nil {
- // traces almost always have less than 6 steps, do this to avoid more than a single allocation
- t.traceItems = make([]traceItem, 0, 6)
- }
- t.traceItems = append(t.traceItems, traceStep{stepTime: time.Now(), msg: msg, fields: fields})
- }
- // Nest adds a nested trace with the given message and fields and returns it.
- // As a convenience, if the receiver is nil, returns a top level trace. This allows
- // one to call FromContext(ctx).Nest without having to check if the trace
- // in the context is nil.
- func (t *Trace) Nest(msg string, fields ...Field) *Trace {
- newTrace := New(msg, fields...)
- if t != nil {
- newTrace.parentTrace = t
- t.traceItems = append(t.traceItems, newTrace)
- }
- return newTrace
- }
- // Log is used to dump all the steps in the Trace. It also logs the nested trace messages using indentation.
- // If the Trace is nested it is not immediately logged. Instead, it is logged when the trace it is nested within
- // is logged.
- func (t *Trace) Log() {
- endTime := time.Now()
- t.endTime = &endTime
- // an explicit logging request should dump all the steps out at the higher level
- if t.parentTrace == nil { // We don't start logging until Log or LogIfLong is called on the root trace
- t.logTrace()
- }
- }
- // LogIfLong only logs the trace if the duration of the trace exceeds the threshold.
- // Only steps that took longer than their share or the given threshold are logged.
- // If klog is at verbosity level 4 or higher and the trace took longer than the threshold,
- // all substeps and subtraces are logged. Otherwise, only those which took longer than
- // their own threshold.
- // If the Trace is nested it is not immediately logged. Instead, it is logged when the trace it
- // is nested within is logged.
- func (t *Trace) LogIfLong(threshold time.Duration) {
- t.threshold = &threshold
- t.Log()
- }
- // logTopLevelTraces finds all traces in a hierarchy of nested traces that should be logged but do not have any
- // parents that will be logged, due to threshold limits, and logs them as top level traces.
- func (t *Trace) logTrace() {
- if t.durationIsWithinThreshold() {
- var buffer bytes.Buffer
- traceNum := rand.Int31()
- totalTime := t.endTime.Sub(t.startTime)
- buffer.WriteString(fmt.Sprintf("Trace[%d]: %q ", traceNum, t.name))
- if len(t.fields) > 0 {
- writeFields(&buffer, t.fields)
- buffer.WriteString(" ")
- }
- // if any step took more than it's share of the total allowed time, it deserves a higher log level
- buffer.WriteString(fmt.Sprintf("(%v) (total time: %vms):", t.startTime.Format("02-Jan-2006 15:04:05.000"), totalTime.Milliseconds()))
- stepThreshold := t.calculateStepThreshold()
- t.writeTraceSteps(&buffer, fmt.Sprintf("\nTrace[%d]: ", traceNum), stepThreshold)
- buffer.WriteString(fmt.Sprintf("\nTrace[%d]: [%v] [%v] END\n", traceNum, t.endTime.Sub(t.startTime), totalTime))
- klog.Info(buffer.String())
- return
- }
- // If the trace should not be logged, still check if nested traces should be logged
- for _, s := range t.traceItems {
- if nestedTrace, ok := s.(*Trace); ok {
- nestedTrace.logTrace()
- }
- }
- }
- func (t *Trace) writeTraceSteps(b *bytes.Buffer, formatter string, stepThreshold *time.Duration) {
- lastStepTime := t.startTime
- for _, stepOrTrace := range t.traceItems {
- stepOrTrace.writeItem(b, formatter, lastStepTime, stepThreshold)
- lastStepTime = stepOrTrace.time()
- }
- }
- func (t *Trace) durationIsWithinThreshold() bool {
- if t.endTime == nil { // we don't assume incomplete traces meet the threshold
- return false
- }
- return t.threshold == nil || *t.threshold == 0 || t.endTime.Sub(t.startTime) >= *t.threshold
- }
- // TotalTime can be used to figure out how long it took since the Trace was created
- func (t *Trace) TotalTime() time.Duration {
- return time.Since(t.startTime)
- }
- // calculateStepThreshold returns a threshold for the individual steps of a trace, or nil if there is no threshold and
- // all steps should be written.
- func (t *Trace) calculateStepThreshold() *time.Duration {
- if t.threshold == nil {
- return nil
- }
- lenTrace := len(t.traceItems) + 1
- traceThreshold := *t.threshold
- for _, s := range t.traceItems {
- nestedTrace, ok := s.(*Trace)
- if ok && nestedTrace.threshold != nil {
- traceThreshold = traceThreshold - *nestedTrace.threshold
- lenTrace--
- }
- }
- // the limit threshold is used when the threshold(
- //remaining after subtracting that of the child trace) is getting very close to zero to prevent unnecessary logging
- limitThreshold := *t.threshold / 4
- if traceThreshold < limitThreshold {
- traceThreshold = limitThreshold
- lenTrace = len(t.traceItems) + 1
- }
- stepThreshold := traceThreshold / time.Duration(lenTrace)
- return &stepThreshold
- }
- // ContextTraceKey provides a common key for traces in context.Context values.
- type ContextTraceKey struct{}
- // FromContext returns the trace keyed by ContextTraceKey in the context values, if one
- // is present, or nil If there is no trace in the Context.
- // It is safe to call Nest() on the returned value even if it is nil because ((*Trace)nil).Nest returns a top level
- // trace.
- func FromContext(ctx context.Context) *Trace {
- if v, ok := ctx.Value(ContextTraceKey{}).(*Trace); ok {
- return v
- }
- return nil
- }
- // ContextWithTrace returns a context with trace included in the context values, keyed by ContextTraceKey.
- func ContextWithTrace(ctx context.Context, trace *Trace) context.Context {
- return context.WithValue(ctx, ContextTraceKey{}, trace)
- }
|