Latest server driver updates in 1.14 branch
These updates include several bug fixes and a new UI
for this driver.
Change-Id: I631cf322ee3724d9f1f97246d4189b5b2a008a76
Signed-off-by: Georgios Katsikas <katsikas.gp@gmail.com>
(cherry picked from commit 6dc11c1e8078a40fcb95586e9bec9336dc2507d5)
Signed-off-by: Georgios Katsikas <katsikas.gp@gmail.com>
diff --git a/drivers/server/BUCK b/drivers/server/BUCK
index 9be0e49..cfb6acd 100644
--- a/drivers/server/BUCK
+++ b/drivers/server/BUCK
@@ -2,6 +2,7 @@
'//lib:CORE_DEPS',
'//lib:JACKSON',
'//lib:javax.ws.rs-api',
+ '//lib:joda-time',
'//incubator/api:onos-incubator-api',
'//utils/rest:onlab-rest',
'//protocols/rest/api:onos-protocols-rest-api',
diff --git a/drivers/server/BUILD b/drivers/server/BUILD
index c2ab312..1dd2700 100644
--- a/drivers/server/BUILD
+++ b/drivers/server/BUILD
@@ -1,5 +1,6 @@
COMPILE_DEPS = CORE_DEPS + JACKSON + [
"@javax_ws_rs_api//jar",
+ "@joda_time//jar",
"//incubator/api:onos-incubator-api",
"//utils/rest:onlab-rest",
"//protocols/rest/api:onos-protocols-rest-api",
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/BasicServerDriver.java b/drivers/server/src/main/java/org/onosproject/drivers/server/BasicServerDriver.java
index 4fffe6e..d1cb020 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/BasicServerDriver.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/BasicServerDriver.java
@@ -194,6 +194,22 @@
}
/**
+ * Raise a connect event by setting the
+ * activity flag of this device.
+ *
+ * @param device a device to connect
+ */
+ protected void raiseDeviceReconnect(RestSBDevice device) {
+ // Already done!
+ if (device.isActive()) {
+ return;
+ }
+
+ log.debug("Setting device {} active", device.deviceId());
+ device.setActive(true);
+ }
+
+ /**
* Upon a failure to contact a device, the driver
* raises a disconnect event by resetting the
* activity flag of this device.
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/FlowRuleProgrammableServerImpl.java b/drivers/server/src/main/java/org/onosproject/drivers/server/FlowRuleProgrammableServerImpl.java
index 9c38fe8..34947cb 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/FlowRuleProgrammableServerImpl.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/FlowRuleProgrammableServerImpl.java
@@ -22,22 +22,25 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Sets;
-import org.slf4j.Logger;
-
import org.onosproject.drivers.server.devices.nic.NicFlowRule;
import org.onosproject.drivers.server.devices.nic.NicRxFilter.RxFilter;
import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverService;
import org.onosproject.net.flow.DefaultFlowEntry;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleProgrammable;
import org.onosproject.net.flow.FlowRuleService;
+import org.slf4j.Logger;
+
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -72,6 +75,12 @@
private static final String PARAM_CPU_RULES = "cpuRules";
private static final String PARAM_RULE_ID = "ruleId";
private static final String PARAM_RULE_CONTENT = "ruleContent";
+ private static final String PARAM_RX_FILTER_FD = "flow";
+
+ /**
+ * Driver's property to specify how many rules the controller can remove at once.
+ */
+ private static final String RULE_DELETE_BATCH_SIZE_PROPERTY = "ruleDeleteBatchSize";
@Override
public Collection<FlowEntry> getFlowEntries() {
@@ -87,7 +96,7 @@
try {
response = getController().get(deviceId, RULE_MANAGEMENT_URL, JSON);
} catch (ProcessingException pEx) {
- log.error("Failed to get flow entries from device: {}", deviceId);
+ log.error("Failed to get NIC flow entries from device: {}", deviceId);
return Collections.EMPTY_LIST;
}
@@ -99,12 +108,12 @@
JsonNode jsonNode = mapper.convertValue(jsonMap, JsonNode.class);
objNode = (ObjectNode) jsonNode;
} catch (IOException ioEx) {
- log.error("Failed to get flow entries from device: {}", deviceId);
+ log.error("Failed to get NIC flow entries from device: {}", deviceId);
return Collections.EMPTY_LIST;
}
if (objNode == null) {
- log.error("Failed to get flow entries from device: {}", deviceId);
+ log.error("Failed to get NIC flow entries from device: {}", deviceId);
return Collections.EMPTY_LIST;
}
@@ -148,8 +157,7 @@
continue;
// Rule trully present in the data plane => Add
} else {
- actualFlowEntries.add(
- new DefaultFlowEntry(
+ actualFlowEntries.add(new DefaultFlowEntry(
r, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
}
}
@@ -189,15 +197,44 @@
DeviceId deviceId = getHandler().data().deviceId();
checkNotNull(deviceId, DEVICE_ID_NULL);
+ int ruleDeleteBatchSize = getRuleDeleteBatchSizeProperty(deviceId);
+
// Set of truly-removed rules to be reported
Set<FlowRule> removedRules = Sets.<FlowRule>newConcurrentHashSet();
- // for (FlowRule rule : rules) {
- rules.forEach(rule -> {
- if (removeNicFlowRule(deviceId, rule.id().value())) {
- removedRules.add(rule);
+ List<FlowRule> ruleList = (List) rules;
+ int ruleCount = rules.size();
+ int ruleStart = 0;
+ int processed = 0;
+ int batchNb = 1;
+ while (processed < ruleCount) {
+ String ruleIds = "";
+
+ for (int i = ruleStart; i < ruleCount; i++) {
+ // Batch completed
+ if (i >= (batchNb * ruleDeleteBatchSize)) {
+ break;
+ }
+
+ // TODO: Turn this string into a list and modify removeNicFlowRuleBatch()
+ // Create a comma-separated sequence of rule IDs
+ ruleIds += Long.toString(ruleList.get(i).id().value()) + ",";
+
+ processed++;
}
- });
+
+ // Remove last comma
+ ruleIds = ruleIds.substring(0, ruleIds.length() - 1);
+
+ // Remove the entire batch of rules at once
+ if (removeNicFlowRuleBatch(deviceId, ruleIds)) {
+ removedRules.addAll(ruleList.subList(ruleStart, processed));
+ }
+
+ // Prepare for the next batch (if any)
+ batchNb++;
+ ruleStart += ruleDeleteBatchSize;
+ }
return removedRules;
}
@@ -214,16 +251,26 @@
rules.forEach(rule -> {
if (!(rule instanceof FlowEntry)) {
- NicFlowRule nicRule = (NicFlowRule) rule;
- String tcId = nicRule.trafficClassId();
+ NicFlowRule nicRule = null;
- // Create a bucket of flow rules for this traffic class
- if (!rulesPerTc.containsKey(tcId)) {
- rulesPerTc.put(tcId, Sets.<FlowRule>newConcurrentHashSet());
+ // Only NicFlowRules are accepted
+ try {
+ nicRule = (NicFlowRule) rule;
+ } catch (ClassCastException cEx) {
+ log.warn("Skipping rule not crafted for NIC: {}", rule);
}
- Set<FlowRule> tcRuleSet = rulesPerTc.get(tcId);
- tcRuleSet.add(nicRule);
+ if (nicRule != null) {
+ String tcId = nicRule.trafficClassId();
+
+ // Create a bucket of flow rules for this traffic class
+ if (!rulesPerTc.containsKey(tcId)) {
+ rulesPerTc.put(tcId, Sets.<FlowRule>newConcurrentHashSet());
+ }
+
+ Set<FlowRule> tcRuleSet = rulesPerTc.get(tcId);
+ tcRuleSet.add(nicRule);
+ }
}
});
@@ -274,7 +321,7 @@
// Create the object node to host the Rx filter method
ObjectNode methodObjNode = mapper.createObjectNode();
- methodObjNode.put(BasicServerDriver.NIC_PARAM_RX_METHOD, "flow");
+ methodObjNode.put(BasicServerDriver.NIC_PARAM_RX_METHOD, PARAM_RX_FILTER_FD);
scsObjNode.put(BasicServerDriver.NIC_PARAM_RX_FILTER, methodObjNode);
// Map each core to an array of rule IDs and rules
@@ -285,14 +332,18 @@
for (FlowRule rule : rules) {
NicFlowRule nicRule = (NicFlowRule) rule;
+ if (nicRule.isFullWildcard() && (rules.size() > 1)) {
+ log.warn("Skipping wildcard rule: {}", nicRule);
+ continue;
+ }
+
long coreIndex = nicRule.cpuCoreIndex();
// Keep the ID of the target NIC
if (nic == null) {
nic = findNicInterfaceWithPort(deviceId, nicRule.interfaceNumber());
- checkArgument(
- !Strings.isNullOrEmpty(nic),
- "Attempted to install rules on an invalid NIC");
+ checkArgument(!Strings.isNullOrEmpty(nic),
+ "Attempted to install rules in an invalid NIC");
}
// Create a JSON array for this CPU core
@@ -341,11 +392,11 @@
// Upon an error, return an empty set of rules
if (!checkStatusCode(response)) {
- log.error("Failed to install flow rules on device {}", deviceId);
+ log.error("Failed to install NIC flow rules in device {}", deviceId);
return Collections.EMPTY_LIST;
}
- log.info("Successfully installed {} flow rules on device {}",
+ log.info("Successfully installed {} NIC flow rules in device {}",
rules.size(), deviceId);
// .. or all of them
@@ -353,31 +404,44 @@
}
/**
- * Removes a FlowRule from a server device.
+ * Removes a batch of FlowRules from a server device
+ * using a single REST command.
*
* @param deviceId target server device ID
- * @param ruleId NIC rule ID to be removed
+ * @param ruleIds a batch of comma-separated NIC rule IDs to be removed
* @return boolean removal status
*/
- private boolean removeNicFlowRule(DeviceId deviceId, long ruleId) {
+ private boolean removeNicFlowRuleBatch(DeviceId deviceId, String ruleIds) {
int response = -1;
+ long ruleCount = ruleIds.chars().filter(ch -> ch == ',').count() + 1;
- // Try to remove the rule, although server might be unreachable
+ // Try to remove the rules, although server might be unreachable
try {
response = getController().delete(deviceId,
- RULE_MANAGEMENT_URL + SLASH + Long.toString(ruleId), null, JSON);
+ RULE_MANAGEMENT_URL + SLASH + ruleIds, null, JSON);
} catch (Exception ex) {
- log.error("Failed to remove flow rule {} from device {}", ruleId, deviceId);
+ log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
return false;
}
if (!checkStatusCode(response)) {
- log.error("Failed to remove flow rule {} from device {}", ruleId, deviceId);
+ log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
return false;
}
- log.info("Successfully removed flow rule {} from device {}", ruleId, deviceId);
+ log.info("Successfully removed NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
return true;
}
+ /**
+ * Returns how many rules this driver can delete at once.
+ *
+ * @param deviceId the device's ID to delete rules from
+ * @return rule deletion batch size
+ */
+ private int getRuleDeleteBatchSizeProperty(DeviceId deviceId) {
+ Driver driver = getHandler().get(DriverService.class).getDriver(deviceId);
+ return Integer.parseInt(driver.getProperty(RULE_DELETE_BATCH_SIZE_PROPERTY));
+ }
+
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/ServerDevicesDiscovery.java b/drivers/server/src/main/java/org/onosproject/drivers/server/ServerDevicesDiscovery.java
index 10203a3..3ff7f05 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/ServerDevicesDiscovery.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/ServerDevicesDiscovery.java
@@ -110,8 +110,8 @@
private static final String PARAM_HW_VENDOR = "hwVersion";
private static final String PARAM_SW_VENDOR = "swVersion";
private static final String PARAM_SERIAL = "serial";
- private static final String PARAM_TIMING_STATS = "timing_stats";
- private static final String PARAM_TIMING_AUTOSCALE = "autoscale_timing_stats";
+ private static final String PARAM_TIMING_STATS = "timingStats";
+ private static final String PARAM_TIMING_AUTOSCALE = "autoScaleTimingStats";
private static final String NIC_PARAM_NAME = "name";
private static final String NIC_PARAM_PORT_INDEX = "index";
@@ -137,20 +137,28 @@
/**
* CPU statistics.
*/
- private static final String CPU_PARAM_ID = "id";
- private static final String CPU_PARAM_VENDOR = "vendor";
- private static final String CPU_PARAM_FREQUENCY = "frequency";
- private static final String CPU_PARAM_LOAD = "load";
- private static final String CPU_PARAM_STATUS = "busy";
- private static final String CPU_STATS_BUSY_CPUS = "busyCpus";
- private static final String CPU_STATS_FREE_CPUS = "freeCpus";
+ private static final String CPU_PARAM_ID = "id";
+ private static final String CPU_PARAM_VENDOR = "vendor";
+ private static final String CPU_PARAM_FREQUENCY = "frequency";
+ private static final String CPU_PARAM_LOAD = "load";
+ private static final String CPU_PARAM_QUEUE = "queue";
+ private static final String CPU_PARAM_STATUS = "busy";
+ private static final String CPU_PARAM_THROUGHPUT = "throughput";
+ private static final String CPU_PARAM_LATENCY = "latency";
+ private static final String MON_PARAM_UNIT = "unit";
+ private static final String MON_PARAM_BUSY_CPUS = "busyCpus";
+ private static final String MON_PARAM_FREE_CPUS = "freeCpus";
+ private static final String MON_PARAM_MIN = "min";
+ private static final String MON_PARAM_AVERAGE = "average";
+ private static final String MON_PARAM_MAX = "max";
/**
* Timing statistics.
*/
- private static final String TIMING_PARAM_PARSE = "parse";
- private static final String TIMING_PARAM_LAUNCH = "launch";
- private static final String TIMING_PARAM_AUTOSCALE = "autoscale";
+ private static final String TIMING_PARAM_PARSE = "parseTime";
+ private static final String TIMING_PARAM_LAUNCH = "launchTime";
+ private static final String TIMING_PARAM_DEPLOY = "deployTime";
+ private static final String TIMING_PARAM_AUTOSCALE = "autoScaleTime";
/**
* Auxiliary constants.
@@ -344,12 +352,15 @@
RestServerSBDevice dev = new DefaultRestServerSBDevice(
device.ip(), device.port(), device.username(),
device.password(), device.protocol(), device.url(),
- device.isActive(), device.testUrl().toString(),
+ device.isActive(), device.testUrl().orElse(""),
vendor, hw, sw, AuthenticationScheme.BASIC, "",
cpuSet, nicSet
);
checkNotNull(dev, DEVICE_NULL);
+ // Set alive
+ raiseDeviceReconnect(dev);
+
// Updates the controller with the complete device information
getController().removeDevice(deviceId);
getController().addDevice((RestSBDevice) dev);
@@ -512,7 +523,7 @@
* @param deviceId the device ID to be queried
* @return global monitoring statistics
*/
- private MonitoringStatistics getGlobalMonitoringStatistics(DeviceId deviceId) {
+ public MonitoringStatistics getGlobalMonitoringStatistics(DeviceId deviceId) {
// Monitoring statistics to return
MonitoringStatistics monStats = null;
@@ -562,8 +573,8 @@
}
// Get high-level CPU statistics
- int busyCpus = objNode.path(CPU_STATS_BUSY_CPUS).asInt();
- int freeCpus = objNode.path(CPU_STATS_FREE_CPUS).asInt();
+ int busyCpus = objNode.path(MON_PARAM_BUSY_CPUS).asInt();
+ int freeCpus = objNode.path(MON_PARAM_FREE_CPUS).asInt();
// Get a list of CPU statistics per core
Collection<CpuStatistics> cpuStats = parseCpuStatistics(deviceId, objNode);
@@ -581,11 +592,13 @@
statsBuilder.setDeviceId(deviceId)
.setTimingStatistics(timinsgStats)
.setCpuStatistics(cpuStats)
- .setNicStatistics(nicStats)
- .build();
+ .setNicStatistics(nicStats);
monStats = statsBuilder.build();
+ // When a device reports monitoring data, it means it is alive
+ raiseDeviceReconnect(device);
+
log.debug("Global monitoring statistics: {}", monStats.toString());
return monStats;
@@ -689,11 +702,13 @@
statsBuilder.setDeviceId(deviceId)
.setTimingStatistics(timinsgStats)
.setCpuStatistics(cpuStats)
- .setNicStatistics(nicStats)
- .build();
+ .setNicStatistics(nicStats);
monStats = statsBuilder.build();
+ // When a device reports monitoring data, it means it is alive
+ raiseDeviceReconnect(device);
+
log.debug("Monitoring statistics: {}", monStats.toString());
return monStats;
@@ -720,22 +735,62 @@
for (JsonNode cn : cpuNode) {
ObjectNode cpuObjNode = (ObjectNode) cn;
+ // CPU statistics builder
+ DefaultCpuStatistics.Builder cpuBuilder = DefaultCpuStatistics.builder();
+
+ // Throughput statistics are optional
+ JsonNode throughputNode = cpuObjNode.get(CPU_PARAM_THROUGHPUT);
+ if (throughputNode != null) {
+ String throughputUnit = get(throughputNode, MON_PARAM_UNIT);
+ if (!Strings.isNullOrEmpty(throughputUnit)) {
+ cpuBuilder.setThroughputUnit(throughputUnit);
+ }
+ float averageThroughput = (float) 0;
+ if (throughputNode.get(MON_PARAM_AVERAGE) != null) {
+ averageThroughput = throughputNode.path(MON_PARAM_AVERAGE).floatValue();
+ }
+ cpuBuilder.setAverageThroughput(averageThroughput);
+ }
+
+ // Latency statistics are optional
+ JsonNode latencyNode = cpuObjNode.get(CPU_PARAM_LATENCY);
+ if (latencyNode != null) {
+ String latencyUnit = get(latencyNode, MON_PARAM_UNIT);
+ if (!Strings.isNullOrEmpty(latencyUnit)) {
+ cpuBuilder.setLatencyUnit(latencyUnit);
+ }
+ float minLatency = (float) 0;
+ if (latencyNode.get(MON_PARAM_MIN) != null) {
+ minLatency = latencyNode.path(MON_PARAM_MIN).floatValue();
+ }
+ float averageLatency = (float) 0;
+ if (latencyNode.get(MON_PARAM_AVERAGE) != null) {
+ averageLatency = latencyNode.path(MON_PARAM_AVERAGE).floatValue();
+ }
+ float maxLatency = (float) 0;
+ if (latencyNode.get(MON_PARAM_MAX) != null) {
+ maxLatency = latencyNode.path(MON_PARAM_MAX).floatValue();
+ }
+
+ cpuBuilder.setMinLatency(minLatency)
+ .setAverageLatency(averageLatency)
+ .setMaxLatency(maxLatency);
+ }
+
// CPU ID with its load and status
- int cpuId = cpuObjNode.path(CPU_PARAM_ID).asInt();
- float cpuLoad = cpuObjNode.path(CPU_PARAM_LOAD).floatValue();
- boolean isBusy = cpuObjNode.path(CPU_PARAM_STATUS).booleanValue();
+ int cpuId = cpuObjNode.path(CPU_PARAM_ID).asInt();
+ float cpuLoad = cpuObjNode.path(CPU_PARAM_LOAD).floatValue();
+ int queueId = cpuObjNode.path(CPU_PARAM_QUEUE).asInt();
+ int busySince = cpuObjNode.path(CPU_PARAM_STATUS).asInt();
- // Incorporate these statistics into an object
- DefaultCpuStatistics.Builder cpuBuilder =
- DefaultCpuStatistics.builder();
-
+ // This is mandatory information
cpuBuilder.setDeviceId(deviceId)
.setId(cpuId)
.setLoad(cpuLoad)
- .setIsBusy(isBusy)
- .build();
+ .setQueue(queueId)
+ .setBusySince(busySince);
- // We have statistics for this CPU core
+ // We have all the statistics for this CPU core
cpuStats.add(cpuBuilder.build());
}
@@ -790,8 +845,7 @@
long txErrors = nicObjNode.path(NIC_STATS_TX_ERRORS).asLong();
// Incorporate these statistics into an object
- DefaultPortStatistics.Builder nicBuilder =
- DefaultPortStatistics.builder();
+ DefaultPortStatistics.Builder nicBuilder = DefaultPortStatistics.builder();
nicBuilder.setDeviceId(deviceId)
.setPort((int) portNumber)
@@ -802,8 +856,7 @@
.setPacketsRxDropped(rxDropped)
.setPacketsRxErrors(rxErrors)
.setPacketsTxDropped(txDropped)
- .setPacketsTxErrors(txErrors)
- .build();
+ .setPacketsTxErrors(txErrors);
// We have statistics for this NIC
nicStats.add(nicBuilder.build());
@@ -813,9 +866,9 @@
}
/**
- * Parse the input JSON object, looking for timing-related
- * statistics. Upon success, construct and return a
- * timing statistics object.
+ * Parse the input JSON object, looking for timing-related statistics.
+ * Upon success, return a timing statistics object with the advertized values.
+ * Upon failure, return a timing statistics object with zero-initialized values.
*
* @param objNode input JSON node with timing statistics information
* @return TimingStatistics object or null
@@ -827,33 +880,56 @@
return timinsgStats;
}
+ // If no timing statistics are present, then send zeros
+ if (objNode.get(PARAM_TIMING_STATS) == null) {
+ return getZeroTimingStatistics();
+ }
+
+ DefaultTimingStatistics.Builder timingBuilder = DefaultTimingStatistics.builder();
+
// Get timing statistics
JsonNode timingNode = objNode.path(PARAM_TIMING_STATS);
ObjectNode timingObjNode = (ObjectNode) timingNode;
+ // The unit of timing statistics
+ String timingStatsUnit = get(timingNode, MON_PARAM_UNIT);
+ if (!Strings.isNullOrEmpty(timingStatsUnit)) {
+ timingBuilder.setUnit(timingStatsUnit);
+ }
+
// Time (ns) to parse the controller's deployment instruction
- long parsingTime = timingObjNode.path(TIMING_PARAM_PARSE).asLong();
+ long parsingTime = 0;
+ if (timingObjNode.get(TIMING_PARAM_PARSE) != null) {
+ parsingTime = timingObjNode.path(TIMING_PARAM_PARSE).asLong();
+ }
// Time (ns) to do the deployment
- long launchingTime = timingObjNode.path(TIMING_PARAM_LAUNCH).asLong();
- // Total time (ns)
- long totalTime = parsingTime + launchingTime;
+ long launchingTime = 0;
+ if (timingObjNode.get(TIMING_PARAM_LAUNCH) != null) {
+ launchingTime = timingObjNode.path(TIMING_PARAM_LAUNCH).asLong();
+ }
+ // Deployment time (ns) equals to time to parse + time to launch
+ long deployTime = 0;
+ if (timingObjNode.get(TIMING_PARAM_DEPLOY) != null) {
+ deployTime = timingObjNode.path(TIMING_PARAM_DEPLOY).asLong();
+ }
+ checkArgument(deployTime == parsingTime + launchingTime, "Inconsistent timing statistics");
+
+ timingBuilder.setParsingTime(parsingTime)
+ .setLaunchingTime(launchingTime);
// Get autoscale timing statistics
JsonNode autoscaleTimingNode = objNode.path(PARAM_TIMING_AUTOSCALE);
- ObjectNode autoscaleTimingObjNode = (ObjectNode) autoscaleTimingNode;
+ if (autoscaleTimingNode == null) {
+ return timingBuilder.build();
+ }
+ ObjectNode autoScaleTimingObjNode = (ObjectNode) autoscaleTimingNode;
// Time (ns) to autoscale a server's load
- long autoscaleTime = autoscaleTimingObjNode.path(
- TIMING_PARAM_AUTOSCALE
- ).asLong();
-
- DefaultTimingStatistics.Builder timingBuilder =
- DefaultTimingStatistics.builder();
-
- timingBuilder.setParsingTime(parsingTime)
- .setLaunchingTime(launchingTime)
- .setAutoscaleTime(autoscaleTime)
- .build();
+ long autoScaleTime = 0;
+ if (autoScaleTimingObjNode.get(TIMING_PARAM_AUTOSCALE) != null) {
+ autoScaleTime = autoScaleTimingObjNode.path(TIMING_PARAM_AUTOSCALE).asLong();
+ }
+ timingBuilder.setAutoScaleTime(autoScaleTime);
return timingBuilder.build();
}
@@ -866,13 +942,11 @@
* @return TimingStatistics object
*/
private TimingStatistics getZeroTimingStatistics() {
- DefaultTimingStatistics.Builder zeroTimingBuilder =
- DefaultTimingStatistics.builder();
+ DefaultTimingStatistics.Builder zeroTimingBuilder = DefaultTimingStatistics.builder();
zeroTimingBuilder.setParsingTime(0)
.setLaunchingTime(0)
- .setAutoscaleTime(0)
- .build();
+ .setAutoScaleTime(0);
return zeroTimingBuilder.build();
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultDpdkNicFlowRule.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultDpdkNicFlowRule.java
index 223f5c3..4c2b7f0 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultDpdkNicFlowRule.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultDpdkNicFlowRule.java
@@ -18,7 +18,6 @@
import org.onosproject.net.flow.FlowRule;
-import org.onlab.packet.Ip4Address;
import org.onlab.packet.MacAddress;
/**
@@ -76,26 +75,25 @@
rule += "ipv4 ";
if (this.ipv4Protocol() > 0) {
- rule += "proto spec " + Integer.toString(this.ipv4Protocol()) + " ";
- rule += "proto mask 0x0 ";
+ rule += "proto is " + Integer.toString(this.ipv4Protocol()) + " ";
}
if (this.ipv4SrcAddress() != null) {
- rule += "src is " + this.ipv4SrcAddress().toString() + " ";
- }
-
- if (this.ipv4SrcMask() != null) {
- rule += "src spec " + this.ipv4SrcMask().address().getIp4Address().toString() + " ";
- rule += "src mask " + Ip4Address.makeMaskPrefix(this.ipv4SrcMask().prefixLength()).toString() + " ";
+ if ((this.ipv4SrcMask() != null) && (this.ipv4SrcMask().prefixLength() < 32)) {
+ rule += "src spec " + this.ipv4SrcAddress().toString() + " ";
+ rule += "src prefix " + this.ipv4SrcMask().prefixLength() + " ";
+ } else {
+ rule += "src is " + this.ipv4SrcAddress().toString() + " ";
+ }
}
if (this.ipv4DstAddress() != null) {
- rule += "dst is " + this.ipv4DstAddress().toString() + " ";
- }
-
- if (this.ipv4DstMask() != null) {
- rule += "dst spec " + this.ipv4DstMask().address().getIp4Address().toString() + " ";
- rule += "dst mask " + Ip4Address.makeMaskPrefix(this.ipv4DstMask().prefixLength()).toString() + " ";
+ if ((this.ipv4DstMask() != null) && (this.ipv4DstMask().prefixLength() < 32)) {
+ rule += "dst spec " + this.ipv4DstAddress().toString() + " ";
+ rule += "dst prefix " + this.ipv4DstMask().prefixLength() + " ";
+ } else {
+ rule += "dst is " + this.ipv4DstAddress().toString() + " ";
+ }
}
rule += "/ ";
@@ -130,15 +128,16 @@
// No subsequent field
if (action.actionField().isEmpty()) {
+ rule += "/ ";
continue;
}
// A subsequent field is associated with a value
rule += action.actionField() + " ";
- rule += Long.toString(action.actionValue()) + " ";
+ rule += Long.toString(action.actionValue()) + " / ";
}
- rule += "/ end";
+ rule += " end";
}
return rule;
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultNicFlowRule.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultNicFlowRule.java
index be89997..8f50986 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultNicFlowRule.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/DefaultNicFlowRule.java
@@ -144,9 +144,8 @@
this.ipv4ProtoCriterion = (IPProtocolCriterion) this.selector().getCriterion(IP_PROTO);
this.ipv4SrcAddrCriterion = (IPCriterion) this.selector().getCriterion(IPV4_SRC);
this.ipv4DstAddrCriterion = (IPCriterion) this.selector().getCriterion(IPV4_DST);
- // Is there a criterion for IP masks?
- this.ipv4SrcMaskCriterion = (IPCriterion) null;
- this.ipv4DstMaskCriterion = (IPCriterion) null;
+ this.ipv4SrcMaskCriterion = (IPCriterion) this.selector().getCriterion(IPV4_SRC);
+ this.ipv4DstMaskCriterion = (IPCriterion) this.selector().getCriterion(IPV4_DST);
this.udpSrcPortCriterion = (UdpPortCriterion) this.selector().getCriterion(UDP_SRC);
this.udpDstPortCriterion = (UdpPortCriterion) this.selector().getCriterion(UDP_DST);
this.tcpSrcPortCriterion = (TcpPortCriterion) this.selector().getCriterion(TCP_SRC);
@@ -168,6 +167,9 @@
new NicRuleAction(NicRuleAction.Action.METER, meterInstruction.meterId().id()));
}
}
+
+ // This action provides basic rule match counters
+ // this.actions.add(new NicRuleAction(NicRuleAction.Action.COUNT));
}
@Override
@@ -305,6 +307,16 @@
}
@Override
+ public boolean isFullWildcard() {
+ if (((ipv4SrcAddress() != null) && !ipv4SrcAddress().isZero()) ||
+ ((ipv4DstAddress() != null) && !ipv4DstAddress().isZero()) ||
+ (ipv4Protocol() > 0) || (sourcePort() > 0) || (destinationPort() > 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public Set<NicRuleAction> actions() {
return actions;
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/FlowRxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/FlowRxFilterValue.java
index 64c792b..c2430c0 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/FlowRxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/FlowRxFilterValue.java
@@ -26,26 +26,29 @@
public final class FlowRxFilterValue extends RxFilterValue
implements Comparable {
- private long cpuCoreId;
+ private long value;
private String flowRule;
/**
* Constructs a flow-based Rx filter.
+ *
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public FlowRxFilterValue() {
- super();
+ public FlowRxFilterValue(int cpuId) {
+ super(cpuId);
setValue(0);
setRule("");
}
/**
- * Constructs a flow-based Rx filter with CPU core ID.
+ * Constructs a flow-based Rx filter with physical CPU core ID.
*
- * @param cpuCoreId a CPU core ID when the flow ends up
+ * @param value Flow tag
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public FlowRxFilterValue(long cpuCoreId) {
- super();
- setValue(cpuCoreId);
+ public FlowRxFilterValue(long value, int cpuId) {
+ super(cpuId);
+ setValue(value);
setRule("");
}
@@ -53,12 +56,13 @@
* Constructs a flow-based Rx filter with CPU core ID
* and an associated rule.
*
- * @param cpuCoreId a CPU core ID
+ * @param value Flow tag
+ * @param cpuId CPU ID of the server this tag will lead to
* @param flowRule a flow rule as a string
*/
- public FlowRxFilterValue(long cpuCoreId, String flowRule) {
- super();
- setValue(cpuCoreId);
+ public FlowRxFilterValue(long value, int cpuId, String flowRule) {
+ super(cpuId);
+ setValue(value);
setRule(flowRule);
}
@@ -68,7 +72,7 @@
* @param other a source FlowRxFilterValue object
*/
public FlowRxFilterValue(FlowRxFilterValue other) {
- super();
+ super(other.cpuId);
setValue(other.value());
setRule(other.rule());
}
@@ -79,18 +83,18 @@
* @return Flow Rx filter value
*/
public long value() {
- return this.cpuCoreId;
+ return this.value;
}
/**
* Sets the value of this Rx filter.
*
- * @param cpuCoreId a CPU core ID for this Rx filter
+ * @param value a CPU core ID for this Rx filter
*/
- public void setValue(long cpuCoreId) {
- checkArgument(cpuCoreId >= 0,
+ private void setValue(long value) {
+ checkArgument(value >= 0,
"NIC flow Rx filter has invalid CPU core ID");
- this.cpuCoreId = cpuCoreId;
+ this.value = value;
}
/**
@@ -115,7 +119,7 @@
@Override
public int hashCode() {
- return Objects.hash(this.cpuCoreId, this.flowRule);
+ return Objects.hash(this.value, this.flowRule, this.cpuId);
}
@Override
@@ -130,8 +134,8 @@
FlowRxFilterValue other = (FlowRxFilterValue) obj;
- return (this.value() == other.value()) &&
- this.rule().equals(other.rule());
+ return this.value() == other.value() &&
+ this.rule().equals(other.rule()) && ((RxFilterValue) this).equals(other);
}
@Override
@@ -147,15 +151,15 @@
if (other instanceof FlowRxFilterValue) {
FlowRxFilterValue otherRxVal = (FlowRxFilterValue) other;
- long thisCoreId = this.value();
- long otherCoreId = otherRxVal.value();
+ long thisCpuId = this.value();
+ long otherCpuId = otherRxVal.value();
- if (thisCoreId > otherCoreId) {
+ if (thisCpuId > otherCpuId) {
return 1;
- } else if (thisCoreId < otherCoreId) {
+ } else if (thisCpuId < otherCpuId) {
return -1;
} else {
- return 0;
+ return this.cpuId - otherRxVal.cpuId;
}
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MacRxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MacRxFilterValue.java
index 2676f65..25b28bb 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MacRxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MacRxFilterValue.java
@@ -30,9 +30,10 @@
/**
* Constructs a MAC-based Rx filter.
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public MacRxFilterValue() {
- super();
+ public MacRxFilterValue(int cpuId) {
+ super(cpuId);
this.mac = null;
}
@@ -40,9 +41,10 @@
* Constructs a MAC-based Rx filter with specific MAC address.
*
* @param mac a MAC address to use as a filter
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public MacRxFilterValue(MacAddress mac) {
- super();
+ public MacRxFilterValue(MacAddress mac, int cpuId) {
+ super(cpuId);
setValue(mac);
}
@@ -52,7 +54,7 @@
* @param other a source MacRxFilterValue object
*/
public MacRxFilterValue(MacRxFilterValue other) {
- super();
+ super(other.cpuId);
setValue(other.value());
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MplsRxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MplsRxFilterValue.java
index 2b5112f..ddc1930c 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MplsRxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/MplsRxFilterValue.java
@@ -30,9 +30,11 @@
/**
* Constructs an MPLS-based Rx filter.
+ *
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public MplsRxFilterValue() {
- super();
+ public MplsRxFilterValue(int cpuId) {
+ super(cpuId);
this.mplsLabel = null;
}
@@ -40,9 +42,10 @@
* Constructs an MPLS-based Rx filter with specific label.
*
* @param mplsLabel an MPLS label to use as a filter
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public MplsRxFilterValue(MplsLabel mplsLabel) {
- super();
+ public MplsRxFilterValue(MplsLabel mplsLabel, int cpuId) {
+ super(cpuId);
setValue(mplsLabel);
}
@@ -52,7 +55,7 @@
* @param other a source MplsRxFilterValue object
*/
public MplsRxFilterValue(MplsRxFilterValue other) {
- super();
+ super(other.cpuId);
setValue(other.value());
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicFlowRule.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicFlowRule.java
index 1215b6f..9083395 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicFlowRule.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicFlowRule.java
@@ -206,6 +206,13 @@
boolean hasTransport();
/**
+ * Returns whether this rule is a full wildcard or not.
+ *
+ * @return boolean full wildcard status
+ */
+ boolean isFullWildcard();
+
+ /**
* Returns the set of actions of this rule.
*
* @return rule's set of actions
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicRuleAction.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicRuleAction.java
index 3ec13c3..ff3afeb 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicRuleAction.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/NicRuleAction.java
@@ -196,7 +196,7 @@
ACTION_FIELD.put(Action.JUMP, "group");
ACTION_FIELD.put(Action.MARK, "id");
ACTION_FIELD.put(Action.FLAG, "");
- ACTION_FIELD.put(Action.COUNT, "id");
+ ACTION_FIELD.put(Action.COUNT, "");
ACTION_FIELD.put(Action.QUEUE, "index");
ACTION_FIELD.put(Action.RSS, "queue");
ACTION_FIELD.put(Action.PF, "");
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RssRxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RssRxFilterValue.java
index 08f2a46..2d54a81 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RssRxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RssRxFilterValue.java
@@ -28,9 +28,11 @@
/**
* Constructs an RSS-based Rx filter.
+ *
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public RssRxFilterValue() {
- super();
+ public RssRxFilterValue(int cpuId) {
+ super(cpuId);
setValue(0);
}
@@ -38,9 +40,10 @@
* Constructs an RSS-based Rx filter with specific hash.
*
* @param rssHash a hash value
+ * @param cpuId CPU ID of the server this tag will lead to
*/
- public RssRxFilterValue(int rssHash) {
- super();
+ public RssRxFilterValue(int rssHash, int cpuId) {
+ super(cpuId);
setValue(rssHash);
}
@@ -50,7 +53,7 @@
* @param other a source RssRxFilterValue object
*/
public RssRxFilterValue(RssRxFilterValue other) {
- super();
+ super(other.cpuId);
setValue(other.value());
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RxFilterValue.java
index 10ebe04..dceff82 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/RxFilterValue.java
@@ -15,12 +15,50 @@
*/
package org.onosproject.drivers.server.devices.nic;
+import java.util.Objects;
+
/**
* The base class that holds the value of a NIC's Rx filter.
*/
public abstract class RxFilterValue {
- public RxFilterValue() {
+ /* CPU id of the server this tag will lead to */
+ protected int cpuId;
+
+ /**
+ * Constructs an Rx filter value.
+ *
+ * @param cpuId CPU ID of the server this tag will lead to
+ */
+ public RxFilterValue(int cpuId) {
+ this.cpuId = cpuId;
+ }
+
+ /**
+ * Returns the CPU ID that corresponds to this Rx filter value.
+ *
+ * @return CPU ID of the server this tag will lead to
+ */
+ public int cpuId() {
+ return this.cpuId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(this.cpuId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if ((obj == null) || (!(obj instanceof RxFilterValue))) {
+ return false;
+ }
+
+ return cpuId == ((RxFilterValue) obj).cpuId;
}
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/VlanRxFilterValue.java b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/VlanRxFilterValue.java
index 18a99be..8e16321 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/VlanRxFilterValue.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/devices/nic/VlanRxFilterValue.java
@@ -30,9 +30,10 @@
/**
* Constructs a VLAN-based Rx filter.
+ * @param cpuId CPU id of the server this tag will lead to
*/
- public VlanRxFilterValue() {
- super();
+ public VlanRxFilterValue(int cpuId) {
+ super(cpuId);
this.vlanId = VlanId.NONE;
}
@@ -40,9 +41,10 @@
* Constructs a VLAN-based Rx filter with specific ID.
*
* @param vlanId a VLAN ID to use as a filter
+ * @param cpuId CPU id of the server this tag will lead to
*/
- public VlanRxFilterValue(VlanId vlanId) {
- super();
+ public VlanRxFilterValue(VlanId vlanId, int cpuId) {
+ super(cpuId);
setValue(vlanId);
}
@@ -52,7 +54,7 @@
* @param other a source VlanRxFilterValue object
*/
public VlanRxFilterValue(VlanRxFilterValue other) {
- super();
+ super(other.cpuId);
setValue(other.value());
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/BaseViewMessageHandler.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/BaseViewMessageHandler.java
new file mode 100644
index 0000000..a2a151c
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/BaseViewMessageHandler.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import org.onosproject.drivers.server.BasicServerDriver;
+import org.onosproject.drivers.server.ServerDevicesDiscovery;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.chart.ChartModel;
+import org.onosproject.ui.chart.ChartRequestHandler;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.IntStream;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Base message handler for passing server data to the Web UI.
+ */
+public abstract class BaseViewMessageHandler extends UiMessageHandler {
+
+ private static final Logger log = getLogger(BaseViewMessageHandler.class);
+
+ // Time axis
+ protected long timestamp = 0L;
+
+ // Instance of the basic server driver
+ protected static BasicServerDriver basicDriver = new BasicServerDriver();
+
+ // Instance of the server driver
+ protected static ServerDevicesDiscovery serverDriver = new ServerDevicesDiscovery();
+
+ // A local memory to store monitoring data
+ protected static Map<DeviceId, Map<Integer, LruCache<Float>>> devDataMap =
+ new HashMap<DeviceId, Map<Integer, LruCache<Float>>>();
+
+ // Data series length
+ public static final int NUM_OF_DATA_POINTS = 30;
+
+ // The maximum number of columns that can be projected
+ public static final int MAX_COLUMNS_NB = 16;
+
+ // Minimum CPU load
+ public static final float MIN_CPU_LOAD = (float) 0.01;
+
+ // Time axis
+ public static final String TIME_FORMAT = "HH:mm:ss";
+
+ // Device IDs
+ public static final String DEVICE_IDS = "deviceIds";
+
+ // Chart designer
+ protected abstract class ControlMessageRequest extends ChartRequestHandler {
+
+ protected ControlMessageRequest(String req, String res, String label) {
+ super(req, res, label);
+ }
+
+ @Override
+ protected abstract String[] getSeries();
+
+ @Override
+ protected abstract void populateChart(ChartModel cm, ObjectNode payload);
+
+ /**
+ * Returns a x-axis label for a monitoring value.
+ *
+ * @param metric label metric
+ * @param index label index
+ * @return a data label
+ */
+ protected String getLabel(MetricType metric, int index) {
+ return StringUtils.lowerCase(metric.name()) + "_" + Integer.toString(index);
+ }
+
+ /**
+ * Fills an array of strings acting as x-axis.
+ *
+ * @param metric x-axis metric
+ * @param length the length of the array
+ * @return an array of strings
+ */
+ protected String[] createSeries(MetricType metric, int length) {
+ if (length <= 0) {
+ return null;
+ }
+
+ if (length > MAX_COLUMNS_NB) {
+ length = MAX_COLUMNS_NB;
+ }
+
+ String[] series = IntStream.range(0, length)
+ .mapToObj(i -> getLabel(metric, i))
+ .toArray(String[]::new);
+
+ return series;
+ }
+
+ /**
+ * Returns a map of monitoring parameters to their load history buffers.
+ *
+ * @param deviceId the device being monitored
+ * @param length the length of the array
+ * @return a map monitoring parameters to their load history buffers
+ */
+ protected Map<Integer, LruCache<Float>> fetchCacheForDevice(DeviceId deviceId, int length) {
+ if (!isValid(deviceId, length - 1)) {
+ log.error("Invalid access to data history by device {} with {} cores", deviceId, length);
+ return null;
+ }
+
+ if (devDataMap.containsKey(deviceId)) {
+ return devDataMap.get(deviceId);
+ }
+
+ Map<Integer, LruCache<Float>> dataMap = new HashMap<Integer, LruCache<Float>>();
+ for (int i = 0; i < length; i++) {
+ dataMap.put(i, new LruCache<Float>(NUM_OF_DATA_POINTS));
+ }
+
+ devDataMap.put(deviceId, dataMap);
+
+ return dataMap;
+ }
+
+ /**
+ * Adds a value into a buffer with the latest data entries.
+ *
+ * @param deviceId the device being monitored
+ * @param length the length of the array
+ * @param index the data index
+ * @param value the data value
+ */
+ protected void addToCache(
+ DeviceId deviceId, int length, int index, float value) {
+ if (!isValid(deviceId, length - 1) ||
+ !isValid(deviceId, index)) {
+ log.error("Invalid access to data {} history by device {} with {} cores",
+ index, deviceId, length);
+ return;
+ }
+
+ Map<Integer, LruCache<Float>> dataMap = devDataMap.get(deviceId);
+ if (dataMap == null) {
+ dataMap = fetchCacheForDevice(deviceId, length);
+ checkNotNull(dataMap, "Failed to add measurement in the cache");
+ }
+
+ dataMap.get(index).add(value);
+ }
+
+ /**
+ * Returns a buffer with the latest
+ * entries of a device's monitoring parameter.
+ *
+ * @param deviceId the device being monitored
+ * @param index a data index
+ * @return a history of values
+ */
+ protected LruCache<Float> getDataHistory(DeviceId deviceId, int index) {
+ if (!isValid(deviceId, index)) {
+ log.error("Invalid access to CPU {} load history by device {}", index, deviceId);
+ return null;
+ }
+
+ Map<Integer, LruCache<Float>> dataMap = devDataMap.get(deviceId);
+ if (dataMap == null) {
+ return null;
+ }
+
+ return dataMap.get(index);
+ }
+
+ /**
+ * Fill the UI memory's current values with zeros.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @return a map of monitoring parameters to their initial values
+ */
+ protected Map<Integer, Float> populateZeroData(DeviceId deviceId, int length) {
+ Map<Integer, Float> data = initializeData(length);
+
+ for (int i = 0; i < length; i++) {
+ // Store it locally
+ addToCache(deviceId, length, i, 0);
+ }
+
+ return data;
+ }
+
+ /**
+ * Fill the UI memory's history with zeros.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @return a map of monitoring parameters to their initial arrays of values
+ */
+ protected Map<Integer, Float[]> populateZeroDataHistory(DeviceId deviceId, int length) {
+ Map<Integer, Float[]> data = initializeDataHistory(length);
+
+ for (int i = 0; i < length; i++) {
+ addToCache(deviceId, length, i, 0);
+ }
+
+ // Keep a timestamp
+ timestamp = System.currentTimeMillis();
+
+ return data;
+ }
+
+ /**
+ * Populate a specific metric with data.
+ *
+ * @param dataPoint the particular part of the chart to be fed
+ * @param data the data to feed the metric of the chart
+ */
+ protected void populateMetric(ChartModel.DataPoint dataPoint, Map<String, Object> data) {
+ data.forEach(dataPoint::data);
+ }
+
+ /**
+ * Populate the metrics to the Web UI.
+ *
+ * @param cm the chart to be fed with data
+ * @param data the data to feed the chart
+ * @param time a timestamp
+ * @param metric a metric
+ * @param numberOfPoints the number of data points
+ */
+ protected void populateMetrics(
+ ChartModel cm,
+ Map<Integer, Float[]> data,
+ LocalDateTime time,
+ MetricType metric,
+ int numberOfPoints) {
+ for (int i = 0; i < numberOfPoints; i++) {
+ Map<String, Object> local = Maps.newHashMap();
+ for (int j = 0; j < data.size(); j++) {
+ if (data.containsKey(j)) {
+ local.put(getLabel(metric, j), data.get(j)[i]);
+ }
+ }
+
+ String calculated = time.minusSeconds(numberOfPoints - i).toString(TIME_FORMAT);
+ local.put(LABEL, calculated);
+
+ populateMetric(cm.addDataPoint(calculated), local);
+ }
+ }
+
+ /**
+ * Checks the validity of a device's information.
+ *
+ * @param deviceId the device being monitored
+ * @param length the length of the array
+ * @return boolean data validity status
+ */
+ protected boolean isValid(DeviceId deviceId, int length) {
+ return ((deviceId != null) && (length >= 0) &&
+ (length < MAX_COLUMNS_NB));
+ }
+
+ /**
+ * Create a data structure with zero-initialized data.
+ *
+ * @param length the length of the array
+ * @return a map of metrics to their initial values
+ */
+ protected Map<Integer, Float> initializeData(int length) {
+ Map<Integer, Float> data = Maps.newHashMap();
+
+ for (int i = 0; i < length; i++) {
+ data.put(i, (float) 0);
+ }
+
+ return data;
+ }
+
+ /**
+ * Create a data structure with zero-initialized arrays of data.
+ *
+ * @param length the length of the array
+ * @return a map of metrics to their initial arrays of values
+ */
+ protected Map<Integer, Float[]> initializeDataHistory(int length) {
+ Map<Integer, Float[]> data = Maps.newHashMap();
+
+ for (int i = 0; i < length; i++) {
+ data.put(i, ArrayUtils.toObject(new float[NUM_OF_DATA_POINTS]));
+ }
+
+ return data;
+ }
+
+ /**
+ * Fill the contents of an input array until a desired point.
+ *
+ * @param origin the original array with the data
+ * @param expectedLength the desired length of the array
+ * @return an array of a certain length
+ */
+ protected float[] fillData(float[] origin, int expectedLength) {
+ if (origin.length == expectedLength) {
+ return origin;
+ } else {
+ int desiredLength = origin.length;
+ if (origin.length > expectedLength) {
+ desiredLength = expectedLength;
+ }
+
+ float[] filled = new float[expectedLength];
+ for (int i = 0; i < desiredLength; i++) {
+ filled[i] = origin[i];
+ }
+
+ for (int i = desiredLength - 1; i < expectedLength; i++) {
+ filled[i] = origin[origin.length - 1];
+ }
+
+ return filled;
+ }
+ }
+
+ /**
+ * Attach the list of all devices to the top of the chart.
+ *
+ * @param cm the chart to be fed with data
+ * @param deviceIds the set of Device IDs to show up
+ */
+ protected void attachDeviceList(ChartModel cm, Set<DeviceId> deviceIds) {
+ checkNotNull(deviceIds, "No device IDs provided to chart");
+ ArrayNode array = arrayNode();
+ deviceIds.forEach(id -> array.add(id.toString()));
+ cm.addAnnotation(DEVICE_IDS, array);
+ }
+
+ /**
+ * Returns zero-initialized data for a metric when no devices are present.
+ *
+ * @param cm the chart to be fed with data
+ * @param metric a metric to reset
+ * @param length the length of the data array
+ */
+ protected void fillDataWhenNoDevicePresent(
+ ChartModel cm, MetricType metric, int length) {
+ Map<String, Object> local = Maps.newHashMap();
+ for (int i = 0; i < length; i++) {
+ local.put(getLabel(metric, i), new Float(0));
+ }
+
+ local.put(LABEL, "No Servers");
+ populateMetric(cm.addDataPoint(""), local);
+ }
+
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuUI.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuUI.java
new file mode 100644
index 0000000..31fc57a
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuUI.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiView;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static org.onosproject.ui.UiView.Category.NETWORK;
+import static org.onosproject.ui.GlyphConstants.ENDSTATION;
+
+/**
+ * Mechanism to stream CPU data to the GUI.
+ */
+@Component(immediate = true, enabled = true)
+@Service(value = CpuUI.class)
+public class CpuUI {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * GUI Information.
+ */
+ private static final String CPU_ID = "cpu";
+ private static final String CPU_TEXT = "Servers-CPU";
+ private static final String RES_PATH = "gui";
+ private static final ClassLoader CL = CpuUI.class.getClassLoader();
+
+ // Factory for UI message handlers
+ private final UiMessageHandlerFactory messageHandlerFactory =
+ () -> ImmutableList.of(new CpuViewMessageHandler());
+
+ // List of application views
+ private final List<UiView> views = ImmutableList.of(
+ new UiView(NETWORK, CPU_ID, CPU_TEXT, ENDSTATION)
+ );
+
+ // Application UI extension
+ private final UiExtension uiExtension =
+ new UiExtension.Builder(CL, views)
+ .messageHandlerFactory(messageHandlerFactory)
+ .resourcePath(RES_PATH)
+ .build();
+
+ /**
+ * Interact with ONOS.
+ */
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected UiExtensionService uiExtensionService;
+
+ @Activate
+ protected void activate() {
+ uiExtensionService.register(uiExtension);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ uiExtensionService.unregister(uiExtension);
+ log.info("Stopped");
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuViewMessageHandler.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuViewMessageHandler.java
new file mode 100644
index 0000000..87be684
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuViewMessageHandler.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Floats;
+
+import org.onosproject.drivers.server.behavior.CpuStatisticsDiscovery;
+import org.onosproject.drivers.server.devices.RestServerSBDevice;
+import org.onosproject.drivers.server.stats.CpuStatistics;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.chart.ChartModel;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.drivers.server.gui.MetricType.CPU;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Message handler for passing CPU load data to the Web UI.
+ */
+public class CpuViewMessageHandler extends BaseViewMessageHandler {
+
+ private static final Logger log = getLogger(CpuViewMessageHandler.class);
+
+ private static final String CPU_DATA_REQ = "cpuDataRequest";
+ private static final String CPU_DATA_RESP = "cpuDataResponse";
+ private static final String CPUS_LABEL = "cpus";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new CpuMessageRequest());
+ }
+
+ private final class CpuMessageRequest extends BaseViewMessageHandler.ControlMessageRequest {
+
+ private CpuMessageRequest() {
+ super(CPU_DATA_REQ, CPU_DATA_RESP, CPUS_LABEL);
+ }
+
+ @Override
+ protected String[] getSeries() {
+ return createSeries(CPU, MAX_COLUMNS_NB);
+ }
+
+ @Override
+ protected void populateChart(ChartModel cm, ObjectNode payload) {
+ DeviceService ds = get(DeviceService.class);
+ if ((ds == null) || (ds.getAvailableDeviceCount() == 0)) {
+ fillDataWhenNoDevicePresent(cm, CPU, MAX_COLUMNS_NB);
+ return;
+ }
+
+ String uri = string(payload, "devId");
+
+ // Project only one device over time
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ List<CpuStatistics> cpuStats = null;
+ Map<Integer, Float[]> data = null;
+ try {
+ cpuStats = new ArrayList(serverDriver.getCpuStatistics(deviceId));
+ data = populateCpuDataHistory(deviceId, serverDev.numberOfCpus(), cpuStats);
+ } catch (Exception ex) {
+ data = populateZeroDataHistory(deviceId, MAX_COLUMNS_NB);
+ }
+ checkNotNull(data, "No CPU data history to visualize");
+
+ // Generate a timestamp
+ LocalDateTime ldt = new LocalDateTime(timestamp);
+
+ // Project the data
+ populateMetrics(cm, data, ldt, CPU, NUM_OF_DATA_POINTS);
+
+ Set<DeviceId> deviceIds = Sets.newHashSet();
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support CPU monitoring are considered
+ if (device.is(CpuStatisticsDiscovery.class) && serverDev.isActive()) {
+ deviceIds.add(device.id());
+ }
+ }
+
+ // Drop down list to select devices
+ attachDeviceList(cm, deviceIds);
+ } else {
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support CPU monitoring are considered
+ if (!device.is(CpuStatisticsDiscovery.class)) {
+ continue;
+ }
+
+ DeviceId deviceId = device.id();
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ List<CpuStatistics> cpuStats = null;
+ Map<Integer, Float> data = null;
+ try {
+ cpuStats = new ArrayList(serverDriver.getCpuStatistics(deviceId));
+ data = populateCpuData(deviceId, serverDev.numberOfCpus(), cpuStats);
+ } catch (Exception ex) {
+ data = populateZeroData(deviceId, MAX_COLUMNS_NB);
+ }
+ checkNotNull(data, "No CPU data to visualize");
+
+ // Map them to the CPU cores
+ Map<String, Object> local = Maps.newHashMap();
+ for (int i = 0; i < data.size(); i++) {
+ local.put(getLabel(CPU, i), data.get(i));
+ }
+
+ // Last piece of data is the device ID
+ if (serverDev.isActive()) {
+ local.put(LABEL, deviceId);
+ populateMetric(cm.addDataPoint(deviceId), local);
+ } else {
+ local.put(LABEL, "");
+ populateMetric(cm.addDataPoint(""), local);
+ }
+ }
+ }
+ }
+
+ /**
+ * Turn the current monitoring data into a data
+ * structure that can feed the CPU UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param cpuStats the CPU load per core
+ * @return a map of CPU metrics to their values
+ */
+ private Map<Integer, Float> populateCpuData(
+ DeviceId deviceId, int length, List<CpuStatistics> cpuStats) {
+ Map<Integer, Float> data = initializeData(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : cpuStats) {
+ int index = stats.id();
+
+ // Store it locally
+ addToCache(deviceId, length, index, stats.load());
+
+ // Project the floating point load value in [0, 1] to [0, 100]
+ Float projectedVal = new Float(stats.load() * (float) 100);
+
+ // Now the data is in the right form
+ data.put(index, projectedVal);
+ }
+
+ return data;
+ }
+
+ /**
+ * Turn the monitoring data history into a
+ * data structure that can feed the CPU UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param cpuStats the CPU load per core
+ * @return a map of CPU metrics to their arrays of values
+ */
+ private Map<Integer, Float[]> populateCpuDataHistory(
+ DeviceId deviceId, int length, List<CpuStatistics> cpuStats) {
+ Map<Integer, Float[]> data = initializeDataHistory(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : cpuStats) {
+ int index = stats.id();
+
+ // Store it locally
+ addToCache(deviceId, length, index, stats.load());
+
+ LruCache<Float> loadCache = getDataHistory(deviceId, index);
+ if (loadCache == null) {
+ continue;
+ }
+ float[] floatArray = Floats.toArray(Arrays.asList(loadCache.values().toArray(new Float[0])));
+
+ // Project the load array to the range of [0, 100]
+ for (int j = 0; j < floatArray.length; j++) {
+ floatArray[j] = floatArray[j] * (float) 100;
+ }
+
+ // Fill the missing points
+ float[] filledLoadArray = fillData(floatArray, NUM_OF_DATA_POINTS);
+
+ // Set the data
+ data.put(index, ArrayUtils.toObject(filledLoadArray));
+ }
+
+ // Keep a timestamp
+ timestamp = System.currentTimeMillis();
+
+ return data;
+ }
+
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyUI.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyUI.java
new file mode 100644
index 0000000..9b8c864
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyUI.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiView;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static org.onosproject.ui.UiView.Category.NETWORK;
+import static org.onosproject.ui.GlyphConstants.ENDSTATION;
+
+/**
+ * Mechanism to stream latency data to the GUI.
+ */
+@Component(immediate = true, enabled = true)
+@Service(value = LatencyUI.class)
+public class LatencyUI {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * GUI Information.
+ */
+ private static final String LATENCY_ID = "latency";
+ private static final String LATENCY_TEXT = "Servers-Latency";
+ private static final String RES_PATH = "gui";
+ private static final ClassLoader CL = LatencyUI.class.getClassLoader();
+
+ // Factory for UI message handlers
+ private final UiMessageHandlerFactory messageHandlerFactory =
+ () -> ImmutableList.of(new LatencyViewMessageHandler());
+
+ // List of application views
+ private final List<UiView> views = ImmutableList.of(
+ new UiView(NETWORK, LATENCY_ID, LATENCY_TEXT, ENDSTATION)
+ );
+
+ // Application UI extension
+ private final UiExtension uiExtension =
+ new UiExtension.Builder(CL, views)
+ .messageHandlerFactory(messageHandlerFactory)
+ .resourcePath(RES_PATH)
+ .build();
+
+ /**
+ * Interact with ONOS.
+ */
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected UiExtensionService uiExtensionService;
+
+ @Activate
+ protected void activate() {
+ uiExtensionService.register(uiExtension);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ uiExtensionService.unregister(uiExtension);
+ log.info("Stopped");
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyViewMessageHandler.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyViewMessageHandler.java
new file mode 100644
index 0000000..c76c97d
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyViewMessageHandler.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Floats;
+
+import org.onosproject.drivers.server.behavior.MonitoringStatisticsDiscovery;
+import org.onosproject.drivers.server.devices.RestServerSBDevice;
+import org.onosproject.drivers.server.stats.CpuStatistics;
+import org.onosproject.drivers.server.stats.MonitoringStatistics;
+import org.onosproject.drivers.server.stats.MonitoringUnit.LatencyUnit;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.chart.ChartModel;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.drivers.server.gui.MetricType.LATENCY;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Message handler for passing latency data to the Web UI.
+ */
+public class LatencyViewMessageHandler extends BaseViewMessageHandler {
+
+ private static final Logger log = getLogger(LatencyViewMessageHandler.class);
+
+ private static final String LATENCY_DATA_REQ = "latencyDataRequest";
+ private static final String LATENCY_DATA_RESP = "latencyDataResponse";
+ private static final String LATENCY_LABEL = "latencys";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new LatencyMessageRequest());
+ }
+
+ private final class LatencyMessageRequest extends BaseViewMessageHandler.ControlMessageRequest {
+
+ private LatencyMessageRequest() {
+ super(LATENCY_DATA_REQ, LATENCY_DATA_RESP, LATENCY_LABEL);
+ }
+
+ @Override
+ protected String[] getSeries() {
+ return createSeries(LATENCY, MAX_COLUMNS_NB);
+ }
+
+ @Override
+ protected void populateChart(ChartModel cm, ObjectNode payload) {
+ DeviceService ds = get(DeviceService.class);
+ if ((ds == null) || (ds.getAvailableDeviceCount() == 0)) {
+ fillDataWhenNoDevicePresent(cm, LATENCY, MAX_COLUMNS_NB);
+ return;
+ }
+
+ String uri = string(payload, "devId");
+
+ // Project only one device over time
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ Map<Integer, Float[]> data = null;
+ MonitoringStatistics monStats = serverDriver.getGlobalMonitoringStatistics(deviceId);
+ if (monStats == null) {
+ data = populateZeroDataHistory(deviceId, MAX_COLUMNS_NB);
+ } else {
+ data = populateLatencyDataHistory(deviceId, serverDev.numberOfCpus(), monStats);
+ }
+ checkNotNull(data, "No latency data history to visualize");
+
+ // Generate a timestamp
+ LocalDateTime ldt = new LocalDateTime(timestamp);
+
+ // Project the data
+ populateMetrics(cm, data, ldt, LATENCY, NUM_OF_DATA_POINTS);
+
+ Set<DeviceId> deviceIds = Sets.newHashSet();
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support this type of monitoring behaviour are considered
+ if (device.is(MonitoringStatisticsDiscovery.class) && serverDev.isActive()) {
+ deviceIds.add(device.id());
+ }
+ }
+
+ // Drop down list to select devices
+ attachDeviceList(cm, deviceIds);
+ } else {
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support this type of monitoring behaviour are considered
+ if (!device.is(MonitoringStatisticsDiscovery.class)) {
+ continue;
+ }
+
+ DeviceId deviceId = device.id();
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ Map<Integer, Float> data = null;
+ MonitoringStatistics monStats = serverDriver.getGlobalMonitoringStatistics(deviceId);
+ if (monStats == null) {
+ data = populateZeroData(deviceId, MAX_COLUMNS_NB);
+ } else {
+ data = populateLatencyData(deviceId, serverDev.numberOfCpus(), monStats);
+ }
+ checkNotNull(data, "No latency data to visualize");
+
+ // Map them to the CPU cores
+ Map<String, Object> local = Maps.newHashMap();
+ for (int i = 0; i < data.size(); i++) {
+ local.put(getLabel(LATENCY, i), data.get(i));
+ }
+
+ // Last piece of data is the device ID
+ if (serverDev.isActive()) {
+ local.put(LABEL, deviceId);
+ populateMetric(cm.addDataPoint(deviceId), local);
+ } else {
+ local.put(LABEL, "");
+ populateMetric(cm.addDataPoint(""), local);
+ }
+ }
+ }
+ }
+
+ /**
+ * Turn the current monitoring data into a data
+ * structure that can feed the Latency UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param monStats a MonitoringStatistics object
+ * @return a map of latency metrics to their values
+ */
+ private Map<Integer, Float> populateLatencyData(
+ DeviceId deviceId, int length, MonitoringStatistics monStats) {
+ Map<Integer, Float> data = initializeData(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : monStats.cpuStatisticsAll()) {
+ int index = stats.id();
+
+ // TODO: Use min and max latency to plot bars plots with error bars
+ Float value = null;
+ if ((stats.averageLatency().isPresent()) && (stats.load() > MIN_CPU_LOAD)) {
+ value = stats.averageLatency().get();
+ } else {
+ value = new Float(0);
+ }
+
+ // Unit conversion
+ LatencyUnit latencyUnit = null;
+ if (stats.latencyUnit().isPresent()) {
+ latencyUnit = (LatencyUnit) stats.latencyUnit().get();
+ } else {
+ latencyUnit = LatencyUnit.NANO_SECOND;
+ }
+ value = LatencyUnit.toNano(value, latencyUnit);
+
+ // Store it locally
+ addToCache(deviceId, length, index, value);
+
+ // And into the map
+ data.put(index, value);
+ }
+
+ return data;
+ }
+
+ /**
+ * Turn the monitoring data history into a
+ * data structure that can feed the Latency UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param monStats a MonitoringStatistics object
+ * @return a map of latency metrics to their arrays of values
+ */
+ private Map<Integer, Float[]> populateLatencyDataHistory(
+ DeviceId deviceId, int length, MonitoringStatistics monStats) {
+ Map<Integer, Float[]> data = initializeDataHistory(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : monStats.cpuStatisticsAll()) {
+ int index = stats.id();
+
+ // TODO: Use min and max latency to plot bars plots with error bars
+ Float value = null;
+ if ((stats.averageLatency().isPresent()) && (stats.load() > MIN_CPU_LOAD)) {
+ value = stats.averageLatency().get();
+ } else {
+ value = new Float(0);
+ }
+
+ // Unit conversion
+ LatencyUnit latencyUnit = null;
+ if (stats.latencyUnit().isPresent()) {
+ latencyUnit = (LatencyUnit) stats.latencyUnit().get();
+ } else {
+ latencyUnit = LatencyUnit.NANO_SECOND;
+ }
+ value = LatencyUnit.toNano(value, latencyUnit);
+
+ // Store it locally
+ addToCache(deviceId, length, index, value);
+
+ LruCache<Float> loadCache = getDataHistory(deviceId, index);
+ if (loadCache == null) {
+ continue;
+ }
+ float[] floatArray = Floats.toArray(Arrays.asList(loadCache.values().toArray(new Float[0])));
+
+ // Fill the missing points
+ float[] filledLoadArray = fillData(floatArray, NUM_OF_DATA_POINTS);
+
+ // Set the data
+ data.put(index, ArrayUtils.toObject(filledLoadArray));
+ }
+
+ // Keep a timestamp
+ timestamp = System.currentTimeMillis();
+
+ return data;
+ }
+
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LruCache.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LruCache.java
new file mode 100644
index 0000000..c38af84
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/LruCache.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableSet;
+import java.util.SortedSet;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Data structure that implements Least Recently Used (LRU) policy.
+ */
+public class LruCache<T> extends LinkedHashMap<Integer, T> {
+ private static final Logger log = getLogger(LruCache.class);
+
+ // After this size, LRU is applied
+ private final int maxEntries;
+ private static final int DEFAULT_INITIAL_CAPACITY = 5;
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+ public LruCache(int initialCapacity,
+ float loadFactor,
+ int maxEntries) {
+ super(initialCapacity, loadFactor, true);
+ this.maxEntries = maxEntries;
+ }
+
+ public LruCache(int initialCapacity, int maxEntries) {
+ this(initialCapacity, DEFAULT_LOAD_FACTOR, maxEntries);
+ }
+
+ public LruCache(int maxEntries) {
+ this(DEFAULT_INITIAL_CAPACITY, maxEntries);
+ }
+
+ @Override
+ protected synchronized boolean removeEldestEntry(
+ Map.Entry<Integer, T> eldest) {
+ // Remove the oldest element when size limit is reached
+ return size() > maxEntries;
+ }
+
+ /**
+ * Adds a new entry to the LRU.
+ *
+ * @param newValue the value to be added
+ */
+ public synchronized void add(T newValue) {
+ this.put(this.getNextKey(), newValue);
+ }
+
+ /**
+ * Returns the first (eldest) key of this LRU cache.
+ *
+ * @return first (eldest) key of this LRU cache
+ */
+ public synchronized Integer getFirstKey() {
+ return this.keySet().iterator().next();
+ }
+
+ /**
+ * Returns the last (newest) key of this LRU cache.
+ *
+ * @return last (newest) key of this LRU cache
+ */
+ public synchronized Integer getLastKey() {
+ Integer out = null;
+ for (Integer key : this.keySet()) {
+ out = key;
+ }
+
+ return out;
+ }
+
+ /**
+ * Returns the first (eldest) value of this LRU cache.
+ *
+ * @return first (eldest) value of this LRU cache
+ */
+ public synchronized T getFirstValue() {
+ // Get all keys sorted
+ SortedSet<Integer> keys =
+ new ConcurrentSkipListSet<Integer>(this.keySet());
+
+ // Return the value that corresponds to the first key
+ return this.get(keys.first());
+ }
+
+ /**
+ * Returns the last (newest) value of this LRU cache.
+ *
+ * @return last (newest) value of this LRU cache
+ */
+ public synchronized T getLastValue() {
+ // Get all keys sorted
+ SortedSet<Integer> keys =
+ new ConcurrentSkipListSet<Integer>(this.keySet());
+
+ // Return the value that corresponds to the last key
+ return this.get(keys.last());
+ }
+
+ /**
+ * Returns the first (oldest) values of this LRU cache.
+ * The number is denoted by the argument.
+ *
+ * @param numberOfEntries the number of entries to include in the list
+ * @return list of first (oldest) values of this LRU cache
+ */
+ public synchronized List<T> getFirstValues(int numberOfEntries) {
+ List<T> outList = new ArrayList<T>();
+
+ if (numberOfEntries <= 0) {
+ return outList;
+ }
+
+ // Get all keys sorted
+ SortedSet<Integer> keys =
+ new ConcurrentSkipListSet<Integer>(this.keySet());
+
+ int i = 0;
+
+ // Iterate the sorted keys
+ for (Integer k : keys) {
+ // Pick up the first 'numberOfEntries' entries
+ if (i >= numberOfEntries) {
+ break;
+ }
+
+ outList.add(this.get(k));
+ i++;
+ }
+
+ return outList;
+ }
+
+ /**
+ * Returns the last (newest) values of this LRU cache.
+ * The number is denoted by the argument.
+ *
+ * @param numberOfEntries the number of entries to include in the list
+ * @return list of last (newest) values of this LRU cache
+ */
+ public synchronized List<T> getLastValues(int numberOfEntries) {
+ List<T> outList = new ArrayList<T>();
+
+ if (numberOfEntries <= 0) {
+ return outList;
+ }
+
+ // Get all keys sorted
+ NavigableSet<Integer> keys =
+ new ConcurrentSkipListSet<Integer>(this.keySet());
+
+ int i = 0;
+
+ // Iterate the sorted keys backwards
+ for (Integer k : keys.descendingSet()) {
+ // Pick up the last 'numberOfEntries' entries
+ if (i >= numberOfEntries) {
+ break;
+ }
+
+ outList.add(this.get(k));
+ i++;
+ }
+
+ return outList;
+ }
+
+ /**
+ * Returns the next position to store data.
+ *
+ * @return next key to store data
+ */
+ private synchronized Integer getNextKey() {
+ // The oldest will be the next..
+ if (this.size() == maxEntries) {
+ return this.getFirstKey();
+ }
+
+ Integer lastKey = this.getLastKey();
+ // First insertion
+ if (lastKey == null) {
+ return new Integer(0);
+ }
+
+ // Regular next key insertion
+ return new Integer(lastKey.intValue() + 1);
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/MetricType.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/MetricType.java
new file mode 100644
index 0000000..841b83e
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/MetricType.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+/**
+ * A set of metrics to be projected.
+ */
+public enum MetricType {
+
+ /**
+ * CPU cores of a commodity server.
+ */
+ CPU,
+
+ /**
+ * Per core latency.
+ */
+ LATENCY,
+
+ /**
+ * Per core throughput.
+ */
+ THROUGHPUT;
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputUI.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputUI.java
new file mode 100644
index 0000000..9820051
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputUI.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiView;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static org.onosproject.ui.UiView.Category.NETWORK;
+import static org.onosproject.ui.GlyphConstants.ENDSTATION;
+
+/**
+ * Mechanism to stream throughput data to the GUI.
+ */
+@Component(immediate = true, enabled = true)
+@Service(value = ThroughputUI.class)
+public class ThroughputUI {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ /**
+ * GUI Information.
+ */
+ private static final String THROUGHPUT_ID = "throughput";
+ private static final String THROUGHPUT_TEXT = "Servers-Throughput";
+ private static final String RES_PATH = "gui";
+ private static final ClassLoader CL = ThroughputUI.class.getClassLoader();
+
+ // Factory for UI message handlers
+ private final UiMessageHandlerFactory messageHandlerFactory =
+ () -> ImmutableList.of(new ThroughputViewMessageHandler());
+
+ // List of application views
+ private final List<UiView> views = ImmutableList.of(
+ new UiView(NETWORK, THROUGHPUT_ID, THROUGHPUT_TEXT, ENDSTATION)
+ );
+
+ // Application UI extension
+ private final UiExtension uiExtension =
+ new UiExtension.Builder(CL, views)
+ .messageHandlerFactory(messageHandlerFactory)
+ .resourcePath(RES_PATH)
+ .build();
+
+ /**
+ * Interact with ONOS.
+ */
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected UiExtensionService uiExtensionService;
+
+ @Activate
+ protected void activate() {
+ uiExtensionService.register(uiExtension);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ uiExtensionService.unregister(uiExtension);
+ log.info("Stopped");
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputViewMessageHandler.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputViewMessageHandler.java
new file mode 100644
index 0000000..91105e9
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputViewMessageHandler.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.drivers.server.gui;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.primitives.Floats;
+
+import org.onosproject.drivers.server.behavior.MonitoringStatisticsDiscovery;
+import org.onosproject.drivers.server.devices.RestServerSBDevice;
+import org.onosproject.drivers.server.stats.CpuStatistics;
+import org.onosproject.drivers.server.stats.MonitoringStatistics;
+import org.onosproject.drivers.server.stats.MonitoringUnit.ThroughputUnit;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.chart.ChartModel;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.joda.time.LocalDateTime;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.drivers.server.gui.MetricType.THROUGHPUT;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Message handler for passing throughput data to the Web UI.
+ */
+public class ThroughputViewMessageHandler extends BaseViewMessageHandler {
+
+ private static final Logger log = getLogger(ThroughputViewMessageHandler.class);
+
+ private static final String THROUGHPUT_DATA_REQ = "throughputDataRequest";
+ private static final String THROUGHPUT_DATA_RESP = "throughputDataResponse";
+ private static final String THROUGHPUT_LABEL = "throughputs";
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new ThroughputMessageRequest());
+ }
+
+ private final class ThroughputMessageRequest extends BaseViewMessageHandler.ControlMessageRequest {
+
+ private ThroughputMessageRequest() {
+ super(THROUGHPUT_DATA_REQ, THROUGHPUT_DATA_RESP, THROUGHPUT_LABEL);
+ }
+
+ @Override
+ protected String[] getSeries() {
+ return createSeries(THROUGHPUT, MAX_COLUMNS_NB);
+ }
+
+ @Override
+ protected void populateChart(ChartModel cm, ObjectNode payload) {
+ DeviceService ds = get(DeviceService.class);
+ if ((ds == null) || (ds.getAvailableDeviceCount() == 0)) {
+ fillDataWhenNoDevicePresent(cm, THROUGHPUT, MAX_COLUMNS_NB);
+ return;
+ }
+
+ String uri = string(payload, "devId");
+
+ // Project only one device over time
+ if (!Strings.isNullOrEmpty(uri)) {
+ DeviceId deviceId = DeviceId.deviceId(uri);
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ Map<Integer, Float[]> data = null;
+ MonitoringStatistics monStats = serverDriver.getGlobalMonitoringStatistics(deviceId);
+ if (monStats == null) {
+ data = populateZeroDataHistory(deviceId, MAX_COLUMNS_NB);
+ } else {
+ data = populateThroughputDataHistory(deviceId, serverDev.numberOfCpus(), monStats);
+ }
+ checkNotNull(data, "No throughput data history to visualize");
+
+ // Generate a timestamp
+ LocalDateTime ldt = new LocalDateTime(timestamp);
+
+ // Project the data
+ populateMetrics(cm, data, ldt, THROUGHPUT, NUM_OF_DATA_POINTS);
+
+ Set<DeviceId> deviceIds = Sets.newHashSet();
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support this type of monitoring behaviour are considered
+ if (device.is(MonitoringStatisticsDiscovery.class) && serverDev.isActive()) {
+ deviceIds.add(device.id());
+ }
+ }
+
+ // Drop down list to select devices
+ attachDeviceList(cm, deviceIds);
+ } else {
+ for (Device device : ds.getAvailableDevices()) {
+ // Only devices that support this type of monitoring behaviour are considered
+ if (!device.is(MonitoringStatisticsDiscovery.class)) {
+ continue;
+ }
+
+ DeviceId deviceId = device.id();
+ RestServerSBDevice serverDev =
+ (RestServerSBDevice) basicDriver.getController().getDevice(deviceId);
+
+ Map<Integer, Float> data = null;
+ MonitoringStatistics monStats = serverDriver.getGlobalMonitoringStatistics(deviceId);
+ if (monStats == null) {
+ data = populateZeroData(deviceId, MAX_COLUMNS_NB);
+ } else {
+ data = populateThroughputData(deviceId, serverDev.numberOfCpus(), monStats);
+ }
+ checkNotNull(data, "No throughput data to visualize");
+
+ // Map them to the CPU cores
+ Map<String, Object> local = Maps.newHashMap();
+ for (int i = 0; i < data.size(); i++) {
+ local.put(getLabel(THROUGHPUT, i), data.get(i));
+ }
+
+ // Last piece of data is the device ID
+ if (serverDev.isActive()) {
+ local.put(LABEL, deviceId);
+ populateMetric(cm.addDataPoint(deviceId), local);
+ } else {
+ local.put(LABEL, "");
+ populateMetric(cm.addDataPoint(""), local);
+ }
+ }
+ }
+ }
+
+ /**
+ * Turn the current monitoring data into a data
+ * structure that can feed the Throughput UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param monStats a MonitoringStatistics object
+ * @return a map of throughput metrics to their values
+ */
+ private Map<Integer, Float> populateThroughputData(
+ DeviceId deviceId, int length, MonitoringStatistics monStats) {
+ Map<Integer, Float> data = initializeData(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : monStats.cpuStatisticsAll()) {
+ int index = stats.id();
+
+ Float value = null;
+ if ((stats.averageThroughput().isPresent()) && (stats.load() > MIN_CPU_LOAD)) {
+ value = stats.averageThroughput().get();
+ } else {
+ value = new Float(0);
+ }
+
+ // Unit conversion
+ ThroughputUnit throughputUnit = null;
+ if (stats.throughputUnit().isPresent()) {
+ throughputUnit = (ThroughputUnit) stats.throughputUnit().get();
+ } else {
+ throughputUnit = ThroughputUnit.BPS;
+ }
+ value = ThroughputUnit.toGbps(value, throughputUnit);
+
+ // Store it locally
+ addToCache(deviceId, length, index, value);
+
+ // And into the map
+ data.put(index, value);
+ }
+
+ return data;
+ }
+
+ /**
+ * Turn the monitoring data history into a
+ * data structure that can feed the Throughput UI memory.
+ *
+ * @param deviceId the device ID being monitored
+ * @param length the length of the array
+ * @param monStats a MonitoringStatistics object
+ * @return a map of throughput metrics to their arrays of values
+ */
+ private Map<Integer, Float[]> populateThroughputDataHistory(
+ DeviceId deviceId, int length, MonitoringStatistics monStats) {
+ Map<Integer, Float[]> data = initializeDataHistory(MAX_COLUMNS_NB);
+
+ for (CpuStatistics stats : monStats.cpuStatisticsAll()) {
+ int index = stats.id();
+
+ Float value = null;
+ if ((stats.averageThroughput().isPresent()) && (stats.load() > MIN_CPU_LOAD)) {
+ value = stats.averageThroughput().get();
+ } else {
+ value = new Float(0);
+ }
+
+ // Unit conversion
+ ThroughputUnit throughputUnit = null;
+ if (stats.throughputUnit().isPresent()) {
+ throughputUnit = (ThroughputUnit) stats.throughputUnit().get();
+ } else {
+ throughputUnit = ThroughputUnit.BPS;
+ }
+ value = ThroughputUnit.toGbps(value, throughputUnit);
+
+ // Store it locally
+ addToCache(deviceId, length, index, value);
+
+ LruCache<Float> loadCache = getDataHistory(deviceId, index);
+ if (loadCache == null) {
+ continue;
+ }
+ float[] floatArray = Floats.toArray(Arrays.asList(loadCache.values().toArray(new Float[0])));
+
+ // Fill the missing points
+ float[] filledLoadArray = fillData(floatArray, NUM_OF_DATA_POINTS);
+
+ // Set the data
+ data.put(index, ArrayUtils.toObject(filledLoadArray));
+ }
+
+ // Keep a timestamp
+ timestamp = System.currentTimeMillis();
+
+ return data;
+ }
+
+ }
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/gui/package-info.java b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/package-info.java
new file mode 100644
index 0000000..0d5cbf8
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/gui/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Web GUI for the server device driver.
+ */
+package org.onosproject.drivers.server.gui;
\ No newline at end of file
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/devices/DefaultRestServerSBDevice.java b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/devices/DefaultRestServerSBDevice.java
index cc2e541..b43c2ec 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/devices/DefaultRestServerSBDevice.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/devices/DefaultRestServerSBDevice.java
@@ -126,11 +126,11 @@
return MoreObjects.toStringHelper(this)
.omitNullValues()
.add("url", url())
- .add("testUrl", testUrl())
.add("protocol", protocol())
.add("username", username())
- .add("port", port())
.add("ip", ip())
+ .add("port", port())
+ .add("testUrl", testUrl().orElse(null))
.add("manufacturer", manufacturer().orElse(null))
.add("hwVersion", hwVersion().orElse(null))
.add("swVersion", swVersion().orElse(null))
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultCpuStatistics.java b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultCpuStatistics.java
index f9c1989..f2b6968 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultCpuStatistics.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultCpuStatistics.java
@@ -17,10 +17,15 @@
package org.onosproject.drivers.server.impl.stats;
import org.onosproject.drivers.server.stats.CpuStatistics;
+import org.onosproject.drivers.server.stats.MonitoringUnit;
import org.onosproject.net.DeviceId;
import com.google.common.base.MoreObjects;
+import java.util.Optional;
+
+import static org.onosproject.drivers.server.stats.MonitoringUnit.LatencyUnit;
+import static org.onosproject.drivers.server.stats.MonitoringUnit.ThroughputUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkArgument;
@@ -32,6 +37,9 @@
private static final float MIN_CPU_LOAD = (float) 0.0;
private static final float MAX_CPU_LOAD = (float) 1.0;
+ private static final LatencyUnit DEF_LATENCY_UNIT = LatencyUnit.NANO_SECOND;
+ private static final ThroughputUnit DEF_THROUGHPUT_UNIT = ThroughputUnit.MBPS;
+
// Upper limit of CPU cores in one machine
public static final int MAX_CPU_NB = 512;
@@ -39,35 +47,62 @@
private final int id;
private final float load;
- private final boolean isBusy;
+ private final int queue;
+ private final int busySince;
+ private final Optional<MonitoringUnit> throughputUnit;
+ private final Optional<Float> averageThroughput;
+ private final Optional<MonitoringUnit> latencyUnit;
+ private final Optional<Float> minLatency;
+ private final Optional<Float> averageLatency;
+ private final Optional<Float> maxLatency;
- private DefaultCpuStatistics(
- DeviceId deviceId,
- int id,
- float load,
- boolean isBusy) {
+ private DefaultCpuStatistics(DeviceId deviceId, int id, float load, int queue, int busySince) {
+ this(deviceId, id, load, queue, busySince, null, -1, null, -1, -1, -1);
+ }
+
+ private DefaultCpuStatistics(DeviceId deviceId, int id, float load, int queue, int busySince,
+ MonitoringUnit throughputUnit, float averageThroughput, MonitoringUnit latencyUnit,
+ float minLatency, float averageLatency, float maxLatency) {
checkNotNull(deviceId, "Device ID is NULL");
- checkArgument(
- (id >= 0) && (id < MAX_CPU_NB),
- "Invalid CPU core ID " + String.valueOf(id) + ", not in [0, " + String.valueOf(MAX_CPU_NB - 1) + "]"
- );
- checkArgument(
- (load >= MIN_CPU_LOAD) && (load <= MAX_CPU_LOAD),
- "Invalid CPU load " + Float.toString(load) + ", not in [" + MIN_CPU_LOAD + ", " + MAX_CPU_LOAD + "]"
- );
+ checkArgument((id >= 0) && (id < MAX_CPU_NB),
+ "Invalid CPU core ID " + String.valueOf(id) + ", not in [0, " + String.valueOf(MAX_CPU_NB - 1) + "]");
+ checkArgument((load >= MIN_CPU_LOAD) && (load <= MAX_CPU_LOAD),
+ "Invalid CPU load " + Float.toString(load) + ", not in [" + MIN_CPU_LOAD + ", " + MAX_CPU_LOAD + "]");
- this.deviceId = deviceId;
- this.id = id;
- this.load = load;
- this.isBusy = isBusy;
+ this.deviceId = deviceId;
+ this.id = id;
+ this.load = load;
+ this.queue = queue;
+ this.busySince = busySince;
+
+ this.throughputUnit = (throughputUnit == null) ?
+ Optional.empty() : Optional.ofNullable(throughputUnit);
+ this.averageThroughput = (averageThroughput < 0) ?
+ Optional.empty() : Optional.ofNullable(averageThroughput);
+ this.latencyUnit = (latencyUnit == null) ?
+ Optional.empty() : Optional.ofNullable(latencyUnit);
+ this.minLatency = (minLatency < 0) ?
+ Optional.empty() : Optional.ofNullable(minLatency);
+ this.averageLatency = (averageLatency < 0) ?
+ Optional.empty() : Optional.ofNullable(averageLatency);
+ this.maxLatency = (maxLatency < 0) ?
+ Optional.empty() : Optional.ofNullable(maxLatency);
}
// Constructor for serializer
private DefaultCpuStatistics() {
- this.deviceId = null;
- this.id = 0;
- this.load = 0;
- this.isBusy = false;
+ this.deviceId = null;
+ this.id = 0;
+ this.load = 0;
+ this.queue = 0;
+ this.busySince = -1;
+
+ this.throughputUnit = null;
+ this.averageThroughput = null;
+ this.latencyUnit = null;
+ this.minLatency = null;
+ this.averageLatency = null;
+ this.maxLatency = null;
}
/**
@@ -90,8 +125,48 @@
}
@Override
+ public int queue() {
+ return this.queue;
+ }
+
+ @Override
public boolean busy() {
- return this.isBusy;
+ return this.busySince >= 0;
+ }
+
+ @Override
+ public int busySince() {
+ return this.busySince;
+ }
+
+ @Override
+ public Optional<MonitoringUnit> throughputUnit() {
+ return this.throughputUnit;
+ }
+
+ @Override
+ public Optional<Float> averageThroughput() {
+ return this.averageThroughput;
+ }
+
+ @Override
+ public Optional<MonitoringUnit> latencyUnit() {
+ return this.latencyUnit;
+ }
+
+ @Override
+ public Optional<Float> minLatency() {
+ return this.minLatency;
+ }
+
+ @Override
+ public Optional<Float> averageLatency() {
+ return this.averageLatency;
+ }
+
+ @Override
+ public Optional<Float> maxLatency() {
+ return this.maxLatency;
}
@Override
@@ -101,7 +176,14 @@
.add("device", deviceId)
.add("id", id())
.add("load", load())
- .add("isBusy", busy())
+ .add("queue", queue())
+ .add("busySince", busySince())
+ .add("throughputUnit", throughputUnit.orElse(null))
+ .add("averageThroughput", averageThroughput.orElse(null))
+ .add("latencyUnit", latencyUnit.orElse(null))
+ .add("minLatency", minLatency.orElse(null))
+ .add("averageLatency", averageLatency.orElse(null))
+ .add("maxLatency", maxLatency.orElse(null))
.toString();
}
@@ -109,8 +191,16 @@
DeviceId deviceId;
int id;
- float load;
- boolean isBusy;
+ float load = 0;
+ int queue = -1;
+ int busySince = -1;
+
+ MonitoringUnit throughputUnit = DEF_THROUGHPUT_UNIT;
+ float averageThroughput = -1;
+ MonitoringUnit latencyUnit = DEF_LATENCY_UNIT;
+ float minLatency = -1;
+ float averageLatency = -1;
+ float maxLatency = -1;
private Builder() {
@@ -131,7 +221,7 @@
/**
* Sets the CPU ID.
*
- * @param id the CPU ID
+ * @param id CPU ID
* @return builder object
*/
public Builder setId(int id) {
@@ -153,13 +243,97 @@
}
/**
- * Sets the CPU status (free or busy).
+ * Sets the hardware queue ID associated with this core.
*
- * @param isBusy CPU status
+ * @param queue hardware queue ID
* @return builder object
*/
- public Builder setIsBusy(boolean isBusy) {
- this.isBusy = isBusy;
+ public Builder setQueue(int queue) {
+ this.queue = queue;
+
+ return this;
+ }
+
+ /**
+ * Sets the CPU status (free or busy since some ms).
+ *
+ * @param busySince CPU busy time in ms, -1 if not busy
+ * @return builder object
+ */
+ public Builder setBusySince(int busySince) {
+ this.busySince = busySince;
+
+ return this;
+ }
+
+ /**
+ * Sets the throughput unit.
+ *
+ * @param throughputUnitStr throughput unit as a string
+ * @return builder object
+ */
+ public Builder setThroughputUnit(String throughputUnitStr) {
+ this.throughputUnit = ThroughputUnit.getByName(throughputUnitStr);
+
+ return this;
+ }
+
+ /**
+ * Sets the average throughput.
+ *
+ * @param averageThroughput average throughput
+ * @return builder object
+ */
+ public Builder setAverageThroughput(float averageThroughput) {
+ this.averageThroughput = averageThroughput;
+
+ return this;
+ }
+
+ /**
+ * Sets the latency unit.
+ *
+ * @param latencyUnitStr latency unit as a string
+ * @return builder object
+ */
+ public Builder setLatencyUnit(String latencyUnitStr) {
+ this.latencyUnit = LatencyUnit.getByName(latencyUnitStr);
+
+ return this;
+ }
+
+ /**
+ * Sets the minimum latency.
+ *
+ * @param minLatency minimum latency
+ * @return builder object
+ */
+ public Builder setMinLatency(float minLatency) {
+ this.minLatency = minLatency;
+
+ return this;
+ }
+
+ /**
+ * Sets the average latency.
+ *
+ * @param averageLatency average latency
+ * @return builder object
+ */
+ public Builder setAverageLatency(float averageLatency) {
+ this.averageLatency = averageLatency;
+
+ return this;
+ }
+
+ /**
+ * Sets the maximum latency.
+ *
+ * @param maxLatency maximum latency
+ * @return builder object
+ */
+ public Builder setMaxLatency(float maxLatency) {
+ this.maxLatency = maxLatency;
return this;
}
@@ -171,11 +345,9 @@
*/
public DefaultCpuStatistics build() {
return new DefaultCpuStatistics(
- deviceId,
- id,
- load,
- isBusy
- );
+ deviceId, id, load, queue, busySince,
+ throughputUnit, averageThroughput,
+ latencyUnit, minLatency, averageLatency, maxLatency);
}
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultTimingStatistics.java b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultTimingStatistics.java
index 8256c5f..6c4f86f 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultTimingStatistics.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultTimingStatistics.java
@@ -16,39 +16,50 @@
package org.onosproject.drivers.server.impl.stats;
+import org.onosproject.drivers.server.stats.MonitoringUnit;
import org.onosproject.drivers.server.stats.TimingStatistics;
+import com.google.common.base.Strings;
import com.google.common.base.MoreObjects;
import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.drivers.server.stats.MonitoringUnit.LatencyUnit;
/**
* Default implementation for timing statistics.
*/
public final class DefaultTimingStatistics implements TimingStatistics {
+ private static final LatencyUnit DEF_UNIT = LatencyUnit.NANO_SECOND;
+
+ private final MonitoringUnit unit;
private final long deployCommandParsingTime;
private final long deployCommandLaunchingTime;
- private long autoscaleTime;
+ private long autoScaleTime;
private DefaultTimingStatistics(
+ MonitoringUnit unit,
long parsingTime,
long launchingTime,
- long autoscaleTime) {
+ long autoScaleTime) {
+ checkNotNull(unit, "Time statistics unit is null");
checkArgument(parsingTime >= 0, "Parsing time is negative");
checkArgument(launchingTime >= 0, "Launching time is negative");
- checkArgument(autoscaleTime >= 0, "Autoscale time is negative");
+ checkArgument(autoScaleTime >= 0, "Auto-scale time is negative");
+ this.unit = unit;
this.deployCommandParsingTime = parsingTime;
this.deployCommandLaunchingTime = launchingTime;
- this.autoscaleTime = autoscaleTime;
+ this.autoScaleTime = autoScaleTime;
}
// Constructor for serializer
private DefaultTimingStatistics() {
+ this.unit = null;
this.deployCommandParsingTime = 0;
this.deployCommandLaunchingTime = 0;
- this.autoscaleTime = 0;
+ this.autoScaleTime = 0;
}
/**
@@ -61,6 +72,11 @@
}
@Override
+ public MonitoringUnit unit() {
+ return this.unit;
+ }
+
+ @Override
public long deployCommandParsingTime() {
return this.deployCommandParsingTime;
}
@@ -76,32 +92,48 @@
}
@Override
- public long autoscaleTime() {
- return this.autoscaleTime;
+ public long autoScaleTime() {
+ return this.autoScaleTime;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.omitNullValues()
- .add("parsing", this.deployCommandParsingTime())
- .add("launching", this.deployCommandLaunchingTime())
- .add("total", this.totalDeploymentTime())
- .add("autoscale", this.autoscaleTime())
+ .add("unit", this.unit().toString())
+ .add("parsingTime", this.deployCommandParsingTime())
+ .add("launchingTime", this.deployCommandLaunchingTime())
+ .add("deploymentTime", this.totalDeploymentTime())
+ .add("autoScaleTime", this.autoScaleTime())
.toString();
}
public static final class Builder {
+ MonitoringUnit unit = DEF_UNIT;
long deployCommandParsingTime;
long deployCommandLaunchingTime;
- long autoscaleTime;
+ long autoScaleTime;
private Builder() {
}
/**
+ * Sets time statistics unit.
+ *
+ * @param unitStr time statistics unit as a string
+ * @return builder object
+ */
+ public Builder setUnit(String unitStr) {
+ if (!Strings.isNullOrEmpty(unitStr)) {
+ this.unit = LatencyUnit.getByName(unitStr);
+ }
+
+ return this;
+ }
+
+ /**
* Sets parsing time.
*
* @param parsingTime parsing time
@@ -128,11 +160,11 @@
/**
* Sets autoscale time.
*
- * @param autoscaleTime time required to autoscale
+ * @param autoScaleTime time required to autoscale
* @return builder object
*/
- public Builder setAutoscaleTime(long autoscaleTime) {
- this.autoscaleTime = autoscaleTime;
+ public Builder setAutoScaleTime(long autoScaleTime) {
+ this.autoScaleTime = autoScaleTime;
return this;
}
@@ -144,10 +176,8 @@
*/
public DefaultTimingStatistics build() {
return new DefaultTimingStatistics(
- deployCommandParsingTime,
- deployCommandLaunchingTime,
- autoscaleTime
- );
+ unit, deployCommandParsingTime,
+ deployCommandLaunchingTime, autoScaleTime);
}
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/stats/CpuStatistics.java b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/CpuStatistics.java
index 9ea22b3..5296d57 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/stats/CpuStatistics.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/CpuStatistics.java
@@ -16,6 +16,8 @@
package org.onosproject.drivers.server.stats;
+import java.util.Optional;
+
/**
* CPU statistics API.
*/
@@ -38,10 +40,71 @@
float load();
/**
+ * Returns the hardware queue identifier associated with this CPU core.
+ *
+ * @return hardware queue identifier
+ */
+ int queue();
+
+ /**
* Returns the status (true=busy, false=free) of a CPU core.
*
* @return boolean CPU core status
*/
boolean busy();
+ /**
+ * Returns the amount of time in ms since the CPU has been busy,
+ * or a negative value if the CPU is idle.
+ *
+ * @return int time in ms since the CPU has been busy
+ */
+ int busySince();
+
+ /**
+ * Returns the unit of throughput values.
+ *
+ * @return throughput monitoring unit
+ */
+ Optional<MonitoringUnit> throughputUnit();
+
+ /**
+ * Returns the average throughput of this CPU core,
+ * expressed in throughputUnit() monitoring units.
+ *
+ * @return average throughput of a CPU core
+ */
+ Optional<Float> averageThroughput();
+
+ /**
+ * Returns the unit of latency values.
+ *
+ * @return latency monitoring unit
+ */
+ Optional<MonitoringUnit> latencyUnit();
+
+ /**
+ * Returns the minimum latency incurred by a CPU core,
+ * expressed in latencyUnit() monitoring units.
+ *
+ * @return minimum latency incurred by a CPU core
+ */
+ Optional<Float> minLatency();
+
+ /**
+ * Returns the average latency incurred by a CPU core,
+ * expressed in latencyUnit() monitoring units.
+ *
+ * @return average latency incurred by a CPU core
+ */
+ Optional<Float> averageLatency();
+
+ /**
+ * Returns the maximum latency incurred by a CPU core,
+ * expressed in latencyUnit() monitoring units.
+ *
+ * @return maximum latency incurred by a CPU core
+ */
+ Optional<Float> maxLatency();
+
}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/stats/MonitoringUnit.java b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/MonitoringUnit.java
new file mode 100644
index 0000000..33d8763
--- /dev/null
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/MonitoringUnit.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.drivers.server.stats;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * Representation of a monitoring unit.
+ */
+public interface MonitoringUnit {
+
+ /**
+ * Throughput-related monitoring units.
+ */
+ public enum ThroughputUnit implements MonitoringUnit {
+
+ BPS("bps"),
+ KBPS("kbps"),
+ MBPS("mbps"),
+ GBPS("gbps");
+
+ private String throughputUnit;
+
+ // Statically maps throughput monitoring units to enum types
+ private static final Map<String, MonitoringUnit> MAP =
+ new HashMap<String, MonitoringUnit>();
+ static {
+ for (ThroughputUnit tu : ThroughputUnit.values()) {
+ MAP.put(tu.toString().toLowerCase(), (MonitoringUnit) tu);
+ }
+ }
+
+ private ThroughputUnit(String throughputUnit) {
+ this.throughputUnit = throughputUnit;
+ }
+
+ public static MonitoringUnit getByName(String tu) {
+ tu = tu.toLowerCase();
+ return MAP.get(tu);
+ }
+
+ public static float toGbps(float value, ThroughputUnit fromUnit) {
+ if (value == 0) {
+ return value;
+ }
+
+ if (fromUnit == BPS) {
+ return value / 1000000000;
+ } else if (fromUnit == KBPS) {
+ return value / 1000000;
+ } else if (fromUnit == MBPS) {
+ return value / 1000;
+ }
+
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return this.throughputUnit;
+ }
+
+ };
+
+ /**
+ * Latency-related monitoring units.
+ */
+ public enum LatencyUnit implements MonitoringUnit {
+
+ NANO_SECOND("ns"),
+ MICRO_SECOND("us"),
+ MILLI_SECOND("ms"),
+ SECOND("s");
+
+ private String latencyUnit;
+
+ // Statically maps latency monitoring units to enum types
+ private static final Map<String, MonitoringUnit> MAP =
+ new HashMap<String, MonitoringUnit>();
+ static {
+ for (LatencyUnit lu : LatencyUnit.values()) {
+ MAP.put(lu.toString().toLowerCase(), (MonitoringUnit) lu);
+ }
+ }
+
+ private LatencyUnit(String latencyUnit) {
+ this.latencyUnit = latencyUnit;
+ }
+
+ public static MonitoringUnit getByName(String lu) {
+ lu = lu.toLowerCase();
+ return MAP.get(lu);
+ }
+
+ public static float toNano(float value, LatencyUnit fromUnit) {
+ if (value == 0) {
+ return value;
+ }
+
+ if (fromUnit == MICRO_SECOND) {
+ return value * 1000;
+ } else if (fromUnit == MILLI_SECOND) {
+ return value * 1000000;
+ } else if (fromUnit == SECOND) {
+ return value * 1000000000;
+ }
+
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return this.latencyUnit;
+ }
+
+ };
+
+ String toString();
+
+}
diff --git a/drivers/server/src/main/java/org/onosproject/drivers/server/stats/TimingStatistics.java b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/TimingStatistics.java
index 102b186..23215ce 100644
--- a/drivers/server/src/main/java/org/onosproject/drivers/server/stats/TimingStatistics.java
+++ b/drivers/server/src/main/java/org/onosproject/drivers/server/stats/TimingStatistics.java
@@ -22,6 +22,13 @@
public interface TimingStatistics {
/**
+ * Returns the unit of timing statistics.
+ *
+ * @return timing statistics' unit
+ */
+ MonitoringUnit unit();
+
+ /**
* Time (ns) to parse the controller's deployment instruction.
*
* @return time in nanoseconds to parse a 'deploy' command
@@ -45,10 +52,10 @@
/**
* Time (ns) to perform a local reconfiguration.
- * (i.e., the agent autoscales the number of CPUs).
+ * (i.e., the agent auto-scales the number of CPUs).
*
- * @return time in nanoseconds to autoscale
+ * @return time in nanoseconds to auto scale
*/
- long autoscaleTime();
+ long autoScaleTime();
}
diff --git a/drivers/server/src/main/resources/app/view/cpu/cpu.css b/drivers/server/src/main/resources/app/view/cpu/cpu.css
new file mode 100644
index 0000000..3f6f72e
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/cpu/cpu.css
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- CPU Manager -- CSS file
+ */
+
+#ov-cpu {
+ padding: 20px;
+ position: relative;
+}
+.light #ov-cpu {
+ color: navy;
+}
+.dark #ov-cpu {
+ color: #88f;
+}
+
+#ov-cpu .button-panel {
+ margin: 10px;
+ width: 200px;
+}
+
+.light #ov-cpu .button-panel {
+ background-color: #ccf;
+}
+.dark #ov-cpu .button-panel {
+ background-color: #444;
+}
+
+#ov-cpu #chart-loader {
+ position: absolute;
+ width: 200px;
+ height: 50px;
+ margin-left: -100px;
+ margin-top: -25px;
+ z-index: 900;
+ top: 50%;
+ text-align: center;
+ left: 50%;
+ font-size: 25px;
+ font-weight: bold;
+ color: #ccc;
+}
\ No newline at end of file
diff --git a/drivers/server/src/main/resources/app/view/cpu/cpu.html b/drivers/server/src/main/resources/app/view/cpu/cpu.html
new file mode 100644
index 0000000..7fd758f
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/cpu/cpu.html
@@ -0,0 +1,27 @@
+<!-- partial HTML -->
+<div id="ov-cpu">
+ <div id="chart-loader" ng-show="!devId && showLoader">
+ No Servers
+ </div>
+ <div ng-show="!devId">
+ <canvas id="bar" class="chart chart-bar" chart-data="data"
+ chart-labels="labels" chart-legend="true" chart-click="onClick"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+ <div ng-show="devId">
+ <h2>
+ Chart for Device {{devId || "(No device selected)"}}
+ </h2>
+ <div class="ctrl-btns">
+ <select ng-options="deviceId as deviceId for deviceId in deviceIds"
+ ng-model="selectedItem" ng-change="onChange(selectedItem)">
+ <option value="">-- select a device --</option>
+ </select>
+ </div>
+ <canvas id="line" class="chart chart-line" chart-data="data"
+ chart-labels="labels" chart-legend="true"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+</div>
diff --git a/drivers/server/src/main/resources/app/view/cpu/cpu.js b/drivers/server/src/main/resources/app/view/cpu/cpu.js
new file mode 100644
index 0000000..93f3ac8
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/cpu/cpu.js
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- CPU Manager View Module
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $scope, $location, ks, fs, cbs, ns;
+
+ var hasDeviceId;
+ // TODO: Pass this dynamically
+ var coresNb = 16;
+
+ var labels = new Array(1);
+ var data = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(1);
+ data[i][0] = 0;
+ }
+
+ angular.module('ovCpu', ["chart.js"])
+ .controller('OvCpuCtrl',
+ ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService', 'NavService',
+
+ function (_$log_, _$scope_, _$location_, _fs_, _cbs_, _ns_) {
+ var params;
+ $log = _$log_;
+ $scope = _$scope_;
+ $location = _$location_;
+ fs = _fs_;
+ cbs = _cbs_;
+ ns = _ns_;
+
+ params = $location.search();
+
+ if (params.hasOwnProperty('devId')) {
+ $scope.devId = params['devId'];
+ hasDeviceId = true;
+ } else {
+ hasDeviceId = false;
+ }
+
+ cbs.buildChart({
+ scope: $scope,
+ tag: 'cpu',
+ query: params
+ });
+
+ $scope.$watch('chartData', function () {
+ if (!fs.isEmptyObject($scope.chartData)) {
+ $scope.showLoader = false;
+ var length = $scope.chartData.length;
+ labels = new Array(length);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(length);
+ }
+
+ $scope.chartData.forEach(
+ function (cm, idx) {
+ // TODO: Squeeze using a working loop?
+ data[0][idx] = cm.cpu_0;
+ data[1][idx] = cm.cpu_1;
+ data[2][idx] = cm.cpu_2;
+ data[3][idx] = cm.cpu_3;
+ data[4][idx] = cm.cpu_4;
+ data[5][idx] = cm.cpu_5;
+ data[6][idx] = cm.cpu_6;
+ data[7][idx] = cm.cpu_7;
+ data[8][idx] = cm.cpu_8;
+ data[9][idx] = cm.cpu_9;
+ data[10][idx] = cm.cpu_10;
+ data[11][idx] = cm.cpu_11;
+ data[12][idx] = cm.cpu_12;
+ data[13][idx] = cm.cpu_13;
+ data[14][idx] = cm.cpu_14;
+ data[15][idx] = cm.cpu_15;
+
+ labels[idx] = cm.label;
+ }
+ );
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ $scope.options = {
+ scales: {
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-cpu',
+ ticks: {
+ min: 0,
+ max: 100,
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ labelString: 'Utilization/CPU Core (%)',
+ fontSize: 28,
+ }
+ }],
+ xAxes: [{
+ id: 'x-axis-servers-cores',
+ ticks: {
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ fontSize: 28,
+ }
+ }]
+ }
+ };
+
+ $scope.onClick = function (points, evt) {
+ var label = labels[points[0]._index];
+ if (label) {
+ ns.navTo('cpu', { devId: label });
+ $log.log(label);
+ }
+ };
+
+ if (!fs.isEmptyObject($scope.annots)) {
+ $scope.deviceIds = JSON.parse($scope.annots.deviceIds);
+ }
+
+ $scope.onChange = function (deviceId) {
+ ns.navTo('cpu', { devId: deviceId });
+ };
+ });
+
+ $scope.series = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ $scope.series[i] = 'CPU ' + i;
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ // TODO: For some reason, this assignment does not work
+ $scope.chartColors = [
+ '#e6194b', // Red
+ '#3cb44b', // Green
+ '#ffe119', // Yellow
+ '#0082c8', // Blue
+ '#f58231', // Orange
+ '#808080', // Grey
+ '#fffac8', // Beige
+ '#aaffc3', // Mint
+ '#911eb4', // Purple
+ '#46f0f0', // Cyan
+ '#d2f53c', // Lime
+ '#800000', // Maroon
+ '#000000', // Black
+ '#f032e6', // Magenta
+ '#008080', // Teal
+ '#808000', // Olive
+ '#aa6e28' // Brown
+ ];
+ Chart.defaults.global.colours = $scope.chartColors;
+
+ $scope.showLoader = true;
+
+ $log.log('OvCpuCtrl has been created');
+ }]);
+
+}());
diff --git a/drivers/server/src/main/resources/app/view/latency/latency.css b/drivers/server/src/main/resources/app/view/latency/latency.css
new file mode 100644
index 0000000..61f9716
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/latency/latency.css
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Latency UI -- CSS file
+ */
+
+#ov-latency {
+ padding: 20px;
+ position: relative;
+}
+.light #ov-latency {
+ color: navy;
+}
+.dark #ov-latency {
+ color: #88f;
+}
+
+#ov-latency .button-panel {
+ margin: 10px;
+ width: 200px;
+}
+
+.light #ov-latency .button-panel {
+ background-color: #ccf;
+}
+.dark #ov-latency .button-panel {
+ background-color: #444;
+}
+
+#ov-latency #chart-loader {
+ position: absolute;
+ width: 200px;
+ height: 50px;
+ margin-left: -100px;
+ margin-top: -25px;
+ z-index: 900;
+ top: 50%;
+ text-align: center;
+ left: 50%;
+ font-size: 25px;
+ font-weight: bold;
+ color: #ccc;
+}
\ No newline at end of file
diff --git a/drivers/server/src/main/resources/app/view/latency/latency.html b/drivers/server/src/main/resources/app/view/latency/latency.html
new file mode 100644
index 0000000..1e5e6b2
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/latency/latency.html
@@ -0,0 +1,27 @@
+<!-- partial HTML -->
+<div id="ov-latency">
+ <div id="chart-loader" ng-show="!devId && showLoader">
+ No Servers
+ </div>
+ <div ng-show="!devId">
+ <canvas id="bar" class="chart chart-bar" chart-data="data"
+ chart-labels="labels" chart-legend="true" chart-click="onClick"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+ <div ng-show="devId">
+ <h2>
+ Chart for Device {{devId || "(No device selected)"}}
+ </h2>
+ <div class="ctrl-btns">
+ <select ng-options="deviceId as deviceId for deviceId in deviceIds"
+ ng-model="selectedItem" ng-change="onChange(selectedItem)">
+ <option value="">-- select a device --</option>
+ </select>
+ </div>
+ <canvas id="line" class="chart chart-line" chart-data="data"
+ chart-labels="labels" chart-legend="true"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+</div>
diff --git a/drivers/server/src/main/resources/app/view/latency/latency.js b/drivers/server/src/main/resources/app/view/latency/latency.js
new file mode 100644
index 0000000..4277506
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/latency/latency.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Latency View Module
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $scope, $location, ks, fs, cbs, ns;
+
+ var hasDeviceId;
+ // TODO: Pass this dynamically
+ var coresNb = 16;
+
+ var labels = new Array(1);
+ var data = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(1);
+ data[i][0] = 0;
+ }
+
+ angular.module('ovLatency', ["chart.js"])
+ .controller('OvLatencyCtrl',
+ ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService', 'NavService',
+
+ function (_$log_, _$scope_, _$location_, _fs_, _cbs_, _ns_) {
+ var params;
+ $log = _$log_;
+ $scope = _$scope_;
+ $location = _$location_;
+ fs = _fs_;
+ cbs = _cbs_;
+ ns = _ns_;
+
+ params = $location.search();
+
+ if (params.hasOwnProperty('devId')) {
+ $scope.devId = params['devId'];
+ hasDeviceId = true;
+ } else {
+ hasDeviceId = false;
+ }
+
+ cbs.buildChart({
+ scope: $scope,
+ tag: 'latency',
+ query: params
+ });
+
+ $scope.$watch('chartData', function () {
+ if (!fs.isEmptyObject($scope.chartData)) {
+ $scope.showLoader = false;
+ var length = $scope.chartData.length;
+ labels = new Array(length);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(length);
+ }
+
+ $scope.chartData.forEach(
+ function (cm, idx) {
+ // TODO: Squeeze using a working loop?
+ data[0][idx] = cm.latency_0;
+ data[1][idx] = cm.latency_1;
+ data[2][idx] = cm.latency_2;
+ data[3][idx] = cm.latency_3;
+ data[4][idx] = cm.latency_4;
+ data[5][idx] = cm.latency_5;
+ data[6][idx] = cm.latency_6;
+ data[7][idx] = cm.latency_7;
+ data[8][idx] = cm.latency_8;
+ data[9][idx] = cm.latency_9;
+ data[10][idx] = cm.latency_10;
+ data[11][idx] = cm.latency_11;
+ data[12][idx] = cm.latency_12;
+ data[13][idx] = cm.latency_13;
+ data[14][idx] = cm.latency_14;
+ data[15][idx] = cm.latency_15;
+
+ labels[idx] = cm.label;
+ }
+ );
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ $scope.options = {
+ scales: {
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-latency',
+ ticks: {
+ beginAtZero: true,
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ labelString: 'Latency/CPU Core (ns)',
+ fontSize: 28,
+ }
+ }],
+ xAxes: [{
+ id: 'x-axis-servers-cores',
+ ticks: {
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ fontSize: 28,
+ }
+ }]
+ }
+ };
+
+ $scope.onClick = function (points, evt) {
+ var label = labels[points[0]._index];
+ if (label) {
+ ns.navTo('latency', { devId: label });
+ $log.log(label);
+ }
+ };
+
+ if (!fs.isEmptyObject($scope.annots)) {
+ $scope.deviceIds = JSON.parse($scope.annots.deviceIds);
+ }
+
+ $scope.onChange = function (deviceId) {
+ ns.navTo('latency', { devId: deviceId });
+ };
+ });
+
+ $scope.series = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ $scope.series[i] = 'Latency-CPU ' + i;
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ // TODO: For some reason, this assignment does not work
+ $scope.chartColors = [
+ '#e6194b', // Red
+ '#3cb44b', // Green
+ '#ffe119', // Yellow
+ '#0082c8', // Blue
+ '#f58231', // Orange
+ '#808080', // Grey
+ '#fffac8', // Beige
+ '#aaffc3', // Mint
+ '#911eb4', // Purple
+ '#46f0f0', // Cyan
+ '#d2f53c', // Lime
+ '#800000', // Maroon
+ '#000000', // Black
+ '#f032e6', // Magenta
+ '#008080', // Teal
+ '#808000', // Olive
+ '#aa6e28' // Brown
+ ];
+ Chart.defaults.global.colours = $scope.chartColors;
+
+ $scope.showLoader = true;
+
+ $log.log('OvLatencyCtrl has been created');
+ }]);
+
+}());
diff --git a/drivers/server/src/main/resources/app/view/throughput/throughput.css b/drivers/server/src/main/resources/app/view/throughput/throughput.css
new file mode 100644
index 0000000..2914588
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/throughput/throughput.css
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Throughput UI -- CSS file
+ */
+
+#ov-throughput {
+ padding: 20px;
+ position: relative;
+}
+.light #ov-throughput {
+ color: navy;
+}
+.dark #ov-throughput {
+ color: #88f;
+}
+
+#ov-throughput .button-panel {
+ margin: 10px;
+ width: 200px;
+}
+
+.light #ov-throughput .button-panel {
+ background-color: #ccf;
+}
+.dark #ov-throughput .button-panel {
+ background-color: #444;
+}
+
+#ov-throughput #chart-loader {
+ position: absolute;
+ width: 200px;
+ height: 50px;
+ margin-left: -100px;
+ margin-top: -25px;
+ z-index: 900;
+ top: 50%;
+ text-align: center;
+ left: 50%;
+ font-size: 25px;
+ font-weight: bold;
+ color: #ccc;
+}
\ No newline at end of file
diff --git a/drivers/server/src/main/resources/app/view/throughput/throughput.html b/drivers/server/src/main/resources/app/view/throughput/throughput.html
new file mode 100644
index 0000000..2535794
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/throughput/throughput.html
@@ -0,0 +1,27 @@
+<!-- partial HTML -->
+<div id="ov-throughput">
+ <div id="chart-loader" ng-show="!devId && showLoader">
+ No Servers
+ </div>
+ <div ng-show="!devId">
+ <canvas id="bar" class="chart chart-bar" chart-data="data"
+ chart-labels="labels" chart-legend="true" chart-click="onClick"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+ <div ng-show="devId">
+ <h2>
+ Chart for Device {{devId || "(No device selected)"}}
+ </h2>
+ <div class="ctrl-btns">
+ <select ng-options="deviceId as deviceId for deviceId in deviceIds"
+ ng-model="selectedItem" ng-change="onChange(selectedItem)">
+ <option value="">-- select a device --</option>
+ </select>
+ </div>
+ <canvas id="line" class="chart chart-line" chart-data="data"
+ chart-labels="labels" chart-legend="true"
+ chart-series="series" chart-options="options" height="100%">
+ </canvas>
+ </div>
+</div>
diff --git a/drivers/server/src/main/resources/app/view/throughput/throughput.js b/drivers/server/src/main/resources/app/view/throughput/throughput.js
new file mode 100644
index 0000000..c16e2c0
--- /dev/null
+++ b/drivers/server/src/main/resources/app/view/throughput/throughput.js
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Throughput View Module
+ */
+(function () {
+ 'use strict';
+
+ // injected references
+ var $log, $scope, $location, ks, fs, cbs, ns;
+
+ var hasDeviceId;
+ // TODO: Pass this dynamically
+ var coresNb = 16;
+
+ var labels = new Array(1);
+ var data = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(1);
+ data[i][0] = 0;
+ }
+
+ angular.module('ovThroughput', ["chart.js"])
+ .controller('OvThroughputCtrl',
+ ['$log', '$scope', '$location', 'FnService', 'ChartBuilderService', 'NavService',
+
+ function (_$log_, _$scope_, _$location_, _fs_, _cbs_, _ns_) {
+ var params;
+ $log = _$log_;
+ $scope = _$scope_;
+ $location = _$location_;
+ fs = _fs_;
+ cbs = _cbs_;
+ ns = _ns_;
+
+ params = $location.search();
+
+ if (params.hasOwnProperty('devId')) {
+ $scope.devId = params['devId'];
+ hasDeviceId = true;
+ } else {
+ hasDeviceId = false;
+ }
+
+ cbs.buildChart({
+ scope: $scope,
+ tag: 'throughput',
+ query: params
+ });
+
+ $scope.$watch('chartData', function () {
+ if (!fs.isEmptyObject($scope.chartData)) {
+ $scope.showLoader = false;
+ var length = $scope.chartData.length;
+ labels = new Array(length);
+ for (var i = 0; i < coresNb; i++) {
+ data[i] = new Array(length);
+ }
+
+ $scope.chartData.forEach(
+ function (cm, idx) {
+ // TODO: Squeeze using a working loop?
+ data[0][idx] = cm.throughput_0;
+ data[1][idx] = cm.throughput_1;
+ data[2][idx] = cm.throughput_2;
+ data[3][idx] = cm.throughput_3;
+ data[4][idx] = cm.throughput_4;
+ data[5][idx] = cm.throughput_5;
+ data[6][idx] = cm.throughput_6;
+ data[7][idx] = cm.throughput_7;
+ data[8][idx] = cm.throughput_8;
+ data[9][idx] = cm.throughput_9;
+ data[10][idx] = cm.throughput_10;
+ data[11][idx] = cm.throughput_11;
+ data[12][idx] = cm.throughput_12;
+ data[13][idx] = cm.throughput_13;
+ data[14][idx] = cm.throughput_14;
+ data[15][idx] = cm.throughput_15;
+
+ labels[idx] = cm.label;
+ }
+ );
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ $scope.options = {
+ scales: {
+ yAxes: [{
+ type: 'linear',
+ position: 'left',
+ id: 'y-axis-throughput',
+ ticks: {
+ min: 0,
+ max: 100,
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ labelString: 'Throughput/CPU Core (Gbps)',
+ fontSize: 28,
+ }
+ }],
+ xAxes: [{
+ id: 'x-axis-servers-cores',
+ ticks: {
+ fontSize: 28,
+ },
+ scaleLabel: {
+ display: true,
+ fontSize: 28,
+ }
+ }]
+ }
+ };
+
+ $scope.onClick = function (points, evt) {
+ var label = labels[points[0]._index];
+ if (label) {
+ ns.navTo('throughput', { devId: label });
+ $log.log(label);
+ }
+ };
+
+ if (!fs.isEmptyObject($scope.annots)) {
+ $scope.deviceIds = JSON.parse($scope.annots.deviceIds);
+ }
+
+ $scope.onChange = function (deviceId) {
+ ns.navTo('throughput', { devId: deviceId });
+ };
+ });
+
+ $scope.series = new Array(coresNb);
+ for (var i = 0; i < coresNb; i++) {
+ $scope.series[i] = 'Throughput-CPU ' + i;
+ }
+
+ $scope.labels = labels;
+ $scope.data = data;
+
+ // TODO: For some reason, this assignment does not work
+ $scope.chartColors = [
+ '#e6194b', // Red
+ '#3cb44b', // Green
+ '#ffe119', // Yellow
+ '#0082c8', // Blue
+ '#f58231', // Orange
+ '#808080', // Grey
+ '#fffac8', // Beige
+ '#aaffc3', // Mint
+ '#911eb4', // Purple
+ '#46f0f0', // Cyan
+ '#d2f53c', // Lime
+ '#800000', // Maroon
+ '#000000', // Black
+ '#f032e6', // Magenta
+ '#008080', // Teal
+ '#808000', // Olive
+ '#aa6e28' // Brown
+ ];
+ Chart.defaults.global.colours = $scope.chartColors;
+
+ $scope.showLoader = true;
+
+ $log.log('OvThroughputCtrl has been created');
+ }]);
+
+}());
diff --git a/drivers/server/src/main/resources/gui/css.html b/drivers/server/src/main/resources/gui/css.html
new file mode 100644
index 0000000..8eca7d4
--- /dev/null
+++ b/drivers/server/src/main/resources/gui/css.html
@@ -0,0 +1,3 @@
+<link rel="stylesheet" href="app/view/cpu/cpu.css">
+<link rel="stylesheet" href="app/view/latency/latency.css">
+<link rel="stylesheet" href="app/view/throughput/throughput.css">
diff --git a/drivers/server/src/main/resources/gui/js.html b/drivers/server/src/main/resources/gui/js.html
new file mode 100644
index 0000000..99a84e8
--- /dev/null
+++ b/drivers/server/src/main/resources/gui/js.html
@@ -0,0 +1,3 @@
+<script src="app/view/cpu/cpu.js"></script>
+<script src="app/view/latency/latency.js"></script>
+<script src="app/view/throughput/throughput.js"></script>
diff --git a/drivers/server/src/main/resources/server-drivers.xml b/drivers/server/src/main/resources/server-drivers.xml
index 9058fa1..11c75d1 100644
--- a/drivers/server/src/main/resources/server-drivers.xml
+++ b/drivers/server/src/main/resources/server-drivers.xml
@@ -36,6 +36,7 @@
<behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
impl="org.onosproject.drivers.server.FlowRuleProgrammableServerImpl"/>
+ <property name="ruleDeleteBatchSize">500</property>
</driver>
</drivers>