Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
70 lines
2.1 KiB
Go
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
|
|
}
|