| /** |
| * 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. |
| **/ |
| |
| /** |
| * Implements a very simple central store for system counters |
| */ |
| 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 java.util.concurrent.Executors; |
| import java.util.concurrent.TimeUnit; |
| |
| import javax.annotation.PostConstruct; |
| |
| 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; |
| |
| |
| /** |
| * @author kyle |
| * |
| */ |
| public class CounterStore implements IFloodlightModule, ICounterStoreService { |
| protected final static Logger log = LoggerFactory.getLogger(CounterStore.class); |
| |
| public enum NetworkLayer { |
| L2, L3, L4 |
| } |
| |
| protected class CounterEntry { |
| protected ICounter counter; |
| String title; |
| } |
| |
| /** |
| * A map of counterName --> Counter |
| */ |
| protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex = |
| new ConcurrentHashMap<String, CounterEntry>(); |
| |
| protected ICounter heartbeatCounter; |
| protected ICounter randomCounter; |
| |
| /** |
| * Counter Categories grouped by network layers |
| * NetworkLayer -> CounterToCategories |
| */ |
| protected static Map<NetworkLayer, Map<String, List<String>>> layeredCategories = |
| new ConcurrentHashMap<NetworkLayer, Map<String, List<String>>> (); |
| |
| public void updatePacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) { |
| OFPacketIn packet = (OFPacketIn)m; |
| |
| // Make sure there is data |
| if (packet.getPacketData().length <= 0) return; |
| |
| /* Extract the etherType and protocol field for IPv4 packet. |
| */ |
| String etherType = String.format("%04x", eth.getEtherType()); |
| |
| /* |
| * Valid EtherType must be greater than or equal to 0x0600 |
| * It is V1 Ethernet Frame if EtherType < 0x0600 |
| */ |
| if (eth.getEtherType() < 0x0600) { |
| etherType = "0599"; |
| } |
| |
| if (TypeAliases.l3TypeAliasMap != null && |
| TypeAliases.l3TypeAliasMap.containsKey(etherType)) { |
| etherType = TypeAliases.l3TypeAliasMap.get(etherType); |
| } else { |
| etherType = "L3_" + etherType; |
| } |
| String switchIdHex = sw.getStringId(); |
| |
| String packetName = m.getType().toClass().getName(); |
| packetName = packetName.substring(packetName.lastIndexOf('.')+1); |
| |
| // Construct controller counter for the packet_in |
| String controllerCounterName = |
| CounterStore.createCounterName(CONTROLLER_NAME, |
| -1, |
| packetName); |
| |
| String controllerL3CategoryCounterName = |
| CounterStore.createCounterName(CONTROLLER_NAME, |
| -1, |
| packetName, |
| etherType, |
| NetworkLayer.L3); |
| |
| String l2Type = null; |
| if (eth.isBroadcast()) { |
| l2Type = BROADCAST; |
| } else if (eth.isMulticast()) { |
| l2Type = MULTICAST; |
| } else { |
| l2Type = UNICAST; |
| } |
| |
| // Construct both port and switch L3 counter for the packet_in |
| String controllerL2CategoryCounterName = CounterStore.createCounterName(CONTROLLER_NAME, |
| -1, |
| packetName, |
| l2Type, |
| NetworkLayer.L2); |
| String switchL2CategoryCounterName = CounterStore.createCounterName(switchIdHex, |
| -1, |
| packetName, |
| l2Type, |
| NetworkLayer.L2); |
| String portL2CategoryCounterName = CounterStore.createCounterName(switchIdHex, |
| packet.getInPort(), |
| packetName, |
| l2Type, |
| NetworkLayer.L2); |
| |
| // Construct both port and switch L3 counter for the packet_in |
| String portCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| packet.getInPort(), |
| packetName); |
| String switchCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| -1, |
| packetName); |
| |
| String portL3CategoryCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| packet.getInPort(), |
| packetName, |
| etherType, |
| NetworkLayer.L3); |
| String switchL3CategoryCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| -1, |
| packetName, |
| etherType, |
| NetworkLayer.L3); |
| |
| // Controller counters |
| ICounter controllerCounter = getCounter(controllerCounterName); |
| if (controllerCounter == null) { |
| controllerCounter = createCounter(controllerCounterName, |
| CounterType.LONG); |
| } |
| controllerCounter.increment(); |
| ICounter portCounter = getCounter(portCounterName); |
| if (portCounter == null) { |
| portCounter = createCounter(portCounterName, |
| CounterType.LONG); |
| } |
| portCounter.increment(); |
| ICounter switchCounter = getCounter(switchCounterName); |
| if (switchCounter == null) { |
| switchCounter = createCounter(switchCounterName, |
| CounterType.LONG); |
| } |
| switchCounter.increment(); |
| |
| // L2 counters |
| ICounter controllerL2Counter = getCounter(controllerL2CategoryCounterName); |
| if (controllerL2Counter == null) { |
| controllerL2Counter = createCounter(controllerL2CategoryCounterName, |
| CounterType.LONG); |
| } |
| controllerL2Counter.increment(); |
| ICounter switchL2Counter = getCounter(switchL2CategoryCounterName); |
| if (switchL2Counter == null) { |
| switchL2Counter = createCounter(switchL2CategoryCounterName, |
| CounterType.LONG); |
| } |
| switchL2Counter.increment(); |
| ICounter portL2Counter = getCounter(portL2CategoryCounterName); |
| if (portL2Counter == null) { |
| portL2Counter = createCounter(portL2CategoryCounterName, |
| CounterType.LONG); |
| } |
| portL2Counter.increment(); |
| |
| // L3 counters |
| ICounter controllerL3Counter = getCounter(controllerL3CategoryCounterName); |
| if (controllerL3Counter == null) { |
| controllerL3Counter = createCounter(controllerL3CategoryCounterName, |
| CounterType.LONG); |
| } |
| controllerL3Counter.increment(); |
| ICounter portL3Counter = getCounter(portL3CategoryCounterName); |
| if (portL3Counter == null) { |
| portL3Counter = createCounter(portL3CategoryCounterName, |
| CounterType.LONG); |
| } |
| portL3Counter.increment(); |
| ICounter switchL3Counter = getCounter(switchL3CategoryCounterName); |
| if (switchL3Counter == null) { |
| switchL3Counter = createCounter(switchL3CategoryCounterName, |
| CounterType.LONG); |
| } |
| switchL3Counter.increment(); |
| |
| // L4 counters |
| if (etherType.compareTo(CounterStore.L3ET_IPV4) == 0) { |
| IPv4 ipV4 = (IPv4)eth.getPayload(); |
| String l4Type = String.format("%02x", ipV4.getProtocol()); |
| if (TypeAliases.l4TypeAliasMap != null && |
| TypeAliases.l4TypeAliasMap.containsKey(l4Type)) { |
| l4Type = TypeAliases.l4TypeAliasMap.get(l4Type); |
| } else { |
| l4Type = "L4_" + l4Type; |
| } |
| String controllerL4CategoryCounterName = |
| CounterStore.createCounterName(CONTROLLER_NAME, |
| -1, |
| packetName, |
| l4Type, |
| NetworkLayer.L4); |
| String portL4CategoryCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| packet.getInPort(), |
| packetName, |
| l4Type, |
| NetworkLayer.L4); |
| String switchL4CategoryCounterName = |
| CounterStore.createCounterName(switchIdHex, |
| -1, |
| packetName, |
| l4Type, |
| NetworkLayer.L4); |
| ICounter controllerL4Counter = getCounter(controllerL4CategoryCounterName); |
| if (controllerL4Counter == null) { |
| controllerL4Counter = createCounter(controllerL4CategoryCounterName, |
| CounterType.LONG); |
| } |
| controllerL4Counter.increment(); |
| ICounter portL4Counter = getCounter(portL4CategoryCounterName); |
| if (portL4Counter == null) { |
| portL4Counter = createCounter(portL4CategoryCounterName, |
| CounterType.LONG); |
| } |
| portL4Counter.increment(); |
| ICounter switchL4Counter = getCounter(switchL4CategoryCounterName); |
| if (switchL4Counter == null) { |
| switchL4Counter = createCounter(switchL4CategoryCounterName, |
| CounterType.LONG); |
| } |
| switchL4Counter.increment(); |
| } |
| } |
| |
| /** |
| * This method can only be used to update packetOut and flowmod counters |
| * |
| * @param sw |
| * @param ofMsg |
| */ |
| public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage ofMsg) { |
| String packetName = ofMsg.getType().toClass().getName(); |
| packetName = packetName.substring(packetName.lastIndexOf('.')+1); |
| // flowmod is per switch and controller. portid = -1 |
| String controllerFMCounterName = CounterStore.createCounterName(CONTROLLER_NAME, -1, packetName); |
| ICounter counter = getCounter(controllerFMCounterName); |
| if (counter == null) { |
| counter = createCounter(controllerFMCounterName, CounterValue.CounterType.LONG); |
| } |
| counter.increment(); |
| |
| String switchFMCounterName = CounterStore.createCounterName(sw.getStringId(), -1, packetName); |
| counter = getCounter(switchFMCounterName); |
| if (counter == null) { |
| counter = createCounter(switchFMCounterName, CounterValue.CounterType.LONG); |
| } |
| counter.increment(); |
| } |
| |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| public 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; |
| } |
| |
| @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; |
| } |
| |
| @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; |
| } |
| |
| /** |
| * Post construction init method to kick off the health check and random (test) counter threads |
| */ |
| @PostConstruct |
| public void startUp() { |
| this.heartbeatCounter = this.createCounter("CounterStore heartbeat", CounterValue.CounterType.LONG); |
| this.randomCounter = this.createCounter("CounterStore random", CounterValue.CounterType.LONG); |
| //Set a background thread to flush any liveCounters every 100 milliseconds |
| Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() { |
| public void run() { |
| heartbeatCounter.increment(); |
| randomCounter.increment(new Date(), (long) (Math.random() * 100)); //TODO - pull this in to random timing |
| }}, 100, 100, TimeUnit.MILLISECONDS); |
| } |
| |
| @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 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 |
| } |
| } |