2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.cluster.datastore.utils;
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;
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;
33 // FIXME: use Strict runner
34 @RunWith(MockitoJUnitRunner.Silent.class)
35 public class TransactionRateLimiterTest {
37 public ActorUtils actorUtils;
39 public DatastoreContext datastoreContext;
41 public Timer commitTimer;
43 private Timer.Context commitTimerContext;
45 private Snapshot commitSnapshot;
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();
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);
65 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
66 rateLimiter.acquire();
68 assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
69 assertEquals(147, rateLimiter.getPollOnCount());
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);
80 doReturn(TimeUnit.MILLISECONDS.toNanos(0) * 1D).when(commitSnapshot).getValue(0.1);
82 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
83 rateLimiter.acquire();
85 assertThat(rateLimiter.getTxCreationLimit(), approximately(192));
86 assertEquals(97, rateLimiter.getPollOnCount());
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);
98 doReturn(TimeUnit.MILLISECONDS.toNanos(10000) * 1D).when(commitSnapshot).getValue(1.0);
100 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
101 rateLimiter.acquire();
103 assertThat(rateLimiter.getTxCreationLimit(), approximately(282));
104 assertEquals(142, rateLimiter.getPollOnCount());
108 public void testAcquireWithAllPercentileValueVeryHigh() {
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);
116 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
117 rateLimiter.acquire();
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());
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);
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);
136 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
137 rateLimiter.acquire();
139 assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
140 assertEquals(51, rateLimiter.getPollOnCount());
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);
151 Timer operationalCommitTimer = mock(Timer.class);
152 Timer.Context operationalCommitTimerContext = mock(Timer.Context.class);
153 Snapshot operationalCommitSnapshot = mock(Snapshot.class);
155 doReturn(operationalCommitTimer).when(actorUtils).getOperationTimer("operational", "commit");
156 doReturn(operationalCommitTimerContext).when(operationalCommitTimer).time();
157 doReturn(operationalCommitSnapshot).when(operationalCommitTimer).getSnapshot();
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);
166 DatastoreContext.getGlobalDatastoreNames().add("config");
167 DatastoreContext.getGlobalDatastoreNames().add("operational");
169 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
170 rateLimiter.acquire();
172 assertThat(rateLimiter.getTxCreationLimit(), approximately(292));
173 assertEquals(147, rateLimiter.getPollOnCount());
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);
182 final TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
183 final Stopwatch watch = Stopwatch.createStarted();
185 rateLimiter.acquire();
186 rateLimiter.acquire();
187 rateLimiter.acquire();
191 assertThat("did not take as much time as expected rate limit : " + rateLimiter.getTxCreationLimit(),
192 watch.elapsed(), greaterThan(Duration.ofSeconds(1)));
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);
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);
207 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
208 rateLimiter.acquire();
210 assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
211 assertEquals(51, rateLimiter.getPollOnCount());
213 for (int i = 0; i < 49; i++) {
214 rateLimiter.acquire();
217 verify(commitTimer, times(1)).getSnapshot();
219 // Acquiring one more time will cause the re-calculation of the rate limit
220 rateLimiter.acquire();
222 verify(commitTimer, times(2)).getSnapshot();
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);
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);
237 TransactionRateLimiter rateLimiter = new TransactionRateLimiter(actorUtils);
238 rateLimiter.setAcquireCount(Long.MAX_VALUE - 1);
239 rateLimiter.setPollOnCount(Long.MAX_VALUE);
240 rateLimiter.acquire();
242 assertThat(rateLimiter.getTxCreationLimit(), approximately(101));
243 assertEquals(-9223372036854775759L, rateLimiter.getPollOnCount());
245 for (int i = 0; i < 50; i++) {
246 rateLimiter.acquire();
249 verify(commitTimer, times(2)).getSnapshot();
252 public Matcher<Double> approximately(final double val) {
253 return new BaseMatcher<>() {
255 public boolean matches(final Object obj) {
256 Double value = (Double) obj;
257 return value >= val && value <= val + 1;
261 public void describeTo(final Description description) {
262 description.appendText("> " + val + " < " + (val + 1));