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  }