Skip to content
Snippets Groups Projects
Commit e810cb71 authored by Ryan Izard's avatar Ryan Izard
Browse files

Initial statistics module commit

parent eaad2b6f
No related branches found
No related tags found
No related merge requests found
package net.floodlightcontroller.statistics;
import java.util.Map;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFPort;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.topology.NodePortTuple;
public interface IStatisticsService extends IFloodlightService {
public SwitchPortBandwidth getBandwidthConsumption(DatapathId dpid, OFPort p);
public Map<NodePortTuple, SwitchPortBandwidth> getBandwidthConsumption();
}
package net.floodlightcontroller.statistics;
import java.lang.Thread.State;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
import org.projectfloodlight.openflow.protocol.OFPortStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsRequest;
import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.ver13.OFMeterSerializerVer13;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.primitives.UnsignedLong;
import com.google.common.util.concurrent.ListenableFuture;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.internal.IOFSwitchService;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.restserver.IRestApiService;
import net.floodlightcontroller.statistics.web.SwitchStatisticsWebRoutable;
import net.floodlightcontroller.threadpool.IThreadPoolService;
import net.floodlightcontroller.topology.NodePortTuple;
public class StatisticsCollector implements IFloodlightModule, IStatisticsService {
private static final Logger log = LoggerFactory.getLogger(StatisticsCollector.class);
private static IOFSwitchService switchService;
private static IThreadPoolService threadPoolService;
private static IRestApiService restApiService;
private static int portStatsInterval = 10;
private static final long BITS_PER_BYTE = 8;
private static final long MILLIS_PER_SEC = 1000;
private static final HashMap<NodePortTuple, SwitchPortBandwidth> portStats = new HashMap<NodePortTuple, SwitchPortBandwidth>();
private static final HashMap<NodePortTuple, SwitchPortBandwidth> tentativePortStats = new HashMap<NodePortTuple, SwitchPortBandwidth>();
private class PortStatsCollector implements Runnable {
@Override
public void run() {
Map<DatapathId, List<OFStatsReply>> replies = getSwitchStatistics(switchService.getAllSwitchDpids(), OFStatsType.PORT);
for (Entry<DatapathId, List<OFStatsReply>> e : replies.entrySet()) {
for (OFStatsReply r : e.getValue()) {
OFPortStatsReply psr = (OFPortStatsReply) r;
for (OFPortStatsEntry pse : psr.getEntries()) {
NodePortTuple npt = new NodePortTuple(e.getKey(), pse.getPortNo());
SwitchPortBandwidth spb;
if (portStats.containsKey(npt) || tentativePortStats.containsKey(npt)) {
if (portStats.containsKey(npt)) { /* update */
spb = portStats.get(npt);
} else if (tentativePortStats.containsKey(npt)) { /* finish */
spb = tentativePortStats.get(npt);
tentativePortStats.remove(npt);
} else {
log.error("Inconsistent state between tentative and official port stats lists.");
return;
}
/* Get counted bytes over the elapsed period. Check for counter overflow. */
U64 rxBytesCounted;
U64 txBytesCounted;
if (spb.getPriorByteValueRx().compareTo(pse.getRxBytes()) > 0) { /* overflow */
U64 upper = U64.NO_MASK.subtract(spb.getPriorByteValueRx());
U64 lower = pse.getRxBytes();
rxBytesCounted = upper.add(lower);
} else {
rxBytesCounted = pse.getRxBytes().subtract(spb.getPriorByteValueRx());
}
if (spb.getPriorByteValueTx().compareTo(pse.getTxBytes()) > 0) { /* overflow */
U64 upper = U64.NO_MASK.subtract(spb.getPriorByteValueTx());
U64 lower = pse.getTxBytes();
txBytesCounted = upper.add(lower);
} else {
txBytesCounted = pse.getTxBytes().subtract(spb.getPriorByteValueTx());
}
long timeDifSec = (System.currentTimeMillis() - spb.getUpdateTime()) / MILLIS_PER_SEC;
portStats.put(npt, SwitchPortBandwidth.of(npt.getNodeId(), npt.getPortId(),
U64.ofRaw((rxBytesCounted.getValue() * BITS_PER_BYTE) / timeDifSec),
U64.ofRaw((txBytesCounted.getValue() * BITS_PER_BYTE) / timeDifSec),
pse.getRxBytes(), pse.getTxBytes())
);
} else { /* initialize */
tentativePortStats.put(npt, SwitchPortBandwidth.of(npt.getNodeId(), npt.getPortId(), U64.ZERO, U64.ZERO, pse.getRxBytes(), pse.getTxBytes()));
}
}
}
}
}
}
private class GetStatisticsThread extends Thread {
private List<OFStatsReply> statsReply;
private DatapathId switchId;
private OFStatsType statType;
public GetStatisticsThread(DatapathId switchId, OFStatsType statType) {
this.switchId = switchId;
this.statType = statType;
this.statsReply = null;
}
public List<OFStatsReply> getStatisticsReply() {
return statsReply;
}
public DatapathId getSwitchId() {
return switchId;
}
@Override
public void run() {
statsReply = getSwitchStatistics(switchId, statType);
}
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IStatisticsService.class);
return l;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
Map<Class<? extends IFloodlightService>, IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
m.put(IStatisticsService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IOFSwitchService.class);
l.add(IThreadPoolService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
switchService = context.getServiceImpl(IOFSwitchService.class);
threadPoolService = context.getServiceImpl(IThreadPoolService.class);
restApiService = context.getServiceImpl(IRestApiService.class);
//TODO Map<String, String> config = context.getConfigParams(this);
}
@Override
public void startUp(FloodlightModuleContext context)
throws FloodlightModuleException {
threadPoolService.getScheduledExecutor().scheduleAtFixedRate(new PortStatsCollector(), portStatsInterval, portStatsInterval, TimeUnit.SECONDS);
restApiService.addRestletRoutable(new SwitchStatisticsWebRoutable());
}
@Override
public SwitchPortBandwidth getBandwidthConsumption(DatapathId dpid, OFPort p) {
return portStats.get(new NodePortTuple(dpid, p));
}
@Override
public Map<NodePortTuple, SwitchPortBandwidth> getBandwidthConsumption() {
return Collections.unmodifiableMap(portStats);
}
private Map<DatapathId, List<OFStatsReply>> getSwitchStatistics(Set<DatapathId> dpids, OFStatsType statsType) {
HashMap<DatapathId, List<OFStatsReply>> model = new HashMap<DatapathId, List<OFStatsReply>>();
List<GetStatisticsThread> activeThreads = new ArrayList<GetStatisticsThread>(dpids.size());
List<GetStatisticsThread> pendingRemovalThreads = new ArrayList<GetStatisticsThread>();
GetStatisticsThread t;
for (DatapathId d : dpids) {
t = new GetStatisticsThread(d, statsType);
activeThreads.add(t);
t.start();
}
// Join all the threads after the timeout. Set a hard timeout
// of 12 seconds for the threads to finish. If the thread has not
// finished the switch has not replied yet and therefore we won't
// add the switch's stats to the reply.
for (int iSleepCycles = 0; iSleepCycles < portStatsInterval; iSleepCycles++) {
for (GetStatisticsThread curThread : activeThreads) {
if (curThread.getState() == State.TERMINATED) {
model.put(curThread.getSwitchId(), curThread.getStatisticsReply());
pendingRemovalThreads.add(curThread);
}
}
// remove the threads that have completed the queries to the switches
for (GetStatisticsThread curThread : pendingRemovalThreads) {
activeThreads.remove(curThread);
}
// clear the list so we don't try to double remove them
pendingRemovalThreads.clear();
// if we are done finish early so we don't always get the worst case
if (activeThreads.isEmpty()) {
break;
}
// sleep for 1 s here
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.error("Interrupted while waiting for statistics", e);
}
}
return model;
}
@SuppressWarnings("unchecked")
protected List<OFStatsReply> getSwitchStatistics(DatapathId switchId, OFStatsType statsType) {
IOFSwitch sw = switchService.getSwitch(switchId);
ListenableFuture<?> future;
List<OFStatsReply> values = null;
Match match;
if (sw != null) {
OFStatsRequest<?> req = null;
switch (statsType) {
case FLOW:
match = sw.getOFFactory().buildMatch().build();
req = sw.getOFFactory().buildFlowStatsRequest()
.setMatch(match)
.setOutPort(OFPort.ANY)
.setTableId(TableId.ALL)
.build();
break;
case AGGREGATE:
match = sw.getOFFactory().buildMatch().build();
req = sw.getOFFactory().buildAggregateStatsRequest()
.setMatch(match)
.setOutPort(OFPort.ANY)
.setTableId(TableId.ALL)
.build();
break;
case PORT:
req = sw.getOFFactory().buildPortStatsRequest()
.setPortNo(OFPort.ANY)
.build();
break;
case QUEUE:
req = sw.getOFFactory().buildQueueStatsRequest()
.setPortNo(OFPort.ANY)
.setQueueId(UnsignedLong.MAX_VALUE.longValue())
.build();
break;
case DESC:
// pass - nothing todo besides set the type above
req = sw.getOFFactory().buildDescStatsRequest()
.build();
break;
case GROUP:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) {
req = sw.getOFFactory().buildGroupStatsRequest()
.build();
}
break;
case METER:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) {
req = sw.getOFFactory().buildMeterStatsRequest()
.setMeterId(OFMeterSerializerVer13.ALL_VAL)
.build();
}
break;
case GROUP_DESC:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) {
req = sw.getOFFactory().buildGroupDescStatsRequest()
.build();
}
break;
case GROUP_FEATURES:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) {
req = sw.getOFFactory().buildGroupFeaturesStatsRequest()
.build();
}
break;
case METER_CONFIG:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) {
req = sw.getOFFactory().buildMeterConfigStatsRequest()
.build();
}
break;
case METER_FEATURES:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) {
req = sw.getOFFactory().buildMeterFeaturesStatsRequest()
.build();
}
break;
case TABLE:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) {
req = sw.getOFFactory().buildTableStatsRequest()
.build();
}
break;
case TABLE_FEATURES:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) {
req = sw.getOFFactory().buildTableFeaturesStatsRequest()
.build();
}
break;
case PORT_DESC:
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) {
req = sw.getOFFactory().buildPortDescStatsRequest()
.build();
}
break;
case EXPERIMENTER:
default:
log.error("Stats Request Type {} not implemented yet", statsType.name());
break;
}
try {
if (req != null) {
future = sw.writeStatsRequest(req);
values = (List<OFStatsReply>) future.get(portStatsInterval / 2, TimeUnit.SECONDS);
}
} catch (Exception e) {
log.error("Failure retrieving statistics from switch {}. {}", sw, e);
}
}
return values;
}
}
\ No newline at end of file
package net.floodlightcontroller.statistics;
import java.util.Date;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.U64;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import net.floodlightcontroller.statistics.web.SwitchPortBandwidthSerializer;
@JsonSerialize(using=SwitchPortBandwidthSerializer.class)
public class SwitchPortBandwidth {
private DatapathId id;
private OFPort pt;
private U64 rx;
private U64 tx;
private Date time;
private U64 rxValue;
private U64 txValue;
private SwitchPortBandwidth() {}
private SwitchPortBandwidth(DatapathId d, OFPort p, U64 rx, U64 tx, U64 rxValue, U64 txValue) {
id = d;
pt = p;
this.rx = rx;
this.tx = tx;
time = new Date();
this.rxValue = rxValue;
this.txValue = txValue;
}
public static SwitchPortBandwidth of(DatapathId d, OFPort p, U64 rx, U64 tx, U64 rxValue, U64 txValue) {
if (d == null) {
throw new IllegalArgumentException("Datapath ID cannot be null");
}
if (p == null) {
throw new IllegalArgumentException("Port cannot be null");
}
if (rx == null) {
throw new IllegalArgumentException("RX bandwidth cannot be null");
}
if (tx == null) {
throw new IllegalArgumentException("TX bandwidth cannot be null");
}
if (rxValue == null) {
throw new IllegalArgumentException("RX value cannot be null");
}
if (txValue == null) {
throw new IllegalArgumentException("TX value cannot be null");
}
return new SwitchPortBandwidth(d, p, rx, tx, rxValue, txValue);
}
public DatapathId getSwitchId() {
return id;
}
public OFPort getSwitchPort() {
return pt;
}
public U64 getBitsPerSecondRx() {
return rx;
}
public U64 getBitsPerSecondTx() {
return tx;
}
protected U64 getPriorByteValueRx() {
return rxValue;
}
protected U64 getPriorByteValueTx() {
return txValue;
}
public long getUpdateTime() {
return time.getTime();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((pt == null) ? 0 : pt.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SwitchPortBandwidth other = (SwitchPortBandwidth) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (pt == null) {
if (other.pt != null)
return false;
} else if (!pt.equals(other.pt))
return false;
return true;
}
}
\ No newline at end of file
package net.floodlightcontroller.statistics.web;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.floodlightcontroller.core.internal.IOFSwitchService;
import net.floodlightcontroller.statistics.IStatisticsService;
import net.floodlightcontroller.statistics.SwitchPortBandwidth;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.OFPort;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BandwidthResource extends ServerResource {
private static final Logger log = LoggerFactory.getLogger(BandwidthResource.class);
@Get("json")
public Object retrieve() {
IStatisticsService statisticsService = (IStatisticsService) getContext().getAttributes().get(IStatisticsService.class.getCanonicalName());
IOFSwitchService switchService = (IOFSwitchService) getContext().getAttributes().get(IOFSwitchService.class.getCanonicalName());
String d = (String) getRequestAttributes().get(SwitchStatisticsWebRoutable.DPID_STR);
String p = (String) getRequestAttributes().get(SwitchStatisticsWebRoutable.PORT_STR);
DatapathId dpid = DatapathId.NONE;
if (!d.trim().equalsIgnoreCase("all")) {
try {
dpid = DatapathId.of(d);
} catch (Exception e) {
log.error("Could not parse DPID {}", d);
return Collections.singletonMap("ERROR", "Could not parse DPID" + d);
}
} /* else assume it's all */
OFPort port = OFPort.ALL;
if (!p.trim().equalsIgnoreCase("all")) {
try {
port = OFPort.of(Integer.parseInt(p));
} catch (Exception e) {
log.error("Could not parse port {}", p);
return Collections.singletonMap("ERROR", "Could not parse port" + p);
}
}
Set<SwitchPortBandwidth> spbs;
if (dpid.equals(DatapathId.NONE)) { /* do all DPIDs */
if (port.equals(OFPort.ALL)) { /* do all ports */
spbs = new HashSet<SwitchPortBandwidth>(statisticsService.getBandwidthConsumption().values());
} else {
spbs = new HashSet<SwitchPortBandwidth>();
for (DatapathId id : switchService.getAllSwitchDpids()) {
SwitchPortBandwidth spb = statisticsService.getBandwidthConsumption(id, port);
if (spb != null) {
spbs.add(spb);
}
}
}
} else {
spbs = new HashSet<SwitchPortBandwidth>();
for (OFPortDesc pd : switchService.getSwitch(dpid).getPorts()) {
SwitchPortBandwidth spb = statisticsService.getBandwidthConsumption(dpid, pd.getPortNo());
if (spb != null) {
spbs.add(spb);
}
}
}
return spbs;
}
}
\ No newline at end of file
package net.floodlightcontroller.statistics.web;
import java.io.IOException;
import java.util.Date;
import net.floodlightcontroller.statistics.SwitchPortBandwidth;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonGenerator.Feature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class SwitchPortBandwidthSerializer extends JsonSerializer<SwitchPortBandwidth> {
@Override
public void serialize(SwitchPortBandwidth spb, JsonGenerator jGen, SerializerProvider serializer) throws IOException, JsonProcessingException {
jGen.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true);
jGen.writeStartObject();
jGen.writeStringField("dpid", spb.getSwitchId().toString());
jGen.writeStringField("port", spb.getSwitchPort().toString());
jGen.writeStringField("updated", new Date(spb.getUpdateTime()).toString());
jGen.writeStringField("bits-per-second-rx", spb.getBitsPerSecondRx().getBigInteger().toString());
jGen.writeStringField("bits-per-second-tx", spb.getBitsPerSecondTx().getBigInteger().toString());
jGen.writeEndObject();
}
}
\ No newline at end of file
package net.floodlightcontroller.statistics.web;
import net.floodlightcontroller.restserver.RestletRoutable;
import org.restlet.Context;
import org.restlet.routing.Router;
public class SwitchStatisticsWebRoutable implements RestletRoutable {
protected static final String DPID_STR = "dpid";
protected static final String PORT_STR = "port";
@Override
public Router getRestlet(Context context) {
Router router = new Router(context);
router.attach("/bandwidth/{" + DPID_STR + "}/{" + PORT_STR + "}/json", BandwidthResource.class);
return router;
}
/**
* Set the base path for the Topology
*/
@Override
public String basePath() {
return "/wm/statistics";
}
}
\ No newline at end of file
......@@ -26,4 +26,5 @@ net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl
net.floodlightcontroller.firewall.Firewall
net.floodlightcontroller.accesscontrollist.ACL
net.floodlightcontroller.dhcpserver.DHCPServer
net.floodlightcontroller.learningswitch.LearningSwitch
\ No newline at end of file
net.floodlightcontroller.learningswitch.LearningSwitch
net.floodlightcontroller.statistics.StatisticsCollector
\ No newline at end of file
......@@ -15,7 +15,8 @@ net.floodlightcontroller.ui.web.StaticWebRoutable,\
net.floodlightcontroller.loadbalancer.LoadBalancer,\
net.floodlightcontroller.firewall.Firewall,\
net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\
net.floodlightcontroller.accesscontrollist.ACL
net.floodlightcontroller.accesscontrollist.ACL,\
net.floodlightcontroller.statistics.StatisticsCollector
org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE
org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/auth_credentials.jceks
org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment