5c7cdc6789774a51997c4e09bbc724c50de61593
[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 package org.opendaylight.controller.cluster.datastore.utils;
9
10 import static org.hamcrest.MatcherAssert.assertThat;
11 import static org.hamcrest.Matchers.greaterThan;
12 import static org.junit.Assert.assertEquals;
13 import static org.mockito.Mockito.doReturn;
14 import static org.mockito.Mockito.mock;
15 import static org.mockito.Mockito.times;
16 import static org.mockito.Mockito.verify;
17
18 import com.codahale.metrics.Snapshot;
19 import com.codahale.metrics.Timer;
20 import com.google.common.base.Stopwatch;
21 import java.time.Duration;
22 import java.util.concurrent.TimeUnit;
23 import org.hamcrest.BaseMatcher;
24 import org.hamcrest.Description;
25 import org.hamcrest.Matcher;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.junit.runner.RunWith;
29 import org.mockito.Mock;
30 import org.mockito.junit.MockitoJUnitRunner;
31 import org.opendaylight.controller.cluster.datastore.DatastoreContext;
32
33 // FIXME: use Strict runner
34 @RunWith(MockitoJUnitRunner.Silent.class)
35 public class TransactionRateLimiterTest {
36     @Mock
37     public ActorUtils actorUtils;
38     @Mock
39     public DatastoreContext datastoreContext;
40     @Mock
41     public Timer commitTimer;
42     @Mock
43     private Timer.Context commitTimerContext;
44     @Mock
45     private Snapshot commitSnapshot;
46
47     @Before
48     public void setUp() {
49         doReturn(datastoreContext).when(actorUtils).getDatastoreContext();
50         doReturn(30).when(datastoreContext).getShardTransactionCommitTimeoutInSeconds();
51         doReturn(100L).when(datastoreContext).getTransactionCreationInitialRateLimit();
52         doReturn(commitTimer).when(actorUtils).getOperationTimer("commit");
53         doReturn(commitTimerContext).when(commitTimer).time();
54         doReturn(commitSnapshot).when(commitTimer).getSnapshot();
55     }
56
57     @Test
58     public void testAcquireRateLimitChanged() {
59         for (int i = 1; i < 11; i++) {
60             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
61             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
62             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
63         }
64
65         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
66         rateLimiter.acquire();
67
68         assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
69         assertEquals(147, rateLimiter.getPollOnCount());
70     }
71
72     @Test
73     public void testAcquirePercentileValueZero() {
74         for (int i = 1; i < 11; i++) {
75             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
76             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
77             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
78         }
79
80         doReturn(TimeUnit.MILLISECONDS.toNanos(0) * 1D).when(commitSnapshot).getValue(0.1);
81
82         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
83         rateLimiter.acquire();
84
85         assertThat(rateLimiter.getTxCreationLimit(), approximately(192));
86         assertEquals(97, rateLimiter.getPollOnCount());
87     }
88
89     @Test
90     public void testAcquireOnePercentileValueVeryHigh() {
91         for (int i = 1; i < 11; i++) {
92             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
93             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
94             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(commitSnapshot).getValue(i * 0.1);
95         }
96
97         // ten seconds
98         doReturn(TimeUnit.MILLISECONDS.toNanos(10000) * 1D).when(commitSnapshot).getValue(1.0);
99
100         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
101         rateLimiter.acquire();
102
103         assertThat(rateLimiter.getTxCreationLimit(), approximately(282));
104         assertEquals(142, rateLimiter.getPollOnCount());
105     }
106
107     @Test
108     public void testAcquireWithAllPercentileValueVeryHigh() {
109
110         for (int i = 1; i < 11; i++) {
111             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
112             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
113             doReturn(TimeUnit.MILLISECONDS.toNanos(10000) * 1D).when(commitSnapshot).getValue(i * 0.1);
114         }
115
116         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
117         rateLimiter.acquire();
118
119         // The initial rate limit will be retained here because the calculated rate limit was too small
120         assertThat(rateLimiter.getTxCreationLimit(), approximately(100));
121         assertEquals(1, rateLimiter.getPollOnCount());
122     }
123
124     @Test
125     public void testAcquireWithRealPercentileValues() {
126         for (int i = 1; i < 11; i++) {
127             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
128             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
129             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
130         }
131
132         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
133         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
134         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
135
136         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
137         rateLimiter.acquire();
138
139         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
140         assertEquals(51, rateLimiter.getPollOnCount());
141     }
142
143     @Test
144     public void testAcquireGetRateLimitFromOtherDataStores() {
145         for (int i = 1; i < 11; i++) {
146             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
147             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
148             doReturn(0.0D).when(commitSnapshot).getValue(i * 0.1);
149         }
150
151         Timer operationalCommitTimer = mock(Timer.class);
152         Timer.Context operationalCommitTimerContext = mock(Timer.Context.class);
153         Snapshot operationalCommitSnapshot = mock(Snapshot.class);
154
155         doReturn(operationalCommitTimer).when(actorUtils).getOperationTimer("operational", "commit");
156         doReturn(operationalCommitTimerContext).when(operationalCommitTimer).time();
157         doReturn(operationalCommitSnapshot).when(operationalCommitTimer).getSnapshot();
158
159         for (int i = 1; i < 11; i++) {
160             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
161             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
162             doReturn(TimeUnit.MILLISECONDS.toNanos(i) * 1D).when(operationalCommitSnapshot).getValue(i * 0.1);
163         }
164
165
166         DatastoreContext.getGlobalDatastoreNames().add("config");
167         DatastoreContext.getGlobalDatastoreNames().add("operational");
168
169         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
170         rateLimiter.acquire();
171
172         assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
173         assertEquals(147, rateLimiter.getPollOnCount());
174     }
175
176     @Test
177     public void testRateLimiting() {
178         for (int i = 1; i < 11; i++) {
179             doReturn(TimeUnit.SECONDS.toNanos(1) * 1D).when(commitSnapshot).getValue(i * 0.1);
180         }
181
182         final TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
183         final Stopwatch watch = Stopwatch.createStarted();
184
185         rateLimiter.acquire();
186         rateLimiter.acquire();
187         rateLimiter.acquire();
188
189         watch.stop();
190
191         assertThat("did not take as much time as expected rate limit : " + rateLimiter.getTxCreationLimit(),
192             watch.elapsed(), greaterThan(Duration.ofSeconds(1)));
193     }
194
195     @Test
196     public void testRateLimitNotCalculatedUntilPollCountReached() {
197         for (int i = 1; i < 11; i++) {
198             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
199             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
200             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
201         }
202
203         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
204         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
205         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
206
207         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
208         rateLimiter.acquire();
209
210         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
211         assertEquals(51, rateLimiter.getPollOnCount());
212
213         for (int i = 0; i < 49; i++) {
214             rateLimiter.acquire();
215         }
216
217         verify(commitTimer, times(1)).getSnapshot();
218
219         // Acquiring one more time will cause the re-calculation of the rate limit
220         rateLimiter.acquire();
221
222         verify(commitTimer, times(2)).getSnapshot();
223     }
224
225     @Test
226     public void testAcquireNegativeAcquireAndPollOnCount() {
227         for (int i = 1; i < 11; i++) {
228             // Keep on increasing the amount of time it takes to complete transaction for each tenth of a
229             // percentile. Essentially this would be 1ms for the 10th percentile, 2ms for 20th percentile and so on.
230             doReturn(TimeUnit.MILLISECONDS.toNanos(8) * 1D).when(commitSnapshot).getValue(i * 0.1);
231         }
232
233         doReturn(TimeUnit.MILLISECONDS.toNanos(20) * 1D).when(commitSnapshot).getValue(0.7);
234         doReturn(TimeUnit.MILLISECONDS.toNanos(100) * 1D).when(commitSnapshot).getValue(0.9);
235         doReturn(TimeUnit.MILLISECONDS.toNanos(200) * 1D).when(commitSnapshot).getValue(1.0);
236
237         TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
238         rateLimiter.setAcquireCount(Long.MAX_VALUE - 1);
239         rateLimiter.setPollOnCount(Long.MAX_VALUE);
240         rateLimiter.acquire();
241
242         assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
243         assertEquals(-9223372036854775759L, rateLimiter.getPollOnCount());
244
245         for (int i = 0; i < 50; i++) {
246             rateLimiter.acquire();
247         }
248
249         verify(commitTimer, times(2)).getSnapshot();
250     }
251
252     public Matcher<Double> approximately(final double val) {
253         return new BaseMatcher<>() {
254             @Override
255             public boolean matches(final Object obj) {
256                 Double value = (Double) obj;
257                 return value >= val && value <= val + 1;
258             }
259
260             @Override
261             public void describeTo(final Description description) {
262                 description.appendText("> " + val + " < " + (val + 1));
263             }
264         };
265     }
266 }