/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* 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 net.floodlightcontroller.counter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.floodlightcontroller.core.IOFSwitch;
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.counter.CounterValue.CounterType;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.IPv4;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements a central store for system counters. These counters include
* overall packet-in, packet-out, and flow-mod counters. Additional packet-in
* counters are maintained for bcast/unicast/multicast traffic, as well as counters
* for traffic types based on ethertype and ip-proto (maintained on a per switch
* and controller level). These counters are maintained without the involvement of
* any other module in the system. For per-module counters and other detailed
* debug services, consider IDebugCounterService.
*
* @authors Kyle, Kanzhe, Mandeep and Saurav
*/
public class CounterStore implements IFloodlightModule, ICounterStoreService {
protected static Logger log = LoggerFactory.getLogger(CounterStore.class);
public enum NetworkLayer {
L2, L3, L4
}
protected class CounterEntry {
protected ICounter counter;
String title;
}
protected class MutableInt {
int value = 0;
public void increment() { value += 1; }
public int get() { return value; }
public void set(int val) { value = val; }
}
protected class CounterKeyTuple {
byte msgType;
long dpid;
short l3type;
byte l4type;
public CounterKeyTuple(byte msgType, long dpid, short l3type, byte l4type){
this.msgType = msgType;
this.dpid = dpid;
this.l3type = l3type;
this.l4type = l4type;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!(obj instanceof CounterKeyTuple)) return false;
CounterKeyTuple other = (CounterKeyTuple) obj;
if (this.msgType == other.msgType &&
this.dpid == other.dpid &&
this.l3type == other.l3type &&
this.l4type == other.l4type)
return true;
return false;
}
@Override
public int hashCode() {
final int prime = 283;
int result = 1;
result = prime * result + msgType;
result = prime * result + (int) (dpid ^ (dpid >>> 32));
result = prime * result + l3type;
result = prime * result + l4type;
return result;
}
}
/**
* Counter storage across all threads. These are periodically updated from the
* local per thread counters by the updateFlush method.
*/
protected ConcurrentHashMap<CounterKeyTuple, List<ICounter>>
pktinCounters = new ConcurrentHashMap<CounterKeyTuple, List<ICounter>>();
protected ConcurrentHashMap<CounterKeyTuple, List<ICounter>>
pktoutCounters = new ConcurrentHashMap<CounterKeyTuple, List<ICounter>>();
/**
* Thread local counter stores
*/
protected final ThreadLocal<Map<CounterKeyTuple,MutableInt>> pktin_local_buffer =
new ThreadLocal<Map<CounterKeyTuple,MutableInt>>() {
@Override
protected Map<CounterKeyTuple,MutableInt> initialValue() {
return new HashMap<CounterKeyTuple,MutableInt>();
}
};
protected final ThreadLocal<Map<CounterKeyTuple,MutableInt>> pktout_local_buffer =
new ThreadLocal<Map<CounterKeyTuple,MutableInt>>() {
@Override
protected Map<CounterKeyTuple,MutableInt> initialValue() {
return new HashMap<CounterKeyTuple,MutableInt>();
}
};
/**
* A cache of counterName --> Counter used to retrieve counters quickly via
* string-counter-keys
*/
protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex =
new ConcurrentHashMap<String, CounterEntry>();
/**
* Counter Categories grouped by network layers
* NetworkLayer -> CounterToCategories
*/
protected static Map<NetworkLayer, Map<String, List<String>>> layeredCategories =
new ConcurrentHashMap<NetworkLayer, Map<String, List<String>>> ();
//*******************************
// ICounterStoreService
//*******************************
@Override
public void updatePacketInCountersLocal(IOFSwitch sw, OFMessage m, Ethernet eth) {
if (((OFPacketIn)m).getPacketData().length <= 0) {
return;
}
CounterKeyTuple countersKey = this.getCountersKey(sw, m, eth);
Map<CounterKeyTuple, MutableInt> pktin_buffer = this.pktin_local_buffer.get();
MutableInt currval = pktin_buffer.get(countersKey);
if (currval == null) {
this.createPacketInCounters(sw, m, eth); // create counters as side effect (if required)
currval = new MutableInt();
pktin_buffer.put(countersKey, currval);
}
currval.increment();
return;
}
@Override
public void updatePktOutFMCounterStoreLocal(IOFSwitch sw, OFMessage m) {
CounterKeyTuple countersKey = this.getCountersKey(sw, m, null);
Map<CounterKeyTuple, MutableInt> pktout_buffer = this.pktout_local_buffer.get();
MutableInt currval = pktout_buffer.get(countersKey);
if (currval == null) {
this.getPktOutFMCounters(sw, m); // create counters as side effect (if required)
currval = new MutableInt();
pktout_buffer.put(countersKey, currval);
}
currval.increment();
return;
}
@Override
public void updateFlush() {
Date date = new Date();
Map<CounterKeyTuple, MutableInt> pktin_buffer = this.pktin_local_buffer.get();
for (CounterKeyTuple key : pktin_buffer.keySet()) {
MutableInt currval = pktin_buffer.get(key);
int delta = currval.get();
if (delta > 0) {
List<ICounter> counters = this.pktinCounters.get(key);
if (counters != null) {
for (ICounter c : counters) {
c.increment(date, delta);
}
}
}
}
// We could do better "GC" of counters that have not been update "recently"
pktin_buffer.clear();
Map<CounterKeyTuple, MutableInt> pktout_buffer = this.pktout_local_buffer.get();
for (CounterKeyTuple key : pktout_buffer.keySet()) {
MutableInt currval = pktout_buffer.get(key);
int delta = currval.get();
if (delta > 0) {
List<ICounter> counters = this.pktoutCounters.get(key);
if (counters != null) {
for (ICounter c : counters) {
c.increment(date, delta);
}
}
}
}
// We could do better "GC" of counters that have not been update "recently"
pktout_buffer.clear();
}
@Override
public ICounter createCounter(String key, CounterValue.CounterType type) {
CounterEntry ce;
ICounter c;
c = SimpleCounter.createCounter(new Date(), type);
ce = new CounterEntry();
ce.counter = c;
ce.title = key;
nameToCEIndex.putIfAbsent(key, ce);
return nameToCEIndex.get(key).counter;
}
@Override
public ICounter getCounter(String key) {
CounterEntry counter = nameToCEIndex.get(key);
if (counter != null) {
return counter.counter;
} else {
return null;
}
}
/* (non-Javadoc)
* @see net.floodlightcontroller.counter.ICounterStoreService#getAll()
*/
@Override
public Map<String, ICounter> getAll() {
Map<String, ICounter> ret = new ConcurrentHashMap<String, ICounter>();
for(Map.Entry<String, CounterEntry> counterEntry : this.nameToCEIndex.entrySet()) {
String key = counterEntry.getKey();
ICounter counter = counterEntry.getValue().counter;
ret.put(key, counter);
}
return ret;
}
@Override
public List<String> getAllCategories(String counterName, NetworkLayer layer) {
if (layeredCategories.containsKey(layer)) {
Map<String, List<String>> counterToCategories = layeredCategories.get(layer);
if (counterToCategories.containsKey(counterName)) {
return counterToCategories.get(counterName);
}
}
return null;
}
/**
* Create a title based on switch ID, portID, vlanID, and counterName
* If portID is -1, the title represents the given switch only
* If portID is a non-negative number, the title represents the port on the given switch
*/
public static String createCounterName(String switchID, int portID, String counterName) {
if (portID < 0) {
return switchID + TitleDelimitor + counterName;
} else {
return switchID + TitleDelimitor + portID + TitleDelimitor + counterName;
}
}
//*******************************
// Internal Methods
//*******************************
protected CounterKeyTuple getCountersKey(IOFSwitch sw, OFMessage m, Ethernet eth) {
byte mtype = m.getType().getTypeValue();
short l3type = 0;
byte l4type = 0;
if (eth != null) {
l3type = eth.getEtherType();
if (eth.getPayload() instanceof IPv4) {
IPv4 ipV4 = (IPv4)eth.getPayload();
l4type = ipV4.getProtocol();
}
}
return new CounterKeyTuple(mtype, sw.getId(), l3type, l4type);
}
protected List<ICounter> createPacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) {
/* If possible, find and return counters for this tuple */
CounterKeyTuple countersKey = this.getCountersKey(sw, m, eth);
List<ICounter> counters =
this.pktinCounters.get(countersKey);
if (counters != null) {
return counters;
}
/*
* Create the required counters
*/
counters = new ArrayList<ICounter>();
int l3type = eth.getEtherType() & 0xffff;
String switchIdHex = sw.getStringId();
String etherType = String.format("%04x", eth.getEtherType());
String packetName = m.getType().toClass().getName();
packetName = packetName.substring(packetName.lastIndexOf('.')+1);
// L2 Type
String l2Type = null;
if (eth.isBroadcast()) {
l2Type = BROADCAST;
}
else if (eth.isMulticast()) {
l2Type = MULTICAST;
}
else {
l2Type = UNICAST;
}
/*
* Use alias for L3 type
* Valid EtherType must be greater than or equal to 0x0600
* It is V1 Ethernet Frame if EtherType < 0x0600
*/
if (l3type < 0x0600) {
etherType = "0599";
}
if (TypeAliases.l3TypeAliasMap != null &&
TypeAliases.l3TypeAliasMap.containsKey(etherType)) {
etherType = TypeAliases.l3TypeAliasMap.get(etherType);
}
else {
etherType = "L3_" + etherType;
}
// overall controller packet counter names
String controllerCounterName =
CounterStore.createCounterName(
CONTROLLER_NAME,
-1,
packetName);
counters.add(createCounter(controllerCounterName,
CounterType.LONG));
String switchCounterName =
CounterStore.createCounterName(
switchIdHex,
-1,
packetName);
counters.add(createCounter(switchCounterName,
CounterType.LONG));
// L2 counter names
String controllerL2CategoryCounterName =
CounterStore.createCounterName(
CONTROLLER_NAME,
-1,
packetName,
l2Type,
NetworkLayer.L2);
counters.add(createCounter(controllerL2CategoryCounterName,
CounterType.LONG));
String switchL2CategoryCounterName =
CounterStore.createCounterName(
switchIdHex,
-1,
packetName,
l2Type,
NetworkLayer.L2);
counters.add(createCounter(switchL2CategoryCounterName,
CounterType.LONG));
// L3 counter names
String controllerL3CategoryCounterName =
CounterStore.createCounterName(
CONTROLLER_NAME,
-1,
packetName,
etherType,
NetworkLayer.L3);
counters.add(createCounter(controllerL3CategoryCounterName,
CounterType.LONG));
String switchL3CategoryCounterName =
CounterStore.createCounterName(
switchIdHex,
-1,
packetName,
etherType,
NetworkLayer.L3);
counters.add(createCounter(switchL3CategoryCounterName,
CounterType.LONG));
// L4 counters
if (eth.getPayload() instanceof IPv4) {
// resolve protocol alias
IPv4 ipV4 = (IPv4)eth.getPayload();
String l4name = String.format("%02x", ipV4.getProtocol());
if (TypeAliases.l4TypeAliasMap != null &&
TypeAliases.l4TypeAliasMap.containsKey(l4name)) {
l4name = TypeAliases.l4TypeAliasMap.get(l4name);
}
else {
l4name = "L4_" + l4name;
}
// create counters
String controllerL4CategoryCounterName =
CounterStore.createCounterName(
CONTROLLER_NAME,
-1,
packetName,
l4name,
NetworkLayer.L4);
counters.add(createCounter(controllerL4CategoryCounterName,
CounterType.LONG));
String switchL4CategoryCounterName =
CounterStore.createCounterName(
switchIdHex,
-1,
packetName,
l4name,
NetworkLayer.L4);
counters.add(createCounter(switchL4CategoryCounterName,
CounterType.LONG));
}
/* Add to map and return */
this.pktinCounters.putIfAbsent(countersKey, counters);
return this.pktinCounters.get(countersKey);
}
protected List<ICounter> getPktOutFMCounters(IOFSwitch sw, OFMessage m) {
/* If possible, find and return counters for this tuple */
CounterKeyTuple countersKey = this.getCountersKey(sw, m, null);
List<ICounter> counters =
this.pktoutCounters.get(countersKey);
if (counters != null) {
return counters;
}
/*
* Create the required counters
*/
counters = new ArrayList<ICounter>();
/* String values for names */
String switchIdHex = sw.getStringId();
String packetName = m.getType().toClass().getName();
packetName = packetName.substring(packetName.lastIndexOf('.')+1);
String controllerFMCounterName =
CounterStore.createCounterName(
CONTROLLER_NAME,
-1,
packetName);
counters.add(createCounter(controllerFMCounterName,
CounterValue.CounterType.LONG));
String switchFMCounterName =
CounterStore.createCounterName(
switchIdHex,
-1,
packetName);
counters.add(createCounter(switchFMCounterName,
CounterValue.CounterType.LONG));
/* Add to map and return */
this.pktoutCounters.putIfAbsent(countersKey, counters);
return this.pktoutCounters.get(countersKey);
}
/**
* Create a title based on switch ID, portID, vlanID, counterName, and subCategory
* If portID is -1, the title represents the given switch only
* If portID is a non-negative number, the title represents the port on the given switch
* For example: PacketIns can be further categorized based on L2 etherType or L3 protocol
*/
protected static String createCounterName(String switchID, int portID, String counterName,
String subCategory, NetworkLayer layer) {
String fullCounterName = "";
String groupCounterName = "";
if (portID < 0) {
groupCounterName = switchID + TitleDelimitor + counterName;
fullCounterName = groupCounterName + TitleDelimitor + subCategory;
} else {
groupCounterName = switchID + TitleDelimitor + portID + TitleDelimitor + counterName;
fullCounterName = groupCounterName + TitleDelimitor + subCategory;
}
Map<String, List<String>> counterToCategories;
if (layeredCategories.containsKey(layer)) {
counterToCategories = layeredCategories.get(layer);
} else {
counterToCategories = new ConcurrentHashMap<String, List<String>> ();
layeredCategories.put(layer, counterToCategories);
}
List<String> categories;
if (counterToCategories.containsKey(groupCounterName)) {
categories = counterToCategories.get(groupCounterName);
} else {
categories = new ArrayList<String>();
counterToCategories.put(groupCounterName, categories);
}
if (!categories.contains(subCategory)) {
categories.add(subCategory);
}
return fullCounterName;
}
//*******************************
// IFloodlightProvider
//*******************************
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> services =
new ArrayList<Class<? extends IFloodlightService>>(1);
services.add(ICounterStoreService.class);
return services;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
Map<Class<? extends IFloodlightService>,
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>,
IFloodlightService>();
m.put(ICounterStoreService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
// no-op, no dependencies
return null;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
// no-op for now
}
@Override
public void startUp(FloodlightModuleContext context) {
// no-op for now
}
}