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
+
@@ -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
```
+
@@ -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)
+}