[ONOS-6841] Sustained primitive throughput tests
Change-Id: Ibdd05bd868a5d481b8967e57797d6106026ba1ac
diff --git a/apps/test/pom.xml b/apps/test/pom.xml
index 181e376..3f91291 100644
--- a/apps/test/pom.xml
+++ b/apps/test/pom.xml
@@ -36,6 +36,7 @@
<module>intent-perf</module>
<module>messaging-perf</module>
<module>flow-perf</module>
+ <module>primitive-perf</module>
<module>transaction-perf</module>
<module>demo</module>
<module>distributed-primitives</module>
diff --git a/apps/test/primitive-perf/BUCK b/apps/test/primitive-perf/BUCK
new file mode 100644
index 0000000..00dade9
--- /dev/null
+++ b/apps/test/primitive-perf/BUCK
@@ -0,0 +1,20 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:org.apache.karaf.shell.console',
+ '//cli:onos-cli',
+ '//utils/rest:onlab-rest',
+ '//lib:javax.ws.rs-api',
+ '//core/store/serializers:onos-core-serializers',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+)
+
+onos_app (
+ app_name = 'org.onosproject.primitiveperf',
+ title = 'Primitive Performance Test App',
+ category = 'Test',
+ url = 'http://onosproject.org',
+ description = 'Primitive performance test application.',
+)
diff --git a/apps/test/primitive-perf/pom.xml b/apps/test/primitive-perf/pom.xml
new file mode 100644
index 0000000..e0abe0d
--- /dev/null
+++ b/apps/test/primitive-perf/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2015-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-test</artifactId>
+ <version>1.11.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-app-primitive-perf</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Primitive performance test application</description>
+
+ <properties>
+ <onos.app.name>org.onosproject.primitiveperf</onos.app.name>
+ <onos.app.title>Primitive Performance Test App</onos.app.title>
+ <onos.app.category>Test</onos.app.category>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+ <onos.app.readme>Primitive performance test application.</onos.app.readme>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ <!-- Required for javadoc generation -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfApp.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfApp.java
new file mode 100644
index 0000000..7af34af
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfApp.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.primitiveperf;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+import com.google.common.collect.Lists;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static java.lang.System.currentTimeMillis;
+import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY;
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Application to test sustained primitive throughput.
+ */
+@Component(immediate = true)
+@Service(value = PrimitivePerfApp.class)
+public class PrimitivePerfApp {
+
+ private final Logger log = getLogger(getClass());
+
+ private static final int DEFAULT_NUM_CLIENTS = 64;
+ private static final int DEFAULT_WRITE_PERCENTAGE = 100;
+
+ private static final int REPORT_PERIOD = 1_000; //ms
+
+ private static final String START = "start";
+ private static final String STOP = "stop";
+ private static final MessageSubject CONTROL = new MessageSubject("primitive-perf-ctl");
+
+ @Property(name = "numClients", intValue = DEFAULT_NUM_CLIENTS,
+ label = "Number of clients to use to submit writes")
+ private int numClients = DEFAULT_NUM_CLIENTS;
+
+ @Property(name = "writePercentage", intValue = DEFAULT_WRITE_PERCENTAGE,
+ label = "Percentage of operations to perform as writes")
+ private int writePercentage = DEFAULT_WRITE_PERCENTAGE;
+
+ @Reference(cardinality = MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = MANDATORY_UNARY)
+ protected ComponentConfigService configService;
+
+ @Reference(cardinality = MANDATORY_UNARY)
+ protected PrimitivePerfCollector sampleCollector;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService communicationService;
+
+ private ExecutorService messageHandlingExecutor;
+
+ private ExecutorService workers;
+ private boolean stopped = true;
+
+ private Timer reportTimer;
+
+ private NodeId nodeId;
+ private TimerTask reporterTask;
+
+ private long startTime;
+ private long currentStartTime;
+ private AtomicLong overallCounter;
+ private AtomicLong currentCounter;
+
+ @Activate
+ public void activate(ComponentContext context) {
+ configService.registerProperties(getClass());
+
+ nodeId = clusterService.getLocalNode().id();
+
+ reportTimer = new Timer("onos-primitive-perf-reporter");
+
+ messageHandlingExecutor = Executors.newSingleThreadExecutor(
+ groupedThreads("onos/perf", "command-handler"));
+
+ communicationService.addSubscriber(CONTROL, String::new, new InternalControl(),
+ messageHandlingExecutor);
+
+ // TODO: investigate why this seems to be necessary for configs to get picked up on initial activation
+ modify(context);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ stopTestRun();
+
+ configService.unregisterProperties(getClass(), false);
+ messageHandlingExecutor.shutdown();
+ communicationService.removeSubscriber(CONTROL);
+
+ if (reportTimer != null) {
+ reportTimer.cancel();
+ reportTimer = null;
+ }
+ }
+
+ @Modified
+ public void modify(ComponentContext context) {
+ if (context == null) {
+ logConfig("Reconfigured");
+ return;
+ }
+
+ Dictionary<?, ?> properties = context.getProperties();
+ int newNumClients;
+ try {
+ String s = get(properties, "numClients");
+ newNumClients = isNullOrEmpty(s) ? numClients : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ log.warn("Malformed configuration detected; using defaults", e);
+ newNumClients = DEFAULT_NUM_CLIENTS;
+ }
+
+ int newWritePercentage;
+ try {
+ String s = get(properties, "writePercentage");
+ newWritePercentage = isNullOrEmpty(s) ? writePercentage : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ log.warn("Malformed configuration detected; using defaults", e);
+ newWritePercentage = DEFAULT_WRITE_PERCENTAGE;
+ }
+
+ if (newNumClients != numClients || newWritePercentage != writePercentage) {
+ numClients = newNumClients;
+ writePercentage = newWritePercentage;
+ logConfig("Reconfigured");
+ if (!stopped) {
+ stop();
+ start();
+ }
+ }
+ }
+
+ public void start() {
+ if (stopped) {
+ stopped = false;
+ communicationService.broadcast(START, CONTROL, str -> str.getBytes());
+ startTestRun();
+ }
+ }
+
+ public void stop() {
+ if (!stopped) {
+ communicationService.broadcast(STOP, CONTROL, str -> str.getBytes());
+ stopTestRun();
+ }
+ }
+
+ private void logConfig(String prefix) {
+ log.info("{} with numClients = {}; writePercentage = {}", prefix, numClients, writePercentage);
+ }
+
+ private void startTestRun() {
+ sampleCollector.clearSamples();
+
+ startTime = System.currentTimeMillis();
+ currentStartTime = startTime;
+ currentCounter = new AtomicLong();
+ overallCounter = new AtomicLong();
+
+ reporterTask = new ReporterTask();
+ reportTimer.scheduleAtFixedRate(reporterTask,
+ REPORT_PERIOD - currentTimeMillis() % REPORT_PERIOD,
+ REPORT_PERIOD);
+
+ stopped = false;
+
+ Map<String, ControllerNode> nodes = new TreeMap<>();
+ for (ControllerNode node : clusterService.getNodes()) {
+ nodes.put(node.id().id(), node);
+ }
+
+ // Compute the index of the local node in a sorted list of nodes.
+ List<String> sortedNodes = Lists.newArrayList(nodes.keySet());
+ int nodeCount = nodes.size();
+ int index = sortedNodes.indexOf(nodeId.id());
+
+ // Count the number of workers assigned to this node.
+ int workerCount = 0;
+ for (int i = 1; i <= numClients; i++) {
+ if (i % nodeCount == index) {
+ workerCount++;
+ }
+ }
+
+ // Create a worker pool and start the workers for this node.
+ workers = Executors.newFixedThreadPool(workerCount, groupedThreads("onos/primitive-perf", "worker-%d"));
+ for (int i = 0; i < workerCount; i++) {
+ workers.submit(new Runner(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
+ }
+ log.info("Started test run");
+ }
+
+ private void stopTestRun() {
+ if (reporterTask != null) {
+ reporterTask.cancel();
+ reporterTask = null;
+ }
+
+ try {
+ workers.awaitTermination(10, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ log.warn("Failed to stop worker", e);
+ }
+
+ sampleCollector.recordSample(0, 0);
+ sampleCollector.recordSample(0, 0);
+ stopped = true;
+
+ log.info("Stopped test run");
+ }
+
+ // Submits primitive operations.
+ final class Runner implements Runnable {
+ private final String key;
+ private final String value;
+ private ConsistentMap<String, String> map;
+
+ private Runner(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ @Override
+ public void run() {
+ setup();
+ while (!stopped) {
+ try {
+ submit();
+ } catch (Exception e) {
+ log.warn("Exception during cycle", e);
+ }
+ }
+ teardown();
+ }
+
+ private void setup() {
+ map = storageService.<String, String>consistentMapBuilder()
+ .withName("perf-test")
+ .withSerializer(Serializer.using(KryoNamespaces.BASIC))
+ .build();
+ }
+
+ private void submit() {
+ if (currentCounter.incrementAndGet() % 100 < writePercentage) {
+ map.put(key, value);
+ } else {
+ map.get(key);
+ }
+ }
+
+ private void teardown() {
+ map.destroy();
+ }
+ }
+
+ private class InternalControl implements Consumer<String> {
+ @Override
+ public void accept(String cmd) {
+ log.info("Received command {}", cmd);
+ if (cmd.equals(START)) {
+ startTestRun();
+ } else {
+ stopTestRun();
+ }
+ }
+ }
+
+ private class ReporterTask extends TimerTask {
+ @Override
+ public void run() {
+ long endTime = System.currentTimeMillis();
+ long overallTime = endTime - startTime;
+ long currentTime = endTime - currentStartTime;
+ long currentCount = currentCounter.getAndSet(0);
+ long overallCount = overallCounter.addAndGet(currentCount);
+ sampleCollector.recordSample(overallTime > 0 ? overallCount / (overallTime / 1000d) : 0,
+ currentTime > 0 ? currentCount / (currentTime / 1000d) : 0);
+ currentStartTime = System.currentTimeMillis();
+ }
+ }
+
+}
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfCollector.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfCollector.java
new file mode 100644
index 0000000..4af23b8
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfCollector.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.primitiveperf;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
+import org.onosproject.store.cluster.messaging.ClusterMessage;
+import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
+import org.onosproject.store.cluster.messaging.MessageSubject;
+import org.slf4j.Logger;
+
+import static org.onlab.util.SharedExecutors.getPoolThreadExecutor;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Collects and distributes performance samples.
+ */
+@Component(immediate = true)
+@Service(value = PrimitivePerfCollector.class)
+public class PrimitivePerfCollector {
+
+ private static final long SAMPLE_TIME_WINDOW_MS = 5_000;
+ private final Logger log = getLogger(getClass());
+
+ private static final int MAX_SAMPLES = 1_000;
+
+ private final List<Sample> samples = new LinkedList<>();
+
+ private static final MessageSubject SAMPLE = new MessageSubject("primitive-perf-sample");
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterCommunicationService communicationService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ // Auxiliary structures used to accrue data for normalized time interval
+ // across all nodes.
+ private long newestTime;
+ private Sample overall;
+ private Sample current;
+
+ private ControllerNode[] nodes;
+ private Map<NodeId, Integer> nodeToIndex;
+
+ private NodeId nodeId;
+
+ @Activate
+ public void activate() {
+ nodeId = clusterService.getLocalNode().id();
+
+ communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(),
+ getPoolThreadExecutor());
+
+ nodes = clusterService.getNodes().toArray(new ControllerNode[]{});
+ Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString()));
+
+ nodeToIndex = new HashMap<>();
+ for (int i = 0; i < nodes.length; i++) {
+ nodeToIndex.put(nodes[i].id(), i);
+ }
+
+ clearSamples();
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ communicationService.removeSubscriber(SAMPLE);
+ log.info("Stopped");
+ }
+
+ /**
+ * Clears all previously accumulated data.
+ */
+ public synchronized void clearSamples() {
+ newestTime = 0;
+ overall = new Sample(0, nodes.length);
+ current = new Sample(0, nodes.length);
+ samples.clear();
+ }
+
+
+ /**
+ * Records a sample point of data about primitive operation rate.
+ *
+ * @param overallRate overall rate
+ * @param currentRate current rate
+ */
+ public void recordSample(double overallRate, double currentRate) {
+ long now = System.currentTimeMillis();
+ addSample(now, nodeId, overallRate, currentRate);
+ broadcastSample(now, nodeId, overallRate, currentRate);
+ }
+
+ /**
+ * Returns set of node ids as headers.
+ *
+ * @return node id headers
+ */
+ public List<String> getSampleHeaders() {
+ List<String> headers = new ArrayList<>();
+ for (ControllerNode node : nodes) {
+ headers.add(node.id().toString());
+ }
+ return headers;
+ }
+
+ /**
+ * Returns set of all accumulated samples normalized to the local set of
+ * samples.
+ *
+ * @return accumulated samples
+ */
+ public synchronized List<Sample> getSamples() {
+ return ImmutableList.copyOf(samples);
+ }
+
+ /**
+ * Returns overall throughput performance for each of the cluster nodes.
+ *
+ * @return overall primitive throughput
+ */
+ public synchronized Sample getOverall() {
+ return overall;
+ }
+
+ // Records a new sample to our collection of samples
+ private synchronized void addSample(long time, NodeId nodeId,
+ double overallRate, double currentRate) {
+ Sample fullSample = createCurrentSampleIfNeeded(time);
+ setSampleData(current, nodeId, currentRate);
+ setSampleData(overall, nodeId, overallRate);
+ pruneSamplesIfNeeded();
+ }
+
+ private Sample createCurrentSampleIfNeeded(long time) {
+ Sample oldSample = time - newestTime > SAMPLE_TIME_WINDOW_MS || current.isComplete() ? current : null;
+ if (oldSample != null) {
+ newestTime = time;
+ current = new Sample(time, nodes.length);
+ if (oldSample.time > 0) {
+ samples.add(oldSample);
+ }
+ }
+ return oldSample;
+ }
+
+ private void setSampleData(Sample sample, NodeId nodeId, double data) {
+ Integer index = nodeToIndex.get(nodeId);
+ if (index != null) {
+ sample.data[index] = data;
+ }
+ }
+
+ private void pruneSamplesIfNeeded() {
+ if (samples.size() > MAX_SAMPLES) {
+ samples.remove(0);
+ }
+ }
+
+ // Performance data sample.
+ static class Sample {
+ final long time;
+ final double[] data;
+
+ public Sample(long time, int nodeCount) {
+ this.time = time;
+ this.data = new double[nodeCount];
+ Arrays.fill(data, -1);
+ }
+
+ public boolean isComplete() {
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private void broadcastSample(long time, NodeId nodeId, double overallRate, double currentRate) {
+ String data = String.format("%d|%f|%f", time, overallRate, currentRate);
+ communicationService.broadcast(data, SAMPLE, str -> str.getBytes());
+ }
+
+ private class InternalSampleCollector implements ClusterMessageHandler {
+ @Override
+ public void handle(ClusterMessage message) {
+ String[] fields = new String(message.payload()).split("\\|");
+ log.debug("Received sample from {}: {}", message.sender(), fields);
+ addSample(Long.parseLong(fields[0]), message.sender(),
+ Double.parseDouble(fields[1]), Double.parseDouble(fields[2]));
+ }
+ }
+}
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfListCommand.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfListCommand.java
new file mode 100644
index 0000000..2e25586
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfListCommand.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.primitiveperf;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.primitiveperf.PrimitivePerfCollector.Sample;
+
+/**
+ * Displays accumulated performance metrics.
+ */
+@Command(scope = "onos", name = "primitive-perf",
+ description = "Displays accumulated performance metrics")
+public class PrimitivePerfListCommand extends AbstractShellCommand {
+
+ @Option(name = "-s", aliases = "--summary", description = "Output just summary",
+ required = false, multiValued = false)
+ private boolean summary = false;
+
+ @Override
+ protected void execute() {
+ if (summary) {
+ printSummary();
+ } else {
+ printSamples();
+ }
+ }
+
+ private void printSummary() {
+ PrimitivePerfCollector collector = get(PrimitivePerfCollector.class);
+ List<String> headers = collector.getSampleHeaders();
+ Sample overall = collector.getOverall();
+ double total = 0;
+ print("%12s: %14s", "Node ID", "Overall Rate");
+ for (int i = 0; i < overall.data.length; i++) {
+ if (overall.data[i] >= 0) {
+ print("%12s: %14.2f", headers.get(i), overall.data[i]);
+ total += overall.data[i];
+ } else {
+ print("%12s: %14s", headers.get(i), " ");
+ }
+ }
+ print("%12s: %14.2f", "total", total);
+ }
+
+ private void printSamples() {
+ PrimitivePerfCollector collector = get(PrimitivePerfCollector.class);
+ List<String> headers = collector.getSampleHeaders();
+ List<Sample> samples = collector.getSamples();
+
+ System.out.print(String.format("%10s ", "Time"));
+ for (String header : headers) {
+ System.out.print(String.format("%12s ", header));
+ }
+ System.out.println(String.format("%12s", "Total"));
+
+ SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+ for (Sample sample : samples) {
+ double total = 0;
+ System.out.print(String.format("%10s ", sdf.format(new Date(sample.time))));
+ for (int i = 0; i < sample.data.length; i++) {
+ if (sample.data[i] >= 0) {
+ System.out.print(String.format("%12.2f ", sample.data[i]));
+ total += sample.data[i];
+ } else {
+ System.out.print(String.format("%12s ", " "));
+ }
+ }
+ System.out.println(String.format("%12.2f", total));
+ }
+ }
+
+}
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStartCommand.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStartCommand.java
new file mode 100644
index 0000000..bdaf5f9
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStartCommand.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.primitiveperf;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+/**
+ * Starts primitive performance test run.
+ */
+@Command(scope = "onos", name = "primitive-perf-start",
+ description = "Starts primitive performance test run")
+public class PrimitivePerfStartCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ get(PrimitivePerfApp.class).start();
+ }
+
+}
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStopCommand.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStopCommand.java
new file mode 100644
index 0000000..241bab3
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/PrimitivePerfStopCommand.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.primitiveperf;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+
+/**
+ * Stops primitive performance test run.
+ */
+@Command(scope = "onos", name = "primitive-perf-stop",
+ description = "Stops primitive performance test run")
+public class PrimitivePerfStopCommand extends AbstractShellCommand {
+
+ @Override
+ protected void execute() {
+ get(PrimitivePerfApp.class).stop();
+ }
+
+}
diff --git a/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/package-info.java b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/package-info.java
new file mode 100644
index 0000000..d9b4fae
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/java/org/onosproject/primitiveperf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Performance test application for the primitives.
+ */
+package org.onosproject.primitiveperf;
diff --git a/apps/test/primitive-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/test/primitive-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..a101d45
--- /dev/null
+++ b/apps/test/primitive-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,28 @@
+<!--
+ ~ Copyright 2017-present Open Networking Laboratory
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+ <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+ <command>
+ <action class="org.onosproject.primitiveperf.PrimitivePerfListCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.primitiveperf.PrimitivePerfStartCommand"/>
+ </command>
+ <command>
+ <action class="org.onosproject.primitiveperf.PrimitivePerfStopCommand"/>
+ </command>
+ </command-bundle>
+</blueprint>
diff --git a/modules.defs b/modules.defs
index 4821ee1..74cd92d 100644
--- a/modules.defs
+++ b/modules.defs
@@ -176,6 +176,7 @@
'//apps/test/loadtest:onos-apps-test-loadtest-oar',
'//apps/test/netcfg-monitor:onos-apps-test-netcfg-monitor-oar',
'//apps/test/messaging-perf:onos-apps-test-messaging-perf-oar',
+ '//apps/test/primitive-perf:onos-apps-test-primitive-perf-oar',
'//apps/test/transaction-perf:onos-apps-test-transaction-perf-oar',
'//apps/virtualbng:onos-apps-virtualbng-oar',
'//apps/vpls:onos-apps-vpls-oar',