histogram.mx raw

   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  	"bytes"
  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, []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) []byte) 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 := &bytes.Buffer{}
  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