/* * Copyright (c) 2011,2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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. * * This file incorporates work covered by the following copyright and * permission notice: * * 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 org.sdnplatform.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 org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.sdnplatform.core.IOFSwitch; import org.sdnplatform.core.module.ModuleContext; import org.sdnplatform.core.module.ModuleException; import org.sdnplatform.core.module.IModule; import org.sdnplatform.core.module.IPlatformService; import org.sdnplatform.counter.CounterValue.CounterType; import org.sdnplatform.packet.Ethernet; import org.sdnplatform.packet.IPv4; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author kyle * */ public class CounterStore implements IModule, 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; } } /** * A map of counterName --> Counter */ protected ConcurrentHashMap<String, CounterEntry> nameToCEIndex = new ConcurrentHashMap<String, CounterEntry>(); protected ICounter heartbeatCounter; protected ICounter randomCounter; protected ConcurrentHashMap<String, List<ICounter>> pktinCounters = new ConcurrentHashMap<String, List<ICounter>>(); protected ConcurrentHashMap<String, List<ICounter>> pktoutCounters = new ConcurrentHashMap<String, List<ICounter>>(); protected final ThreadLocal<Map<String,MutableInt>> pktin_local_buffer = new ThreadLocal<Map<String,MutableInt>>() { @Override protected Map<String,MutableInt> initialValue() { return new HashMap<String,MutableInt>(); } }; protected final ThreadLocal<Map<String,MutableInt>> pktout_local_buffer = new ThreadLocal<Map<String,MutableInt>>() { @Override protected Map<String,MutableInt> initialValue() { return new HashMap<String,MutableInt>(); } }; /** * 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) { if (((OFPacketIn)m).getPacketData().length <= 0) { return; } List<ICounter> counters = this.getPacketInCounters(sw, m, eth); if (counters != null) { for (ICounter c : counters) { c.increment(); } } return; } @Override public void updatePacketInCountersLocal(IOFSwitch sw, OFMessage m, Ethernet eth) { if (((OFPacketIn)m).getPacketData().length <= 0) { return; } String countersKey = this.getCountersKey(sw, m, eth); Map<String, MutableInt> pktin_buffer = this.pktin_local_buffer.get(); MutableInt currval = pktin_buffer.get(countersKey); if ( currval == null ) { this.getPacketInCounters(sw, m, eth); // create counters as side effect (if required) currval = new MutableInt(); pktin_buffer.put(countersKey, currval); } currval.increment(); return; } /** * This method can only be used to update packetOut and flowmod counters * NOTE: flowmod is counted per switch and for controller, not per port/proto * * @param sw * @param m */ public void updatePktOutFMCounterStore(IOFSwitch sw, OFMessage m) { List<ICounter> counters = this.getPktOutFMCounters(sw, m); if (counters != null) { for (ICounter c : counters) { c.increment(); } } return; } @Override public void updatePktOutFMCounterStoreLocal(IOFSwitch sw, OFMessage m) { String countersKey = this.getCountersKey(sw, m, null); Map<String, 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<String, MutableInt> pktin_buffer = this.pktin_local_buffer.get(); for (String 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<String, MutableInt> pktout_buffer = this.pktout_local_buffer.get(); for (String 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(); } protected String getCountersKey(IOFSwitch sw, OFMessage m, Ethernet eth) { byte mtype = m.getType().getTypeValue(); //long swid = sw.getId(); String swsid = sw.getStringId(); short port = 0; short l3type = 0; byte l4type = 0; if (eth != null) { // Packet in counters // Need port and protocol level differentiation OFPacketIn packet = (OFPacketIn)m; port = packet.getInPort(); l3type = eth.getEtherType(); if (l3type == (short)0x0800) { IPv4 ipV4 = (IPv4)eth.getPayload(); l4type = ipV4.getProtocol(); } } /* If possible, find and return counters for this tuple * * NOTE: this can be converted to a tuple for better performance, * for now we are using a string representation as a the key */ String countersKey = Byte.toString(mtype) + "-" + swsid + "-" + Short.toString(port) + "-" + Short.toString(l3type) + "-" + Byte.toString(l4type); return countersKey; } protected List<ICounter> getPacketInCounters(IOFSwitch sw, OFMessage m, Ethernet eth) { /* If possible, find and return counters for this tuple */ String 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>(); /* values for names */ short port = ((OFPacketIn)m).getInPort(); short l3type = eth.getEtherType(); 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)); String portCounterName = CounterStore.createCounterName( switchIdHex, port, packetName); counters.add(createCounter(portCounterName, 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)); String portL2CategoryCounterName = CounterStore.createCounterName( switchIdHex, port, packetName, l2Type, NetworkLayer.L2); counters.add(createCounter(portL2CategoryCounterName, 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)); String portL3CategoryCounterName = CounterStore.createCounterName( switchIdHex, port, packetName, etherType, NetworkLayer.L3); counters.add(createCounter(portL3CategoryCounterName, CounterType.LONG)); // L4 counters if (l3type == (short)0x0800) { // 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)); String portL4CategoryCounterName = CounterStore.createCounterName( switchIdHex, port, packetName, l4name, NetworkLayer.L4); counters.add(createCounter(portL4CategoryCounterName, 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 */ String 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, 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 org.sdnplatform.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 IPlatformService>> getModuleServices() { Collection<Class<? extends IPlatformService>> services = new ArrayList<Class<? extends IPlatformService>>(1); services.add(ICounterStoreService.class); return services; } @Override public Map<Class<? extends IPlatformService>, IPlatformService> getServiceImpls() { Map<Class<? extends IPlatformService>, IPlatformService> m = new HashMap<Class<? extends IPlatformService>, IPlatformService>(); m.put(ICounterStoreService.class, this); return m; } @Override public Collection<Class<? extends IPlatformService>> getModuleDependencies() { // no-op, no dependencies return null; } @Override public void init(ModuleContext context) throws ModuleException { // no-op for now } @Override public void startUp(ModuleContext context) { // no-op for now } }