blob: 873d23dfddca20e1e830ed8615d4a4d9c83f04be [file] [log] [blame]
/*
* Copyright 2016-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.cpman.gui;
import com.fasterxml.jackson.databind.node.ArrayNode;
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 org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.cpman.ControlLoadSnapshot;
import org.onosproject.cpman.ControlMetricType;
import org.onosproject.cpman.ControlPlaneMonitorService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.chart.ChartModel;
import org.onosproject.ui.chart.ChartRequestHandler;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.LongStream;
import static org.onosproject.cpman.ControlResource.CONTROL_MESSAGE_METRICS;
import static org.onosproject.cpman.ControlResource.Type.CONTROL_MESSAGE;
/**
* Message handler for control plane monitoring view related messages.
*/
public class CpmanViewMessageHandler extends UiMessageHandler {
private static final String CPMAN_DATA_REQ = "cpmanDataRequest";
private static final String CPMAN_DATA_RESP = "cpmanDataResponse";
private static final String CPMANS = "cpmans";
private static final String DEVICE_IDS = "deviceIds";
// TODO: we assume that server side always returns 20 data points
// to feed 20 minutes time slots, later this should make to be configurable
private static final int NUM_OF_DATA_POINTS = 20;
private static final int MILLI_CONV_UNIT = 1000;
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ISO_LOCAL_TIME;
private long timestamp = 0L;
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
new ControlMessageRequest()
);
}
private final class ControlMessageRequest extends ChartRequestHandler {
private ControlMessageRequest() {
super(CPMAN_DATA_REQ, CPMAN_DATA_RESP, CPMANS);
}
@Override
protected String[] getSeries() {
return CONTROL_MESSAGE_METRICS.stream().map(type ->
StringUtils.lowerCase(type.name())).toArray(String[]::new);
}
@Override
protected void populateChart(ChartModel cm, ObjectNode payload) {
String uri = string(payload, "devId");
ControlPlaneMonitorService cpms = get(ControlPlaneMonitorService.class);
ClusterService cs = get(ClusterService.class);
DeviceService ds = get(DeviceService.class);
NodeId localNodeId = cs.getLocalNode().id();
if (!Strings.isNullOrEmpty(uri)) {
DeviceId deviceId = DeviceId.deviceId(uri);
if (cpms.availableResourcesSync(localNodeId, CONTROL_MESSAGE).contains(deviceId.toString())) {
Map<ControlMetricType, Long[]> data = generateMatrix(cpms, cs, deviceId);
LocalDateTime ldt = LocalDateTime.from(Instant.ofEpochMilli(timestamp * MILLI_CONV_UNIT));
populateMetrics(cm, data, ldt, NUM_OF_DATA_POINTS);
Set<DeviceId> deviceIds = Sets.newHashSet();
ds.getAvailableDevices().forEach(device -> deviceIds.add(device.id()));
attachDeviceList(cm, deviceIds);
}
} else {
Set<String> deviceIds = cpms.availableResourcesSync(localNodeId, CONTROL_MESSAGE);
for (String deviceId : deviceIds) {
Map<ControlMetricType, Long> data =
populateDeviceMetrics(cpms, cs, DeviceId.deviceId(deviceId));
Map<String, Object> local = Maps.newHashMap();
for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt));
}
local.put(LABEL, deviceId);
populateMetric(cm.addDataPoint(deviceId), local);
}
}
}
private Map<ControlMetricType, Long> populateDeviceMetrics(ControlPlaneMonitorService cpms,
ClusterService cs, DeviceId deviceId) {
Map<ControlMetricType, Long> data = Maps.newHashMap();
for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
ControlLoadSnapshot cls = cpms.getLoadSync(cs.getLocalNode().id(),
cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES, Optional.of(deviceId));
data.put(cmt, Math.round(LongStream.of(cls.recent()).average().getAsDouble()));
timestamp = cls.time();
}
return data;
}
private Map<ControlMetricType, Long[]> generateMatrix(ControlPlaneMonitorService cpms,
ClusterService cs, DeviceId deviceId) {
Map<ControlMetricType, Long[]> data = Maps.newHashMap();
for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
ControlLoadSnapshot cls = cpms.getLoadSync(cs.getLocalNode().id(),
cmt, NUM_OF_DATA_POINTS, TimeUnit.MINUTES, Optional.of(deviceId));
// TODO: in some cases, the number of returned data set is
// less than what we expected (expected -1)
// As a workaround, we simply fill the slot with 0 values,
// such a bug should be fixed with updated RRD4J lib...
data.put(cmt, ArrayUtils.toObject(fillData(cls.recent(), NUM_OF_DATA_POINTS)));
timestamp = cls.time();
}
return data;
}
private long[] fillData(long[] origin, int expected) {
if (origin.length == expected) {
return origin;
} else {
long[] filled = new long[expected];
for (int i = 0; i < expected; i++) {
if (i == 0) {
filled[i] = 0;
} else {
filled[i] = origin[i - 1];
}
}
return filled;
}
}
// FIXME using local time in timestamps likely to be sign of problem
private void populateMetrics(ChartModel cm,
Map<ControlMetricType, Long[]> data,
LocalDateTime time, int numOfDp) {
for (int i = 0; i < numOfDp; i++) {
Map<String, Object> local = Maps.newHashMap();
for (ControlMetricType cmt : CONTROL_MESSAGE_METRICS) {
local.put(StringUtils.lowerCase(cmt.name()), data.get(cmt)[i]);
}
String calculated = time.minusMinutes((long) numOfDp - i).format(TIME_FORMAT);
local.put(LABEL, calculated);
populateMetric(cm.addDataPoint(calculated), local);
}
}
private void populateMetric(ChartModel.DataPoint dataPoint,
Map<String, Object> data) {
data.forEach(dataPoint::data);
}
private void attachDeviceList(ChartModel cm, Set<DeviceId> deviceIds) {
ArrayNode array = arrayNode();
deviceIds.forEach(id -> array.add(id.toString()));
cm.addAnnotation(DEVICE_IDS, array);
}
}
}