Skip to content

Commit

Permalink
add simulated latency (#583)
Browse files Browse the repository at this point in the history
Signed-off-by: James Ranson <[email protected]>
  • Loading branch information
jranson authored Sep 6, 2021
1 parent 072708f commit f319dbd
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 3 deletions.
29 changes: 29 additions & 0 deletions docs/simulated-latency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Simulated Latency

Trickster supports simulating latency on a per-backend basis, and can simulate both consistent and random durations of latency. Simulated latency is introduced in the frontend part of the proxy, and thus works with any backend provider.

In `rule`, `alb` and other backend providers, where a request may transit multiple backend routes, only the Simulated Latency configs associated with the request entrypoint (first route) will be procssed, and not any subsequent routes the request is sent through.

## Consistent Latency Duration

In the Backend configuration, add a `latency_min_ms` value > 0, and the configured amount of latency will be introduced for each incoming request.

## Random Latency

To simulate random latency, set `latency_max_ms` to a value > `latency_min_ms`, which may be 0 for random latency. Trickster will introduce a random amount of latency between (inclusive) the provided values.

## Example Config

```yaml
frontend:
listen_port: 8480

backends:
default:
origin_url: https://www.example.com
provider: reverseproxy
#
# introduce random latency between 50 and 150 milliseconds on each request
latency_min_ms: 50
latency_max_ms: 150
```
6 changes: 6 additions & 0 deletions examples/conf/example.full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,12 @@ backends:
# # default is 0
# shard_step_ms: 0

# # Simulated Latency
# # set latency_min_ms > 0 to apply a consistent latency to each request to this backend
# # set latency_max_ms > latency_max_ms (which can be 0) to apply a random latency between the two
# latency_max_ms = 0
# latency_max_ms = 0

# #
# # Each backend provider implements their own defaults for health checking
# # which can be overridden per backend configuration. See /docs/health.md for more information
Expand Down
24 changes: 21 additions & 3 deletions pkg/backends/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,16 @@ type Options struct {
// fronting backends that only support single range requests
DearticulateUpstreamRanges bool `yaml:"dearticulate_upstream_ranges,omitempty"`

// Simulated Latency
// When LatencyMinMS > 0 and LatencyMaxMS < LatencyMinMS (e.g., 0), then LatencyMinMS of latency
// are applied to the request. When LatencyMaxMS > LatencyMinMS, then a random amount of
// latency between the two values will be applied to the request
//
// LatencyMin is the minimum amount of simulated latency to apply to each incoming request
LatencyMinMS int `yaml:"latency_min_ms"`
// LatencyMax is the maximum amount of simulated latency to apply to each incoming request
LatencyMaxMS int `yaml:"latency_max_ms"`

// Synthesized Configurations
// These configurations are parsed versions of those defined above, and are what Trickster uses internally
//
Expand Down Expand Up @@ -263,6 +273,8 @@ func (o *Options) Clone() *Options {
no.FastForwardTTLMS = o.FastForwardTTLMS
no.ForwardedHeaders = o.ForwardedHeaders
no.Host = o.Host
no.LatencyMinMS = o.LatencyMinMS
no.LatencyMaxMS = o.LatencyMaxMS
no.Name = o.Name
no.IsDefault = o.IsDefault
no.KeepAliveTimeoutMS = o.KeepAliveTimeoutMS
Expand Down Expand Up @@ -360,9 +372,7 @@ func (l Lookup) Validate(ncl negative.Lookups) error {
if err != nil {
return err
}
if strings.HasSuffix(url.Path, "/") {
url.Path = url.Path[0 : len(url.Path)-1]
}
url.Path = strings.TrimSuffix(url.Path, "/")
o.Name = k
o.Scheme = url.Scheme
o.Host = url.Host
Expand Down Expand Up @@ -679,6 +689,14 @@ func SetDefaults(
no.Prometheus = o.Prometheus.Clone()
}

if metadata.IsDefined("backends", name, "latency_min_ms") {
no.LatencyMinMS = o.LatencyMinMS
}

if metadata.IsDefined("backends", name, "latency_max_ms") {
no.LatencyMaxMS = o.LatencyMaxMS
}

return no, nil
}

Expand Down
22 changes: 22 additions & 0 deletions pkg/util/middleware/config_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
package middleware

import (
"math/rand"
"net/http"
"time"

"github.com/trickstercache/trickster/v2/pkg/backends"
bo "github.com/trickstercache/trickster/v2/pkg/backends/options"
Expand All @@ -28,6 +30,10 @@ import (
"github.com/trickstercache/trickster/v2/pkg/proxy/request"
)

func init() {
rand.Seed(time.Now().UnixNano())
}

// WithResourcesContext ...
func WithResourcesContext(client backends.Backend, o *bo.Options,
c cache.Cache, p *po.Options, t *tracing.Tracer,
Expand All @@ -46,6 +52,22 @@ func WithResourcesContext(client backends.Backend, o *bo.Options,
return
}
rsc.Merge(resources)

if o != nil && (o.LatencyMinMS > 0 || o.LatencyMaxMS > 0) {
processSimulatedLatency(o.LatencyMinMS, o.LatencyMaxMS)
}

next.ServeHTTP(w, r.WithContext(context.WithResources(r.Context(), rsc)))
})
}

func processSimulatedLatency(minMS, maxMS int) {
if (minMS == 0 && maxMS == 0) || (minMS < 0 || maxMS < 0) {
return
}
if minMS >= maxMS {
time.Sleep(time.Duration(minMS) * time.Millisecond)
}
diff := int64(maxMS - minMS)
time.Sleep(time.Duration((rand.Int63()%diff)+int64(minMS)) * time.Millisecond)
}

0 comments on commit f319dbd

Please sign in to comment.