From 973a26572980cea130ecd7ebfb5145ad2fbac080 Mon Sep 17 00:00:00 2001 From: Georgios Katsikas Date: Thu, 28 Jun 2018 08:45:47 +0200 Subject: [PATCH] UI for server device driver This patch introduces a graphical user interface that visualizes the CPU utilization of server devices using bar charts. Code optimizations and a bug fix is applied after first code review. Additional bar plots are implemented, visualizing average throughput and latency per core, when these statistics are present. Added external library in Bazel's BUILD file. Fixed scaling and font issues in the UIs after getting feedback from ONOS reviewers. Change-Id: I92972ef871e6a91dd70cdffd8cd650f498ffca26 Signed-off-by: Georgios Katsikas --- drivers/server/BUCK | 1 + drivers/server/BUILD | 1 + .../server/ServerDevicesDiscovery.java | 21 +- .../server/gui/BaseViewMessageHandler.java | 384 ++++++++++++++++++ .../onosproject/drivers/server/gui/CpuUI.java | 91 +++++ .../server/gui/CpuViewMessageHandler.java | 232 +++++++++++ .../drivers/server/gui/LatencyUI.java | 91 +++++ .../server/gui/LatencyViewMessageHandler.java | 256 ++++++++++++ .../drivers/server/gui/LruCache.java | 214 ++++++++++ .../drivers/server/gui/MetricType.java | 39 ++ .../drivers/server/gui/ThroughputUI.java | 91 +++++ .../gui/ThroughputViewMessageHandler.java | 254 ++++++++++++ .../drivers/server/gui/package-info.java | 20 + .../impl/stats/DefaultCpuStatistics.java | 28 +- .../impl/stats/DefaultTimingStatistics.java | 31 +- .../drivers/server/stats/CpuStatistics.java | 6 +- .../drivers/server/stats/MonitoringUnit.java | 32 ++ .../server/stats/TimingStatistics.java | 6 +- .../src/main/resources/app/view/cpu/cpu.css | 57 +++ .../src/main/resources/app/view/cpu/cpu.html | 27 ++ .../src/main/resources/app/view/cpu/cpu.js | 184 +++++++++ .../resources/app/view/latency/latency.css | 57 +++ .../resources/app/view/latency/latency.html | 27 ++ .../resources/app/view/latency/latency.js | 183 +++++++++ .../app/view/throughput/throughput.css | 57 +++ .../app/view/throughput/throughput.html | 27 ++ .../app/view/throughput/throughput.js | 184 +++++++++ .../server/src/main/resources/gui/css.html | 3 + drivers/server/src/main/resources/gui/js.html | 3 + 29 files changed, 2559 insertions(+), 48 deletions(-) create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/BaseViewMessageHandler.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuUI.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/CpuViewMessageHandler.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyUI.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/LatencyViewMessageHandler.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/LruCache.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/MetricType.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputUI.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/ThroughputViewMessageHandler.java create mode 100644 drivers/server/src/main/java/org/onosproject/drivers/server/gui/package-info.java create mode 100644 drivers/server/src/main/resources/app/view/cpu/cpu.css create mode 100644 drivers/server/src/main/resources/app/view/cpu/cpu.html create mode 100644 drivers/server/src/main/resources/app/view/cpu/cpu.js create mode 100644 drivers/server/src/main/resources/app/view/latency/latency.css create mode 100644 drivers/server/src/main/resources/app/view/latency/latency.html create mode 100644 drivers/server/src/main/resources/app/view/latency/latency.js create mode 100644 drivers/server/src/main/resources/app/view/throughput/throughput.css create mode 100644 drivers/server/src/main/resources/app/view/throughput/throughput.html create mode 100644 drivers/server/src/main/resources/app/view/throughput/throughput.js create mode 100644 drivers/server/src/main/resources/gui/css.html create mode 100644 drivers/server/src/main/resources/gui/js.html diff --git a/drivers/server/BUCK b/drivers/server/BUCK index 9be0e49395..cfb6acd7b4 100644 --- a/drivers/server/BUCK +++ b/drivers/server/BUCK @@ -2,6 +2,7 @@ COMPILE_DEPS = [ '//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 c2ab3123d2..1dd27001a0 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/ServerDevicesDiscovery.java b/drivers/server/src/main/java/org/onosproject/drivers/server/ServerDevicesDiscovery.java index e82411f4eb..ca4e809e14 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 @@ -150,7 +150,6 @@ public class ServerDevicesDiscovery extends BasicServerDriver 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_MEDIAN = "median"; private static final String MON_PARAM_MAX = "max"; /** @@ -764,9 +763,9 @@ public class ServerDevicesDiscovery extends BasicServerDriver if (latencyNode.get(MON_PARAM_MIN) != null) { minLatency = latencyNode.path(MON_PARAM_MIN).floatValue(); } - float medianLatency = (float) 0; - if (latencyNode.get(MON_PARAM_MEDIAN) != null) { - medianLatency = latencyNode.path(MON_PARAM_MEDIAN).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) { @@ -774,7 +773,7 @@ public class ServerDevicesDiscovery extends BasicServerDriver } cpuBuilder.setMinLatency(minLatency) - .setMedianLatency(medianLatency) + .setAverageLatency(averageLatency) .setMaxLatency(maxLatency); } @@ -924,13 +923,13 @@ public class ServerDevicesDiscovery extends BasicServerDriver return timingBuilder.build(); } - ObjectNode autoscaleTimingObjNode = (ObjectNode) autoscaleTimingNode; + ObjectNode autoScaleTimingObjNode = (ObjectNode) autoscaleTimingNode; // Time (ns) to autoscale a server's load - long autoscaleTime = 0; - if (autoscaleTimingObjNode.get(TIMING_PARAM_AUTOSCALE) != null) { - autoscaleTime = autoscaleTimingObjNode.path(TIMING_PARAM_AUTOSCALE).asLong(); + long autoScaleTime = 0; + if (autoScaleTimingObjNode.get(TIMING_PARAM_AUTOSCALE) != null) { + autoScaleTime = autoScaleTimingObjNode.path(TIMING_PARAM_AUTOSCALE).asLong(); } - timingBuilder.setAutoscaleTime(autoscaleTime); + timingBuilder.setAutoScaleTime(autoScaleTime); return timingBuilder.build(); } @@ -947,7 +946,7 @@ public class ServerDevicesDiscovery extends BasicServerDriver zeroTimingBuilder.setParsingTime(0) .setLaunchingTime(0) - .setAutoscaleTime(0); + .setAutoScaleTime(0); return zeroTimingBuilder.build(); } 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 0000000000..a2a151c8ec --- /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>> devDataMap = + new HashMap>>(); + + // 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> 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> dataMap = new HashMap>(); + for (int i = 0; i < length; i++) { + dataMap.put(i, new LruCache(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> 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 getDataHistory(DeviceId deviceId, int index) { + if (!isValid(deviceId, index)) { + log.error("Invalid access to CPU {} load history by device {}", index, deviceId); + return null; + } + + Map> 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 populateZeroData(DeviceId deviceId, int length) { + Map 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 populateZeroDataHistory(DeviceId deviceId, int length) { + Map 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 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 data, + LocalDateTime time, + MetricType metric, + int numberOfPoints) { + for (int i = 0; i < numberOfPoints; i++) { + Map 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 initializeData(int length) { + Map 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 initializeDataHistory(int length) { + Map 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 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 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 0000000000..31fc57aba2 --- /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 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 0000000000..87be684a87 --- /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 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 cpuStats = null; + Map 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 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 cpuStats = null; + Map 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 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 populateCpuData( + DeviceId deviceId, int length, List cpuStats) { + Map 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 populateCpuDataHistory( + DeviceId deviceId, int length, List cpuStats) { + Map data = initializeDataHistory(MAX_COLUMNS_NB); + + for (CpuStatistics stats : cpuStats) { + int index = stats.id(); + + // Store it locally + addToCache(deviceId, length, index, stats.load()); + + LruCache 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 0000000000..9b8c8644cf --- /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 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 0000000000..c76c97d978 --- /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 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 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 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 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 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 populateLatencyData( + DeviceId deviceId, int length, MonitoringStatistics monStats) { + Map 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 populateLatencyDataHistory( + DeviceId deviceId, int length, MonitoringStatistics monStats) { + Map 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 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 0000000000..c38af844d6 --- /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 extends LinkedHashMap { + 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 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 keys = + new ConcurrentSkipListSet(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 keys = + new ConcurrentSkipListSet(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 getFirstValues(int numberOfEntries) { + List outList = new ArrayList(); + + if (numberOfEntries <= 0) { + return outList; + } + + // Get all keys sorted + SortedSet keys = + new ConcurrentSkipListSet(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 getLastValues(int numberOfEntries) { + List outList = new ArrayList(); + + if (numberOfEntries <= 0) { + return outList; + } + + // Get all keys sorted + NavigableSet keys = + new ConcurrentSkipListSet(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 0000000000..841b83e05d --- /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 0000000000..98200510bc --- /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 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 0000000000..91105e911d --- /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 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 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 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 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 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 populateThroughputData( + DeviceId deviceId, int length, MonitoringStatistics monStats) { + Map 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 populateThroughputDataHistory( + DeviceId deviceId, int length, MonitoringStatistics monStats) { + Map 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 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 0000000000..0d5cbf818f --- /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/stats/DefaultCpuStatistics.java b/drivers/server/src/main/java/org/onosproject/drivers/server/impl/stats/DefaultCpuStatistics.java index 2f5305d840..99e6c844cc 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 @@ -53,7 +53,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { private final Optional averageThroughput; private final Optional latencyUnit; private final Optional minLatency; - private final Optional medianLatency; + private final Optional averageLatency; private final Optional maxLatency; private DefaultCpuStatistics(DeviceId deviceId, int id, float load, int queue, boolean isBusy) { @@ -62,7 +62,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { private DefaultCpuStatistics(DeviceId deviceId, int id, float load, int queue, boolean isBusy, MonitoringUnit throughputUnit, float averageThroughput, MonitoringUnit latencyUnit, - float minLatency, float medianLatency, float maxLatency) { + 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) + "]"); @@ -83,8 +83,8 @@ public final class DefaultCpuStatistics implements CpuStatistics { Optional.empty() : Optional.ofNullable(latencyUnit); this.minLatency = (minLatency < 0) ? Optional.empty() : Optional.ofNullable(minLatency); - this.medianLatency = (medianLatency < 0) ? - Optional.empty() : Optional.ofNullable(medianLatency); + this.averageLatency = (averageLatency < 0) ? + Optional.empty() : Optional.ofNullable(averageLatency); this.maxLatency = (maxLatency < 0) ? Optional.empty() : Optional.ofNullable(maxLatency); } @@ -101,7 +101,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { this.averageThroughput = null; this.latencyUnit = null; this.minLatency = null; - this.medianLatency = null; + this.averageLatency = null; this.maxLatency = null; } @@ -155,8 +155,8 @@ public final class DefaultCpuStatistics implements CpuStatistics { } @Override - public Optional medianLatency() { - return this.medianLatency; + public Optional averageLatency() { + return this.averageLatency; } @Override @@ -177,7 +177,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { .add("averageThroughput", averageThroughput.orElse(null)) .add("latencyUnit", latencyUnit.orElse(null)) .add("minLatency", minLatency.orElse(null)) - .add("medianLatency", medianLatency.orElse(null)) + .add("averageLatency", averageLatency.orElse(null)) .add("maxLatency", maxLatency.orElse(null)) .toString(); } @@ -194,7 +194,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { float averageThroughput = -1; MonitoringUnit latencyUnit = DEF_LATENCY_UNIT; float minLatency = -1; - float medianLatency = -1; + float averageLatency = -1; float maxLatency = -1; private Builder() { @@ -310,13 +310,13 @@ public final class DefaultCpuStatistics implements CpuStatistics { } /** - * Sets the median latency. + * Sets the average latency. * - * @param medianLatency median latency + * @param averageLatency average latency * @return builder object */ - public Builder setMedianLatency(float medianLatency) { - this.medianLatency = medianLatency; + public Builder setAverageLatency(float averageLatency) { + this.averageLatency = averageLatency; return this; } @@ -342,7 +342,7 @@ public final class DefaultCpuStatistics implements CpuStatistics { return new DefaultCpuStatistics( deviceId, id, load, queue, isBusy, throughputUnit, averageThroughput, - latencyUnit, minLatency, medianLatency, maxLatency); + 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 0fe253d134..6c4f86fb88 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 @@ -36,22 +36,22 @@ public final class DefaultTimingStatistics implements TimingStatistics { 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 @@ -59,7 +59,7 @@ public final class DefaultTimingStatistics implements TimingStatistics { this.unit = null; this.deployCommandParsingTime = 0; this.deployCommandLaunchingTime = 0; - this.autoscaleTime = 0; + this.autoScaleTime = 0; } /** @@ -92,8 +92,8 @@ public final class DefaultTimingStatistics implements TimingStatistics { } @Override - public long autoscaleTime() { - return this.autoscaleTime; + public long autoScaleTime() { + return this.autoScaleTime; } @Override @@ -104,7 +104,7 @@ public final class DefaultTimingStatistics implements TimingStatistics { .add("parsingTime", this.deployCommandParsingTime()) .add("launchingTime", this.deployCommandLaunchingTime()) .add("deploymentTime", this.totalDeploymentTime()) - .add("autoScaleTime", this.autoscaleTime()) + .add("autoScaleTime", this.autoScaleTime()) .toString(); } @@ -113,7 +113,7 @@ public final class DefaultTimingStatistics implements TimingStatistics { MonitoringUnit unit = DEF_UNIT; long deployCommandParsingTime; long deployCommandLaunchingTime; - long autoscaleTime; + long autoScaleTime; private Builder() { @@ -160,11 +160,11 @@ public final class DefaultTimingStatistics implements TimingStatistics { /** * 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; } @@ -176,11 +176,8 @@ public final class DefaultTimingStatistics implements TimingStatistics { */ public DefaultTimingStatistics build() { return new DefaultTimingStatistics( - unit, - 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 f30134d2c0..c43b826a30 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 @@ -84,12 +84,12 @@ public interface CpuStatistics { Optional minLatency(); /** - * Returns the median latency incurred by a CPU core, + * Returns the average latency incurred by a CPU core, * expressed in latencyUnit() monitoring units. * - * @return median latency incurred by a CPU core + * @return average latency incurred by a CPU core */ - Optional medianLatency(); + Optional averageLatency(); /** * Returns the maximum latency incurred by a CPU core, 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 index 0528a692f5..33d8763794 100644 --- 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 @@ -53,6 +53,22 @@ public interface MonitoringUnit { 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; @@ -90,6 +106,22 @@ public interface MonitoringUnit { 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; 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 9cf46dd952..23215ced39 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 @@ -52,10 +52,10 @@ public interface TimingStatistics { /** * 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 0000000000..3f6f72e18d --- /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 0000000000..7fd758f567 --- /dev/null +++ b/drivers/server/src/main/resources/app/view/cpu/cpu.html @@ -0,0 +1,27 @@ + +
+
+ No Servers +
+
+ + +
+
+

