Add support for tracing via OpenCencus
This adds a few flags for configuring the tracer. Includes support for jaeger tracing (built into OC).
This commit is contained in:
442
vendor/go.opencensus.io/zpages/tracez.go
generated
vendored
Normal file
442
vendor/go.opencensus.io/zpages/tracez.go
generated
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
// 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"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/internal"
|
||||
"go.opencensus.io/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"
|
||||
// spanSubtypeQueryField is the header for sub-type:
|
||||
// * for latency based samples [0, 8] representing the latency buckets, where 0 is the first one;
|
||||
// * for error based samples, 0 means all, otherwise the error code;
|
||||
spanSubtypeQueryField = "zsubtype"
|
||||
// maxTraceMessageLength is the maximum length of a message in tracez output.
|
||||
maxTraceMessageLength = 1024
|
||||
)
|
||||
|
||||
var (
|
||||
defaultLatencies = [...]time.Duration{
|
||||
10 * time.Microsecond,
|
||||
100 * time.Microsecond,
|
||||
time.Millisecond,
|
||||
10 * time.Millisecond,
|
||||
100 * time.Millisecond,
|
||||
time.Second,
|
||||
10 * time.Second,
|
||||
100 * time.Second,
|
||||
}
|
||||
canonicalCodes = [...]string{
|
||||
"OK",
|
||||
"CANCELLED",
|
||||
"UNKNOWN",
|
||||
"INVALID_ARGUMENT",
|
||||
"DEADLINE_EXCEEDED",
|
||||
"NOT_FOUND",
|
||||
"ALREADY_EXISTS",
|
||||
"PERMISSION_DENIED",
|
||||
"RESOURCE_EXHAUSTED",
|
||||
"FAILED_PRECONDITION",
|
||||
"ABORTED",
|
||||
"OUT_OF_RANGE",
|
||||
"UNIMPLEMENTED",
|
||||
"INTERNAL",
|
||||
"UNAVAILABLE",
|
||||
"DATA_LOSS",
|
||||
"UNAUTHENTICATED",
|
||||
}
|
||||
)
|
||||
|
||||
func canonicalCodeString(code int32) string {
|
||||
if code < 0 || int(code) >= len(canonicalCodes) {
|
||||
return "error code " + strconv.FormatInt(int64(code), 10)
|
||||
}
|
||||
return canonicalCodes[code]
|
||||
}
|
||||
|
||||
func tracezHandler(w http.ResponseWriter, r *http.Request) {
|
||||
r.ParseForm()
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
name := r.Form.Get(spanNameQueryField)
|
||||
t, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField))
|
||||
st, _ := strconv.Atoi(r.Form.Get(spanSubtypeQueryField))
|
||||
WriteHTMLTracezPage(w, name, t, st)
|
||||
}
|
||||
|
||||
// WriteHTMLTracezPage writes an HTML document to w containing locally-sampled trace spans.
|
||||
func WriteHTMLTracezPage(w io.Writer, spanName string, spanType, spanSubtype int) {
|
||||
if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil {
|
||||
log.Printf("zpages: executing template: %v", err)
|
||||
}
|
||||
WriteHTMLTracezSummary(w)
|
||||
WriteHTMLTracezSpans(w, spanName, spanType, spanSubtype)
|
||||
if err := footerTemplate.Execute(w, nil); err != nil {
|
||||
log.Printf("zpages: executing template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHTMLTracezSummary writes HTML to w containing a summary of locally-sampled trace spans.
|
||||
//
|
||||
// It includes neither a header nor footer, so you can embed this data in other pages.
|
||||
func WriteHTMLTracezSummary(w io.Writer) {
|
||||
if err := summaryTableTemplate.Execute(w, getSummaryPageData()); err != nil {
|
||||
log.Printf("zpages: executing template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteHTMLTracezSpans writes HTML to w containing locally-sampled trace spans.
|
||||
//
|
||||
// It includes neither a header nor footer, so you can embed this data in other pages.
|
||||
func WriteHTMLTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
|
||||
if spanName == "" {
|
||||
return
|
||||
}
|
||||
if err := tracesTableTemplate.Execute(w, traceDataFromSpans(spanName, traceSpans(spanName, spanType, spanSubtype))); err != nil {
|
||||
log.Printf("zpages: executing template: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTextTracezSpans writes formatted text to w containing locally-sampled trace spans.
|
||||
func WriteTextTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
|
||||
spans := traceSpans(spanName, spanType, spanSubtype)
|
||||
data := traceDataFromSpans(spanName, spans)
|
||||
writeTextTraces(w, data)
|
||||
}
|
||||
|
||||
// WriteTextTracezSummary writes formatted text to w containing a summary of locally-sampled trace spans.
|
||||
func WriteTextTracezSummary(w io.Writer) {
|
||||
w.Write([]byte("Locally sampled spans summary\n\n"))
|
||||
|
||||
data := getSummaryPageData()
|
||||
if len(data.Rows) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tw := tabwriter.NewWriter(w, 8, 8, 1, ' ', 0)
|
||||
|
||||
for i, s := range data.Header {
|
||||
if i != 0 {
|
||||
tw.Write([]byte("\t"))
|
||||
}
|
||||
tw.Write([]byte(s))
|
||||
}
|
||||
tw.Write([]byte("\n"))
|
||||
|
||||
put := func(x int) {
|
||||
if x == 0 {
|
||||
tw.Write([]byte(".\t"))
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(tw, "%d\t", x)
|
||||
}
|
||||
for _, r := range data.Rows {
|
||||
tw.Write([]byte(r.Name))
|
||||
tw.Write([]byte("\t"))
|
||||
put(r.Active)
|
||||
for _, l := range r.Latency {
|
||||
put(l)
|
||||
}
|
||||
put(r.Errors)
|
||||
tw.Write([]byte("\n"))
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
// traceData contains data for the trace data template.
|
||||
type traceData struct {
|
||||
Name string
|
||||
Num int
|
||||
Rows []traceRow
|
||||
}
|
||||
|
||||
type traceRow struct {
|
||||
Fields [3]string
|
||||
trace.SpanContext
|
||||
ParentSpanID trace.SpanID
|
||||
}
|
||||
|
||||
type events []interface{}
|
||||
|
||||
func (e events) Len() int { return len(e) }
|
||||
func (e events) Less(i, j int) bool {
|
||||
var ti time.Time
|
||||
switch x := e[i].(type) {
|
||||
case *trace.Annotation:
|
||||
ti = x.Time
|
||||
case *trace.MessageEvent:
|
||||
ti = x.Time
|
||||
}
|
||||
switch x := e[j].(type) {
|
||||
case *trace.Annotation:
|
||||
return ti.Before(x.Time)
|
||||
case *trace.MessageEvent:
|
||||
return ti.Before(x.Time)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
|
||||
func traceRows(s *trace.SpanData) []traceRow {
|
||||
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 := traceRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext, ParentSpanID: s.ParentSpanID}
|
||||
if s.EndTime.IsZero() {
|
||||
firstRow.Fields[1] = " "
|
||||
} else {
|
||||
firstRow.Fields[1] = formatElapsed(s.EndTime)
|
||||
lastTime = start
|
||||
}
|
||||
out := []traceRow{firstRow}
|
||||
|
||||
formatAttributes := func(a map[string]interface{}) string {
|
||||
if len(a) == 0 {
|
||||
return ""
|
||||
}
|
||||
var keys []string
|
||||
for key := range a {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
var s []string
|
||||
for _, key := range keys {
|
||||
val := a[key]
|
||||
switch val.(type) {
|
||||
case string:
|
||||
s = append(s, fmt.Sprintf("%s=%q", key, val))
|
||||
default:
|
||||
s = append(s, fmt.Sprintf("%s=%v", key, val))
|
||||
}
|
||||
}
|
||||
return "Attributes:{" + strings.Join(s, ", ") + "}"
|
||||
}
|
||||
|
||||
if s.Status != (trace.Status{}) {
|
||||
msg := fmt.Sprintf("Status{canonicalCode=%s, description=%q}",
|
||||
canonicalCodeString(s.Status.Code), s.Status.Message)
|
||||
out = append(out, traceRow{Fields: [3]string{"", "", msg}})
|
||||
}
|
||||
|
||||
if len(s.Attributes) != 0 {
|
||||
out = append(out, traceRow{Fields: [3]string{"", "", formatAttributes(s.Attributes)}})
|
||||
}
|
||||
|
||||
var es events
|
||||
for i := range s.Annotations {
|
||||
es = append(es, &s.Annotations[i])
|
||||
}
|
||||
for i := range s.MessageEvents {
|
||||
es = append(es, &s.MessageEvents[i])
|
||||
}
|
||||
sort.Sort(es)
|
||||
for _, e := range es {
|
||||
switch e := e.(type) {
|
||||
case *trace.Annotation:
|
||||
msg := e.Message
|
||||
if len(e.Attributes) != 0 {
|
||||
msg = msg + " " + formatAttributes(e.Attributes)
|
||||
}
|
||||
row := traceRow{Fields: [3]string{
|
||||
formatTime(e.Time),
|
||||
formatElapsed(e.Time),
|
||||
msg,
|
||||
}}
|
||||
out = append(out, row)
|
||||
case *trace.MessageEvent:
|
||||
row := traceRow{Fields: [3]string{formatTime(e.Time), formatElapsed(e.Time)}}
|
||||
switch e.EventType {
|
||||
case trace.MessageEventTypeSent:
|
||||
row.Fields[2] = fmt.Sprintf("sent message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
|
||||
case trace.MessageEventTypeRecv:
|
||||
row.Fields[2] = fmt.Sprintf("received message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func traceSpans(spanName string, spanType, spanSubtype int) []*trace.SpanData {
|
||||
internalTrace := internal.Trace.(interface {
|
||||
ReportActiveSpans(name string) []*trace.SpanData
|
||||
ReportSpansByError(name string, code int32) []*trace.SpanData
|
||||
ReportSpansByLatency(name string, minLatency, maxLatency time.Duration) []*trace.SpanData
|
||||
})
|
||||
var spans []*trace.SpanData
|
||||
switch spanType {
|
||||
case 0: // active
|
||||
spans = internalTrace.ReportActiveSpans(spanName)
|
||||
case 1: // latency
|
||||
var min, max time.Duration
|
||||
n := len(defaultLatencies)
|
||||
if spanSubtype == 0 {
|
||||
max = defaultLatencies[0]
|
||||
} else if spanSubtype == n {
|
||||
min, max = defaultLatencies[spanSubtype-1], (1<<63)-1
|
||||
} else if 0 < spanSubtype && spanSubtype < n {
|
||||
min, max = defaultLatencies[spanSubtype-1], defaultLatencies[spanSubtype]
|
||||
}
|
||||
spans = internalTrace.ReportSpansByLatency(spanName, min, max)
|
||||
case 2: // error
|
||||
spans = internalTrace.ReportSpansByError(spanName, 0)
|
||||
}
|
||||
return spans
|
||||
}
|
||||
|
||||
func traceDataFromSpans(name string, spans []*trace.SpanData) traceData {
|
||||
data := traceData{
|
||||
Name: name,
|
||||
Num: len(spans),
|
||||
}
|
||||
for _, s := range spans {
|
||||
data.Rows = append(data.Rows, traceRows(s)...)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func writeTextTraces(w io.Writer, data traceData) {
|
||||
tw := tabwriter.NewWriter(w, 1, 8, 1, ' ', 0)
|
||||
fmt.Fprint(tw, "When\tElapsed(s)\tType\n")
|
||||
for _, r := range data.Rows {
|
||||
tw.Write([]byte(r.Fields[0]))
|
||||
tw.Write([]byte("\t"))
|
||||
tw.Write([]byte(r.Fields[1]))
|
||||
tw.Write([]byte("\t"))
|
||||
tw.Write([]byte(r.Fields[2]))
|
||||
if sc := r.SpanContext; sc != (trace.SpanContext{}) {
|
||||
fmt.Fprintf(tw, "trace_id: %s span_id: %s", sc.TraceID, sc.SpanID)
|
||||
if r.ParentSpanID != (trace.SpanID{}) {
|
||||
fmt.Fprintf(tw, " parent_span_id: %s", r.ParentSpanID)
|
||||
}
|
||||
}
|
||||
tw.Write([]byte("\n"))
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
|
||||
type summaryPageData struct {
|
||||
Header []string
|
||||
LatencyBucketNames []string
|
||||
Links bool
|
||||
TracesEndpoint string
|
||||
Rows []summaryPageRow
|
||||
}
|
||||
|
||||
type summaryPageRow struct {
|
||||
Name string
|
||||
Active int
|
||||
Latency []int
|
||||
Errors int
|
||||
}
|
||||
|
||||
func getSummaryPageData() summaryPageData {
|
||||
data := summaryPageData{
|
||||
Links: true,
|
||||
TracesEndpoint: "tracez",
|
||||
}
|
||||
internalTrace := internal.Trace.(interface {
|
||||
ReportSpansPerMethod() map[string]internal.PerMethodSummary
|
||||
})
|
||||
for name, s := range internalTrace.ReportSpansPerMethod() {
|
||||
if len(data.Header) == 0 {
|
||||
data.Header = []string{"Name", "Active"}
|
||||
for _, b := range s.LatencyBuckets {
|
||||
l := b.MinLatency
|
||||
s := fmt.Sprintf(">%v", l)
|
||||
if l == 100*time.Second {
|
||||
s = ">100s"
|
||||
}
|
||||
data.Header = append(data.Header, s)
|
||||
data.LatencyBucketNames = append(data.LatencyBucketNames, s)
|
||||
}
|
||||
data.Header = append(data.Header, "Errors")
|
||||
}
|
||||
row := summaryPageRow{Name: name, Active: s.Active}
|
||||
for _, l := range s.LatencyBuckets {
|
||||
row.Latency = append(row.Latency, l.Size)
|
||||
}
|
||||
for _, e := range s.ErrorBuckets {
|
||||
row.Errors += e.Size
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user