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
|
package trace2
import (
"context"
"fmt"
"sort"
"strings"
"time"
)
// Trace denotes a node in the tree representation of Git Trace2 events. A node is not necessary
// a one-one mapping of an event.
type Trace struct {
// Thread is the name of the thread of the corresponding event. The default thread name is
// "main". A new thread is assigned with a new name.
Thread string
// Name denotes the name of the trace. The node name depends on the event types. Data-type
// trace name is the most significant. It can be used to access the accurate data trace node
// For example: data:index:refresh/sum_scan
Name string
// StartTime is the starting time of the trace
StartTime time.Time
// FinishTime is the starting time of the trace
FinishTime time.Time
// Metadata is a map of metadata and data extracted from the event. A data-type trace always
// stores its data under "data" key of this map
Metadata map[string]string
// ChildID is the unique ID assigned by the parent process when it spawns a sub-process
// The ID of root process is empty.
ChildID string
// Parent points to the parent node of the current trace. The root node's parent is nil
Parent *Trace
// Children stores the list of order-significant traces belong to the current trace
Children []*Trace
// Depth indicates the depth of the trace node
Depth int
}
// IsRoot returns true if the current trace is the root of the tree
func (trace *Trace) IsRoot() bool {
return trace.Parent == nil
}
// Walk performs in-order tree traversal. It stops at each node and trigger handler function with
// the current trace.
func (trace *Trace) Walk(ctx context.Context, handler func(context.Context, *Trace) context.Context) {
if trace == nil {
return
}
ctx = handler(ctx, trace)
for _, child := range trace.Children {
child.Walk(ctx, handler)
}
}
// Inspect returns the formatted string of the tree. It mimics the format for trace2's performance
// target: https://git-scm.com/docs/api-trace2#_perf_format. It's mostly used for testing and
// debugging purpose.
func (trace *Trace) Inspect(detailed bool) string {
var output strings.Builder
trace.Walk(context.Background(), func(ctx context.Context, t *Trace) context.Context {
if output.Len() != 0 {
output.WriteString("\n")
}
if detailed {
output.WriteString(fmt.Sprintf("%s | %s ",
t.StartTime.UTC().Format(time.RFC3339),
t.FinishTime.UTC().Format(time.RFC3339)))
}
output.WriteString(fmt.Sprintf("| %-1s | %s | %s%s",
t.ChildID,
t.Thread,
strings.Repeat(".", t.Depth),
t.Name))
if detailed && len(t.Metadata) > 0 {
output.WriteString(fmt.Sprintf(" %s", t.inspectMetadata()))
}
return ctx
})
return output.String()
}
func (trace *Trace) setName(hints []string) {
var parts []string
for _, s := range hints {
if strings.TrimSpace(s) != "" {
parts = append(parts, s)
}
}
trace.Name = strings.Join(parts, ":")
}
func (trace *Trace) setMetadata(key, value string) {
if trace.Metadata == nil {
trace.Metadata = map[string]string{}
}
trace.Metadata[key] = value
}
func (trace *Trace) inspectMetadata() string {
var metadata strings.Builder
if len(trace.Metadata) > 0 {
metadata.WriteString("(")
keys := make([]string, 0, len(trace.Metadata))
for key := range trace.Metadata {
keys = append(keys, key)
}
// Sort metadata by key to make output deterministic
sort.Strings(keys)
for index, key := range keys {
if index != 0 {
metadata.WriteString(" ")
}
metadata.WriteString(fmt.Sprintf("%s=%q", key, trace.Metadata[key]))
}
metadata.WriteString(")")
}
return metadata.String()
}
|