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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
package metricsx
import (
"io"
"strconv"
"strings"
"sync/atomic"
"github.com/VictoriaMetrics/metrics"
"github.com/mmcloughlin/geohash"
)
// GeoCounter is like a *metrics.Counter, but split by location using geohashes.
type GeoCounter struct {
level uint
ctr []*metrics.Counter
unk *metrics.Counter
set *metrics.Set
base string
arg string
}
// NewGeoCounter creates a new GeoCounter writing to metrics in set named name,
// with level chars in the geohash.
func NewGeoCounter(set *metrics.Set, name string, level uint) *GeoCounter {
if h, p := geohash.ConvertStringToInt(strings.Repeat("z", int(level))); h != 1<<(5*level)-1 || p != 5*uint(level) {
panic("wtf") // this shouldn't happen... geohashes are base32, and int encoding is 5 bits per char
}
base, arg := splitName(name)
return &GeoCounter{
level: level,
ctr: make([]*metrics.Counter, 1<<(5*level)),
unk: set.NewCounter(formatName(base, arg, "geohash", "")),
set: set,
base: base,
arg: arg,
}
}
// Inc increments the counter for the specified latitude and longitude.
func (c *GeoCounter) Inc(lat, lng float64) {
c.Counter(lat, lng).Inc()
}
// Set sets the counter for the specified latitude and longitude.
func (c *GeoCounter) Set(lat, lng float64, v uint64) {
c.Counter(lat, lng).Set(v)
}
// IncUnknown increments the unknown counter.
func (c *GeoCounter) IncUnknown() {
c.unk.Inc()
}
// SetUnknown sets the unknown counter.
func (c *GeoCounter) SetUnknown(v uint64) {
c.unk.Set(v)
}
// Counter gets the underlying counter for the specified latitude and longitude.
func (c *GeoCounter) Counter(lat, lng float64) *metrics.Counter {
h := geohash.EncodeIntWithPrecision(lat, lng, c.level*5)
if int(h) >= len(c.ctr) {
return nil // wtf (this shouldn't even be possible, but we don't panic here for performance reasons)
}
m := c.ctr[h]
if m == nil {
m = c.set.NewCounter(formatName(c.base, c.arg, "geohash", geohash.EncodeWithPrecision(lat, lng, c.level)))
c.ctr[h] = m
}
return m
}
// CounterUnknown gets the underlying counter for unknown positions.
func (c *GeoCounter) CounterUnknown() *metrics.Counter {
return c.unk
}
// GeoCounter2 is an optimized standalone level 2 geocounter metric. It must not
// be copied (it uses atomics).
type GeoCounter2 struct {
name string
ctr [1 << (5 * 2)]uint64
unk uint64
}
// NewGeoCounter2 creates a new GeoCounter2 with the provided metric name.
//
// Note: The maximum cardinality of metrics produced will be 1024.
func NewGeoCounter2(name string) *GeoCounter2 {
b, a := splitName(name)
n := formatName(b, a, "geohash", "")
if !strings.HasSuffix(n, `geohash=""}`) {
panic("wtf") // should never happen
}
return &GeoCounter2{name: n}
}
// Inc increments the counter for the specified latitude and longitude.
func (c *GeoCounter2) Inc(lat, lng float64) {
if c != nil {
// this should always be true, but we need it to satisfy the bounds checker
if h := geohash2(lat, lng); h < 1<<(5*2) {
atomic.AddUint64(&c.ctr[h], 1)
}
}
}
// Set sets the counter for the specified latitude and longitude.
func (c *GeoCounter2) Set(lat, lng float64, v uint64) {
if c != nil {
// this should always be true, but we need it to satisfy the bounds checker
if h := geohash2(lat, lng); h < 1<<(5*2) {
atomic.StoreUint64(&c.ctr[h], 1)
}
}
}
// IncUnknown increments the unknown counter.
func (c *GeoCounter2) IncUnknown() {
atomic.AddUint64(&c.unk, 1)
}
// SetUnknown sets the unknown counter.
func (c *GeoCounter2) SetUnknown(v uint64) {
atomic.StoreUint64(&c.unk, v)
}
// WritePrometheus writes the Promethus text metrics.
func (c *GeoCounter2) WritePrometheus(w io.Writer) {
n := len(c.name)
b := make([]byte, 0, n+2+1+20+1)
b = append(b, c.name...)
w.Write(append(strconv.AppendUint(append(b, ' '), atomic.LoadUint64(&c.unk), 10), '\n'))
b = append(b, `"} `...)
_ = b[n-2] // bounds check hint
for h := uint64(0); h < 1<<(5*2); h++ {
if v := atomic.LoadUint64(&c.ctr[h]); v != 0 {
b[n-1] = "0123456789bcdefghjkmnpqrstuvwxyz"[(h>>0)&0x1f]
b[n-2] = "0123456789bcdefghjkmnpqrstuvwxyz"[(h>>5)&0x1f]
w.Write(append(strconv.AppendUint(b, v, 10), '\n'))
}
}
}
func geohash2(lat, lng float64) uint64 {
return geohash.EncodeIntWithPrecision(lat, lng, 5*2)
}
|