1// Copyright 2018 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package lease
16
17import (
18	"math/rand"
19	"os"
20	"testing"
21	"time"
22
23	"go.etcd.io/etcd/mvcc/backend"
24	"go.uber.org/zap"
25)
26
27func BenchmarkLessorGrant1000(b *testing.B)   { benchmarkLessorGrant(1000, b) }
28func BenchmarkLessorGrant100000(b *testing.B) { benchmarkLessorGrant(100000, b) }
29
30func BenchmarkLessorRevoke1000(b *testing.B)   { benchmarkLessorRevoke(1000, b) }
31func BenchmarkLessorRevoke100000(b *testing.B) { benchmarkLessorRevoke(100000, b) }
32
33func BenchmarkLessorRenew1000(b *testing.B)   { benchmarkLessorRenew(1000, b) }
34func BenchmarkLessorRenew100000(b *testing.B) { benchmarkLessorRenew(100000, b) }
35
36// Use findExpired10000 replace findExpired1000, which takes too long.
37func BenchmarkLessorFindExpired10000(b *testing.B)  { benchmarkLessorFindExpired(10000, b) }
38func BenchmarkLessorFindExpired100000(b *testing.B) { benchmarkLessorFindExpired(100000, b) }
39
40func init() {
41	rand.Seed(time.Now().UTC().UnixNano())
42}
43
44const (
45	// minTTL keep lease will not auto expire in benchmark
46	minTTL = 1000
47	// maxTTL control repeat probability of ttls
48	maxTTL = 2000
49)
50
51func randomTTL(n int, min, max int64) (out []int64) {
52	for i := 0; i < n; i++ {
53		out = append(out, rand.Int63n(max-min)+min)
54	}
55	return out
56}
57
58// demote lessor from being the primary, but don't change any lease's expiry
59func demote(le *lessor) {
60	le.mu.Lock()
61	defer le.mu.Unlock()
62	close(le.demotec)
63	le.demotec = nil
64}
65
66// return new lessor and tearDown to release resource
67func setUp() (le *lessor, tearDown func()) {
68	lg := zap.NewNop()
69	be, tmpPath := backend.NewDefaultTmpBackend()
70	// MinLeaseTTL is negative, so we can grant expired lease in benchmark.
71	// ExpiredLeasesRetryInterval should small, so benchmark of findExpired will recheck expired lease.
72	le = newLessor(lg, be, LessorConfig{MinLeaseTTL: -1000, ExpiredLeasesRetryInterval: 10 * time.Microsecond})
73	le.SetRangeDeleter(func() TxnDelete {
74		ftd := &FakeTxnDelete{be.BatchTx()}
75		ftd.Lock()
76		return ftd
77	})
78	le.Promote(0)
79
80	return le, func() {
81		le.Stop()
82		be.Close()
83		os.Remove(tmpPath)
84	}
85}
86
87func benchmarkLessorGrant(benchSize int, b *testing.B) {
88	ttls := randomTTL(benchSize, minTTL, maxTTL)
89
90	var le *lessor
91	var tearDown func()
92
93	b.ResetTimer()
94	for i := 0; i < b.N; {
95		b.StopTimer()
96		if tearDown != nil {
97			tearDown()
98			tearDown = nil
99		}
100		le, tearDown = setUp()
101		b.StartTimer()
102
103		for j := 1; j <= benchSize; j++ {
104			le.Grant(LeaseID(j), ttls[j-1])
105		}
106		i += benchSize
107	}
108	b.StopTimer()
109
110	if tearDown != nil {
111		tearDown()
112	}
113}
114
115func benchmarkLessorRevoke(benchSize int, b *testing.B) {
116	ttls := randomTTL(benchSize, minTTL, maxTTL)
117
118	var le *lessor
119	var tearDown func()
120	b.ResetTimer()
121	for i := 0; i < b.N; i++ {
122		b.StopTimer()
123		if tearDown != nil {
124			tearDown()
125			tearDown = nil
126		}
127		le, tearDown = setUp()
128		for j := 1; j <= benchSize; j++ {
129			le.Grant(LeaseID(j), ttls[j-1])
130		}
131		b.StartTimer()
132
133		for j := 1; j <= benchSize; j++ {
134			le.Revoke(LeaseID(j))
135		}
136		i += benchSize
137	}
138	b.StopTimer()
139
140	if tearDown != nil {
141		tearDown()
142	}
143}
144
145func benchmarkLessorRenew(benchSize int, b *testing.B) {
146	ttls := randomTTL(benchSize, minTTL, maxTTL)
147
148	var le *lessor
149	var tearDown func()
150
151	b.ResetTimer()
152	for i := 0; i < b.N; {
153		b.StopTimer()
154		if tearDown != nil {
155			tearDown()
156			tearDown = nil
157		}
158		le, tearDown = setUp()
159		for j := 1; j <= benchSize; j++ {
160			le.Grant(LeaseID(j), ttls[j-1])
161		}
162		b.StartTimer()
163
164		for j := 1; j <= benchSize; j++ {
165			le.Renew(LeaseID(j))
166		}
167		i += benchSize
168	}
169	b.StopTimer()
170
171	if tearDown != nil {
172		tearDown()
173	}
174}
175
176func benchmarkLessorFindExpired(benchSize int, b *testing.B) {
177	// 50% lease are expired.
178	ttls := randomTTL(benchSize, -500, 500)
179	findExpiredLimit := 50
180
181	var le *lessor
182	var tearDown func()
183
184	b.ResetTimer()
185	for i := 0; i < b.N; {
186		b.StopTimer()
187		if tearDown != nil {
188			tearDown()
189			tearDown = nil
190		}
191		le, tearDown = setUp()
192		for j := 1; j <= benchSize; j++ {
193			le.Grant(LeaseID(j), ttls[j-1])
194		}
195		// lessor's runLoop should not call findExpired
196		demote(le)
197		b.StartTimer()
198
199		// refresh fixture after pop all expired lease
200		for ; ; i++ {
201			le.mu.Lock()
202			ls := le.findExpiredLeases(findExpiredLimit)
203			if len(ls) == 0 {
204				break
205			}
206			le.mu.Unlock()
207
208			// simulation: revoke lease after expired
209			b.StopTimer()
210			for _, lease := range ls {
211				le.Revoke(lease.ID)
212			}
213			b.StartTimer()
214		}
215	}
216	b.StopTimer()
217
218	if tearDown != nil {
219		tearDown()
220	}
221}
222