tracez.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright 2017, OpenCensus Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. //
  15. package zpages
  16. import (
  17. "fmt"
  18. "io"
  19. "log"
  20. "net/http"
  21. "sort"
  22. "strconv"
  23. "strings"
  24. "text/tabwriter"
  25. "time"
  26. "go.opencensus.io/internal"
  27. "go.opencensus.io/trace"
  28. )
  29. const (
  30. // spanNameQueryField is the header for span name.
  31. spanNameQueryField = "zspanname"
  32. // spanTypeQueryField is the header for type (running = 0, latency = 1, error = 2) to display.
  33. spanTypeQueryField = "ztype"
  34. // spanSubtypeQueryField is the header for sub-type:
  35. // * for latency based samples [0, 8] representing the latency buckets, where 0 is the first one;
  36. // * for error based samples, 0 means all, otherwise the error code;
  37. spanSubtypeQueryField = "zsubtype"
  38. // maxTraceMessageLength is the maximum length of a message in tracez output.
  39. maxTraceMessageLength = 1024
  40. )
  41. var (
  42. defaultLatencies = [...]time.Duration{
  43. 10 * time.Microsecond,
  44. 100 * time.Microsecond,
  45. time.Millisecond,
  46. 10 * time.Millisecond,
  47. 100 * time.Millisecond,
  48. time.Second,
  49. 10 * time.Second,
  50. 100 * time.Second,
  51. }
  52. canonicalCodes = [...]string{
  53. "OK",
  54. "CANCELLED",
  55. "UNKNOWN",
  56. "INVALID_ARGUMENT",
  57. "DEADLINE_EXCEEDED",
  58. "NOT_FOUND",
  59. "ALREADY_EXISTS",
  60. "PERMISSION_DENIED",
  61. "RESOURCE_EXHAUSTED",
  62. "FAILED_PRECONDITION",
  63. "ABORTED",
  64. "OUT_OF_RANGE",
  65. "UNIMPLEMENTED",
  66. "INTERNAL",
  67. "UNAVAILABLE",
  68. "DATA_LOSS",
  69. "UNAUTHENTICATED",
  70. }
  71. )
  72. func canonicalCodeString(code int32) string {
  73. if code < 0 || int(code) >= len(canonicalCodes) {
  74. return "error code " + strconv.FormatInt(int64(code), 10)
  75. }
  76. return canonicalCodes[code]
  77. }
  78. func tracezHandler(w http.ResponseWriter, r *http.Request) {
  79. r.ParseForm()
  80. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  81. name := r.Form.Get(spanNameQueryField)
  82. t, _ := strconv.Atoi(r.Form.Get(spanTypeQueryField))
  83. st, _ := strconv.Atoi(r.Form.Get(spanSubtypeQueryField))
  84. WriteHTMLTracezPage(w, name, t, st)
  85. }
  86. // WriteHTMLTracezPage writes an HTML document to w containing locally-sampled trace spans.
  87. func WriteHTMLTracezPage(w io.Writer, spanName string, spanType, spanSubtype int) {
  88. if err := headerTemplate.Execute(w, headerData{Title: "Trace Spans"}); err != nil {
  89. log.Printf("zpages: executing template: %v", err)
  90. }
  91. WriteHTMLTracezSummary(w)
  92. WriteHTMLTracezSpans(w, spanName, spanType, spanSubtype)
  93. if err := footerTemplate.Execute(w, nil); err != nil {
  94. log.Printf("zpages: executing template: %v", err)
  95. }
  96. }
  97. // WriteHTMLTracezSummary writes HTML to w containing a summary of locally-sampled trace spans.
  98. //
  99. // It includes neither a header nor footer, so you can embed this data in other pages.
  100. func WriteHTMLTracezSummary(w io.Writer) {
  101. if err := summaryTableTemplate.Execute(w, getSummaryPageData()); err != nil {
  102. log.Printf("zpages: executing template: %v", err)
  103. }
  104. }
  105. // WriteHTMLTracezSpans writes HTML to w containing locally-sampled trace spans.
  106. //
  107. // It includes neither a header nor footer, so you can embed this data in other pages.
  108. func WriteHTMLTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
  109. if spanName == "" {
  110. return
  111. }
  112. if err := tracesTableTemplate.Execute(w, traceDataFromSpans(spanName, traceSpans(spanName, spanType, spanSubtype))); err != nil {
  113. log.Printf("zpages: executing template: %v", err)
  114. }
  115. }
  116. // WriteTextTracezSpans writes formatted text to w containing locally-sampled trace spans.
  117. func WriteTextTracezSpans(w io.Writer, spanName string, spanType, spanSubtype int) {
  118. spans := traceSpans(spanName, spanType, spanSubtype)
  119. data := traceDataFromSpans(spanName, spans)
  120. writeTextTraces(w, data)
  121. }
  122. // WriteTextTracezSummary writes formatted text to w containing a summary of locally-sampled trace spans.
  123. func WriteTextTracezSummary(w io.Writer) {
  124. w.Write([]byte("Locally sampled spans summary\n\n"))
  125. data := getSummaryPageData()
  126. if len(data.Rows) == 0 {
  127. return
  128. }
  129. tw := tabwriter.NewWriter(w, 8, 8, 1, ' ', 0)
  130. for i, s := range data.Header {
  131. if i != 0 {
  132. tw.Write([]byte("\t"))
  133. }
  134. tw.Write([]byte(s))
  135. }
  136. tw.Write([]byte("\n"))
  137. put := func(x int) {
  138. if x == 0 {
  139. tw.Write([]byte(".\t"))
  140. return
  141. }
  142. fmt.Fprintf(tw, "%d\t", x)
  143. }
  144. for _, r := range data.Rows {
  145. tw.Write([]byte(r.Name))
  146. tw.Write([]byte("\t"))
  147. put(r.Active)
  148. for _, l := range r.Latency {
  149. put(l)
  150. }
  151. put(r.Errors)
  152. tw.Write([]byte("\n"))
  153. }
  154. tw.Flush()
  155. }
  156. // traceData contains data for the trace data template.
  157. type traceData struct {
  158. Name string
  159. Num int
  160. Rows []traceRow
  161. }
  162. type traceRow struct {
  163. Fields [3]string
  164. trace.SpanContext
  165. ParentSpanID trace.SpanID
  166. }
  167. type events []interface{}
  168. func (e events) Len() int { return len(e) }
  169. func (e events) Less(i, j int) bool {
  170. var ti time.Time
  171. switch x := e[i].(type) {
  172. case *trace.Annotation:
  173. ti = x.Time
  174. case *trace.MessageEvent:
  175. ti = x.Time
  176. }
  177. switch x := e[j].(type) {
  178. case *trace.Annotation:
  179. return ti.Before(x.Time)
  180. case *trace.MessageEvent:
  181. return ti.Before(x.Time)
  182. }
  183. return false
  184. }
  185. func (e events) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
  186. func traceRows(s *trace.SpanData) []traceRow {
  187. start := s.StartTime
  188. lasty, lastm, lastd := start.Date()
  189. wholeTime := func(t time.Time) string {
  190. return t.Format("2006/01/02-15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
  191. }
  192. formatTime := func(t time.Time) string {
  193. y, m, d := t.Date()
  194. if y == lasty && m == lastm && d == lastd {
  195. return t.Format(" 15:04:05") + fmt.Sprintf(".%06d", t.Nanosecond()/1000)
  196. }
  197. lasty, lastm, lastd = y, m, d
  198. return wholeTime(t)
  199. }
  200. lastTime := start
  201. formatElapsed := func(t time.Time) string {
  202. d := t.Sub(lastTime)
  203. lastTime = t
  204. u := int64(d / 1000)
  205. // There are five cases for duration printing:
  206. // -1234567890s
  207. // -1234.123456
  208. // .123456
  209. // 12345.123456
  210. // 12345678901s
  211. switch {
  212. case u < -9999999999:
  213. return fmt.Sprintf("%11ds", u/1e6)
  214. case u < 0:
  215. sec := u / 1e6
  216. u -= sec * 1e6
  217. return fmt.Sprintf("%5d.%06d", sec, -u)
  218. case u < 1e6:
  219. return fmt.Sprintf(" .%6d", u)
  220. case u <= 99999999999:
  221. sec := u / 1e6
  222. u -= sec * 1e6
  223. return fmt.Sprintf("%5d.%06d", sec, u)
  224. default:
  225. return fmt.Sprintf("%11ds", u/1e6)
  226. }
  227. }
  228. firstRow := traceRow{Fields: [3]string{wholeTime(start), "", ""}, SpanContext: s.SpanContext, ParentSpanID: s.ParentSpanID}
  229. if s.EndTime.IsZero() {
  230. firstRow.Fields[1] = " "
  231. } else {
  232. firstRow.Fields[1] = formatElapsed(s.EndTime)
  233. lastTime = start
  234. }
  235. out := []traceRow{firstRow}
  236. formatAttributes := func(a map[string]interface{}) string {
  237. if len(a) == 0 {
  238. return ""
  239. }
  240. var keys []string
  241. for key := range a {
  242. keys = append(keys, key)
  243. }
  244. sort.Strings(keys)
  245. var s []string
  246. for _, key := range keys {
  247. val := a[key]
  248. switch val.(type) {
  249. case string:
  250. s = append(s, fmt.Sprintf("%s=%q", key, val))
  251. default:
  252. s = append(s, fmt.Sprintf("%s=%v", key, val))
  253. }
  254. }
  255. return "Attributes:{" + strings.Join(s, ", ") + "}"
  256. }
  257. if s.Status != (trace.Status{}) {
  258. msg := fmt.Sprintf("Status{canonicalCode=%s, description=%q}",
  259. canonicalCodeString(s.Status.Code), s.Status.Message)
  260. out = append(out, traceRow{Fields: [3]string{"", "", msg}})
  261. }
  262. if len(s.Attributes) != 0 {
  263. out = append(out, traceRow{Fields: [3]string{"", "", formatAttributes(s.Attributes)}})
  264. }
  265. var es events
  266. for i := range s.Annotations {
  267. es = append(es, &s.Annotations[i])
  268. }
  269. for i := range s.MessageEvents {
  270. es = append(es, &s.MessageEvents[i])
  271. }
  272. sort.Sort(es)
  273. for _, e := range es {
  274. switch e := e.(type) {
  275. case *trace.Annotation:
  276. msg := e.Message
  277. if len(e.Attributes) != 0 {
  278. msg = msg + " " + formatAttributes(e.Attributes)
  279. }
  280. row := traceRow{Fields: [3]string{
  281. formatTime(e.Time),
  282. formatElapsed(e.Time),
  283. msg,
  284. }}
  285. out = append(out, row)
  286. case *trace.MessageEvent:
  287. row := traceRow{Fields: [3]string{formatTime(e.Time), formatElapsed(e.Time)}}
  288. switch e.EventType {
  289. case trace.MessageEventTypeSent:
  290. row.Fields[2] = fmt.Sprintf("sent message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
  291. case trace.MessageEventTypeRecv:
  292. row.Fields[2] = fmt.Sprintf("received message [%d bytes, %d compressed bytes]", e.UncompressedByteSize, e.CompressedByteSize)
  293. }
  294. out = append(out, row)
  295. }
  296. }
  297. for i := range out {
  298. if len(out[i].Fields[2]) > maxTraceMessageLength {
  299. out[i].Fields[2] = out[i].Fields[2][:maxTraceMessageLength]
  300. }
  301. }
  302. return out
  303. }
  304. func traceSpans(spanName string, spanType, spanSubtype int) []*trace.SpanData {
  305. internalTrace := internal.Trace.(interface {
  306. ReportActiveSpans(name string) []*trace.SpanData
  307. ReportSpansByError(name string, code int32) []*trace.SpanData
  308. ReportSpansByLatency(name string, minLatency, maxLatency time.Duration) []*trace.SpanData
  309. })
  310. var spans []*trace.SpanData
  311. switch spanType {
  312. case 0: // active
  313. spans = internalTrace.ReportActiveSpans(spanName)
  314. case 1: // latency
  315. var min, max time.Duration
  316. n := len(defaultLatencies)
  317. if spanSubtype == 0 {
  318. max = defaultLatencies[0]
  319. } else if spanSubtype == n {
  320. min, max = defaultLatencies[spanSubtype-1], (1<<63)-1
  321. } else if 0 < spanSubtype && spanSubtype < n {
  322. min, max = defaultLatencies[spanSubtype-1], defaultLatencies[spanSubtype]
  323. }
  324. spans = internalTrace.ReportSpansByLatency(spanName, min, max)
  325. case 2: // error
  326. spans = internalTrace.ReportSpansByError(spanName, 0)
  327. }
  328. return spans
  329. }
  330. func traceDataFromSpans(name string, spans []*trace.SpanData) traceData {
  331. data := traceData{
  332. Name: name,
  333. Num: len(spans),
  334. }
  335. for _, s := range spans {
  336. data.Rows = append(data.Rows, traceRows(s)...)
  337. }
  338. return data
  339. }
  340. func writeTextTraces(w io.Writer, data traceData) {
  341. tw := tabwriter.NewWriter(w, 1, 8, 1, ' ', 0)
  342. fmt.Fprint(tw, "When\tElapsed(s)\tType\n")
  343. for _, r := range data.Rows {
  344. tw.Write([]byte(r.Fields[0]))
  345. tw.Write([]byte("\t"))
  346. tw.Write([]byte(r.Fields[1]))
  347. tw.Write([]byte("\t"))
  348. tw.Write([]byte(r.Fields[2]))
  349. if sc := r.SpanContext; sc != (trace.SpanContext{}) {
  350. fmt.Fprintf(tw, "trace_id: %s span_id: %s", sc.TraceID, sc.SpanID)
  351. if r.ParentSpanID != (trace.SpanID{}) {
  352. fmt.Fprintf(tw, " parent_span_id: %s", r.ParentSpanID)
  353. }
  354. }
  355. tw.Write([]byte("\n"))
  356. }
  357. tw.Flush()
  358. }
  359. type summaryPageData struct {
  360. Header []string
  361. LatencyBucketNames []string
  362. Links bool
  363. TracesEndpoint string
  364. Rows []summaryPageRow
  365. }
  366. type summaryPageRow struct {
  367. Name string
  368. Active int
  369. Latency []int
  370. Errors int
  371. }
  372. func getSummaryPageData() summaryPageData {
  373. data := summaryPageData{
  374. Links: true,
  375. TracesEndpoint: "tracez",
  376. }
  377. internalTrace := internal.Trace.(interface {
  378. ReportSpansPerMethod() map[string]internal.PerMethodSummary
  379. })
  380. for name, s := range internalTrace.ReportSpansPerMethod() {
  381. if len(data.Header) == 0 {
  382. data.Header = []string{"Name", "Active"}
  383. for _, b := range s.LatencyBuckets {
  384. l := b.MinLatency
  385. s := fmt.Sprintf(">%v", l)
  386. if l == 100*time.Second {
  387. s = ">100s"
  388. }
  389. data.Header = append(data.Header, s)
  390. data.LatencyBucketNames = append(data.LatencyBucketNames, s)
  391. }
  392. data.Header = append(data.Header, "Errors")
  393. }
  394. row := summaryPageRow{Name: name, Active: s.Active}
  395. for _, l := range s.LatencyBuckets {
  396. row.Latency = append(row.Latency, l.Size)
  397. }
  398. for _, e := range s.ErrorBuckets {
  399. row.Errors += e.Size
  400. }
  401. data.Rows = append(data.Rows, row)
  402. }
  403. sort.Slice(data.Rows, func(i, j int) bool {
  404. return data.Rows[i].Name < data.Rows[j].Name
  405. })
  406. return data
  407. }