github.com/waldiirawan/apm-agent-go/v2@v2.2.2/apmtest/debug.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package apmtest // import "github.com/waldiirawan/apm-agent-go/v2/apmtest" 19 20 import ( 21 "bytes" 22 "fmt" 23 "io" 24 "math" 25 "sort" 26 "text/tabwriter" 27 "time" 28 "unicode/utf8" 29 30 "github.com/waldiirawan/apm-agent-go/v2/model" 31 ) 32 33 // WriteTraceTable displays the trace as a table which can be used on tests to aid 34 // debugging. 35 func WriteTraceTable(writer io.Writer, tx model.Transaction, spans []model.Span) { 36 w := tabwriter.NewWriter(writer, 2, 4, 2, ' ', tabwriter.TabIndent) 37 fmt.Fprintln(w, "#\tNAME\tTYPE\tCOMP\tN\tDURATION(ms)\tOFFSET\tSPAN ID\tPARENT ID\tTRACE ID") 38 39 fmt.Fprintf(w, "TX\t%s\t%s\t-\t-\t%f\t%d\t%x\t%x\t%x\n", tx.Name, 40 tx.Type, tx.Duration, 41 0, 42 tx.ID, tx.ParentID, tx.TraceID, 43 ) 44 45 sort.SliceStable(spans, func(i, j int) bool { 46 return time.Time(spans[i].Timestamp).Before(time.Time(spans[j].Timestamp)) 47 }) 48 for i, span := range spans { 49 count := 1 50 if span.Composite != nil { 51 count = span.Composite.Count 52 } 53 54 fmt.Fprintf(w, "%d\t%s\t%s\t%v\t%d\t%f\t+%d\t%x\t%x\t%x\n", i, span.Name, 55 span.Type, span.Composite != nil, count, span.Duration, 56 time.Time(span.Timestamp).Sub(time.Time(tx.Timestamp))/1e3, 57 span.ID, span.ParentID, span.TraceID, 58 ) 59 } 60 w.Flush() 61 } 62 63 // WriteTraceWaterfall the trace waterfall "console output" to the specified 64 // writer sorted by timestamp. 65 func WriteTraceWaterfall(w io.Writer, tx model.Transaction, spans []model.Span) { 66 maxDuration := time.Duration(tx.Duration * float64(time.Millisecond)) 67 if maxDuration == 0 { 68 for _, span := range spans { 69 maxDuration += time.Duration(span.Duration * float64(time.Millisecond)) 70 } 71 } 72 73 maxWidth := int64(72) 74 buf := new(bytes.Buffer) 75 if tx.Duration > 0.0 { 76 writeSpan(buf, int(maxWidth), 0, fmt.Sprintf("transaction (%x) - %s", tx.ID, maxDuration.String())) 77 } 78 79 sort.SliceStable(spans, func(i, j int) bool { 80 return time.Time(spans[i].Timestamp).Before(time.Time(spans[j].Timestamp)) 81 }) 82 83 for _, span := range spans { 84 pos := int(math.Round( 85 float64(time.Time(span.Timestamp).Sub(time.Time(tx.Timestamp))) / 86 float64(maxDuration) * float64(maxWidth), 87 )) 88 tDur := time.Duration(span.Duration * float64(time.Millisecond)) 89 dur := float64(tDur) / float64(maxDuration) 90 width := int(math.Round(dur * float64(maxWidth))) 91 if width == int(maxWidth) { 92 width = int(maxWidth) - 1 93 } 94 95 spancontent := fmt.Sprintf("%s %s - %s", 96 span.Type, span.Name, 97 time.Duration(span.Duration*float64(time.Millisecond)).String(), 98 ) 99 if span.Composite != nil { 100 spancontent = fmt.Sprintf("%d %s - %s", 101 span.Composite.Count, span.Name, 102 time.Duration(span.Duration*float64(time.Millisecond)).String(), 103 ) 104 } 105 writeSpan(buf, width, pos, spancontent) 106 } 107 108 io.Copy(w, buf) 109 } 110 111 func writeSpan(buf *bytes.Buffer, width, pos int, content string) { 112 spaceRune := ' ' 113 fillRune := '_' 114 startRune := '|' 115 endRune := '|' 116 117 // Prevent the spans from going out of bounds. 118 if pos == width { 119 pos = pos - 2 120 } else if pos >= width { 121 pos = pos - 1 122 } 123 124 for i := 0; i < int(pos); i++ { 125 buf.WriteRune(spaceRune) 126 } 127 128 if width <= 1 { 129 width = 1 130 // Write the first letter of the span type when the width is too small. 131 startRune, _ = utf8.DecodeRuneInString(content) 132 } 133 134 var written int 135 written, _ = buf.WriteRune(startRune) 136 if len(content) >= int(width)-1 { 137 content = content[:int(width)-1] 138 } 139 140 spacing := (width - len(content) - 2) / 2 141 for i := 0; i < spacing; i++ { 142 n, _ := buf.WriteRune(fillRune) 143 written += n 144 } 145 146 n, _ := buf.WriteString(content) 147 written += n 148 for i := 0; i < spacing; i++ { 149 n, _ := buf.WriteRune(fillRune) 150 written += n 151 } 152 153 if written < width { 154 buf.WriteRune(fillRune) 155 } 156 if width > 1 { 157 buf.WriteRune(endRune) 158 } 159 160 buf.WriteString("\n") 161 }