1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package traceviewer
6
7import (
8	"fmt"
9	"html/template"
10	"math"
11	"strings"
12	"time"
13)
14
15// TimeHistogram is an high-dynamic-range histogram for durations.
16type TimeHistogram struct {
17	Count                int
18	Buckets              []int
19	MinBucket, MaxBucket int
20}
21
22// Five buckets for every power of 10.
23var logDiv = math.Log(math.Pow(10, 1.0/5))
24
25// Add adds a single sample to the histogram.
26func (h *TimeHistogram) Add(d time.Duration) {
27	var bucket int
28	if d > 0 {
29		bucket = int(math.Log(float64(d)) / logDiv)
30	}
31	if len(h.Buckets) <= bucket {
32		h.Buckets = append(h.Buckets, make([]int, bucket-len(h.Buckets)+1)...)
33		h.Buckets = h.Buckets[:cap(h.Buckets)]
34	}
35	h.Buckets[bucket]++
36	if bucket < h.MinBucket || h.MaxBucket == 0 {
37		h.MinBucket = bucket
38	}
39	if bucket > h.MaxBucket {
40		h.MaxBucket = bucket
41	}
42	h.Count++
43}
44
45// BucketMin returns the minimum duration value for a provided bucket.
46func (h *TimeHistogram) BucketMin(bucket int) time.Duration {
47	return time.Duration(math.Exp(float64(bucket) * logDiv))
48}
49
50// ToHTML renders the histogram as HTML.
51func (h *TimeHistogram) ToHTML(urlmaker func(min, max time.Duration) string) template.HTML {
52	if h == nil || h.Count == 0 {
53		return template.HTML("")
54	}
55
56	const barWidth = 400
57
58	maxCount := 0
59	for _, count := range h.Buckets {
60		if count > maxCount {
61			maxCount = count
62		}
63	}
64
65	w := new(strings.Builder)
66	fmt.Fprintf(w, `<table>`)
67	for i := h.MinBucket; i <= h.MaxBucket; i++ {
68		// Tick label.
69		if h.Buckets[i] > 0 {
70			fmt.Fprintf(w, `<tr><td class="histoTime" align="right"><a href=%s>%s</a></td>`, urlmaker(h.BucketMin(i), h.BucketMin(i+1)), h.BucketMin(i))
71		} else {
72			fmt.Fprintf(w, `<tr><td class="histoTime" align="right">%s</td>`, h.BucketMin(i))
73		}
74		// Bucket bar.
75		width := h.Buckets[i] * barWidth / maxCount
76		fmt.Fprintf(w, `<td><div style="width:%dpx;background:blue;position:relative">&nbsp;</div></td>`, width)
77		// Bucket count.
78		fmt.Fprintf(w, `<td align="right"><div style="position:relative">%d</div></td>`, h.Buckets[i])
79		fmt.Fprintf(w, "</tr>\n")
80
81	}
82	// Final tick label.
83	fmt.Fprintf(w, `<tr><td align="right">%s</td></tr>`, h.BucketMin(h.MaxBucket+1))
84	fmt.Fprintf(w, `</table>`)
85	return template.HTML(w.String())
86}
87