Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions internal/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ func New() uint64 {

// Add adds a string to a fnv64a hash value, returning the updated hash.
func Add(h uint64, s string) uint64 {
h = AddNoScramble(h, s)

h ^= magicRune
h *= prime64

return h
}

func AddNoScramble(h uint64, s string) uint64 {
for i := 0; i < len(s); i++ {
h ^= uint64(s[i])
h *= prime64
}

h ^= magicRune
h *= prime64

return h
}
184 changes: 184 additions & 0 deletions multi_incarnation_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package stats

import (
"fmt"
"strconv"
"sync"

"github.com/upfluence/stats/internal/hash"
)

var globalIncarnationRegistry = &incarnationRegistry{key: "incarnation", counters: make(map[counterKey]uint)}

type incarnationRegistry struct {
countersMu sync.Mutex
counters map[counterKey]uint
key string
}

func (ir *incarnationRegistry) next(ck counterKey) uint {
ir.countersMu.Lock()
defer ir.countersMu.Unlock()

current, ok := ir.counters[ck]

next := current + 1

if !ok {
next--
}

ir.counters[ck] = next

return next
}

type counterKey struct {
keySum uint64
tagsSum uint64
}

func newCounterKey() counterKey {
return counterKey{keySum: hash.New()}
}

func (ck counterKey) add(key string, tags map[string]string) counterKey {
if key != "" {
if ck.keySum != hash.New() {
key = "_" + key
}

ck.keySum = hash.AddNoScramble(ck.keySum, key)
}

for k, v := range tags {
ck.tagsSum |= hash.Add(hash.Add(hash.New(), k), v)
}

return ck
}

func (ck counterKey) appendTag(k, v string) counterKey {
ck.tagsSum |= hash.Add(hash.Add(hash.New(), k), v)

return ck
}

type multiIncarnationScope struct {
scope Scope

currentKey counterKey
registry *incarnationRegistry
}

func newMultiIncarnationScope(sc Scope, r *incarnationRegistry) *multiIncarnationScope {
return &multiIncarnationScope{
scope: sc,
currentKey: newCounterKey().add(sc.namespace(), sc.tags()),
registry: r,
}
}

func GlobalIncarnationScope(sc Scope) Scope {
return newMultiIncarnationScope(sc, globalIncarnationRegistry)
}

func LocalIncarnationScope(sc Scope, k string) Scope {
return newMultiIncarnationScope(
sc,
&incarnationRegistry{key: k, counters: make(map[counterKey]uint)},
)
}

func (mis *multiIncarnationScope) namespace() string { return mis.scope.namespace() }
func (mis *multiIncarnationScope) tags() map[string]string { return mis.scope.tags() }
func (mis *multiIncarnationScope) rootScope() *rootScope { return mis.scope.rootScope() }

func (mis *multiIncarnationScope) Scope(k string, vs map[string]string) Scope {
if _, ok := vs[mis.registry.key]; ok {
panic(fmt.Sprintf("scope can not include the incarnation key %q", mis.registry.key))
}

return &multiIncarnationScope{
scope: mis.scope.Scope(k, vs),
currentKey: mis.currentKey.add(k, vs),
registry: mis.registry,
}
}

func (mis *multiIncarnationScope) RootScope() Scope {
return &multiIncarnationScope{
scope: mis.scope.RootScope(),
registry: mis.registry,
}
}

type abstractVector[T any] interface {
WithLabels(...string) T
}

type multiIncarnationVector[T any] struct {
cv abstractVector[T]
ls []string

currentKey counterKey
registry *incarnationRegistry
}

func (micv *multiIncarnationVector[T]) WithLabels(vs ...string) T {
if len(vs) != len(micv.ls) {
panic("wrong number of label values")
}

ck := micv.currentKey

for i, l := range micv.ls {
ck = ck.appendTag(l, vs[i])
}

return micv.cv.WithLabels(
append(
vs,
strconv.Itoa(int(micv.registry.next(ck))),

Check failure on line 142 in multi_incarnation_scope.go

View workflow job for this annotation

GitHub Actions / runner / golangci-lint

G115: integer overflow conversion uint -> int (gosec)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
G115: integer overflow conversion uint -> int (gosec)

)...,
)
}

func (mis *multiIncarnationScope) Counter(k string) Counter {
return mis.CounterVector(k, nil).WithLabels()
}

func (mis *multiIncarnationScope) CounterVector(k string, ls []string) CounterVector {
return &multiIncarnationVector[Counter]{
cv: mis.scope.CounterVector(k, append(ls, mis.registry.key)),
ls: ls,
currentKey: mis.currentKey.add(k, nil),
registry: mis.registry,
}
}

func (mis *multiIncarnationScope) Gauge(k string) Gauge {
return mis.GaugeVector(k, nil).WithLabels()
}

func (mis *multiIncarnationScope) GaugeVector(k string, ls []string) GaugeVector {
return &multiIncarnationVector[Gauge]{
cv: mis.scope.GaugeVector(k, append(ls, mis.registry.key)),
ls: ls,
currentKey: mis.currentKey.add(k, nil),
registry: mis.registry,
}
}

func (mis *multiIncarnationScope) Histogram(k string, opts ...HistogramOption) Histogram {
return mis.HistogramVector(k, nil, opts...).WithLabels()
}

func (mis *multiIncarnationScope) HistogramVector(k string, ls []string, opts ...HistogramOption) HistogramVector {
return &multiIncarnationVector[Histogram]{
cv: mis.scope.HistogramVector(k, append(ls, mis.registry.key), opts...),
ls: ls,
currentKey: mis.currentKey.add(k, nil),
registry: mis.registry,
}
}
43 changes: 43 additions & 0 deletions multi_incarnation_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package stats

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestMultiIncarnationScope(t *testing.T) {
c := NewStaticCollector()

sc := LocalIncarnationScope(RootScope(c), "incarnation")

g1 := sc.Scope("foo", map[string]string{"bar": "buz"}).Gauge("bar")

g1.Update(1)

sc.GaugeVector("foo_bar", []string{"bar"}).WithLabels("buz").Update(2)

g1.Update(3)

sc.Scope("foo", map[string]string{"bar": "buz"}).Gauge("bar").Update(4)

sc.Counter("foo_bar_1").Inc()

assert.Equal(
t,
[]Int64Snapshot{
{Name: "foo_bar", Labels: map[string]string{"bar": "buz", "incarnation": "0"}, Value: 3},
{Name: "foo_bar", Labels: map[string]string{"bar": "buz", "incarnation": "1"}, Value: 2},
{Name: "foo_bar", Labels: map[string]string{"bar": "buz", "incarnation": "2"}, Value: 4},
},
c.Get().Gauges,
)

assert.Equal(
t,
[]Int64Snapshot{
{Name: "foo_bar_1", Labels: map[string]string{"incarnation": "0"}, Value: 1},
},
c.Get().Counters,
)
}
Loading