Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: add sawtooth wave latency summand #1

Merged
merged 3 commits into from
Jul 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ usage: speedbump [<flags>] <destination>
TCP proxy for simulating variable network latency.

Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--port=8000 Port number to listen on.
--buffer=64KB Size of the buffer used for TCP reads.
--latency=5ms Base latency added to proxied traffic.
--sine-amplitude=0ms Amplitude of the latency sine wave.
--sine-period=5s Period of the latency sine wave.
--version Show application version.
--help Show context-sensitive help (also try --help-long and --help-man).
--port=8000 Port number to listen on.
--buffer=64KB Size of the buffer used for TCP reads.
--latency=5ms Base latency added to proxied traffic.
--sine-amplitude=0 Amplitude of the latency sine wave.
--sine-period=0 Period of the latency sine wave.
--saw-amplitude=0 Amplitude of the latency sawtooth wave.
--saw-period=0 Period of the latency sawtooth wave.
--version Show application version.

Args:
<destination> TCP proxy destination in host:post format.
Expand Down
14 changes: 11 additions & 3 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ func parseArgs(args []string) (*SpeedbumpCfg, error) {
Default("5ms").
Duration()
sineAmplitude = app.Flag("sine-amplitude", "Amplitude of the latency sine wave.").
Default("0ms").
PlaceHolder("0").
Duration()
sinePeriod = app.Flag("sine-period", "Period of the latency sine wave.").
Default("5s").
PlaceHolder("0").
Duration()
sawAmplitute = app.Flag("saw-amplitude", "Amplitude of the latency sawtooth wave.").
PlaceHolder("0").
Duration()
sawPeriod = app.Flag("saw-period", "Period of the latency sawtooth wave.").
PlaceHolder("0").
Duration()
destAddr = app.Arg("destination", "TCP proxy destination in host:post format.").
Required().
String()
)

app.Version("0.1.0-rc1")
app.Version("0.1.0-rc2")
_, err := app.Parse(args)

if err != nil {
Expand All @@ -41,6 +47,8 @@ func parseArgs(args []string) (*SpeedbumpCfg, error) {
base: *latency,
sineAmplitude: *sineAmplitude,
sinePeriod: *sinePeriod,
sawAmplitute: *sawAmplitute,
sawPeriod: *sawPeriod,
},
}

Expand Down
2 changes: 2 additions & 0 deletions args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ func TestParseArgsAll(t *testing.T) {
assert.Equal(t, time.Millisecond*100, cfg.Latency.base)
assert.Equal(t, time.Millisecond*50, cfg.Latency.sineAmplitude)
assert.Equal(t, time.Minute, cfg.Latency.sinePeriod)
assert.Equal(t, time.Duration(0), cfg.Latency.sawAmplitute)
assert.Equal(t, time.Duration(0), cfg.Latency.sawPeriod)
}
11 changes: 11 additions & 0 deletions base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import "time"

type baseLatencySummand struct {
latency time.Duration
}

func (b baseLatencySummand) getLatency(elapsed time.Duration) time.Duration {
return b.latency
}
45 changes: 34 additions & 11 deletions latency_generator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package main

import (
"math"
"time"
)

Expand All @@ -13,20 +12,44 @@ type LatencyCfg struct {
base time.Duration
sineAmplitude time.Duration
sinePeriod time.Duration
sawAmplitute time.Duration
sawPeriod time.Duration
}

type latencySummand interface {
getLatency(elapsed time.Duration) time.Duration
}

type simpleLatencyGenerator struct {
start time.Time
cfg *LatencyCfg
start time.Time
summands []latencySummand
}

