File: slicer.go

package info (click to toggle)
toxiproxy 2.0.0%2Bdfsg1-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 388 kB
  • ctags: 321
  • sloc: sh: 91; makefile: 59
file content (83 lines) | stat: -rw-r--r-- 2,202 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package toxics

import (
	"math/rand"
	"time"

	"github.com/Shopify/toxiproxy/stream"
)

// The SlicerToxic slices data into multiple smaller packets
// to simulate real-world TCP behaviour.
type SlicerToxic struct {
	// Average number of bytes to slice at
	AverageSize int `json:"average_size"`
	// +/- bytes to vary sliced amounts. Must be less than
	// the average size
	SizeVariation int `json:"size_variation"`
	// Microseconds to delay each packet. May be useful since there's
	// usually some kind of buffering of network data
	Delay int `json:"delay"`
}

// Returns a list of chunk offsets to slice up a packet of the
// given total size. For example, for a size of 100, output might be:
//
//     []int{0, 18, 18, 43, 43, 67, 67, 77, 77, 100}
//           ^---^  ^----^  ^----^  ^----^  ^-----^
//
// This tries to get fairly evenly-varying chunks (no tendency
// to have a small/large chunk at the start/end).
func (t *SlicerToxic) chunk(start int, end int) []int {
	// Base case:
	// If the size is within the random varation, _or already
	// less than the average size_, just return it.
	// Otherwise split the chunk in about two, and recurse.
	if (end-start)-t.AverageSize <= t.SizeVariation {
		return []int{start, end}
	}

	// +1 in the size variation to offset favoring of smaller
	// numbers by integer division
	mid := start + (end-start)/2 + (rand.Intn(t.SizeVariation*2) - t.SizeVariation) + rand.Intn(1)
	left := t.chunk(start, mid)
	right := t.chunk(mid, end)

	return append(left, right...)
}

func (t *SlicerToxic) Pipe(stub *ToxicStub) {
	for {
		select {
		case <-stub.Interrupt:
			return
		case c := <-stub.Input:
			if c == nil {
				stub.Close()
				return
			}

			chunks := t.chunk(0, len(c.Data))
			for i := 1; i < len(chunks); i += 2 {
				stub.Output <- &stream.StreamChunk{
					Data:      c.Data[chunks[i-1]:chunks[i]],
					Timestamp: c.Timestamp,
				}

				select {
				case <-stub.Interrupt:
					stub.Output <- &stream.StreamChunk{
						Data:      c.Data[chunks[i]:],
						Timestamp: c.Timestamp,
					}
					return
				case <-time.After(time.Duration(t.Delay) * time.Microsecond):
				}
			}
		}
	}
}

func init() {
	Register("slicer", new(SlicerToxic))
}