123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- // 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
- }
|