/* * Copyright 2017-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.onosproject.ui.impl; import org.onosproject.incubator.net.PortStatisticsService.MetricType; import org.onosproject.net.DeviceId; import org.onosproject.net.Link; import org.onosproject.net.statistic.Load; import org.onosproject.ui.impl.topo.util.ServicesBundle; import org.onosproject.ui.impl.topo.util.TrafficLink; import org.onosproject.ui.impl.topo.util.TrafficLinkMap; import org.onosproject.ui.topo.AbstractTopoMonitor; import org.onosproject.ui.topo.Highlights; import org.onosproject.ui.topo.TopoUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import static org.onosproject.incubator.net.PortStatisticsService.MetricType.BYTES; import static org.onosproject.incubator.net.PortStatisticsService.MetricType.PACKETS; import static org.onosproject.net.DefaultEdgeLink.createEdgeLink; import static org.onosproject.ui.impl.TrafficMonitorBase.Mode.IDLE; /** * Base superclass for traffic monitor (both 'classic' and 'topo2' versions). */ public abstract class TrafficMonitorBase extends AbstractTopoMonitor { private final Logger log = LoggerFactory.getLogger(getClass()); // 4 Kilo Bytes as threshold protected static final double BPS_THRESHOLD = 4 * TopoUtils.N_KILO; /** * Designates the different modes of operation. */ public enum Mode { IDLE, ALL_FLOW_TRAFFIC_BYTES, ALL_PORT_TRAFFIC_BIT_PS, ALL_PORT_TRAFFIC_PKT_PS, DEV_LINK_FLOWS, RELATED_INTENTS, SELECTED_INTENT } /** * Number of milliseconds between invocations of sending traffic data. */ protected final long trafficPeriod; /** * Holds references to services. */ protected final ServicesBundle services; /** * Current operating mode. */ protected Mode mode = Mode.IDLE; private final Timer timer; private TimerTask trafficTask = null; /** * Constructs the monitor, initializing the task period and * services bundle reference. * * @param trafficPeriod traffic task period in ms * @param servicesBundle bundle of services */ protected TrafficMonitorBase(long trafficPeriod, ServicesBundle servicesBundle) { this.trafficPeriod = trafficPeriod; this.services = servicesBundle; timer = new Timer("uiTopo-" + getClass().getSimpleName()); } /** * Initiates monitoring of traffic for a given mode. * This causes a background traffic task to be * scheduled to repeatedly compute and transmit the appropriate traffic * data to the client. * <p> * The monitoring mode is expected to be one of: * <ul> * <li>ALL_FLOW_TRAFFIC_BYTES</li> * <li>ALL_PORT_TRAFFIC_BIT_PS</li> * <li>ALL_PORT_TRAFFIC_PKT_PS</li> * <li>SELECTED_INTENT</li> * </ul> * * @param mode the monitoring mode */ public synchronized void monitor(Mode mode) { this.mode = mode; switch (mode) { case ALL_FLOW_TRAFFIC_BYTES: clearSelection(); scheduleTask(); sendAllFlowTraffic(); break; case ALL_PORT_TRAFFIC_BIT_PS: clearSelection(); scheduleTask(); sendAllPortTrafficBits(); break; case ALL_PORT_TRAFFIC_PKT_PS: clearSelection(); scheduleTask(); sendAllPortTrafficPackets(); break; case SELECTED_INTENT: sendSelectedIntentTraffic(); scheduleTask(); break; default: log.warn("Unexpected call to monitor({})", mode); clearAll(); break; } } /** * Subclass should compile and send appropriate highlights data showing * flow traffic (bytes on links). */ protected abstract void sendAllFlowTraffic(); /** * Subclass should compile and send appropriate highlights data showing * bits per second, as computed using port stats. */ protected abstract void sendAllPortTrafficBits(); /** * Subclass should compile and send appropriate highlights data showing * packets per second, as computed using port stats. */ protected abstract void sendAllPortTrafficPackets(); /** * Subclass should compile and send appropriate highlights data showing * number of flows traversing links for the "selected" device(s). */ protected abstract void sendDeviceLinkFlows(); /** * Subclass should compile and send appropriate highlights data showing * traffic traversing links for the "selected" intent. */ protected abstract void sendSelectedIntentTraffic(); /** * Subclass should send a "clear highlights" event. */ protected abstract void sendClearHighlights(); /** * Subclasses should clear any selection state. */ protected abstract void clearSelection(); /** * Sets the mode to IDLE, clears the selection, cancels the background * task, and sends a clear highlights event to the client. */ protected void clearAll() { this.mode = Mode.IDLE; clearSelection(); cancelTask(); sendClearHighlights(); } /** * Schedules the background monitor task to run. */ protected synchronized void scheduleTask() { if (trafficTask == null) { log.debug("Starting up background traffic task..."); trafficTask = new TrafficUpdateTask(); timer.schedule(trafficTask, trafficPeriod, trafficPeriod); } else { log.debug("(traffic task already running)"); } } /** * Cancels the background monitor task. */ protected synchronized void cancelTask() { if (trafficTask != null) { trafficTask.cancel(); trafficTask = null; } } /** * Stops monitoring. (Invokes {@link #clearAll}, if not idle). */ public synchronized void stopMonitoring() { log.debug("STOP monitoring"); if (mode != IDLE) { clearAll(); } } // ======================================================================= // === Methods for computing traffic on links /** * Generates a {@link Highlights} object summarizing the traffic on the * network, ready to be transmitted back to the client for display on * the topology view. * * @param type the type of statistics to be displayed * @return highlights, representing links to be labeled/colored */ protected Highlights trafficSummary(TrafficLink.StatsType type) { Highlights highlights = new Highlights(); // TODO: consider whether a map would be better... Set<TrafficLink> linksWithTraffic = computeLinksWithTraffic(type); Set<TrafficLink> aggregatedLinks = doAggregation(linksWithTraffic); for (TrafficLink tlink : aggregatedLinks) { highlights.add(tlink.highlight(type)); } return highlights; } /** * Generates a set of "traffic links" encapsulating information about the * traffic on each link (that is deemed to have traffic). * * @param type the type of statistics to be displayed * @return the set of links with traffic */ protected Set<TrafficLink> computeLinksWithTraffic(TrafficLink.StatsType type) { TrafficLinkMap linkMap = new TrafficLinkMap(); compileLinks(linkMap); addEdgeLinks(linkMap); Set<TrafficLink> linksWithTraffic = new HashSet<>(); for (TrafficLink tlink : linkMap.biLinks()) { if (type == TrafficLink.StatsType.FLOW_STATS) { attachFlowLoad(tlink); } else if (type == TrafficLink.StatsType.PORT_STATS) { attachPortLoad(tlink, BYTES); } else if (type == TrafficLink.StatsType.PORT_PACKET_STATS) { attachPortLoad(tlink, PACKETS); } // we only want to report on links deemed to have traffic if (tlink.hasTraffic()) { linksWithTraffic.add(tlink); } } return linksWithTraffic; } /** * Iterates across the set of links in the topology and generates the * appropriate set of traffic links. * * @param linkMap link map to augment with traffic links */ protected void compileLinks(TrafficLinkMap linkMap) { services.link().getLinks().forEach(linkMap::add); } /** * Iterates across the set of hosts in the topology and generates the * appropriate set of traffic links for the edge links. * * @param linkMap link map to augment with traffic links */ protected void addEdgeLinks(TrafficLinkMap linkMap) { services.host().getHosts().forEach(host -> { linkMap.add(createEdgeLink(host, true)); linkMap.add(createEdgeLink(host, false)); }); } /** * Processes the given traffic link to attach the "flow load" attributed * to the underlying topology links. * * @param link the traffic link to process */ protected void attachFlowLoad(TrafficLink link) { link.addLoad(getLinkFlowLoad(link.one())); link.addLoad(getLinkFlowLoad(link.two())); } /** * Returns the load for the given link, as determined by the statistics * service. May return null. * * @param link the link on which to look up the stats * @return the corresponding load (or null) */ protected Load getLinkFlowLoad(Link link) { if (link != null && link.src().elementId() instanceof DeviceId) { return services.flowStats().load(link); } return null; } /** * Processes the given traffic link to attach the "port load" attributed * to the underlying topology links, for the specified metric type (either * bytes/sec or packets/sec). * * @param link the traffic link to process * @param metricType the metric type (bytes or packets) */ protected void attachPortLoad(TrafficLink link, MetricType metricType) { // For bi-directional traffic links, use // the max link rate of either direction // (we choose 'one' since we know that is never null) Link one = link.one(); Load egressSrc = services.portStats().load(one.src(), metricType); Load egressDst = services.portStats().load(one.dst(), metricType); link.addLoad(maxLoad(egressSrc, egressDst), metricType == BYTES ? BPS_THRESHOLD : 0); } /** * Returns the load with the greatest rate. * * @param a load a * @param b load b * @return the larger of the two */ protected Load maxLoad(Load a, Load b) { if (a == null) { return b; } if (b == null) { return a; } return a.rate() > b.rate() ? a : b; } /** * Subclasses (well, Traffic2Monitor really) can override this method and * process the traffic links before generating the highlights object. * In particular, links that roll up into "synthetic links" between * regions should show aggregated data from the constituent links. * <p> * This default implementation does nothing. * * @param linksWithTraffic link data for all links * @return transformed link data appropriate to the region display */ protected Set<TrafficLink> doAggregation(Set<TrafficLink> linksWithTraffic) { return linksWithTraffic; } // ======================================================================= // === Background Task // Provides periodic update of traffic information to the client private class TrafficUpdateTask extends TimerTask { @Override public void run() { try { switch (mode) { case ALL_FLOW_TRAFFIC_BYTES: sendAllFlowTraffic(); break; case ALL_PORT_TRAFFIC_BIT_PS: sendAllPortTrafficBits(); break; case ALL_PORT_TRAFFIC_PKT_PS: sendAllPortTrafficPackets(); break; case DEV_LINK_FLOWS: sendDeviceLinkFlows(); break; case SELECTED_INTENT: sendSelectedIntentTraffic(); break; default: // RELATED_INTENTS and IDLE modes should never invoke // the background task, but if they do, they have // nothing to do break; } } catch (Exception e) { log.warn("Unable to process traffic task due to {}", e.getMessage()); log.warn("Boom!", e); } } } }