blob: ccd29abd115dc80680a74171652e9b69cf5aedba [file] [log] [blame]
Brian O'Connor6ccba962015-02-17 18:16:02 -08001/*
2 * Copyright 2015 Open Networking Laboratory
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 */
16package org.onosproject.intentperf;
17
18import com.google.common.collect.Lists;
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Reference;
Brian O'Connor6ccba962015-02-17 18:16:02 -080025import org.onlab.util.Counter;
26import org.onosproject.cluster.ClusterService;
27import org.onosproject.core.ApplicationId;
28import org.onosproject.core.CoreService;
29import org.onosproject.net.ConnectPoint;
30import org.onosproject.net.Device;
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080031import org.onosproject.net.MastershipRole;
Brian O'Connor6ccba962015-02-17 18:16:02 -080032import org.onosproject.net.PortNumber;
33import org.onosproject.net.device.DeviceService;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.TrafficSelector;
37import org.onosproject.net.flow.TrafficTreatment;
38import org.onosproject.net.intent.Intent;
39import org.onosproject.net.intent.IntentEvent;
40import org.onosproject.net.intent.IntentListener;
41import org.onosproject.net.intent.IntentService;
42import org.onosproject.net.intent.Key;
43import org.onosproject.net.intent.PointToPointIntent;
44import org.slf4j.Logger;
45
46import java.util.Collections;
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080047import java.util.HashSet;
Brian O'Connor6ccba962015-02-17 18:16:02 -080048import java.util.Iterator;
49import java.util.List;
50import java.util.Map;
51import java.util.Set;
52import java.util.Timer;
53import java.util.TimerTask;
54import java.util.concurrent.ExecutorService;
55import java.util.concurrent.Executors;
56import java.util.concurrent.TimeUnit;
57
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080058import static com.google.common.base.Preconditions.checkState;
Thomas Vachuska0249b532015-02-20 16:46:18 -080059import static java.lang.String.format;
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080060import static java.lang.System.currentTimeMillis;
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080061import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY;
Brian O'Connor6ccba962015-02-17 18:16:02 -080062import static org.onlab.util.Tools.delay;
63import static org.onlab.util.Tools.groupedThreads;
Brian O'Connor36ef71a2015-02-24 12:05:01 -080064import static org.onosproject.net.intent.IntentEvent.Type.*;
Brian O'Connor6ccba962015-02-17 18:16:02 -080065import static org.slf4j.LoggerFactory.getLogger;
66
67/**
Brian O'Connor36ef71a2015-02-24 12:05:01 -080068 * Application to test sustained intent throughput.
Brian O'Connor6ccba962015-02-17 18:16:02 -080069 */
70@Component(immediate = true)
71public class IntentPerfInstaller {
72
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080073 //FIXME make this configurable
74 private static final int NUM_WORKERS = 1;
Brian O'Connor36ef71a2015-02-24 12:05:01 -080075 private static final int NUM_KEYS = 20_000;
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080076
77 public static final int START_DELAY = 5_000; // ms
78 private static final int REPORT_PERIOD = 5_000; //ms
Brian O'Connor36ef71a2015-02-24 12:05:01 -080079 private static final int GOAL_CYCLE_PERIOD = 1_000; //ms
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080080
Brian O'Connor6ccba962015-02-17 18:16:02 -080081 private final Logger log = getLogger(getClass());
82
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080083 @Reference(cardinality = MANDATORY_UNARY)
Brian O'Connor6ccba962015-02-17 18:16:02 -080084 protected CoreService coreService;
85
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080086 @Reference(cardinality = MANDATORY_UNARY)
Brian O'Connor6ccba962015-02-17 18:16:02 -080087 protected IntentService intentService;
88
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080089 @Reference(cardinality = MANDATORY_UNARY)
Brian O'Connor6ccba962015-02-17 18:16:02 -080090 protected ClusterService clusterService;
91
Brian O'Connorbcfeadb2015-02-19 21:50:01 -080092 @Reference(cardinality = MANDATORY_UNARY)
Brian O'Connor6ccba962015-02-17 18:16:02 -080093 protected DeviceService deviceService;
94
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -080095 private ExecutorService workers;
Brian O'Connor6ccba962015-02-17 18:16:02 -080096 private ApplicationId appId;
97 private Listener listener;
Brian O'Connor6ccba962015-02-17 18:16:02 -080098 private boolean stopped;
99
Brian O'Connor6ccba962015-02-17 18:16:02 -0800100 private Timer reportTimer;
101
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800102 // FIXME this variable isn't shared properly between multiple worker threads
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800103 private int lastKey = 0;
Brian O'Connor6ccba962015-02-17 18:16:02 -0800104
105 @Activate
106 public void activate() {
107 String nodeId = clusterService.getLocalNode().ip().toString();
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800108 appId = coreService.registerApplication("org.onosproject.intentperf." + nodeId);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800109
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800110 reportTimer = new Timer("onos-intent-perf-reporter");
111 workers = Executors.newFixedThreadPool(NUM_WORKERS, groupedThreads("onos/intent-perf", "worker-%d"));
Brian O'Connor6ccba962015-02-17 18:16:02 -0800112 log.info("Started with Application ID {}", appId.id());
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800113
114 // Schedule delayed start
115 reportTimer.schedule(new TimerTask() {
116 @Override
117 public void run() {
118 start();
119 }
120 }, START_DELAY);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800121 }
122
123 @Deactivate
124 public void deactivate() {
125 stop();
126 log.info("Stopped");
127 }
128
129 public void start() {
130 // perhaps we want to prime before listening...
131 // we will need to discard the first few results for priming and warmup
132 listener = new Listener();
133 intentService.addListener(listener);
Thomas Vachuska0249b532015-02-20 16:46:18 -0800134
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800135 // Schedule reporter task on report period boundary
Brian O'Connor6ccba962015-02-17 18:16:02 -0800136 reportTimer.scheduleAtFixedRate(new TimerTask() {
137 @Override
138 public void run() {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800139 //adjustRates(); // FIXME we currently adjust rates in the cycle thread
Brian O'Connor6ccba962015-02-17 18:16:02 -0800140 listener.report();
141 }
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800142 }, REPORT_PERIOD - currentTimeMillis() % REPORT_PERIOD, REPORT_PERIOD);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800143
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800144 // Submit workers
Brian O'Connor6ccba962015-02-17 18:16:02 -0800145 stopped = false;
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800146 Set<Device> devices = new HashSet<>();
147 for (int i = 0; i < NUM_WORKERS; i++) {
148 workers.submit(new Submitter(createIntents(NUM_KEYS, 2, lastKey, devices)));
149 }
Brian O'Connor6ccba962015-02-17 18:16:02 -0800150 }
151
152 public void stop() {
153 if (listener != null) {
154 reportTimer.cancel();
155 intentService.removeListener(listener);
156 listener = null;
157 reportTimer = null;
158 }
159 stopped = true;
160 try {
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800161 workers.awaitTermination(5, TimeUnit.SECONDS);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800162 } catch (InterruptedException e) {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800163 log.warn("Failed to stop worker", e);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800164 }
165 }
166
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800167 /**
168 * Creates a specified number of intents for testing purposes.
169 *
170 * @param numberOfKeys number of intents
171 * @param pathLength path depth
172 * @param firstKey first key to attempt
173 * @param devices set of previously utilized devices @return set of intents
174 */
175 private Set<Intent> createIntents(int numberOfKeys, int pathLength,
176 int firstKey, Set<Device> devices) {
Brian O'Connor6ccba962015-02-17 18:16:02 -0800177 Iterator<Device> deviceItr = deviceService.getAvailableDevices().iterator();
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800178 Set<Intent> result = new HashSet<>();
Brian O'Connor6ccba962015-02-17 18:16:02 -0800179
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800180 Device ingressDevice = null;
181 while (deviceItr.hasNext()) {
182 Device device = deviceItr.next();
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800183 if (deviceService.getRole(device.id()) == MastershipRole.MASTER &&
184 !devices.contains(device)) {
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800185 ingressDevice = device;
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800186 devices.add(device);
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800187 break;
188 }
Brian O'Connor6ccba962015-02-17 18:16:02 -0800189 }
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800190 checkState(ingressDevice != null, "There are no local devices");
Brian O'Connor6ccba962015-02-17 18:16:02 -0800191
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800192 for (int count = 0, k = firstKey; count < numberOfKeys; k++) {
193 Key key = Key.of(k, appId);
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800194 if (!intentService.isLocal(key)) {
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800195 // Bail if the key is not local
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800196 continue;
197 }
Brian O'Connor6ccba962015-02-17 18:16:02 -0800198
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800199 //FIXME we currently ignore the path length and always use the same device
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800200 TrafficSelector selector = DefaultTrafficSelector.builder().build();
201 TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
Brian O'Connor6ccba962015-02-17 18:16:02 -0800202 ConnectPoint ingress = new ConnectPoint(ingressDevice.id(), PortNumber.portNumber(1));
203 ConnectPoint egress = new ConnectPoint(ingressDevice.id(), PortNumber.portNumber(2));
204
205 Intent intent = new PointToPointIntent(appId, key,
206 selector, treatment,
207 ingress, egress,
208 Collections.emptyList());
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800209 result.add(intent);
210
211 // Bump up the counter and remember this as the last key used.
212 count++;
213 lastKey = k;
214 if (lastKey % 1000 == 0) {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800215 log.info("Building intents... {} (attempt: {})", lastKey, count);
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800216 }
Brian O'Connor6ccba962015-02-17 18:16:02 -0800217 }
Brian O'Connorbcfeadb2015-02-19 21:50:01 -0800218 log.info("Created {} intents", numberOfKeys);
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800219 return result;
Brian O'Connor6ccba962015-02-17 18:16:02 -0800220 }
221
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800222 // Submits intent operations.
223 final class Submitter implements Runnable {
224
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800225 private long lastDuration;
226 private int lastCount;
227
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800228 private Set<Intent> intents = Sets.newHashSet();
229 private Set<Intent> submitted = Sets.newHashSet();
230 private Set<Intent> withdrawn = Sets.newHashSet();
231
232 private Submitter(Set<Intent> intents) {
233 this.intents = intents;
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800234 lastCount = NUM_KEYS / 4;
235 lastDuration = 1000; // 1 second
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800236 }
237
238 @Override
239 public void run() {
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800240 prime();
241 while (!stopped) {
242 cycle();
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800243 }
244 }
245
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800246 private Iterable<Intent> subset(Set<Intent> intents) {
247 List<Intent> subset = Lists.newArrayList(intents);
248 Collections.shuffle(subset);
249 return subset.subList(0, lastCount);
250 }
251
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800252 // Submits the specified intent.
253 private void submit(Intent intent) {
254 intentService.submit(intent);
255 submitted.add(intent);
256 withdrawn.remove(intent); //TODO could check result here...
257 }
258
259 // Withdraws the specified intent.
260 private void withdraw(Intent intent) {
261 intentService.withdraw(intent);
262 withdrawn.add(intent);
263 submitted.remove(intent); //TODO could check result here...
264 }
265
266 // Primes the cycle.
267 private void prime() {
268 int i = 0;
269 withdrawn.addAll(intents);
270 for (Intent intent : intents) {
271 submit(intent);
272 // only submit half of the intents to start
273 if (i++ >= intents.size() / 2) {
274 break;
275 }
276 }
277 }
278
279 // Runs a single operation cycle.
280 private void cycle() {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800281 //TODO consider running without rate adjustment
282 adjustRates();
283
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800284 long start = currentTimeMillis();
285 subset(submitted).forEach(this::withdraw);
286 subset(withdrawn).forEach(this::submit);
287 long delta = currentTimeMillis() - start;
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800288
289 if (delta > GOAL_CYCLE_PERIOD * 3 || delta < 0) {
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800290 log.warn("Cycle took {} ms", delta);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800291 }
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800292
293 int difference = GOAL_CYCLE_PERIOD - (int) delta;
294 if (difference > 0) {
295 delay(difference);
296 }
297
298 lastDuration = delta;
299 }
300
301 int cycleCount = 0;
302 private void adjustRates() {
303 //FIXME need to iron out the rate adjustment
304 if (++cycleCount % 5 == 0) { //TODO: maybe use a timer (we should do this every 5-10 sec)
305 if (listener.requestThroughput() - listener.processedThroughput() <= 500 &&
306 lastDuration <= GOAL_CYCLE_PERIOD) {
307 lastCount = Math.min(lastCount + 100, intents.size() / 2);
308 } else {
309 lastCount *= 0.8;
310 }
311 log.info("last count: {}, last duration: {} ms (sub: {} vs inst: {})",
312 lastCount, lastDuration, listener.requestThroughput(), listener.processedThroughput());
313 }
314
Brian O'Connor6ccba962015-02-17 18:16:02 -0800315 }
316 }
317
Thomas Vachuskaa132e3a2015-02-21 01:53:14 -0800318 // Event listener to monitor throughput.
319 final class Listener implements IntentListener {
Brian O'Connor6ccba962015-02-17 18:16:02 -0800320
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800321 private Map<IntentEvent.Type, Counter> counters;
Thomas Vachuska0249b532015-02-20 16:46:18 -0800322 private final Counter runningTotal = new Counter();
Brian O'Connor6ccba962015-02-17 18:16:02 -0800323
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800324 private volatile double processedThroughput = 0;
325 private volatile double requestThroughput = 0;
326
Brian O'Connor6ccba962015-02-17 18:16:02 -0800327 public Listener() {
328 counters = initCounters();
Brian O'Connor6ccba962015-02-17 18:16:02 -0800329 }
330
331 private Map<IntentEvent.Type, Counter> initCounters() {
332 Map<IntentEvent.Type, Counter> map = Maps.newHashMap();
333 for (IntentEvent.Type type : IntentEvent.Type.values()) {
334 map.put(type, new Counter());
335 }
336 return map;
337 }
338
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800339 public double processedThroughput() {
340 return processedThroughput;
341 }
342
343 public double requestThroughput() {
344 return requestThroughput;
345 }
346
Brian O'Connor6ccba962015-02-17 18:16:02 -0800347 @Override
348 public void event(IntentEvent event) {
349 if (event.subject().appId().equals(appId)) {
350 counters.get(event.type()).add(1);
351 }
352 }
353
354 public void report() {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800355 Map<IntentEvent.Type, Counter> reportCounters = counters;
356 counters = initCounters();
357
358 // update running total and latest throughput
359 Counter installed = reportCounters.get(INSTALLED);
360 Counter withdrawn = reportCounters.get(WITHDRAWN);
361 processedThroughput = installed.throughput() + withdrawn.throughput();
Thomas Vachuska0249b532015-02-20 16:46:18 -0800362 runningTotal.add(installed.total() + withdrawn.total());
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800363
364 Counter installReq = reportCounters.get(INSTALL_REQ);
365 Counter withdrawReq = reportCounters.get(WITHDRAW_REQ);
366 requestThroughput = installReq.throughput() + withdrawReq.throughput();
367
368 // build the string to report
369 StringBuilder stringBuilder = new StringBuilder();
Brian O'Connor6ccba962015-02-17 18:16:02 -0800370 for (IntentEvent.Type type : IntentEvent.Type.values()) {
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800371 Counter counter = reportCounters.get(type);
372 stringBuilder.append(format("%s=%.2f;", type, counter.throughput()));
Brian O'Connor6ccba962015-02-17 18:16:02 -0800373 }
Thomas Vachuska0249b532015-02-20 16:46:18 -0800374 log.info("Throughput: OVERALL={}; CURRENT={}; {}",
375 format("%.2f", runningTotal.throughput()),
Brian O'Connor36ef71a2015-02-24 12:05:01 -0800376 format("%.2f", processedThroughput),
377 stringBuilder);
Brian O'Connor6ccba962015-02-17 18:16:02 -0800378 }
379 }
Brian O'Connor6ccba962015-02-17 18:16:02 -0800380}