mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2024-12-30 01:31:18 +00:00
443 lines
12 KiB
Go
443 lines
12 KiB
Go
|
// 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
|
||
|
}
|