apmtest/debug.go (117 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 apmtest // import "go.elastic.co/apm/v2/apmtest"
import (
"bytes"
"fmt"
"io"
"math"
"sort"
"text/tabwriter"
"time"
"unicode/utf8"
"go.elastic.co/apm/v2/model"
)
// WriteTraceTable displays the trace as a table which can be used on tests to aid
// debugging.
func WriteTraceTable(writer io.Writer, tx model.Transaction, spans []model.Span) {
w := tabwriter.NewWriter(writer, 2, 4, 2, ' ', tabwriter.TabIndent)
fmt.Fprintln(w, "#\tNAME\tTYPE\tCOMP\tN\tDURATION(ms)\tOFFSET\tSPAN ID\tPARENT ID\tTRACE ID")
fmt.Fprintf(w, "TX\t%s\t%s\t-\t-\t%f\t%d\t%x\t%x\t%x\n", tx.Name,
tx.Type, tx.Duration,
0,
tx.ID, tx.ParentID, tx.TraceID,
)
sort.SliceStable(spans, func(i, j int) bool {
return time.Time(spans[i].Timestamp).Before(time.Time(spans[j].Timestamp))
})
for i, span := range spans {
count := 1
if span.Composite != nil {
count = span.Composite.Count
}
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,
span.Type, span.Composite != nil, count, span.Duration,
time.Time(span.Timestamp).Sub(time.Time(tx.Timestamp))/1e3,
span.ID, span.ParentID, span.TraceID,
)
}
w.Flush()
}
// WriteTraceWaterfall the trace waterfall "console output" to the specified
// writer sorted by timestamp.
func WriteTraceWaterfall(w io.Writer, tx model.Transaction, spans []model.Span) {
maxDuration := time.Duration(tx.Duration * float64(time.Millisecond))
if maxDuration == 0 {
for _, span := range spans {
maxDuration += time.Duration(span.Duration * float64(time.Millisecond))
}
}
maxWidth := int64(72)
buf := new(bytes.Buffer)
if tx.Duration > 0.0 {
writeSpan(buf, int(maxWidth), 0, fmt.Sprintf("transaction (%x) - %s", tx.ID, maxDuration.String()))
}
sort.SliceStable(spans, func(i, j int) bool {
return time.Time(spans[i].Timestamp).Before(time.Time(spans[j].Timestamp))
})
for _, span := range spans {
pos := int(math.Round(
float64(time.Time(span.Timestamp).Sub(time.Time(tx.Timestamp))) /
float64(maxDuration) * float64(maxWidth),
))
tDur := time.Duration(span.Duration * float64(time.Millisecond))
dur := float64(tDur) / float64(maxDuration)
width := int(math.Round(dur * float64(maxWidth)))
if width == int(maxWidth) {
width = int(maxWidth) - 1
}
spancontent := fmt.Sprintf("%s %s - %s",
span.Type, span.Name,
time.Duration(span.Duration*float64(time.Millisecond)).String(),
)
if span.Composite != nil {
spancontent = fmt.Sprintf("%d %s - %s",
span.Composite.Count, span.Name,
time.Duration(span.Duration*float64(time.Millisecond)).String(),
)
}
writeSpan(buf, width, pos, spancontent)
}
io.Copy(w, buf)
}
func writeSpan(buf *bytes.Buffer, width, pos int, content string) {
spaceRune := ' '
fillRune := '_'
startRune := '|'
endRune := '|'
// Prevent the spans from going out of bounds.
if pos == width {
pos = pos - 2
} else if pos >= width {
pos = pos - 1
}
for i := 0; i < int(pos); i++ {
buf.WriteRune(spaceRune)
}
if width <= 1 {
width = 1
// Write the first letter of the span type when the width is too small.
startRune, _ = utf8.DecodeRuneInString(content)
}
var written int
written, _ = buf.WriteRune(startRune)
if len(content) >= int(width)-1 {
content = content[:int(width)-1]
}
spacing := (width - len(content) - 2) / 2
for i := 0; i < spacing; i++ {
n, _ := buf.WriteRune(fillRune)
written += n
}
n, _ := buf.WriteString(content)
written += n
for i := 0; i < spacing; i++ {
n, _ := buf.WriteRune(fillRune)
written += n
}
if written < width {
buf.WriteRune(fillRune)
}
if width > 1 {
buf.WriteRune(endRune)
}
buf.WriteString("\n")
}