BUG 3125 : Set Rate Limit just before acquiring a permit to avoid contention
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / test / java / org / opendaylight / controller / cluster / datastore / utils / TransactionRateLimiterTest.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.cluster.datastore.utils;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertThat;
13 import static org.junit.Assert.assertTrue;
14 import static org.mockito.Mockito.doReturn;
15 import static org.mockito.Mockito.mock;
16 import static org.mockito.Mockito.times;
17 import static org.mockito.Mockito.verify;
18 import com.codahale.metrics.Snapshot;
19 import com.codahale.metrics.Timer;
20 import java.util.concurrent.TimeUnit;
21 import org.apache.commons.lang3.time.StopWatch;
22 import org.hamcrest.BaseMatcher;
23 import org.hamcrest.Description;
24 import org.hamcrest.Matcher;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.mockito.Mock;
28 import org.mockito.MockitoAnnotations;
29 import org.opendaylight.controller.cluster.datastore.DatastoreContext;
30
31 public class TransactionRateLimiterTest {
32
33     @Mock
34     public ActorContext actorContext;
35
36     @Mock
37     public DatastoreContext datastoreContext;
38
39     @Mock
40     public Timer commitTimer;
41
42     @Mock
43     private Timer.Context commitTimerContext;
44
45     @Mock
46     private Snapshot commitSnapshot;
47
48     @Before
49     public void setUp(){
50         MockitoAnnotations.initMocks(this);
51         doReturn(datastoreContext).when(actorContext).getDatastoreContext();
52         doReturn(30).when(datastoreContext).getShardTransactionCommitTimeoutInSeconds();
53         doReturn(100L).when(datastoreContext).getTransactionCreationInitialRateLimit();
54         doReturn(commitTimer).when(actorContext).getOperationTimer("commit");
55         doReturn(commitTimerContext).when(commitTimer).time();
56         doReturn(commitSnapshot).when(commitTimer).getSnapshot();
57     }
58
59     @Test
60     public void testAcquireRateLimitChanged(){
61         for(int i=1;i<11;i++){
62             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
63             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
64             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
65         }
66
67         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
68
69         rateLimiter.acquire();
70
71         assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
72
73         assertEquals(147, rateLimiter.getPollOnCount());
74     }
75
76
77     @Test
78     public void testAcquirePercentileValueZero(){
79
80         for(int i=1;i<11;i++){
81             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
82             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
83             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
84         }
85
86         doReturn(TimeUnit.MILLISECONDS.toNanos(0) * 1D).when(commitSnapshot).getValue(0.1);
87
88         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
89
90         rateLimiter.acquire();
91
92         assertThat(rateLimiter.getTxCreationLimit(), approximately(192));
93
94         assertEquals(97, rateLimiter.getPollOnCount());
95     }
96
97     @Test
98     public void testAcquireOnePercentileValueVeryHigh(){
99
100         for(int i=1;i<11;i++){
101             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
102             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
103             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
104         }
105
106         // ten seconds
107         doReturn(TimeUnit.MILLISECONDS.toNanos(10000) * 1D).when(commitSnapshot).getValue(1.0);
108
109         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
110
111         rateLimiter.acquire();
112
113         assertThat(rateLimiter.getTxCreationLimit(), approximately(282));
114
115         assertEquals(142, rateLimiter.getPollOnCount());
116     }
117
118     @Test
119     public void testAcquireWithAllPercentileValueVeryHigh(){
120
121         for(int i=1;i<11;i++){
122             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
123             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
124             doReturn(TimeUnit.MILLISECONDS.toNanos(10000) * 1D).when(commitSnapshot).getValue(i * 0.1);
125         }
126
127         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
128
129         rateLimiter.acquire();
130
131         // The initial rate limit will be retained here because the calculated rate limit was too small
132         assertThat(rateLimiter.getTxCreationLimit(), approximately(100));
133
134         assertEquals(1, rateLimiter.getPollOnCount());
135     }
136
137     @Test
138     public void testAcquireWithRealPercentileValues(){
139
140         for(int i=1;i<11;i++){
141             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
142             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
143             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
144         }
145
146         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
147         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
148         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
149
150         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
151
152         rateLimiter.acquire();
153
154         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
155
156         assertEquals(51, rateLimiter.getPollOnCount());
157     }
158
159
160
161     @Test
162     public void testAcquireGetRateLimitFromOtherDataStores(){
163         for(int i=1;i<11;i++){
164             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
165             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
166             doReturn(0.0D).when(commitSnapshot).getValue(i * 0.1);
167         }
168
169         Timer operationalCommitTimer = mock(Timer.class);
170         Timer.Context operationalCommitTimerContext = mock(Timer.Context.class);
171         Snapshot operationalCommitSnapshot = mock(Snapshot.class);
172
173         doReturn(operationalCommitTimer).when(actorContext).getOperationTimer("operational", "commit");
174         doReturn(operationalCommitTimerContext).when(operationalCommitTimer).time();
175         doReturn(operationalCommitSnapshot).when(operationalCommitTimer).getSnapshot();
176
177         for(int i=1;i<11;i++){
178             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
179             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
180             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(operationalCommitSnapshot).getValue(i * 0.1);
181         }
182
183
184         DatastoreContext.getGlobalDatastoreTypes().add("config");
185         DatastoreContext.getGlobalDatastoreTypes().add("operational");
186
187         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
188
189         rateLimiter.acquire();
190
191         assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
192
193         assertEquals(147, rateLimiter.getPollOnCount());
194     }
195
196     @Test
197     public void testRateLimiting(){
198
199         for(int i=1;i<11;i++){
200             doReturn(TimeUnit.SECONDS.toNanos(1) * 1D).when(commitSnapshot).getValue(i * 0.1);
201         }
202
203         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
204
205         StopWatch watch = new StopWatch();
206
207         watch.start();
208
209         rateLimiter.acquire();
210         rateLimiter.acquire();
211         rateLimiter.acquire();
212
213         watch.stop();
214
215         assertTrue("did not take as much time as expected rate limit : " + rateLimiter.getTxCreationLimit(),
216                 watch.getTime() > 1000);
217     }
218
219     @Test
220     public void testRateLimitNotCalculatedUntilPollCountReached(){
221
222         for(int i=1;i<11;i++){
223             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
224             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
225             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
226         }
227
228         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
229         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
230         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
231
232         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
233
234         rateLimiter.acquire();
235
236         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
237
238         assertEquals(51, rateLimiter.getPollOnCount());
239
240         for(int i=0;i<49;i++){
241             rateLimiter.acquire();
242         }
243
244         verify(commitTimer, times(1)).getSnapshot();
245
246         // Acquiring one more time will cause the re-calculation of the rate limit
247         rateLimiter.acquire();
248
249         verify(commitTimer, times(2)).getSnapshot();
250     }
251
252     @Test
253     public void testAcquireNegativeAcquireAndPollOnCount(){
254
255         for(int i=1;i<11;i++){
256             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
257             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
258             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
259         }
260
261         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
262         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
263         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
264
265         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorContext);
266         rateLimiter.setAcquireCount(Long.MAX_VALUE-1);
267         rateLimiter.setPollOnCount(Long.MAX_VALUE);
268
269         rateLimiter.acquire();
270
271         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
272
273         assertEquals(-9223372036854775759L, rateLimiter.getPollOnCount());
274
275         for(int i=0;i<50;i++){
276             rateLimiter.acquire();
277         }
278
279         verify(commitTimer, times(2)).getSnapshot();
280
281     }
282
283     public Matcher<Double> approximately(final double val){
284         return new BaseMatcher<Double>() {
285             @Override
286             public boolean matches(Object o) {
287                 Double aDouble = (Double) o;
288                 return aDouble >= val && aDouble <= val+1;
289             }
290
291             @Override
292             public void describeTo(Description description) {
293                 description.appendText("> " + val +" < " + (val+1));
294             }
295         };
296     }
297
298
299 }