diff --git a/README.md b/README.md index aacb1d8..eaecc8c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # speedbump - TCP proxy with variable latency +
speedbump logo
@@ -32,6 +33,7 @@ Spawn a new instance with a base latency of 300ms and a sawtooth wave latency su ``` speedbump --latency=300ms --saw-amplitude=200ms --saw-period=2m --port=2000 localhost:80 ``` +
speedbump sawtooth wave graph
@@ -54,17 +56,19 @@ usage: speedbump [] 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. - --queue-size=1024 Size of the delay queue storing read buffers. - --latency=5ms Base latency added to proxied traffic. - --log-level=INFO Log level. Possible values: DEBUG, TRACE, INFO, WARN, ERROR. - --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. + --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. + --queue-size=1024 Size of the delay queue storing read buffers. + --latency=5ms Base latency added to proxied traffic. + --log-level=INFO Log level. Possible values: DEBUG, TRACE, INFO, WARN, ERROR. + --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. + --square-amplitude=0 Amplitude of the latency square wave. + --square-period=0 Period of the latency square wave. + --version Show application version. Args: TCP proxy destination in host:post format. @@ -78,4 +82,4 @@ Speedbump can be used as a Go library via its `lib` package. Check `lib` [README Copyright Paweł Kuffel 2022, licensed under Apache 2.0 License. -Speedbump logo contains the Go Gopher mascot which was originally designed by Renee French (http://reneefrench.blogspot.com/) and licensed under Creative Commons 3.0 Attributions license. \ No newline at end of file +Speedbump logo contains the Go Gopher mascot which was originally designed by Renee French (http://reneefrench.blogspot.com/) and licensed under Creative Commons 3.0 Attributions license. diff --git a/args.go b/args.go index fe2992d..58142f9 100644 --- a/args.go +++ b/args.go @@ -34,6 +34,12 @@ func parseArgs(args []string) (*lib.SpeedbumpCfg, error) { sawPeriod = app.Flag("saw-period", "Period of the latency sawtooth wave."). PlaceHolder("0"). Duration() + squareAmplitude = app.Flag("square-amplitude", "Amplitude of the latency square wave."). + PlaceHolder("0"). + Duration() + suqarePeriod = app.Flag("square-period", "Period of the latency square wave."). + PlaceHolder("0"). + Duration() destAddr = app.Arg("destination", "TCP proxy destination in host:post format."). Required(). String() @@ -52,11 +58,13 @@ func parseArgs(args []string) (*lib.SpeedbumpCfg, error) { BufferSize: int(*bufferSize), QueueSize: *queueSize, Latency: &lib.LatencyCfg{ - Base: *latency, - SineAmplitude: *sineAmplitude, - SinePeriod: *sinePeriod, - SawAmplitute: *sawAmplitute, - SawPeriod: *sawPeriod, + Base: *latency, + SineAmplitude: *sineAmplitude, + SinePeriod: *sinePeriod, + SawAmplitute: *sawAmplitute, + SawPeriod: *sawPeriod, + SquareAmplitude: *squareAmplitude, + SquarePeriod: *suqarePeriod, }, LogLevel: *logLevel, } diff --git a/args_test.go b/args_test.go index 146931c..e57d3fb 100644 --- a/args_test.go +++ b/args_test.go @@ -30,6 +30,8 @@ func TestParseArgsAll(t *testing.T) { "--latency=100ms", "--sine-amplitude=50ms", "--sine-period=1m", + "--square-amplitude=123ms", + "--square-period=3m", "host:777", }, ) @@ -42,4 +44,6 @@ func TestParseArgsAll(t *testing.T) { 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) + assert.Equal(t, time.Millisecond*123, cfg.Latency.SquareAmplitude) + assert.Equal(t, time.Minute*3, cfg.Latency.SquarePeriod) } diff --git a/lib/latency_generator.go b/lib/latency_generator.go index 3d19daa..76c5f76 100644 --- a/lib/latency_generator.go +++ b/lib/latency_generator.go @@ -9,11 +9,13 @@ type LatencyGenerator interface { } type LatencyCfg struct { - Base time.Duration - SineAmplitude time.Duration - SinePeriod time.Duration - SawAmplitute time.Duration - SawPeriod time.Duration + Base time.Duration + SineAmplitude time.Duration + SinePeriod time.Duration + SawAmplitute time.Duration + SawPeriod time.Duration + SquareAmplitude time.Duration + SquarePeriod time.Duration } type latencySummand interface { @@ -39,6 +41,12 @@ func newSimpleLatencyGenerator(start time.Time, cfg *LatencyCfg) simpleLatencyGe cfg.SawPeriod, }) } + if cfg.SquareAmplitude > 0 && cfg.SquarePeriod > 0 { + summands = append(summands, squareLatencySummand{ + cfg.SquareAmplitude, + cfg.SquarePeriod, + }) + } return simpleLatencyGenerator{ start: start, summands: summands, diff --git a/lib/speedbump_test.go b/lib/speedbump_test.go index 41137da..ff94e96 100644 --- a/lib/speedbump_test.go +++ b/lib/speedbump_test.go @@ -140,7 +140,20 @@ func TestSpeedbumpWithEchoServer(t *testing.T) { assert.Nil(t, err) tcpAddr, _ := net.ResolveTCPAddr("tcp", "localhost:8000") - conn, _ := net.DialTCP("tcp", nil, tcpAddr) + + var conn *net.TCPConn + + // Wait for the speedbump instance to start listening + // since it is started in a separate goroutine, we don't know + // if it has already started listening by this point + for { + conn, err = net.DialTCP("tcp", nil, tcpAddr) + if err != nil { + time.Sleep(10 * time.Millisecond) + } else { + break + } + } firstOpStart := time.Now() diff --git a/lib/square.go b/lib/square.go new file mode 100644 index 0000000..22063cb --- /dev/null +++ b/lib/square.go @@ -0,0 +1,15 @@ +package lib + +import "time" + + +type squareLatencySummand struct { + amplitude time.Duration + period time.Duration +} + +func (s squareLatencySummand) getLatency(elapsed time.Duration) time.Duration { + return time.Duration( + (4 * (elapsed / s.period) - 2 * ((2 * elapsed) / s.period) + 1) * s.amplitude, + ) +} diff --git a/lib/square_test.go b/lib/square_test.go new file mode 100644 index 0000000..54d693d --- /dev/null +++ b/lib/square_test.go @@ -0,0 +1,23 @@ +package lib + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestSquareLatencySummand(t *testing.T) { + s := squareLatencySummand{ + amplitude: time.Second * 2, + period: time.Minute, + } + + assert.Equal(t, s.getLatency(time.Duration(0)), time.Second * 2) + assert.Equal(t, s.getLatency(time.Second * 15), time.Second * 2) + assert.Equal(t, s.getLatency(time.Second * 30), time.Second * -2) + assert.Equal(t, s.getLatency(time.Second * 45), time.Second * -2) + assert.Equal(t, s.getLatency(time.Second * 60), time.Second * 2) + assert.Equal(t, s.getLatency(time.Second * 84), time.Second * 2) + assert.Equal(t, s.getLatency(time.Second * 90), time.Second * -2) +}