carlos / todomvc-bakeoff Public

Clone
git clone https://amadan.net/carlos/todomvc-bakeoff

Plain git — no account needed to clone.

Download

Download this file

1package bakeoff
2
3// A sweep doubles the concurrent connections (1, 2, 4, …) and runs one
4// short load step at each, to find where an app starts to slow down.
5
6// flatGain is the throughput gain below which doubling the load counts
7// as no real gain: extra connections only add waiting.
8const flatGain = 1.10
9
10// maxErrorRate stops a sweep: past it, the numbers describe failures.
11const maxErrorRate = 0.01
12
13// KneeResult says where a sweep stopped scaling.
14type KneeResult struct {
15 Found bool // false: still scaling at MaxTested
16 Concurrency int // the last level that still scaled
17 PeakRPS float64 // the most requests per second any level reached
18 P50Ms float64 // median latency at Concurrency
19 MaxTested int
20 ErrorsAt int // the first level with more than 1% errors, if any
21 // ErrorsStatus is the commonest failing status at ErrorsAt: "503" when
22 // Carloku's edge turned requests away, "502" when the app didn't answer.
23 ErrorsStatus string
24}
25
26// Knee finds where the app slows down: the fewest connections from which
27// no further load in the sweep raised throughput by 10% or more. A single
28// noisy step can't end the search early, because later growth resets it.
29// A step with more than 1% errors ends the sweep: the app failed there
30// rather than slowed, so the last clean step is where it stopped coping,
31// and peak throughput counts clean steps only.
32func Knee(sweep []LoadResult) KneeResult {
33 var k KneeResult
34 recorded := false
35 for _, r := range sweep {
36 recorded = recorded || len(r.Status) > 0
37 }
38 for i, r := range sweep {
39 // A step with no responses at all, in a sweep that records them,
40 // stalled: nothing completed. (Older results stored oha's rate from
41 // aborted requests and no errors.)
42 if recorded && len(r.Status) == 0 {
43 k.ErrorsAt, k.ErrorsStatus = r.Concurrency, "stalled"
44 sweep = sweep[:i]
45 break
46 }
47 if r.ErrorRate > maxErrorRate {
48 k.ErrorsAt = r.Concurrency
49 n := 0
50 for code, c := range r.Status {
51 if code != "200" && code != "303" && c > n {
52 k.ErrorsStatus, n = code, c
53 }
54 }
55 sweep = sweep[:i]
56 break
57 }
58 }
59 for _, r := range sweep {
60 if r.RPS > k.PeakRPS {
61 k.PeakRPS = r.RPS
62 }
63 k.MaxTested = r.Concurrency
64 }
65 for i, r := range sweep {
66 if i == len(sweep)-1 {
67 break
68 }
69 gains := false
70 for _, later := range sweep[i+1:] {
71 if later.RPS >= r.RPS*flatGain {
72 gains = true
73 break
74 }
75 }
76 if !gains {
77 k.Found, k.Concurrency, k.P50Ms = true, r.Concurrency, r.P50Ms
78 break
79 }
80 }
81 if !k.Found && k.ErrorsAt > 0 && len(sweep) > 0 {
82 last := sweep[len(sweep)-1]
83 k.Found, k.Concurrency, k.P50Ms = true, last.Concurrency, last.P50Ms
84 }
85 return k
86}
87
88// SweepShouldStop is true once the last step had too many errors, or the
89// last two doublings were both flat.
90func SweepShouldStop(sweep []LoadResult) bool {
91 n := len(sweep)
92 if n == 0 {
93 return false
94 }
95 if sweep[n-1].ErrorRate > maxErrorRate {
96 return true
97 }
98 return n >= 3 && sweep[n-1].RPS < sweep[n-2].RPS*flatGain && sweep[n-2].RPS < sweep[n-3].RPS*flatGain
99}
100