Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 1 | /* |
Brian O'Connor | a09fe5b | 2017-08-03 21:12:30 -0700 | [diff] [blame] | 2 | * Copyright 2017-present Open Networking Foundation |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | package org.onosproject.primitiveperf; |
| 17 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 18 | import java.util.ArrayList; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 19 | import java.util.Dictionary; |
| 20 | import java.util.List; |
| 21 | import java.util.Map; |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 22 | import java.util.Random; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 23 | import java.util.Timer; |
| 24 | import java.util.TimerTask; |
| 25 | import java.util.TreeMap; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 26 | import java.util.concurrent.ExecutorService; |
| 27 | import java.util.concurrent.Executors; |
| 28 | import java.util.concurrent.TimeUnit; |
| 29 | import java.util.concurrent.atomic.AtomicLong; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 30 | |
| 31 | import com.google.common.collect.Lists; |
| 32 | import org.apache.felix.scr.annotations.Activate; |
| 33 | import org.apache.felix.scr.annotations.Component; |
| 34 | import org.apache.felix.scr.annotations.Deactivate; |
| 35 | import org.apache.felix.scr.annotations.Modified; |
| 36 | import org.apache.felix.scr.annotations.Property; |
| 37 | import org.apache.felix.scr.annotations.Reference; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 38 | import org.apache.felix.scr.annotations.Service; |
| 39 | import org.onosproject.cfg.ComponentConfigService; |
| 40 | import org.onosproject.cluster.ClusterService; |
| 41 | import org.onosproject.cluster.ControllerNode; |
| 42 | import org.onosproject.cluster.NodeId; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 43 | import org.onosproject.store.serializers.KryoNamespaces; |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 44 | import org.onosproject.store.service.AtomicValue; |
| 45 | import org.onosproject.store.service.AtomicValueEventListener; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 46 | import org.onosproject.store.service.ConsistentMap; |
| 47 | import org.onosproject.store.service.Serializer; |
| 48 | import org.onosproject.store.service.StorageService; |
| 49 | import org.osgi.service.component.ComponentContext; |
| 50 | import org.slf4j.Logger; |
| 51 | |
| 52 | import static com.google.common.base.Strings.isNullOrEmpty; |
| 53 | import static java.lang.System.currentTimeMillis; |
| 54 | import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY; |
| 55 | import static org.onlab.util.Tools.get; |
| 56 | import static org.onlab.util.Tools.groupedThreads; |
| 57 | import static org.slf4j.LoggerFactory.getLogger; |
| 58 | |
| 59 | /** |
| 60 | * Application to test sustained primitive throughput. |
| 61 | */ |
| 62 | @Component(immediate = true) |
| 63 | @Service(value = PrimitivePerfApp.class) |
| 64 | public class PrimitivePerfApp { |
| 65 | |
| 66 | private final Logger log = getLogger(getClass()); |
| 67 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 68 | private static final int DEFAULT_NUM_CLIENTS = 8; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 69 | private static final int DEFAULT_WRITE_PERCENTAGE = 100; |
| 70 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 71 | private static final int DEFAULT_NUM_KEYS = 100_000; |
| 72 | private static final int DEFAULT_KEY_LENGTH = 32; |
| 73 | private static final int DEFAULT_NUM_UNIQUE_VALUES = 100; |
| 74 | private static final int DEFAULT_VALUE_LENGTH = 1024; |
| 75 | private static final boolean DEFAULT_INCLUDE_EVENTS = false; |
| 76 | private static final boolean DEFAULT_DETERMINISTIC = true; |
| 77 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 78 | private static final int REPORT_PERIOD = 1_000; //ms |
| 79 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 80 | private static final char[] CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray(); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 81 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 82 | @Property( |
| 83 | name = "numClients", |
| 84 | intValue = DEFAULT_NUM_CLIENTS, |
| 85 | label = "Number of clients to use to submit writes") |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 86 | private int numClients = DEFAULT_NUM_CLIENTS; |
| 87 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 88 | @Property( |
| 89 | name = "writePercentage", |
| 90 | intValue = DEFAULT_WRITE_PERCENTAGE, |
| 91 | label = "Percentage of operations to perform as writes") |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 92 | private int writePercentage = DEFAULT_WRITE_PERCENTAGE; |
| 93 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 94 | @Property( |
| 95 | name = "numKeys", |
| 96 | intValue = DEFAULT_NUM_KEYS, |
| 97 | label = "Number of unique keys to write") |
| 98 | private int numKeys = DEFAULT_NUM_KEYS; |
| 99 | |
| 100 | @Property( |
| 101 | name = "keyLength", |
| 102 | intValue = DEFAULT_KEY_LENGTH, |
| 103 | label = "Key length") |
| 104 | private int keyLength = DEFAULT_KEY_LENGTH; |
| 105 | |
| 106 | @Property( |
| 107 | name = "numValues", |
| 108 | intValue = DEFAULT_NUM_UNIQUE_VALUES, |
| 109 | label = "Number of unique values to write") |
| 110 | private int numValues = DEFAULT_NUM_UNIQUE_VALUES; |
| 111 | |
| 112 | @Property( |
| 113 | name = "valueLength", |
| 114 | intValue = DEFAULT_VALUE_LENGTH, |
| 115 | label = "Value length") |
| 116 | private int valueLength = DEFAULT_VALUE_LENGTH; |
| 117 | |
| 118 | @Property( |
| 119 | name = "includeEvents", |
| 120 | boolValue = DEFAULT_INCLUDE_EVENTS, |
| 121 | label = "Whether to include events in test") |
| 122 | private boolean includeEvents = DEFAULT_INCLUDE_EVENTS; |
| 123 | |
| 124 | @Property( |
| 125 | name = "deterministic", |
| 126 | boolValue = DEFAULT_DETERMINISTIC, |
| 127 | label = "Whether to deterministically populate entries") |
| 128 | private boolean deterministic = DEFAULT_DETERMINISTIC; |
| 129 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 130 | @Reference(cardinality = MANDATORY_UNARY) |
| 131 | protected ClusterService clusterService; |
| 132 | |
| 133 | @Reference(cardinality = MANDATORY_UNARY) |
| 134 | protected StorageService storageService; |
| 135 | |
| 136 | @Reference(cardinality = MANDATORY_UNARY) |
| 137 | protected ComponentConfigService configService; |
| 138 | |
| 139 | @Reference(cardinality = MANDATORY_UNARY) |
| 140 | protected PrimitivePerfCollector sampleCollector; |
| 141 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 142 | private ExecutorService messageHandlingExecutor; |
| 143 | |
| 144 | private ExecutorService workers; |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 145 | |
| 146 | // Tracks whether the test has been started in the cluster. |
| 147 | private AtomicValue<Boolean> started; |
| 148 | private AtomicValueEventListener<Boolean> startedListener = event -> { |
| 149 | if (event.newValue()) { |
| 150 | startTestRun(); |
| 151 | } else { |
| 152 | stopTestRun(); |
| 153 | } |
| 154 | }; |
| 155 | |
| 156 | // Tracks whether local clients are running. |
| 157 | private volatile boolean running; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 158 | |
| 159 | private Timer reportTimer; |
| 160 | |
| 161 | private NodeId nodeId; |
| 162 | private TimerTask reporterTask; |
| 163 | |
| 164 | private long startTime; |
| 165 | private long currentStartTime; |
| 166 | private AtomicLong overallCounter; |
| 167 | private AtomicLong currentCounter; |
| 168 | |
| 169 | @Activate |
| 170 | public void activate(ComponentContext context) { |
| 171 | configService.registerProperties(getClass()); |
| 172 | |
| 173 | nodeId = clusterService.getLocalNode().id(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 174 | started = storageService.<Boolean>atomicValueBuilder() |
| 175 | .withName("perf-test-started") |
| 176 | .withSerializer(Serializer.using(KryoNamespaces.BASIC)) |
| 177 | .build() |
| 178 | .asAtomicValue(); |
| 179 | started.addListener(startedListener); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 180 | |
| 181 | reportTimer = new Timer("onos-primitive-perf-reporter"); |
| 182 | |
| 183 | messageHandlingExecutor = Executors.newSingleThreadExecutor( |
| 184 | groupedThreads("onos/perf", "command-handler")); |
| 185 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 186 | // TODO: investigate why this seems to be necessary for configs to get picked up on initial activation |
| 187 | modify(context); |
| 188 | } |
| 189 | |
| 190 | @Deactivate |
| 191 | public void deactivate() { |
| 192 | stopTestRun(); |
| 193 | |
| 194 | configService.unregisterProperties(getClass(), false); |
| 195 | messageHandlingExecutor.shutdown(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 196 | started.removeListener(startedListener); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 197 | |
| 198 | if (reportTimer != null) { |
| 199 | reportTimer.cancel(); |
| 200 | reportTimer = null; |
| 201 | } |
| 202 | } |
| 203 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 204 | private int parseInt(Dictionary<?, ?> properties, String name, int currentValue, int defaultValue) { |
| 205 | try { |
| 206 | String s = get(properties, name); |
| 207 | return isNullOrEmpty(s) ? currentValue : Integer.parseInt(s.trim()); |
| 208 | } catch (NumberFormatException | ClassCastException e) { |
| 209 | log.warn("Malformed configuration detected; using defaults", e); |
| 210 | return defaultValue; |
| 211 | } |
| 212 | } |
| 213 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 214 | @Modified |
| 215 | public void modify(ComponentContext context) { |
| 216 | if (context == null) { |
| 217 | logConfig("Reconfigured"); |
| 218 | return; |
| 219 | } |
| 220 | |
| 221 | Dictionary<?, ?> properties = context.getProperties(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 222 | int newNumClients = parseInt(properties, "numClients", numClients, DEFAULT_NUM_CLIENTS); |
| 223 | int newWritePercentage = parseInt(properties, "writePercentage", writePercentage, DEFAULT_WRITE_PERCENTAGE); |
| 224 | int newNumKeys = parseInt(properties, "numKeys", numKeys, DEFAULT_NUM_KEYS); |
| 225 | int newKeyLength = parseInt(properties, "keyLength", keyLength, DEFAULT_KEY_LENGTH); |
| 226 | int newNumValues = parseInt(properties, "numValues", numValues, DEFAULT_NUM_UNIQUE_VALUES); |
| 227 | int newValueLength = parseInt(properties, "valueLength", valueLength, DEFAULT_VALUE_LENGTH); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 228 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 229 | String includeEventsString = get(properties, "includeEvents"); |
| 230 | boolean newIncludeEvents = isNullOrEmpty(includeEventsString) |
| 231 | ? includeEvents |
| 232 | : Boolean.parseBoolean(includeEventsString.trim()); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 233 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 234 | String deterministicString = get(properties, "deterministic"); |
| 235 | boolean newDeterministic = isNullOrEmpty(deterministicString) |
| 236 | ? includeEvents |
| 237 | : Boolean.parseBoolean(deterministicString.trim()); |
| 238 | |
| 239 | if (newNumClients != numClients |
| 240 | || newWritePercentage != writePercentage |
| 241 | || newNumKeys != numKeys |
| 242 | || newKeyLength != keyLength |
| 243 | || newNumValues != numValues |
| 244 | || newValueLength != valueLength |
| 245 | || newIncludeEvents != includeEvents |
| 246 | || newDeterministic != deterministic) { |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 247 | numClients = newNumClients; |
| 248 | writePercentage = newWritePercentage; |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 249 | numKeys = newNumKeys; |
| 250 | keyLength = newKeyLength; |
| 251 | numValues = newNumValues; |
| 252 | valueLength = newValueLength; |
| 253 | includeEvents = newIncludeEvents; |
| 254 | deterministic = newDeterministic; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 255 | logConfig("Reconfigured"); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 256 | Boolean started = this.started.get(); |
| 257 | if (started != null && started) { |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 258 | stop(); |
| 259 | start(); |
| 260 | } |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 261 | } else { |
| 262 | Boolean started = this.started.get(); |
| 263 | if (started != null && started) { |
| 264 | if (running) { |
| 265 | stopTestRun(); |
| 266 | } |
| 267 | startTestRun(); |
| 268 | } |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 269 | } |
| 270 | } |
| 271 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 272 | /** |
| 273 | * Starts a new test. |
| 274 | */ |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 275 | public void start() { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 276 | // To stop the test, we simply update the "started" value. Events from the change will notify all |
| 277 | // nodes to start the test. |
| 278 | Boolean started = this.started.get(); |
| 279 | if (started == null || !started) { |
| 280 | this.started.set(true); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 281 | } |
| 282 | } |
| 283 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 284 | /** |
| 285 | * Stops a test. |
| 286 | */ |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 287 | public void stop() { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 288 | // To stop the test, we simply update the "started" value. Events from the change will notify all |
| 289 | // nodes to stop the test. |
| 290 | Boolean started = this.started.get(); |
| 291 | if (started != null && started) { |
| 292 | this.started.set(false); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 293 | } |
| 294 | } |
| 295 | |
| 296 | private void logConfig(String prefix) { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 297 | log.info("{} with " + |
| 298 | "numClients = {}; " + |
| 299 | "writePercentage = {}; " + |
| 300 | "numKeys = {}; " + |
| 301 | "keyLength = {}; " + |
| 302 | "numValues = {}; " + |
| 303 | "valueLength = {}; " + |
| 304 | "includeEvents = {}; " + |
| 305 | "deterministic = {}", |
| 306 | prefix, |
| 307 | numClients, |
| 308 | writePercentage, |
| 309 | numKeys, |
| 310 | keyLength, |
| 311 | numValues, |
| 312 | valueLength, |
| 313 | includeEvents, |
| 314 | deterministic); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 315 | } |
| 316 | |
| 317 | private void startTestRun() { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 318 | if (running) { |
| 319 | return; |
| 320 | } |
| 321 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 322 | sampleCollector.clearSamples(); |
| 323 | |
| 324 | startTime = System.currentTimeMillis(); |
| 325 | currentStartTime = startTime; |
| 326 | currentCounter = new AtomicLong(); |
| 327 | overallCounter = new AtomicLong(); |
| 328 | |
| 329 | reporterTask = new ReporterTask(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 330 | reportTimer.scheduleAtFixedRate( |
| 331 | reporterTask, REPORT_PERIOD - currentTimeMillis() % REPORT_PERIOD, REPORT_PERIOD); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 332 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 333 | running = true; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 334 | |
| 335 | Map<String, ControllerNode> nodes = new TreeMap<>(); |
| 336 | for (ControllerNode node : clusterService.getNodes()) { |
| 337 | nodes.put(node.id().id(), node); |
| 338 | } |
| 339 | |
| 340 | // Compute the index of the local node in a sorted list of nodes. |
| 341 | List<String> sortedNodes = Lists.newArrayList(nodes.keySet()); |
| 342 | int nodeCount = nodes.size(); |
| 343 | int index = sortedNodes.indexOf(nodeId.id()); |
| 344 | |
| 345 | // Count the number of workers assigned to this node. |
| 346 | int workerCount = 0; |
| 347 | for (int i = 1; i <= numClients; i++) { |
| 348 | if (i % nodeCount == index) { |
| 349 | workerCount++; |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | // Create a worker pool and start the workers for this node. |
Jordan Halterman | f533389 | 2017-08-29 10:42:13 -0700 | [diff] [blame] | 354 | if (workerCount > 0) { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 355 | String[] keys = createStrings(keyLength, numKeys); |
| 356 | String[] values = createStrings(valueLength, numValues); |
| 357 | |
Jordan Halterman | f533389 | 2017-08-29 10:42:13 -0700 | [diff] [blame] | 358 | workers = Executors.newFixedThreadPool(workerCount, groupedThreads("onos/primitive-perf", "worker-%d")); |
| 359 | for (int i = 0; i < workerCount; i++) { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 360 | if (deterministic) { |
| 361 | workers.submit(new DeterministicRunner(keys, values)); |
| 362 | } else { |
| 363 | workers.submit(new NonDeterministicRunner(keys, values)); |
| 364 | } |
Jordan Halterman | f533389 | 2017-08-29 10:42:13 -0700 | [diff] [blame] | 365 | } |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 366 | } |
| 367 | log.info("Started test run"); |
| 368 | } |
| 369 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 370 | /** |
| 371 | * Creates a deterministic array of strings to write to the cluster. |
| 372 | * |
| 373 | * @param length the string lengths |
| 374 | * @param count the string count |
| 375 | * @return a deterministic array of strings |
| 376 | */ |
| 377 | private String[] createStrings(int length, int count) { |
| 378 | Random random = new Random(length); |
| 379 | List<String> stringsList = new ArrayList<>(count); |
| 380 | for (int i = 0; i < count; i++) { |
| 381 | stringsList.add(randomString(length, random)); |
| 382 | } |
| 383 | return stringsList.toArray(new String[stringsList.size()]); |
| 384 | } |
| 385 | |
| 386 | /** |
| 387 | * Creates a deterministic string based on the given seed. |
| 388 | * |
| 389 | * @param length the seed from which to create the string |
| 390 | * @param random the random object from which to create the string characters |
| 391 | * @return the string |
| 392 | */ |
| 393 | private String randomString(int length, Random random) { |
| 394 | char[] buffer = new char[length]; |
| 395 | for (int i = 0; i < length; i++) { |
| 396 | buffer[i] = CHARS[random.nextInt(CHARS.length)]; |
| 397 | } |
| 398 | return new String(buffer); |
| 399 | } |
| 400 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 401 | private void stopTestRun() { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 402 | if (!running) { |
| 403 | return; |
| 404 | } |
| 405 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 406 | if (reporterTask != null) { |
| 407 | reporterTask.cancel(); |
| 408 | reporterTask = null; |
| 409 | } |
| 410 | |
Jordan Halterman | f533389 | 2017-08-29 10:42:13 -0700 | [diff] [blame] | 411 | if (workers != null) { |
| 412 | workers.shutdown(); |
| 413 | try { |
| 414 | workers.awaitTermination(10, TimeUnit.MILLISECONDS); |
| 415 | } catch (InterruptedException e) { |
| 416 | log.warn("Failed to stop worker", e); |
Ray Milkey | 5c7d488 | 2018-02-05 14:50:39 -0800 | [diff] [blame] | 417 | Thread.currentThread().interrupt(); |
Jordan Halterman | f533389 | 2017-08-29 10:42:13 -0700 | [diff] [blame] | 418 | } |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 419 | } |
| 420 | |
| 421 | sampleCollector.recordSample(0, 0); |
| 422 | sampleCollector.recordSample(0, 0); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 423 | |
| 424 | running = false; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 425 | |
| 426 | log.info("Stopped test run"); |
| 427 | } |
| 428 | |
| 429 | // Submits primitive operations. |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 430 | abstract class Runner implements Runnable { |
| 431 | final String[] keys; |
| 432 | final String[] values; |
| 433 | final Random random = new Random(); |
| 434 | ConsistentMap<String, String> map; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 435 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 436 | Runner(String[] keys, String[] values) { |
| 437 | this.keys = keys; |
| 438 | this.values = values; |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 439 | } |
| 440 | |
| 441 | @Override |
| 442 | public void run() { |
| 443 | setup(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 444 | while (running) { |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 445 | try { |
| 446 | submit(); |
| 447 | } catch (Exception e) { |
| 448 | log.warn("Exception during cycle", e); |
| 449 | } |
| 450 | } |
| 451 | teardown(); |
| 452 | } |
| 453 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 454 | void setup() { |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 455 | map = storageService.<String, String>consistentMapBuilder() |
| 456 | .withName("perf-test") |
| 457 | .withSerializer(Serializer.using(KryoNamespaces.BASIC)) |
| 458 | .build(); |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 459 | if (includeEvents) { |
| 460 | map.addListener(event -> { |
| 461 | }); |
| 462 | } |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 463 | } |
| 464 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 465 | abstract void submit(); |
| 466 | |
| 467 | void teardown() { |
| 468 | //map.destroy(); |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | private class NonDeterministicRunner extends Runner { |
| 473 | NonDeterministicRunner(String[] keys, String[] values) { |
| 474 | super(keys, values); |
| 475 | } |
| 476 | |
| 477 | @Override |
| 478 | void submit() { |
| 479 | currentCounter.incrementAndGet(); |
| 480 | String key = keys[random.nextInt(keys.length)]; |
| 481 | if (random.nextInt(100) < writePercentage) { |
| 482 | map.put(key, values[random.nextInt(values.length)]); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 483 | } else { |
| 484 | map.get(key); |
| 485 | } |
| 486 | } |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 487 | } |
| 488 | |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 489 | private class DeterministicRunner extends Runner { |
| 490 | private int index; |
| 491 | |
| 492 | DeterministicRunner(String[] keys, String[] values) { |
| 493 | super(keys, values); |
| 494 | } |
| 495 | |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 496 | @Override |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 497 | void submit() { |
| 498 | currentCounter.incrementAndGet(); |
| 499 | if (random.nextInt(100) < writePercentage) { |
| 500 | map.put(keys[index++ % keys.length], values[random.nextInt(values.length)]); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 501 | } else { |
Jordan Halterman | 7329609 | 2018-03-08 16:06:28 -0500 | [diff] [blame] | 502 | map.get(keys[random.nextInt(keys.length)]); |
Jordan Halterman | 29718e6 | 2017-07-20 13:24:33 -0700 | [diff] [blame] | 503 | } |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | private class ReporterTask extends TimerTask { |
| 508 | @Override |
| 509 | public void run() { |
| 510 | long endTime = System.currentTimeMillis(); |
| 511 | long overallTime = endTime - startTime; |
| 512 | long currentTime = endTime - currentStartTime; |
| 513 | long currentCount = currentCounter.getAndSet(0); |
| 514 | long overallCount = overallCounter.addAndGet(currentCount); |
| 515 | sampleCollector.recordSample(overallTime > 0 ? overallCount / (overallTime / 1000d) : 0, |
| 516 | currentTime > 0 ? currentCount / (currentTime / 1000d) : 0); |
| 517 | currentStartTime = System.currentTimeMillis(); |
| 518 | } |
| 519 | } |
| 520 | |
| 521 | } |