+ Chart for Device {{devId || "(No device selected)"}} +

+
+ +
+ + +
+
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 0000000000..93f3ac8517 --- /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 0000000000..61f9716e3b --- /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 0000000000..1e5e6b2053 --- /dev/null +++ b/drivers/server/src/main/resources/app/view/latency/latency.html @@ -0,0 +1,27 @@ + +
+
+ No Servers +
+
+ + +
+
+

+ Chart for Device {{devId || "(No device selected)"}} +

+
+ +
+ + +
+
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 0000000000..4277506c48 --- /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 0000000000..2914588a0c --- /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 0000000000..2535794c24 --- /dev/null +++ b/drivers/server/src/main/resources/app/view/throughput/throughput.html @@ -0,0 +1,27 @@ + +
+
+ No Servers +
+
+ + +
+
+

+ Chart for Device {{devId || "(No device selected)"}} +

+
+ +
+ + +
+
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 0000000000..c16e2c00f3 --- /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 0000000000..8eca7d45fb --- /dev/null +++ b/drivers/server/src/main/resources/gui/css.html @@ -0,0 +1,3 @@ + + + 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 0000000000..99a84e8714 --- /dev/null +++ b/drivers/server/src/main/resources/gui/js.html @@ -0,0 +1,3 @@ + + +