Do lockless last minute latency metrics (#17576)

Collect metrics in one second and accumulate lockless before sending upstream.
This commit is contained in:
Klaus Post 2023-07-05 10:40:45 -07:00 committed by GitHub
parent 0bc34952eb
commit 6efcf9c982
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 41 additions and 22 deletions

View File

@ -90,16 +90,6 @@ func (a *AccElem) add(dur time.Duration) {
a.N++ a.N++
} }
// Add a duration to a single element.
func (a *AccElem) addSize(dur time.Duration, sz int64) {
if dur < 0 {
dur = 0
}
a.Total += int64(dur)
a.Size += sz
a.N++
}
// Merge b into a. // Merge b into a.
func (a *AccElem) merge(b AccElem) { func (a *AccElem) merge(b AccElem) {
a.N += b.N a.N += b.N
@ -156,11 +146,10 @@ func (l *lastMinuteLatency) add(t time.Duration) {
} }
// Add a new duration data // Add a new duration data
func (l *lastMinuteLatency) addSize(t time.Duration, sz int64) { func (l *lastMinuteLatency) addAll(sec int64, a AccElem) {
sec := time.Now().Unix()
l.forwardTo(sec) l.forwardTo(sec)
winIdx := sec % 60 winIdx := sec % 60
l.Totals[winIdx].addSize(t, sz) l.Totals[winIdx].merge(a)
l.LastSec = sec l.LastSec = sec
} }

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"io" "io"
"math/rand" "math/rand"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -105,28 +106,57 @@ func (p *xlStorageDiskIDCheck) getMetrics() DiskMetrics {
return m.(DiskMetrics) return m.(DiskMetrics)
} }
// lockedLastMinuteLatency accumulates totals lockless for each second.
type lockedLastMinuteLatency struct { type lockedLastMinuteLatency struct {
sync.Mutex cachedSec int64
cached atomic.Pointer[AccElem]
mu sync.Mutex
init sync.Once
lastMinuteLatency lastMinuteLatency
} }
func (e *lockedLastMinuteLatency) add(value time.Duration) { func (e *lockedLastMinuteLatency) add(value time.Duration) {
e.Lock() e.addSize(value, 0)
defer e.Unlock()
e.lastMinuteLatency.add(value)
} }
// addSize will add a duration and size. // addSize will add a duration and size.
func (e *lockedLastMinuteLatency) addSize(value time.Duration, sz int64) { func (e *lockedLastMinuteLatency) addSize(value time.Duration, sz int64) {
e.Lock() // alloc on every call, so we have a clean entry to swap in.
defer e.Unlock() t := time.Now().Unix()
e.lastMinuteLatency.addSize(value, sz) e.init.Do(func() {
e.cached.Store(&AccElem{})
atomic.StoreInt64(&e.cachedSec, t)
})
acc := e.cached.Load()
if lastT := atomic.LoadInt64(&e.cachedSec); lastT != t {
// Check if lastT was changed by someone else.
if atomic.CompareAndSwapInt64(&e.cachedSec, lastT, t) {
// Now we swap in a new.
newAcc := &AccElem{}
old := e.cached.Swap(newAcc)
var a AccElem
a.Size = atomic.LoadInt64(&old.Size)
a.Total = atomic.LoadInt64(&old.Total)
a.N = atomic.LoadInt64(&old.N)
e.mu.Lock()
e.lastMinuteLatency.addAll(t-1, a)
e.mu.Unlock()
acc = newAcc
} else {
// We may be able to grab the new accumulator by yielding.
runtime.Gosched()
acc = e.cached.Load()
}
}
atomic.AddInt64(&acc.N, 1)
atomic.AddInt64(&acc.Total, int64(value))
atomic.AddInt64(&acc.Size, sz)
} }
// total returns the total call count and latency for the last minute. // total returns the total call count and latency for the last minute.
func (e *lockedLastMinuteLatency) total() AccElem { func (e *lockedLastMinuteLatency) total() AccElem {
e.Lock() e.mu.Lock()
defer e.Unlock() defer e.mu.Unlock()
return e.lastMinuteLatency.getTotal() return e.lastMinuteLatency.getTotal()
} }