mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:22:25 -05:00 
			
		
		
		
	feat: initial tracing support (#1623)
This commit is contained in:
		
					parent
					
						
							
								878ed48de3
							
						
					
				
			
			
				commit
				
					
						6392e00653
					
				
			
		
					 472 changed files with 102600 additions and 12 deletions
				
			
		
							
								
								
									
										365
									
								
								vendor/golang.org/x/net/trace/histogram.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								vendor/golang.org/x/net/trace/histogram.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,365 @@ | |||
| // Copyright 2015 The Go Authors. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| package trace | ||||
| 
 | ||||
| // This file implements histogramming for RPC statistics collection. | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"log" | ||||
| 	"math" | ||||
| 	"sync" | ||||
| 
 | ||||
| 	"golang.org/x/net/internal/timeseries" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	bucketCount = 38 | ||||
| ) | ||||
| 
 | ||||
| // histogram keeps counts of values in buckets that are spaced | ||||
| // out in powers of 2: 0-1, 2-3, 4-7... | ||||
| // histogram implements timeseries.Observable | ||||
| type histogram struct { | ||||
| 	sum          int64   // running total of measurements | ||||
| 	sumOfSquares float64 // square of running total | ||||
| 	buckets      []int64 // bucketed values for histogram | ||||
| 	value        int     // holds a single value as an optimization | ||||
| 	valueCount   int64   // number of values recorded for single value | ||||
| } | ||||
| 
 | ||||
