mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
265 lines
6.1 KiB
Go
265 lines
6.1 KiB
Go
package streamaggr
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/VictoriaMetrics/metrics"
|
|
"github.com/cespare/xxhash/v2"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/atomicutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/slicesutil"
|
|
)
|
|
|
|
const dedupAggrShardsCount = 128
|
|
|
|
type dedupAggr struct {
|
|
shards []dedupAggrShard
|
|
flushDuration *metrics.Histogram
|
|
flushTimeouts *metrics.Counter
|
|
}
|
|
|
|
type dedupAggrShard struct {
|
|
dedupAggrShardNopad
|
|
|
|
// The padding prevents false sharing
|
|
_ [atomicutil.CacheLineSize - unsafe.Sizeof(dedupAggrShardNopad{})%atomicutil.CacheLineSize]byte
|
|
}
|
|
|
|
type dedupAggrState struct {
|
|
m map[string]*dedupAggrSample
|
|
mu sync.Mutex
|
|
samplesBuf []dedupAggrSample
|
|
sizeBytes atomic.Uint64
|
|
itemsCount atomic.Uint64
|
|
}
|
|
|
|
type dedupAggrShardNopad struct {
|
|
blue dedupAggrState
|
|
green dedupAggrState
|
|
}
|
|
|
|
type dedupAggrSample struct {
|
|
value float64
|
|
timestamp int64
|
|
}
|
|
|
|
func newDedupAggr() *dedupAggr {
|
|
return &dedupAggr{
|
|
shards: make([]dedupAggrShard, dedupAggrShardsCount),
|
|
}
|
|
}
|
|
|
|
func (da *dedupAggr) sizeBytes() uint64 {
|
|
n := uint64(unsafe.Sizeof(*da))
|
|
var shard *dedupAggrShard
|
|
for i := range da.shards {
|
|
shard = &da.shards[i]
|
|
n += shard.blue.sizeBytes.Load()
|
|
n += shard.green.sizeBytes.Load()
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (da *dedupAggr) itemsCount() uint64 {
|
|
n := uint64(0)
|
|
var shard *dedupAggrShard
|
|
for i := range da.shards {
|
|
shard = &da.shards[i]
|
|
n += shard.blue.itemsCount.Load()
|
|
n += shard.green.itemsCount.Load()
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (da *dedupAggr) pushSamples(samples []pushSample, _ int64, isGreen bool) {
|
|
pss := getPerShardSamples()
|
|
shards := pss.shards
|
|
for _, sample := range samples {
|
|
h := xxhash.Sum64(bytesutil.ToUnsafeBytes(sample.key))
|
|
idx := h % uint64(len(shards))
|
|
shards[idx] = append(shards[idx], sample)
|
|
}
|
|
for i, shardSamples := range shards {
|
|
if len(shardSamples) == 0 {
|
|
continue
|
|
}
|
|
da.shards[i].pushSamples(shardSamples, isGreen)
|
|
}
|
|
putPerShardSamples(pss)
|
|
}
|
|
|
|
func getDedupFlushCtx(deleteDeadline int64, isGreen bool) *dedupFlushCtx {
|
|
v := dedupFlushCtxPool.Get()
|
|
if v == nil {
|
|
v = &dedupFlushCtx{}
|
|
}
|
|
ctx := v.(*dedupFlushCtx)
|
|
ctx.deleteDeadline = deleteDeadline
|
|
ctx.isGreen = isGreen
|
|
return ctx
|
|
}
|
|
|
|
func putDedupFlushCtx(ctx *dedupFlushCtx) {
|
|
ctx.reset()
|
|
dedupFlushCtxPool.Put(ctx)
|
|
}
|
|
|
|
var dedupFlushCtxPool sync.Pool
|
|
|
|
type dedupFlushCtx struct {
|
|
samples []pushSample
|
|
deleteDeadline int64
|
|
isGreen bool
|
|
}
|
|
|
|
func (ctx *dedupFlushCtx) reset() {
|
|
clear(ctx.samples)
|
|
ctx.samples = ctx.samples[:0]
|
|
ctx.deleteDeadline = 0
|
|
}
|
|
|
|
func (da *dedupAggr) flush(f aggrPushFunc, deleteDeadline int64, isGreen bool) {
|
|
var wg sync.WaitGroup
|
|
for shardIdx := range da.shards {
|
|
flushConcurrencyCh <- struct{}{}
|
|
wg.Go(func() {
|
|
ctx := getDedupFlushCtx(deleteDeadline, isGreen)
|
|
da.shards[shardIdx].flush(ctx, f)
|
|
putDedupFlushCtx(ctx)
|
|
<-flushConcurrencyCh
|
|
})
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
type perShardSamples struct {
|
|
shards [][]pushSample
|
|
}
|
|
|
|
func (pss *perShardSamples) reset() {
|
|
shards := pss.shards
|
|
for i, shardSamples := range shards {
|
|
if len(shardSamples) > 0 {
|
|
clear(shardSamples)
|
|
shards[i] = shardSamples[:0]
|
|
}
|
|
}
|
|
}
|
|
|
|
func getPerShardSamples() *perShardSamples {
|
|
v := perShardSamplesPool.Get()
|
|
if v == nil {
|
|
return &perShardSamples{
|
|
shards: make([][]pushSample, dedupAggrShardsCount),
|
|
}
|
|
}
|
|
return v.(*perShardSamples)
|
|
}
|
|
|
|
func putPerShardSamples(pss *perShardSamples) {
|
|
pss.reset()
|
|
perShardSamplesPool.Put(pss)
|
|
}
|
|
|
|
var perShardSamplesPool sync.Pool
|
|
|
|
func (das *dedupAggrShard) pushSamples(samples []pushSample, isGreen bool) {
|
|
var state *dedupAggrState
|
|
|
|
if isGreen {
|
|
state = &das.green
|
|
} else {
|
|
state = &das.blue
|
|
}
|
|
|
|
state.mu.Lock()
|
|
defer state.mu.Unlock()
|
|
if state.m == nil {
|
|
state.m = make(map[string]*dedupAggrSample, len(samples))
|
|
}
|
|
samplesBuf := state.samplesBuf
|
|
for _, sample := range samples {
|
|
s, ok := state.m[sample.key]
|
|
if !ok {
|
|
samplesBuf = slicesutil.SetLength(samplesBuf, len(samplesBuf)+1)
|
|
s = &samplesBuf[len(samplesBuf)-1]
|
|
s.value = sample.value
|
|
s.timestamp = sample.timestamp
|
|
|
|
key := bytesutil.InternString(sample.key)
|
|
state.m[key] = s
|
|
|
|
state.itemsCount.Add(1)
|
|
state.sizeBytes.Add(uint64(len(key)) + uint64(unsafe.Sizeof(key)+unsafe.Sizeof(s)+unsafe.Sizeof(*s)))
|
|
continue
|
|
}
|
|
s.timestamp, s.value = deduplicateSamples(s.timestamp, sample.timestamp, s.value, sample.value)
|
|
}
|
|
state.samplesBuf = samplesBuf
|
|
}
|
|
|
|
// deduplicateSamples returns deduplicated timestamp and value results.
|
|
// See https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/#deduplication
|
|
func deduplicateSamples(oldT, newT int64, oldV, newV float64) (int64, float64) {
|
|
if newT > oldT {
|
|
return newT, newV
|
|
}
|
|
// if both samples have the same timestamp, choose the maximum value, see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3333;
|
|
// always prefer a non-decimal.StaleNaN value, see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10196
|
|
if newT == oldT {
|
|
if decimal.IsStaleNaN(oldV) {
|
|
return newT, newV
|
|
}
|
|
if newV > oldV {
|
|
return newT, newV
|
|
}
|
|
}
|
|
return oldT, oldV
|
|
}
|
|
|
|
func (das *dedupAggrShard) flush(ctx *dedupFlushCtx, f aggrPushFunc) {
|
|
var m map[string]*dedupAggrSample
|
|
var state *dedupAggrState
|
|
if ctx.isGreen {
|
|
state = &das.green
|
|
} else {
|
|
state = &das.blue
|
|
}
|
|
|
|
state.mu.Lock()
|
|
if len(state.m) > 0 {
|
|
m = state.m
|
|
state.m = make(map[string]*dedupAggrSample, len(state.m))
|
|
state.samplesBuf = make([]dedupAggrSample, 0, len(state.samplesBuf))
|
|
state.sizeBytes.Store(0)
|
|
state.itemsCount.Store(0)
|
|
}
|
|
state.mu.Unlock()
|
|
|
|
if len(m) == 0 {
|
|
return
|
|
}
|
|
|
|
dstSamples := ctx.samples
|
|
for key, s := range m {
|
|
dstSamples = append(dstSamples, pushSample{
|
|
key: key,
|
|
value: s.value,
|
|
timestamp: s.timestamp,
|
|
})
|
|
|
|
// Limit the number of samples per each flush in order to limit memory usage.
|
|
if len(dstSamples) >= 10_000 {
|
|
f(dstSamples, ctx.deleteDeadline, false)
|
|
clear(dstSamples)
|
|
dstSamples = dstSamples[:0]
|
|
}
|
|
}
|
|
f(dstSamples, ctx.deleteDeadline, false)
|
|
ctx.samples = dstSamples
|
|
}
|