-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
tracer.go
141 lines (127 loc) · 3.13 KB
/
tracer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package tracer
import (
"path"
"strconv"
"strings"
"time"
)
// Trace holds information about a current execution flow.
type Trace struct {
stack []*Call
allocates int
}
// Start creates a call entry and marks its start time.
//
// func Do(ctx context.Context) {
// call := tracer.Fetch(ctx).Start("id", "labelX", "labelY")
// defer call.Stop()
// }
//
func (trace *Trace) Start(labels ...string) *Call {
if trace == nil {
return nil
}
var id string
if len(labels) > 0 {
id, labels = labels[0], labels[1:]
}
call := &Call{id: id, labels: labels, caller: Caller(3), start: time.Now()}
if len(trace.stack) == cap(trace.stack) {
trace.allocates
}
trace.stack = append(trace.stack, call)
return call
}
// String returns a string representation of the current execution flow.
func (trace *Trace) String() string {
if trace == nil {
return ""
}
builder := strings.Builder{}
builder.WriteString("allocates at call stack: ")
builder.WriteString(strconv.Itoa(trace.allocates))
builder.WriteString(", detailed call stack:")
if len(trace.stack) == 0 {
builder.WriteString(" ~")
}
for _, call := range trace.stack {
builder.WriteString("\n\tcall ")
builder.WriteString(path.Base(call.caller.Name))
if call.id != "" {
builder.WriteString(" [")
builder.WriteString(call.id)
builder.WriteRune(']')
}
builder.WriteString(": ")
builder.WriteString(call.stop.Sub(call.start).String())
builder.WriteString(", allocates: ")
builder.WriteString(strconv.Itoa(call.allocates))
prev := call.start
for _, checkpoint := range call.checkpoints {
builder.WriteString("\n\t\tcheckpoint")
if checkpoint.id != "" {
builder.WriteString(" [")
builder.WriteString(checkpoint.id)
builder.WriteRune(']')
}
builder.WriteString(": ")
builder.WriteString(checkpoint.timestamp.Sub(prev).String())
prev = checkpoint.timestamp
}
}
return builder.String()
}
// Call holds information about a current function call.
type Call struct {
id string
labels []string
caller CallerInfo
start, stop time.Time
checkpoints []Checkpoint
allocates int
}
// Checkpoint stores timestamp of a current execution position of the current call.
//
// func Do(ctx context.Context) {
// call := tracer.Fetch(ctx).Start()
// defer call.Stop()
// ...
// call.Checkpoint()
// ...
// call.Checkpoint("id", "labelX", "labelY")
// ...
// }
//
func (call *Call) Checkpoint(labels ...string) {
if call == nil {
return
}
var id string
if len(labels) > 0 {
id, labels = labels[0], labels[1:]
}
checkpoint := Checkpoint{id: id, labels: labels, timestamp: time.Now()}
if len(call.checkpoints) == cap(call.checkpoints) {
call.allocates
}
call.checkpoints = append(call.checkpoints, checkpoint)
}
// Stop marks the end time of the current call.
//
// func Do(ctx context.Context) {
// defer tracer.Fetch(ctx).Start().Stop()
// ...
// }
//
func (call *Call) Stop() {
if call == nil {
return
}
call.stop = time.Now()
}
// Checkpoint holds information about a current execution position of a current call.
type Checkpoint struct {
id string
labels []string
timestamp time.Time
}