262 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			262 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright The OpenTelemetry Authors
 | |
| // Copyright 2017, OpenCensus Authors
 | |
| //
 | |
| // Licensed 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 zpages
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"go.opentelemetry.io/otel/attribute"
 | |
| 	sdktrace "go.opentelemetry.io/otel/sdk/trace"
 | |
| 	"go.opentelemetry.io/otel/trace"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	// spanNameQueryField is the header for span name.
 | |
| 	spanNameQueryField = "zspanname"
 | |
| 	// spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display.
 | |
| 	spanTypeQueryField = "ztype"
 | |
| 	// spanLatencyBucketQueryField is the header for latency based samples.
 | |
| 	// Default is [0, 8] representing the latency buckets, where 0 is the first one.
 | |
| 	spanLatencyBucketQueryField = "zlatencybucket"
 | |
| 	// maxTraceMessageLength is the maximum length of a message in tracez output.
 | |
| 	maxTraceMessageLength = 1024
 | |
| )
 | |
| 
 | |
| type summaryTableData struct {
 | |
| 	Header             []string
 | |
| 	LatencyBucketNames []string
 | |
| 	Links              bool
 | |
| 	TracesEndpoint     string
 | |
| 	Rows               []summaryTableRowData
 | |
| }
 | |
| 
 | |
| type summaryTableRowData struct {
 | |
| 	Name    string
 | |
| 	Active  int
 | |
| 	Latency []int
 | |
| 	Errors  int
 | |
| }
 | |
| 
 | |
| // traceTableData contains data for the trace data template.
 | |
| type traceTableData struct {
 | |
| 	Name string
 | |
| 	Num  int
 | |
| 	Rows []spanRow
 | |
| }
 | |
| 
 | |
| var _ http.Handler = (*tracezHandler)(nil)
 | |
| 
 | |
| type tracezHandler struct {
 | |
| 	sp *SpanProcessor
 | |
| }
 | |
| 
 | |
| // NewTracezHandler returns an http.Handler that can be used to serve HTTP requests for trace zpages.
 | |
| func NewTracezHandler(sp *SpanProcessor) http.Handler {
 | |
| 	return &tracezHandler{sp: sp}
 | |
| }
 | |
| 
 | |
| // ServeHTTP implements the http.Handler and is capable of serving "tracez" HTTP requests.
 | |
| func (th *tracezHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	w.Header().Set("Content-Type", "text/html; charset=utf-8")
 | |
| 	if err := r.ParseForm(); err != nil {
 | |
| 		w.WriteHeader(http.StatusBadRequest)
 | |
| 		return
 | |
| 	}
 | |
| 	spanName := r.Form.Get(spanNameQueryField)
 | |
| 	spanType, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField))
 | |
| 	spanSubtype, _ := strconv.Atoi(r.Form.Get(spanLatencyBucketQueryField))
 | |
| 
 | |
| 	if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil {
 | |
| 		log.Printf("zpages: executing template: %v", err)
 | |
| 	}
 | |
| 	if err := summaryTableTemplate.Execute(w, th.getSummaryTableData()); err != nil {
 | |
| 		log.Printf("zpages: executing template: %v", err)
 | |
| 	}
 | |
