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