Source file src/internal/trace/traceviewer/histogram.go

     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  
     5  package traceviewer
     6  
     7  import (
     8  	"fmt"
     9  	"html/template"
    10  	"math"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  // TimeHistogram is an high-dynamic-range histogram for durations.
    16  type TimeHistogram struct {
    17  	Count                int
    18  	Buckets              []int
    19  	MinBucket, MaxBucket int
    20  }
    21  
    22  // Five buckets for every power of 10.
    23  var logDiv = math.Log(math.Pow(10, 1.0/5))
    24  
    25  // Add adds a single sample to the histogram.
    26  func (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.
    46  func (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.
    51  func (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  

View as plain text