aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/go.opencensus.io/trace/spanbucket.go
blob: fbabad34c000d6e57820edb49e1bbc9b4cd74220 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// Copyright 2017, OpenCensus Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package trace

import (
	"time"
)

// samplePeriod is the minimum time between accepting spans in a single bucket.
const samplePeriod = time.Second

// defaultLatencies contains the default latency bucket bounds.
// TODO: consider defaults, make configurable
var defaultLatencies = [...]time.Duration{
	10 * time.Microsecond,
	100 * time.Microsecond,
	time.Millisecond,
	10 * time.Millisecond,
	100 * time.Millisecond,
	time.Second,
	10 * time.Second,
	time.Minute,
}

// bucket is a container for a set of spans for a particular error code or latency range.
type bucket struct {
	nextTime  time.Time   // next time we can accept a span
	buffer    []*SpanData // circular buffer of spans
	nextIndex int         // location next SpanData should be placed in buffer
	overflow  bool        // whether the circular buffer has wrapped around
}

func makeBucket(bufferSize int) bucket {
	return bucket{
		buffer: make([]*SpanData, bufferSize),
	}
}

// add adds a span to the bucket, if nextTime has been reached.
func (b *bucket) add(s *SpanData) {
	if s.EndTime.Before(b.nextTime) {
		return
	}
	if len(b.buffer) == 0 {
		return
	}
	b.nextTime = s.EndTime.Add(samplePeriod)
	b.buffer[b.nextIndex] = s
	b.nextIndex++
	if b.nextIndex == len(b.buffer) {
		b.nextIndex = 0
		b.overflow = true
	}
}

// size returns the number of spans in the bucket.
func (b *bucket) size() int {
	if b.overflow {
		return len(b.buffer)
	}
	return b.nextIndex
}

// span returns the ith span in the bucket.
func (b *bucket) span(i int) *SpanData {
	if !b.overflow {
		return b.buffer[i]
	}
	if i < len(b.buffer)-b.nextIndex {
		return b.buffer[b.nextIndex+i]
	}
	return b.buffer[b.nextIndex+i-len(b.buffer)]
}

// resize changes the size of the bucket to n, keeping up to n existing spans.
func (b *bucket) resize(n int) {
	cur := b.size()
	newBuffer := make([]*SpanData, n)
	if cur < n {
		for i := 0; i < cur; i++ {
			newBuffer[i] = b.span(i)
		}
		b.buffer = newBuffer
		b.nextIndex = cur
		b.overflow = false
		return
	}
	for i := 0; i < n; i++ {
		newBuffer[i] = b.span(i + cur - n)
	}
	b.buffer = newBuffer
	b.nextIndex = 0
	b.overflow = true
}

// latencyBucket returns the appropriate bucket number for a given latency.
func latencyBucket(latency time.Duration) int {
	i := 0
	for i < len(defaultLatencies) && latency >= defaultLatencies[i] {
		i++
	}
	return i
}

// latencyBucketBounds returns the lower and upper bounds for a latency bucket
// number.
//
// The lower bound is inclusive, the upper bound is exclusive (except for the
// last bucket.)
func latencyBucketBounds(index int) (lower time.Duration, upper time.Duration) {
	if index == 0 {
		return 0, defaultLatencies[index]
	}
	if index == len(defaultLatencies) {
		return defaultLatencies[index-1], 1<<63 - 1
	}
	return defaultLatencies[index-1], defaultLatencies[index]
}