| 	if spanName != "" {
 | |
| 		if err := tracesTableTemplate.Execute(w, th.getTraceTableData(spanName, spanType, spanSubtype)); err != nil {
 | |
| 			log.Printf("zpages: executing template: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 	if err := footerTemplate.Execute(w, nil); err != nil {
 | |
| 		log.Printf("zpages: executing template: %v", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (th *tracezHandler) getTraceTableData(spanName string, spanType, latencyBucket int) traceTableData {
 | |
| 	var spans []sdktrace.ReadOnlySpan
 | |
| 	switch spanType {
 | |
| 	case 0: // active
 | |
| 		spans = th.sp.activeSpans(spanName)
 | |
| 	case 1: // latency
 | |
| 		spans = th.sp.spansByLatency(spanName, latencyBucket)
 | |
| 	case 2: // error
 | |
| 		spans = th.sp.errorSpans(spanName)
 | |
| 	}
 | |
| 
 | |
| 	data := traceTableData{
 | |
| 		Name: spanName,
 | |
| 		Num:  len(spans),
 | |
| 	}
 | |
| 	for _, s := range spans {
 | |
| 		data.Rows = append(data.Rows, spanRows(s)...)
 | |
| 	}
 | |
| 	return data
 | |
| }
 | |
| 
 | |
| func (th *tracezHandler) getSummaryTableData() summaryTableData {
 | |
| 	data := summaryTableData{
 | |
| 		Links:          true,
 | |
| 		TracesEndpoint: "tracez",
 | |
| 	}
 | |
| 	data.Header = []string{"Name", "active"}
 | |
| 	// An implicit 0 lower bound latency bucket is always present.
 | |
| 	latencyBuckets := append([]time.Duration{0}, defaultBoundaries.durations...)
 | |
| 	for _, l := range latencyBuckets {
 | |
| 		s := fmt.Sprintf(">%v", l)
 | |
| 		data.Header = append(data.Header, s)
 | |
| 		data.LatencyBucketNames = append(data.LatencyBucketNames, s)
 | |
| 	}
 | |
| 	data.Header = append(data.Header, "Errors")
 | |
| 	for name, s := range th.sp.spansPerMethod() {
 | |
| 		row := summaryTableRowData{Name: name, Active: s.activeSpans, Errors: s.errorSpans, Latency: s.latencySpans}
 | |
| 		data.Rows = append(data.Rows, row)
 | |
| 	}
 | |
| 	sort.Slice(data.Rows, func(i, j int) bool {
 | |
| 		return data.Rows[i].Name < data.Rows[j].Name
 | |
| 	})
 | |
| 	return data
 | |
| }
 | |
| 
 | |
| type spanRow struct {
 | |
| 	Fields [3]string
 | |
| 	trace.SpanContext
 | |
| 	ParentSpanContext trace.SpanContext
 | |
| }
 | |
| 
 | |
| type events []sdktrace.Event
 | |
| 
 | |
| func (e events) Len() int { return len(e) }
 | |
| func (e events) Less(i, j int) bool {
 | |
| 	return e[i].Time.Before(e[j].Time)
 | |
| }
 | |
| func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
 | |
| 
 | |
| type attributes []attribute.KeyValue
 | |
| 
 | |
| func (e attributes) Len() int { return len(e) }
 | |
| func (e attributes) Less(i, j int) bool {
 | |
| 	return string(e[i].Key) < string(e[j].Key)
 | |
| }
 | |
| func (e attributes) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
 | |
| 
 | |
| func spanRows(s sdktrace.ReadOnlySpan) []spanRow {
 | |
| 	start := s.StartTime()
 | |
| 
 | |
| 	lasty, lastm, lastd := start.Date()
 | |
| 	wholeTime := func(t time.Time) string {
 | |
| 		return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
 | |
| 	}
 | |
| 	formatTime := func(t time.Time) string {
 | |
| 		y, m, d := t.Date()
 | |
| 		if y == lasty && m == lastm && d == lastd {
 | |
| 			return t.Format("           15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
 | |
| 		}
 | |
| 		lasty, lastm, lastd = y, m, d
 | |
| 		return wholeTime(t)
 | |
| 	}
 | |
| 
 | |
| 	lastTime := start
 | |
| 	formatElapsed := func(t time.Time) string {
 | |
| 		d := t.Sub(lastTime)
 | |
| 		lastTime = t
 | |
| 		u := int64(d / 1000)
 | |
| 		// There are five cases for duration printing:
 | |
| 		// -1234567890s
 | |
| 		// -1234.123456
 | |
| 		//      .123456
 | |
| 		// 12345.123456
 | |
| 		// 12345678901s
 | |
| 		switch {
 | |
| 		case u < -9999999999:
 | |
| 			return fmt.Sprintf("%11ds", u/1e6)
 | |
| 		case u < 0:
 | |
| 			sec := u / 1e6
 | |
| 			u -= sec * 1e6
 | |
| 			return fmt.Sprintf("%5d.%06d", sec, -u)
 | |
| 		case u < 1e6:
 | |
| 			return fmt.Sprintf("     .%6d", u)
 | |
| 		case u <= 99999999999:
 | |
| 			sec := u / 1e6
 | |
| 			u -= sec * 1e6
 | |
| 			return fmt.Sprintf("%5d.%06d", sec, u)
 | |
| 		default:
 | |
| 			return fmt.Sprintf("%11ds", u/1e6)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	firstRow := spanRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext(), ParentSpanContext: s.Parent()}
 | |
| 	if s.EndTime().IsZero() {
 | |
| 		firstRow.Fields[1] = "            "
 | |
| 	} else {
 | |
| 		firstRow.Fields[1] = formatElapsed(s.EndTime())
 | |
| 		lastTime = start
 | |
| 	}
 | |
| 	out := []spanRow{firstRow}
 | |
| 
 | |
| 	formatAttributes := func(a attributes) string {
 | |
| 		sort.Sort(a)
 | |
| 		var s []string
 | |
| 		for i := range a {
 | |
| 			s = append(s, fmt.Sprintf("%s=%v", a[i].Key, a[i].Value.Emit()))
 | |
| 		}
 | |
| 		return "Attributes:{" + strings.Join(s, ", ") + "}"
 | |
| 	}
 | |
| 
 | |
| 	msg := fmt.Sprintf("Status{Code=%s, description=%q}", s.Status().Code.String(), s.Status().Description)
 | |
| 	out = append(out, spanRow{Fields: [3]string{"", "", msg}})
 | |
| 
 | |
| 	if len(s.Attributes()) != 0 {
 | |
| 		out = append(out, spanRow{Fields: [3]string{"", "", formatAttributes(s.Attributes())}})
 | |
| 	}
 | |
| 
 | |
| 	es := events(s.Events())
 | |
| 	sort.Sort(es)
 | |
| 	for _, e := range es {
 | |
| 		msg := e.Name
 | |
| 		if len(e.Attributes) != 0 {
 | |
| 			msg = msg + "  " + formatAttributes(e.Attributes)
 | |
| 		}
 | |
| 		row := spanRow{Fields: [3]string{
 | |
| 			formatTime(e.Time),
 | |
| 			formatElapsed(e.Time),
 | |
| 			msg,
 | |
| 		}}
 | |
| 		out = append(out, row)
 | |
| 	}
 | |
| 	for i := range out {
 | |
| 		if len(out[i].Fields[2]) > maxTraceMessageLength {
 | |
| 			out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength]
 | |
| 		}
 | |
| 	}
 | |
| 	return out
 | |
| }
 |