package org.opennaas.extensions.genericnetwork.capability.nclmonitoring;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opennaas.core.events.IEventManager;
import org.opennaas.core.resources.IResource;
import org.opennaas.core.resources.ResourceException;
import org.opennaas.core.resources.capability.CapabilityException;
import org.opennaas.core.resources.configurationadmin.ConfigurationAdminUtil;
import org.opennaas.extensions.genericnetwork.Activator;
import org.opennaas.extensions.genericnetwork.capability.statistics.INetworkStatisticsCapability;
import org.opennaas.extensions.genericnetwork.events.PortCongestionEvent;
import org.opennaas.extensions.genericnetwork.model.GenericNetworkModel;
import org.opennaas.extensions.genericnetwork.model.NetworkStatistics;
import org.opennaas.extensions.genericnetwork.model.portstatistics.TimedStatistics;
import org.opennaas.extensions.genericnetwork.model.portstatistics.TimedSwitchPortStatistics;
import org.opennaas.extensions.genericnetwork.model.topology.NetworkElement;
import org.opennaas.extensions.genericnetwork.model.topology.Port;
import org.opennaas.extensions.openflowswitch.capability.portstatistics.PortStatistics;
import org.opennaas.extensions.openflowswitch.capability.portstatistics.SwitchPortStatistics;
/*
* #%L
* OpenNaaS :: Generic Network
* %%
* Copyright (C) 2007 - 2014 FundaciĆ³ Privada i2CAT, Internet i InnovaciĆ³ a Catalunya
* %%
* 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.
* #L%
*/
/**
* NCL monitoring component
*
* @author Julio Carlos Barrera
* @author Isart Canyameres Gimenez (i2cat)
*
*/
public class NCLMonitoring {
private static final Log log = LogFactory.getLog(NCLMonitoring.class);
private static final String NCL_MONITORING_FILE_ID = "org.ofertie.ncl.monitoring";
private static final String THROUGHPUT_THRESHOLD = "throughput_threshold";
private static final String STATISCS_POLLER_FREQUENCY = "statistics_poller_freq";
private static final DecimalFormat DF = new DecimalFormat("0.000");
// FIXME hardcoded link capacity to 1Gbits/s
private static final double linkCapacity = 1.0;
private IEventManager eventManager;
private IResource resource;
private int bandwidthThreshold;
private int statisticsPollerFreq;
private long previousTimestamp = -1;
private NetworkStatistics previousNetworkStatistics = null;
private Timer statisticsPollerTimer;
/**
*
* @return the resource
*/
public IResource getResource() {
return resource;
}
/**
*
* @param resource
* the resource to set
*/
public void setResource(IResource resource) {
this.resource = resource;
}
/**
* @return the eventManager
*/
public IEventManager getEventManager() {
return eventManager;
}
/**
* @param eventManager
* the eventManager to set
*/
public void setEventManager(IEventManager eventManager) {
this.eventManager = eventManager;
}
public void init() {
log.info("Initializing NCL monitoring...");
// get configuration file properties
ConfigurationAdminUtil configurationAdmin = new ConfigurationAdminUtil(Activator.getContext());
try {
bandwidthThreshold = Integer.parseInt(configurationAdmin.getProperty(NCL_MONITORING_FILE_ID, THROUGHPUT_THRESHOLD));
statisticsPollerFreq = Integer.parseInt(configurationAdmin.getProperty(NCL_MONITORING_FILE_ID, STATISCS_POLLER_FREQUENCY));
} catch (IOException e) {
log.error("Error getting configuration!", e);
} catch (NumberFormatException e) {
log.error("Number format error getting configuration property!", e);
}
// initialize statistics poller
TimerTask statisticsPoller = new StatisticsPoller();
statisticsPollerTimer = new Timer("NCL Monitoring statistics poller", true);
statisticsPollerTimer.scheduleAtFixedRate(statisticsPoller, 0, statisticsPollerFreq * 1000);
log.info("NCL monitoring initialized");
}
public void stop() {
statisticsPollerTimer.cancel();
statisticsPollerTimer = null;
}
private void notifyCongestion(Port port) {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(PortCongestionEvent.PORT_ID_KEY, port.getId());
properties.put(PortCongestionEvent.NETWORK_ID_KEY, resource.getResourceIdentifier().getId());
PortCongestionEvent event = new PortCongestionEvent(properties);
eventManager.publishEvent(event);
}
/**
* Thread class to execute the periodic poller to get the statistics from the network
*/
private class StatisticsPoller extends TimerTask {
@Override
public void run() {
try {
IResource network = getResource();
if (network == null) {
// no network found
log.debug("No networks present. Skipping monitoring tasks");
} else {
// congested ports temp var
Set<Port> newCongestedPorts = new HashSet<Port>();
log.debug("Getting network statistics...");
NetworkStatistics currentNetworkStatistics = readCurrentNetworkStatistics(network);
long currentTimestamp = System.currentTimeMillis();
if (currentNetworkStatistics == null) {
log.debug("Network statistics not available.");
} else {
if (previousNetworkStatistics != null && previousTimestamp > 0) {
// calculate throughput and check threshold for each port
for (String switchName : currentNetworkStatistics.getSwitchStatistics().keySet()) {
log.debug("Analizing switch statistics for switch " + switchName);
for (String portId : currentNetworkStatistics.getSwitchStatistics().get(switchName).getStatistics().keySet()) {
try {
// calculate throughput
double throughput = calculateThroughput(previousNetworkStatistics, currentNetworkStatistics,
switchName, portId, previousTimestamp, currentTimestamp);
log.debug("\tPort " + portId + ", calculated throughput = " + DF.format(throughput) + " Gbits/s");
// check if throughput exceeds
if (isThresholdExceeded(throughput, linkCapacity, bandwidthThreshold)) {
log.debug("\tPort " + portId + ", congestion detected. Throughput > " + DF
.format((linkCapacity * bandwidthThreshold) / 100) + " Gbits/s");
// add congested port
Port congestedPort = new Port();
congestedPort.setId(portId);
newCongestedPorts.add(congestedPort);
}
double packetLoss = calculatePacketLoss(previousNetworkStatistics, currentNetworkStatistics,
switchName, portId, previousTimestamp, currentTimestamp);
log.debug("\tPort " + portId + ", calculated packetLoss = " + DF.format(packetLoss) + " %");
TimedStatistics timedStats = new TimedStatistics();
timedStats.setTimestamp(currentTimestamp);
timedStats.setSwitchId(switchName);
timedStats.setPortId(portId);
timedStats.setThroughput(String.valueOf(throughput));
timedStats.setPacketLoss(String.valueOf(packetLoss));
// store TimedStats in network model
TimedSwitchPortStatistics allStats = ((GenericNetworkModel)resource.getModel()).getTimedSwitchPortStatistics();
if (! allStats.getStatisticsMap().containsKey(Long.valueOf(currentTimestamp))) {
allStats.getStatisticsMap().put(currentTimestamp, new HashMap<String, List<TimedStatistics>>());
}
if (! allStats.getStatisticsMap().get(currentTimestamp).containsKey(switchName)) {
allStats.getStatisticsMap().get(currentTimestamp).put(switchName, new ArrayList<TimedStatistics>());
}
allStats.getStatisticsMap().get(currentTimestamp).get(switchName).add(timedStats);
} catch (Exception e) {
log.debug("Failed to calculate throughput or packetloss for port " + portId + " in switch " + switchName + ": " + e.getMessage());
}
}
}
}
// reset congested ports
resetCongestedPorts(newCongestedPorts);
// raise alarm event for each congested port
for (Port congestedPort : newCongestedPorts) {
log.info("Throughput threshold exceeded, congestion detected. Raising alarm event!");
notifyCongestion(congestedPort);
}
// store current statistics and timestamp
previousNetworkStatistics = currentNetworkStatistics;
previousTimestamp = currentTimestamp;
}
}
} catch (ResourceException e) {
log.error("Error processing network statistics", e);
}
}
/**
* Resets isCongested in all ports in the model.
*
* All ports included in congestedPorts will have isCongested = true after this method execution
*
* All ports not included in congestedPorts will have isCongested = false after this method execution
*
* @param congestedPorts
*/
private void resetCongestedPorts(Set<Port> congestedPorts) {
if (resource.getModel() != null) {
if (((GenericNetworkModel) resource.getModel()).getTopology() != null) {
for (NetworkElement ne : ((GenericNetworkModel) resource.getModel()).getTopology().getNetworkElements()) {
for (Port port : ne.getPorts()) {
boolean isCongested = false;
for (Port congestedPort : congestedPorts) {
if (port.getId().equals(congestedPort.getId())) {
isCongested = true;
}
}
port.getState().setCongested(isCongested);
}
}
}
}
}
private NetworkStatistics readCurrentNetworkStatistics(IResource network) throws CapabilityException {
NetworkStatistics currentNetworkStatistics = null;
INetworkStatisticsCapability networkStatisticsCapability = null;
try {
// get port switch statistics for each network
networkStatisticsCapability = (INetworkStatisticsCapability) network
.getCapabilityByInterface(INetworkStatisticsCapability.class);
} catch (ResourceException e) {
// there is not INetworkStatisticsCapability in this network
log.debug("Openflow network resource without INetworkStatisticsCapability.");
}
if (networkStatisticsCapability != null) {
log.debug("Getting network statistics...");
currentNetworkStatistics = networkStatisticsCapability.getNetworkStatistics();
}
return currentNetworkStatistics;
}
/**
*
* @param statistics
* to take information from
* @param switchName
* indicates the switch holding the port
* @param portId
* indicates the port
* @return value of receivedBytes for given port that is in statistics
* @throws Exception
* in case statistics contains no value matching given arguments
*/
private long getPortReceivedBytes(NetworkStatistics statistics, String switchName, String portId) throws Exception {
if (statistics != null) {
SwitchPortStatistics switchPortStatistics = statistics.getSwitchPortStatistic(switchName);
if (switchPortStatistics != null) {
PortStatistics portStatistics = switchPortStatistics.getStatistics().get(portId);
if (portStatistics != null) {
return portStatistics.getReceiveBytes();
}
}
}
throw new Exception("No statistics for switch " + switchName + " and port " + portId);
}
/**
* Returns if throughput is exceeding threshold
*
* @param currentThroughput
* current throughput of the port (in Gbits/s)
* @param linkCapacity
* link capacity (in Gbits/s)
* @param threshold
* threshold (in percentage)
* @return true if threshold is exceeded, false otherwise
*/
private boolean isThresholdExceeded(double currentThroughput, double linkCapacity, int threshold) {
return currentThroughput > ((linkCapacity * threshold) / 100);
}
/**
* Calculate throughput (Gbits/s)
*
* @param previousBytes
* @param currentBytes
* @param previousTimestamp
* @param currentTimestamp
* @return throughput in Gbits/s
*/
private double calculateThroughput(long previousBytes, long currentBytes, long previousTimestamp, long currentTimestamp) {
// bytes / millisecond ~ kBytes/s
double kBytesPerSecond = ((double) (currentBytes - previousBytes)) / ((double) (currentTimestamp - previousTimestamp));
// convert kBytes/s to Gbits/s
return (kBytesPerSecond * 8) / (1000 * 1000);
}
/**
*
* @param previousStats
* @param currentStats
* @param switchName
* @param portId
* @param previousTimestamp
* @param currentTimestamp
* @return
* @throws Exception if failed to get required data to calculate the throughput
*/
private double calculateThroughput(
NetworkStatistics previousStats,
NetworkStatistics currentStats,
String switchName, String portId,
long previousTimestamp, long currentTimestamp) throws Exception {
long currentBytes = getPortReceivedBytes(currentStats, switchName, portId);
long previousBytes = getPortReceivedBytes(previousStats, switchName, portId);
// calculate throughput
return calculateThroughput(previousBytes, currentBytes,
previousTimestamp, currentTimestamp);
}
}
public double calculatePacketLoss(
NetworkStatistics previousStats,
NetworkStatistics currentStats,
String switchName, String portId,
long previousTimestamp, long currentTimestamp) throws Exception {
// pk_loss = (receiveDropped(t1) + receiveErrors(t1) + receiveFrameErrors(t1) + receiveOverrunErrors(t1) + receiveCRCErrors(t1) -
// (receiveDropped(t0) + receiveErrors(t0) + receiveFrameErrors(t0) + receiveOverrunErrors(t0) + receiveCRCErrors(t0))) /
// (receivePacket(t1) - receivePackets(t0))
long currentReceiveErrors = getPortReceiveErrors(currentStats, switchName, portId);
long previousReceiveErrors = getPortReceiveErrors(previousStats, switchName, portId);
long currentReceivedPackets = getPortReceivedPackets(currentStats, switchName, portId);
long previousReceivedPackets = getPortReceivedPackets(previousStats, switchName, portId);
if (currentReceivedPackets == previousReceivedPackets)
return 0;
return (currentReceiveErrors - previousReceiveErrors) / (currentReceivedPackets - previousReceivedPackets);
}
private long getPortReceivedPackets(NetworkStatistics statistics,
String switchName, String portId) throws Exception {
if (statistics != null) {
SwitchPortStatistics switchPortStatistics = statistics.getSwitchPortStatistic(switchName);
if (switchPortStatistics != null) {
PortStatistics portStatistics = switchPortStatistics.getStatistics().get(portId);
if (portStatistics != null) {
return portStatistics.getReceivePackets();
}
}
}
throw new Exception("No statistics for switch " + switchName + " and port " + portId);
}
private long getPortReceiveErrors(NetworkStatistics statistics,
String switchName, String portId) throws Exception {
if (statistics != null) {
SwitchPortStatistics switchPortStatistics = statistics.getSwitchPortStatistic(switchName);
if (switchPortStatistics != null) {
PortStatistics portStatistics = switchPortStatistics.getStatistics().get(portId);
if (portStatistics != null) {
return portStatistics.getReceiveCRCErrors() +
portStatistics.getReceiveDropped() +
portStatistics.getReceiveErrors() +
portStatistics.getReceiveFrameErrors() +
portStatistics.getReceiveOverrunErrors();
}
}
}
throw new Exception("No statistics for switch " + switchName + " and port " + portId);
}
}