mirror of
https://github.com/hauler-dev/hauler.git
synced 2026-08-19 20:36:30 +00:00
85 lines
2.8 KiB
Go
85 lines
2.8 KiB
Go
package content
|
|
|
|
import (
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
// IOStats accumulates disk-contention counters for a single OCI store, so
|
|
// one pasted log line explains a field performance report -- the previous
|
|
// parallel-blob work was abandoned partly because regression reports were
|
|
// unreproducible with the reporter's contention invisible. Counters live on
|
|
// OCI rather than being context-attached (the store.WithImageStats idiom)
|
|
// because they measure store-level semaphore/mutex contention, not
|
|
// per-image state like ImageStats. All fields are atomic since every
|
|
// increment runs concurrently by design.
|
|
type IOStats struct {
|
|
BlobsWritten atomic.Int64
|
|
BlobsCached atomic.Int64
|
|
BlobBytesWritten atomic.Int64
|
|
BlobSemWaitNanos atomic.Int64
|
|
BlobPeakInFlight atomic.Int64
|
|
IndexWrites atomic.Int64
|
|
IndexDurableWrites atomic.Int64
|
|
IndexBytesWritten atomic.Int64
|
|
IndexLockWaitNanos atomic.Int64
|
|
|
|
// blobInFlight backs BlobPeakInFlight. It is never reported: the
|
|
// instantaneous value at the end of a run is always 0 and says nothing.
|
|
blobInFlight atomic.Int64
|
|
}
|
|
|
|
// IOStatsSnapshot is a plain-value copy of IOStats, so formatting code never
|
|
// touches atomics and can be unit-tested with a literal.
|
|
type IOStatsSnapshot struct {
|
|
BlobsWritten int64
|
|
BlobsCached int64
|
|
BlobBytesWritten int64
|
|
BlobSemWait time.Duration
|
|
BlobPeakInFlight int64
|
|
IndexWrites int64
|
|
IndexDurableWrites int64
|
|
IndexBytesWritten int64
|
|
IndexLockWait time.Duration
|
|
}
|
|
|
|
// Snapshot reads every counter. It is not atomic as a whole -- counters are
|
|
// read one at a time and may skew relative to each other if a sync is still
|
|
// running. Callers report it after all work has finished, where that does
|
|
// not matter.
|
|
func (s *IOStats) Snapshot() IOStatsSnapshot {
|
|
return IOStatsSnapshot{
|
|
BlobsWritten: s.BlobsWritten.Load(),
|
|
BlobsCached: s.BlobsCached.Load(),
|
|
BlobBytesWritten: s.BlobBytesWritten.Load(),
|
|
BlobSemWait: time.Duration(s.BlobSemWaitNanos.Load()),
|
|
BlobPeakInFlight: s.BlobPeakInFlight.Load(),
|
|
IndexWrites: s.IndexWrites.Load(),
|
|
IndexDurableWrites: s.IndexDurableWrites.Load(),
|
|
IndexBytesWritten: s.IndexBytesWritten.Load(),
|
|
IndexLockWait: time.Duration(s.IndexLockWaitNanos.Load()),
|
|
}
|
|
}
|
|
|
|
// enterBlob records that a blob write has just acquired a permit, updating
|
|
// the high-water mark with a CAS loop. Must be paired with exitBlob.
|
|
func (s *IOStats) enterBlob() {
|
|
cur := s.blobInFlight.Add(1)
|
|
for {
|
|
peak := s.BlobPeakInFlight.Load()
|
|
if cur <= peak || s.BlobPeakInFlight.CompareAndSwap(peak, cur) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// exitBlob records that a blob write has released its permit.
|
|
func (s *IOStats) exitBlob() {
|
|
s.blobInFlight.Add(-1)
|
|
}
|
|
|
|
// addSemWait records time spent blocked acquiring blobSem.
|
|
func (s *IOStats) addSemWait(d time.Duration) {
|
|
s.BlobSemWaitNanos.Add(int64(d))
|
|
}
|