| // addMeasurement records a value measurement observation to the histogram. | ||||
| func (h *histogram) addMeasurement(value int64) { | ||||
| 	// TODO: assert invariant | ||||
| 	h.sum += value | ||||
| 	h.sumOfSquares += float64(value) * float64(value) | ||||
| 
 | ||||
| 	bucketIndex := getBucket(value) | ||||
| 
 | ||||
| 	if h.valueCount == 0 || (h.valueCount > 0 && h.value == bucketIndex) { | ||||
| 		h.value = bucketIndex | ||||
| 		h.valueCount++ | ||||
| 	} else { | ||||
| 		h.allocateBuckets() | ||||
| 		h.buckets[bucketIndex]++ | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (h *histogram) allocateBuckets() { | ||||
| 	if h.buckets == nil { | ||||
| 		h.buckets = make([]int64, bucketCount) | ||||
| 		h.buckets[h.value] = h.valueCount | ||||
| 		h.value = 0 | ||||
| 		h.valueCount = -1 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func log2(i int64) int { | ||||
| 	n := 0 | ||||
| 	for ; i >= 0x100; i >>= 8 { | ||||
| 		n += 8 | ||||
| 	} | ||||
| 	for ; i > 0; i >>= 1 { | ||||
| 		n += 1 | ||||
| 	} | ||||
| 	return n | ||||
| } | ||||
| 
 | ||||
| func getBucket(i int64) (index int) { | ||||
| 	index = log2(i) - 1 | ||||
| 	if index < 0 { | ||||
| 		index = 0 | ||||
| 	} | ||||
| 	if index >= bucketCount { | ||||
| 		index = bucketCount - 1 | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Total returns the number of recorded observations. | ||||
| func (h *histogram) total() (total int64) { | ||||
| 	if h.valueCount >= 0 { | ||||
| 		total = h.valueCount | ||||
| 	} | ||||
| 	for _, val := range h.buckets { | ||||
| 		total += int64(val) | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // Average returns the average value of recorded observations. | ||||
| func (h *histogram) average() float64 { | ||||
| 	t := h.total() | ||||
| 	if t == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return float64(h.sum) / float64(t) | ||||
| } | ||||
| 
 | ||||
| // Variance returns the variance of recorded observations. | ||||
| func (h *histogram) variance() float64 { | ||||
| 	t := float64(h.total()) | ||||
| 	if t == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	s := float64(h.sum) / t | ||||
| 	return h.sumOfSquares/t - s*s | ||||
| } | ||||
| 
 | ||||
| // StandardDeviation returns the standard deviation of recorded observations. | ||||
| func (h *histogram) standardDeviation() float64 { | ||||
| 	return math.Sqrt(h.variance()) | ||||
| } | ||||
| 
 | ||||
| // PercentileBoundary estimates the value that the given fraction of recorded | ||||
| // observations are less than. | ||||
| func (h *histogram) percentileBoundary(percentile float64) int64 { | ||||
| 	total := h.total() | ||||
| 
 | ||||
| 	// Corner cases (make sure result is strictly less than Total()) | ||||
| 	if total == 0 { | ||||
| 		return 0 | ||||
| 	} else if total == 1 { | ||||
| 		return int64(h.average()) | ||||
| 	} | ||||
| 
 | ||||
| 	percentOfTotal := round(float64(total) * percentile) | ||||
| 	var runningTotal int64 | ||||
| 
 | ||||
| 	for i := range h.buckets { | ||||
| 		value := h.buckets[i] | ||||
| 		runningTotal += value | ||||
| 		if runningTotal == percentOfTotal { | ||||
| 			// We hit an exact bucket boundary. If the next bucket has data, it is a | ||||
| 			// good estimate of the value. If the bucket is empty, we interpolate the | ||||
| 			// midpoint between the next bucket's boundary and the next non-zero | ||||
| 			// bucket. If the remaining buckets are all empty, then we use the | ||||
| 			// boundary for the next bucket as the estimate. | ||||
| 			j := uint8(i + 1) | ||||
| 			min := bucketBoundary(j) | ||||
| 			if runningTotal < total { | ||||
| 				for h.buckets[j] == 0 { | ||||
| 					j++ | ||||
| 				} | ||||
| 			} | ||||
| 			max := bucketBoundary(j) | ||||
| 			return min + round(float64(max-min)/2) | ||||
| 		} else if runningTotal > percentOfTotal { | ||||
| 			// The value is in this bucket. Interpolate the value. | ||||
| 			delta := runningTotal - percentOfTotal | ||||
| 			percentBucket := float64(value-delta) / float64(value) | ||||
| 			bucketMin := bucketBoundary(uint8(i)) | ||||
| 			nextBucketMin := bucketBoundary(uint8(i + 1)) | ||||
| 			bucketSize := nextBucketMin - bucketMin | ||||
| 			return bucketMin + round(percentBucket*float64(bucketSize)) | ||||
| 		} | ||||
| 	} | ||||
| 	return bucketBoundary(bucketCount - 1) | ||||
| } | ||||
| 
 | ||||
| // Median returns the estimated median of the observed values. | ||||
| func (h *histogram) median() int64 { | ||||
| 	return h.percentileBoundary(0.5) | ||||
| } | ||||
| 
 | ||||
| // Add adds other to h. | ||||
| func (h *histogram) Add(other timeseries.Observable) { | ||||
| 	o := other.(*histogram) | ||||
| 	if o.valueCount == 0 { | ||||
| 		// Other histogram is empty | ||||
| 	} else if h.valueCount >= 0 && o.valueCount > 0 && h.value == o.value { | ||||
| 		// Both have a single bucketed value, aggregate them | ||||
| 		h.valueCount += o.valueCount | ||||
| 	} else { | ||||
| 		// Two different values necessitate buckets in this histogram | ||||
| 		h.allocateBuckets() | ||||
| 		if o.valueCount >= 0 { | ||||
| 			h.buckets[o.value] += o.valueCount | ||||
| 		} else { | ||||
| 			for i := range h.buckets { | ||||
| 				h.buckets[i] += o.buckets[i] | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	h.sumOfSquares += o.sumOfSquares | ||||
| 	h.sum += o.sum | ||||
| } | ||||
| 
 | ||||
| // Clear resets the histogram to an empty state, removing all observed values. | ||||
| func (h *histogram) Clear() { | ||||
| 	h.buckets = nil | ||||
| 	h.value = 0 | ||||
| 	h.valueCount = 0 | ||||
| 	h.sum = 0 | ||||
| 	h.sumOfSquares = 0 | ||||
| } | ||||
| 
 | ||||
| // CopyFrom copies from other, which must be a *histogram, into h. | ||||
| func (h *histogram) CopyFrom(other timeseries.Observable) { | ||||
| 	o := other.(*histogram) | ||||
| 	if o.valueCount == -1 { | ||||
| 		h.allocateBuckets() | ||||
| 		copy(h.buckets, o.buckets) | ||||
| 	} | ||||
| 	h.sum = o.sum | ||||
| 	h.sumOfSquares = o.sumOfSquares | ||||
| 	h.value = o.value | ||||
| 	h.valueCount = o.valueCount | ||||
| } | ||||
| 
 | ||||
| // Multiply scales the histogram by the specified ratio. | ||||
| func (h *histogram) Multiply(ratio float64) { | ||||
| 	if h.valueCount == -1 { | ||||
| 		for i := range h.buckets { | ||||
| 			h.buckets[i] = int64(float64(h.buckets[i]) * ratio) | ||||
| 		} | ||||
| 	} else { | ||||
| 		h.valueCount = int64(float64(h.valueCount) * ratio) | ||||
| 	} | ||||
| 	h.sum = int64(float64(h.sum) * ratio) | ||||
| 	h.sumOfSquares = h.sumOfSquares * ratio | ||||
| } | ||||
| 
 | ||||
| // New creates a new histogram. | ||||
| func (h *histogram) New() timeseries.Observable { | ||||
| 	r := new(histogram) | ||||
| 	r.Clear() | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| func (h *histogram) String() string { | ||||
| 	return fmt.Sprintf("%d, %f, %d, %d, %v", | ||||
| 		h.sum, h.sumOfSquares, h.value, h.valueCount, h.buckets) | ||||
| } | ||||
| 
 | ||||
| // round returns the closest int64 to the argument | ||||
| func round(in float64) int64 { | ||||
| 	return int64(math.Floor(in + 0.5)) | ||||
| } | ||||
| 
 | ||||
| // bucketBoundary returns the first value in the bucket. | ||||
| func bucketBoundary(bucket uint8) int64 { | ||||
| 	if bucket == 0 { | ||||
| 		return 0 | ||||
| 	} | ||||
| 	return 1 << bucket | ||||
| } | ||||
| 
 | ||||
| // bucketData holds data about a specific bucket for use in distTmpl. | ||||
| type bucketData struct { | ||||
| 	Lower, Upper       int64 | ||||
| 	N                  int64 | ||||
| 	Pct, CumulativePct float64 | ||||
| 	GraphWidth         int | ||||
| } | ||||
| 
 | ||||
| // data holds data about a Distribution for use in distTmpl. | ||||
| type data struct { | ||||
| 	Buckets                 []*bucketData | ||||
| 	Count, Median           int64 | ||||
| 	Mean, StandardDeviation float64 | ||||
| } | ||||
| 
 | ||||
| // maxHTMLBarWidth is the maximum width of the HTML bar for visualizing buckets. | ||||
| const maxHTMLBarWidth = 350.0 | ||||
| 
 | ||||
| // newData returns data representing h for use in distTmpl. | ||||
| func (h *histogram) newData() *data { | ||||
| 	// Force the allocation of buckets to simplify the rendering implementation | ||||
| 	h.allocateBuckets() | ||||
| 	// We scale the bars on the right so that the largest bar is | ||||
| 	// maxHTMLBarWidth pixels in width. | ||||
| 	maxBucket := int64(0) | ||||
| 	for _, n := range h.buckets { | ||||
| 		if n > maxBucket { | ||||
| 			maxBucket = n | ||||
| 		} | ||||
| 	} | ||||
| 	total := h.total() | ||||
| 	barsizeMult := maxHTMLBarWidth / float64(maxBucket) | ||||
| 	var pctMult float64 | ||||
| 	if total == 0 { | ||||
| 		pctMult = 1.0 | ||||
| 	} else { | ||||
| 		pctMult = 100.0 / float64(total) | ||||
| 	} | ||||
| 
 | ||||
| 	buckets := make([]*bucketData, len(h.buckets)) | ||||
| 	runningTotal := int64(0) | ||||
| 	for i, n := range h.buckets { | ||||
| 		if n == 0 { | ||||
| 			continue | ||||
| 		} | ||||
| 		runningTotal += n | ||||
| 		var upperBound int64 | ||||
| 		if i < bucketCount-1 { | ||||
| 			upperBound = bucketBoundary(uint8(i + 1)) | ||||
| 		} else { | ||||
| 			upperBound = math.MaxInt64 | ||||
| 		} | ||||
| 		buckets[i] = &bucketData{ | ||||
| 			Lower:         bucketBoundary(uint8(i)), | ||||
| 			Upper:         upperBound, | ||||
| 			N:             n, | ||||
| 			Pct:           float64(n) * pctMult, | ||||
| 			CumulativePct: float64(runningTotal) * pctMult, | ||||
| 			GraphWidth:    int(float64(n) * barsizeMult), | ||||
| 		} | ||||
| 	} | ||||
| 	return &data{ | ||||
| 		Buckets:           buckets, | ||||
| 		Count:             total, | ||||
| 		Median:            h.median(), | ||||
| 		Mean:              h.average(), | ||||
| 		StandardDeviation: h.standardDeviation(), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (h *histogram) html() template.HTML { | ||||
| 	buf := new(bytes.Buffer) | ||||
| 	if err := distTmpl().Execute(buf, h.newData()); err != nil { | ||||
| 		buf.Reset() | ||||
| 		log.Printf("net/trace: couldn't execute template: %v", err) | ||||
| 	} | ||||
| 	return template.HTML(buf.String()) | ||||
| } | ||||
| 
 | ||||
| var distTmplCache *template.Template | ||||
| var distTmplOnce sync.Once | ||||
| 
 | ||||
| func distTmpl() *template.Template { | ||||
| 	distTmplOnce.Do(func() { | ||||
| 		// Input: data | ||||
| 		distTmplCache = template.Must(template.New("distTmpl").Parse(` | ||||
| <table> | ||||
| <tr> | ||||
|     <td style="padding:0.25em">Count: {{.Count}}</td> | ||||
|     <td style="padding:0.25em">Mean: {{printf "%.0f" .Mean}}</td> | ||||
|     <td style="padding:0.25em">StdDev: {{printf "%.0f" .StandardDeviation}}</td> | ||||
|     <td style="padding:0.25em">Median: {{.Median}}</td> | ||||
| </tr> | ||||
| </table> | ||||
| <hr> | ||||
| <table> | ||||
| {{range $b := .Buckets}} | ||||
| {{if $b}} | ||||
|   <tr> | ||||
|     <td style="padding:0 0 0 0.25em">[</td> | ||||
|     <td style="text-align:right;padding:0 0.25em">{{.Lower}},</td> | ||||
|     <td style="text-align:right;padding:0 0.25em">{{.Upper}})</td> | ||||
|     <td style="text-align:right;padding:0 0.25em">{{.N}}</td> | ||||
|     <td style="text-align:right;padding:0 0.25em">{{printf "%#.3f" .Pct}}%</td> | ||||
|     <td style="text-align:right;padding:0 0.25em">{{printf "%#.3f" .CumulativePct}}%</td> | ||||
|     <td><div style="background-color: blue; height: 1em; width: {{.GraphWidth}};"></div></td> | ||||
|   </tr> | ||||
| {{end}} | ||||
| {{end}} | ||||
| </table> | ||||
| `)) | ||||
| 	}) | ||||
| 	return distTmplCache | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue