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 @@ + + +