func (g *simpleLatencyGenerator) generateLatency(when time.Time) time.Duration {
func newSimpleLatencyGenerator(start time.Time, cfg *LatencyCfg) simpleLatencyGenerator {
summands := []latencySummand{baseLatencySummand{cfg.base}}
if cfg.sineAmplitude > 0 && cfg.sinePeriod > 0 {
summands = append(summands, sineLatencySummand{
cfg.sineAmplitude,
cfg.sinePeriod,
})
}
if cfg.sawAmplitute > 0 && cfg.sawPeriod > 0 {
summands = append(summands, sawtoothLatencySummand{
cfg.sawAmplitute,
cfg.sawPeriod,
})
}
return simpleLatencyGenerator{
start: start,
summands: summands,
}
}

return g.cfg.base + time.Duration(
math.Sin(
float64(when.Sub(g.start))/float64(g.cfg.sinePeriod)*math.Pi*2,
)*float64(
g.cfg.sineAmplitude,
),
)
func (g simpleLatencyGenerator) generateLatency(when time.Time) time.Duration {
var latency time.Duration = 0
elapsed := when.Sub(g.start)
for _, s := range g.summands {
latency += s.getLatency(elapsed)
}
return latency
}
34 changes: 25 additions & 9 deletions latency_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ import (
"github.com/stretchr/testify/assert"
)

func TestSimpleLatencyGenerator(t *testing.T) {
func TestSimpleLatencyGeneratorWithSine(t *testing.T) {
start := time.Now()
g := &simpleLatencyGenerator{
start: start,
cfg: &LatencyCfg{
base: time.Second * 3,
sineAmplitude: time.Second * 2,
sinePeriod: time.Second * 8,
},
}
g := newSimpleLatencyGenerator(start, &LatencyCfg{
base: time.Second * 3,
sineAmplitude: time.Second * 2,
sinePeriod: time.Second * 8,
})

startingVal := g.generateLatency(start)
after2Sec := g.generateLatency(start.Add(time.Second * 2))
after4Sec := g.generateLatency(start.Add(time.Second * 4))
Expand All @@ -26,3 +24,21 @@ func TestSimpleLatencyGenerator(t *testing.T) {
assert.Equal(t, time.Second*3, after4Sec)
assert.Equal(t, time.Second*3, after2Periods)
}

func TestSimpleLatencyGeneratorWithSawtooth(t *testing.T) {
start := time.Now()
g := newSimpleLatencyGenerator(start, &LatencyCfg{
base: time.Second * 3,
sawAmplitute: time.Second * 2,
sawPeriod: time.Second * 8,
})

startingVal := g.generateLatency(start)
after2Sec := g.generateLatency(start.Add(time.Second * 2))
after4Sec := g.generateLatency(start.Add(time.Second * 4))
after2Periods := g.generateLatency(start.Add(time.Second * 16))
assert.Equal(t, time.Second*3, startingVal)
assert.Equal(t, time.Second*4, after2Sec)
assert.Equal(t, time.Second*1, after4Sec)
assert.Equal(t, time.Second*3, after2Periods)
}
14 changes: 14 additions & 0 deletions sawtooth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import "time"

type sawtoothLatencySummand struct {
amplitude time.Duration
period time.Duration
}

func (s sawtoothLatencySummand) getLatency(elapsed time.Duration) time.Duration {
return time.Duration(
(float64((elapsed+s.period/2)%s.period)/float64(s.period))*float64(s.amplitude*2),
) - s.amplitude
}
21 changes: 21 additions & 0 deletions sawtooth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestSawtoothLatencySummand(t *testing.T) {
s := sawtoothLatencySummand{
amplitude: time.Second,
period: time.Minute,
}

assert.Equal(t, s.getLatency(time.Duration(0)), time.Millisecond*0)
assert.Equal(t, s.getLatency(time.Second*15), time.Millisecond*500)
assert.Equal(t, s.getLatency(time.Second*30), time.Millisecond*-1000)
assert.Equal(t, s.getLatency(time.Second*36), time.Millisecond*-800)
assert.Equal(t, s.getLatency(time.Second*60), time.Millisecond*0)
}
20 changes: 20 additions & 0 deletions sine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"math"
"time"
)

type sineLatencySummand struct {
amplitude time.Duration
period time.Duration
}

func (s sineLatencySummand) getLatency(elapsed time.Duration) time.Duration {
return time.Duration(
math.Sin(
float64(elapsed)/float64(s.period)*math.Pi*2,
) * float64(
s.amplitude,
))
}
5 changes: 1 addition & 4 deletions speedbump.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,7 @@ func NewSpeedbump(cfg *SpeedbumpCfg) (*Speedbump, error) {
bufferSize: int(cfg.BufferSize),
srcAddr: *localTCPAddr,
destAddr: *destTCPAddr,
latencyGen: &simpleLatencyGenerator{
start: time.Now(),
cfg: cfg.Latency,
},
latencyGen: newSimpleLatencyGenerator(time.Now(), cfg.Latency),
}
return s, nil
}
Expand Down