Files
nmon/cmd/nmon-devtools/pattern.go
jleung f85c3ef781
Some checks failed
ci / lint (push) Has been cancelled
ci / test (ubuntu-24.04-arm) (push) Has been cancelled
ci / test (ubuntu-latest) (push) Has been cancelled
ci / postgres (push) Has been cancelled
ci / openapi (push) Has been cancelled
M3: Go API per UI contract, embedded UI, openapi compat check, nmon-devtools, live gate + screenshots; M4 WP1 reachability and dependency suppression
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
2026-10-22 20:34:14 +00:00

70 lines
2.1 KiB
Go

package main
import (
"math"
"time"
)
// Synthetic traffic shapes for `nmon devtools backfill`. Everything here is a
// pure function of its inputs, so a back-fill is reproducible and testable.
const (
patternDiurnal = "diurnal"
patternFlat = "flat"
)
// splitmix64 is a tiny deterministic hash used for noise; math/rand would make
// two runs differ and a global source would be shared state.
func splitmix64(x uint64) uint64 {
x += 0x9e3779b97f4a7c15
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9
x = (x ^ (x >> 27)) * 0x94d049bb133111eb
return x ^ (x >> 31)
}
// unit maps a hash to [0, 1).
func unit(h uint64) float64 { return float64(h>>11) / (1 << 53) }
// rateFactor is the multiple of the mean rate at t: 1 for "flat"; for
// "diurnal" an evening peak (about 20:00 UTC), an early-morning trough, quieter
// weekends, per-sample noise and the odd short burst.
func rateFactor(pattern string, t time.Time, salt uint64) float64 {
if pattern == patternFlat {
return 1
}
h := float64(t.Hour()) + float64(t.Minute())/60
f := 1 + 0.6*math.Cos(2*math.Pi*(h-20)/24)
if wd := t.Weekday(); wd == time.Saturday || wd == time.Sunday {
f *= 0.8
}
slot := uint64(t.Unix() / 60)
f *= 0.88 + 0.24*unit(splitmix64(salt^slot))
if unit(splitmix64(salt^(slot/4)^0xb5ad4eceda1ce2a9)) < 0.01 { // ~1% of 4-minute slots burst
f *= 2.5
}
return math.Max(f, 0.05)
}
// counterSeries returns n cumulative octet-counter values, one per step from
// start, beginning at 0 and never decreasing. mean is bytes per second.
func counterSeries(pattern string, start time.Time, n int, step time.Duration, mean float64, salt uint64) []float64 {
out := make([]float64, n)
var total float64
for i := 1; i < n; i++ {
t := start.Add(time.Duration(i) * step)
total += mean * rateFactor(pattern, t, salt) * step.Seconds()
out[i] = math.Floor(total)
}
return out
}
// defaultMeanBytesPerSec is a stable per-interface mean (2 to 60 Mbit/s) for when
// the live rate is unknown.
func defaultMeanBytesPerSec(iface string, salt uint64) float64 {
h := salt
for _, c := range []byte(iface) {
h = splitmix64(h ^ uint64(c))
}
return (2 + 58*unit(h)*unit(h>>7)) * 125_000
}