blob: e167b26714f07bfb7fa9d3f881a9cbee63a20cd2 [file] [log] [blame]
package net.floodlightcontroller.debugevent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.debugevent.web.DebugEventRoutable;
import net.floodlightcontroller.restserver.IRestApiService;
import com.google.common.collect.Sets;
/**
* This class implements a central store for all events used for debugging the
* system. The basic idea is that given the functionality provided by this class,
* it should be unnecessary to resort to scraping through system DEBUG/TRACE logs
* to understand behavior in a running system.
*
* @author saurav
*/
public class DebugEvent implements IFloodlightModule, IDebugEventService {
protected static Logger log = LoggerFactory.getLogger(DebugEvent.class);
/**
* Every registered event type gets an event id, the value for which is obtained
* while holding the lock.
*/
protected int eventIdCounter = 0;
protected Object eventIdLock = new Object();
private static final int PCT_LOCAL_CAP = 10; // % of global capacity
private static final int MIN_LOCAL_CAPACITY = 10; //elements
/**
* Event Information
*/
public class EventInfo {
int eventId;
boolean enabled;
int bufferCapacity;
EventType etype;
String eventDesc;
String eventName;
String moduleName;
String moduleEventName;
Class<?> eventClass;
String[] metaData;
public EventInfo(int eventId, boolean enabled, int bufferCapacity,
EventType etype, Class<?> eventClass, String eventDesc,
String eventName, String moduleName, String... metaData) {
this.enabled = enabled;
this.eventId = eventId;
this.bufferCapacity = bufferCapacity;
this.etype = etype;
this.eventClass = eventClass;
this.eventDesc = eventDesc;
this.eventName = eventName;
this.moduleName = moduleName;
this.moduleEventName = moduleName + "/" + eventName;
this.metaData = metaData;
}
public int getEventId() { return eventId; }
public boolean isEnabled() { return enabled; }
public int getBufferCapacity() { return bufferCapacity; }
public EventType getEtype() { return etype; }
public String getEventDesc() { return eventDesc; }
public String getEventName() { return eventName; }
public String getModuleName() { return moduleName; }
public String getModuleEventName() { return moduleEventName; }
public String[] getMetaData() { return metaData; }
}
//******************
// Global stores
//******************
/**
* Event history for a particular event-id is stored in a circular buffer
*/
protected class DebugEventHistory {
EventInfo einfo;
CircularBuffer<Event> eventBuffer;
public DebugEventHistory(EventInfo einfo, int capacity) {
this.einfo = einfo;
this.eventBuffer = new CircularBuffer<Event>(capacity);
}
}
/**
* Global storage for all event types and their corresponding event buffers.
* A particular event type is accessed by directly indexing into the array
* with the corresponding event-id.
*/
protected DebugEventHistory[] allEvents =
new DebugEventHistory[MAX_EVENTS];
/**
* Global storage for all event ids registered for a module. The map is indexed
* by the module name and event name and returns the event-ids that correspond to the
* event types registered by that module (for example module 'linkdiscovery'
* may register events that have ids 0 and 1 that correspond to link up/down
* events, and receiving malformed LLDP packets, respectively).
*/
protected ConcurrentHashMap<String, ConcurrentHashMap<String, Integer>>
moduleEvents = new ConcurrentHashMap<String,
ConcurrentHashMap<String, Integer>>();
/**
* A collection of event ids that are currently enabled for logging
*/
protected Set<Integer> currentEvents = Collections.newSetFromMap(
new ConcurrentHashMap<Integer,Boolean>());
//******************
// Thread local stores
//******************
/**
* Thread local storage for events
*/
protected class LocalEventHistory {
int nextIndex;
int maxCapacity;
boolean enabled;
ArrayList<Event> eventList;
public LocalEventHistory(boolean enabled, int maxCapacity) {
this.nextIndex = 0;
this.maxCapacity = maxCapacity;
this.enabled = enabled;
this.eventList = new ArrayList<Event>();
}
}
/**
* Thread local event buffers used for maintaining event history local to
* a thread. Eventually this locally maintained information is flushed
* into the global event buffers.
*/
protected final ThreadLocal<LocalEventHistory[]> threadlocalEvents =
new ThreadLocal<LocalEventHistory[]>() {
@Override
protected LocalEventHistory[] initialValue() {
return new LocalEventHistory[MAX_EVENTS];
}
};
/**
* Thread local cache for event-ids that are currently active.
*/
protected final ThreadLocal<Set<Integer>> threadlocalCurrentEvents =
new ThreadLocal<Set<Integer>>() {
@Override
protected Set<Integer> initialValue() {
return new HashSet<Integer>();
}
};
//*******************************
// IEventUpdater
//*******************************
protected class EventUpdaterImpl<T> implements IEventUpdater<T> {
private final int eventId;
public EventUpdaterImpl(int evId) {
this.eventId = evId;
}
@Override
public void updateEventNoFlush(Object event) {
if (!validEventId()) return;
updateEvent(eventId, false, event);
}
@Override
public void updateEventWithFlush(Object event) {
if (!validEventId()) return;
updateEvent(eventId, true, event);
}
private boolean validEventId() {
if (eventId < 0 || eventId >= MAX_EVENTS) {
throw new IllegalStateException();
}
return true;
}
}
//*******************************
// IDebugEventService
//*******************************
@Override
public <T> IEventUpdater<T> registerEvent(String moduleName, String eventName,
String eventDescription, EventType et,
Class<T> eventClass, int bufferCapacity,
String... metaData) throws MaxEventsRegistered {
int eventId = -1;
synchronized (eventIdLock) {
eventId = Integer.valueOf(eventIdCounter++);
}
if (eventId > MAX_EVENTS-1) {
throw new MaxEventsRegistered();
}
// register event id for moduleName
if (!moduleEvents.containsKey(moduleName)) {
moduleEvents.put(moduleName, new ConcurrentHashMap<String, Integer>());
}
if (!moduleEvents.get(moduleName).containsKey(eventName)) {
moduleEvents.get(moduleName).put(eventName, new Integer(eventId));
} else {
int existingEventId = moduleEvents.get(moduleName).get(eventName);
log.error("Duplicate event registration for moduleName {} eventName {}",
moduleName, eventName);
return new EventUpdaterImpl<T>(existingEventId);
}
// create storage for event-type
boolean enabled = (et == EventType.ALWAYS_LOG) ? true : false;
EventInfo ei = new EventInfo(eventId, enabled, bufferCapacity,
et, eventClass, eventDescription, eventName,
moduleName, metaData);
allEvents[eventId] = new DebugEventHistory(ei, bufferCapacity);
if (enabled) {
currentEvents.add(eventId);
}
return new EventUpdaterImpl<T>(eventId);
}
private void updateEvent(int eventId, boolean flushNow, Object eventData) {
if (eventId < 0 || eventId > MAX_EVENTS-1) return;
LocalEventHistory[] thishist = this.threadlocalEvents.get();
if (thishist[eventId] == null) {
// seeing this event for the first time in this thread - create local
// store by consulting global store
DebugEventHistory de = allEvents[eventId];
if (de != null) {
boolean enabled = de.einfo.enabled;
int localCapacity = de.einfo.bufferCapacity * PCT_LOCAL_CAP/ 100;
if (localCapacity < 10) localCapacity = MIN_LOCAL_CAPACITY;
thishist[eventId] = new LocalEventHistory(enabled, localCapacity);
if (enabled) {
Set<Integer> thisset = this.threadlocalCurrentEvents.get();
thisset.add(eventId);
}
} else {
log.error("updateEvent seen locally for event {} but no global"
+ "storage exists for it yet .. not updating", eventId);
return;
}
}
// update local store if enabled locally for updating
LocalEventHistory le = thishist[eventId];
if (le.enabled) {
long timestamp = System.currentTimeMillis();
long thisthread = Thread.currentThread().getId();
String thisthreadname = Thread.currentThread().getName();
if (le.nextIndex < le.eventList.size()) {
if (le.eventList.get(le.nextIndex) == null) {
le.eventList.set(le.nextIndex, new Event(timestamp, thisthread,
thisthreadname,
eventData));
} else {
Event e = le.eventList.get(le.nextIndex);
e.timestamp = timestamp;
e.threadId = thisthread;
e.threadName = thisthreadname;
e.eventData = eventData;
e.nullifyCachedFormattedEvent();
}
} else {
le.eventList.add(new Event(timestamp, thisthread, thisthreadname, eventData));
}
le.nextIndex++;
if (le.nextIndex >= le.maxCapacity || flushNow) {
// flush this buffer now
DebugEventHistory de = allEvents[eventId];
if (de.einfo.enabled) {
le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex);
} else {
// global buffer is disabled - don't flush, disable locally
le.enabled = false;
Set<Integer> thisset = this.threadlocalCurrentEvents.get();
thisset.remove(eventId);
}
le.nextIndex = 0;
}
}
}
@Override
public void flushEvents() {
LocalEventHistory[] thishist = this.threadlocalEvents.get();
Set<Integer> thisset = this.threadlocalCurrentEvents.get();
ArrayList<Integer> temp = new ArrayList<Integer>();
for (int eventId : thisset) {
LocalEventHistory le = thishist[eventId];
if (le != null && le.nextIndex > 0) {
// flush this buffer now
DebugEventHistory de = allEvents[eventId];
if (de.einfo.enabled) {
le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex);
} else {
// global buffer is disabled - don't flush, disable locally
le.enabled = false;
temp.add(eventId);
}
le.nextIndex = 0;
}
}
for (int eId : temp)
thisset.remove(eId);
// sync thread local currently enabled set of eventIds with global set.
Sets.SetView<Integer> sv = Sets.difference(currentEvents, thisset);
for (int eventId : sv) {
if (thishist[eventId] != null) {
thishist[eventId].enabled = true;
thisset.add(eventId);
}
}
}
@Override
public boolean containsModuleEventName(String moduleName, String eventName) {
if (!moduleEvents.containsKey(moduleName)) return false;
if (moduleEvents.get(moduleName).containsKey(eventName)) return true;
return false;
}
@Override
public boolean containsModuleName(String moduleName) {
return moduleEvents.containsKey(moduleName);
}
@Override
public List<DebugEventInfo> getAllEventHistory() {
List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>();
for (Map<String, Integer> modev : moduleEvents.values()) {
for (int eventId : modev.values()) {
DebugEventHistory de = allEvents[eventId];
if (de != null) {
List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
for (Event e : de.eventBuffer) {
ret.add(e.getFormattedEvent(de.einfo.eventClass,
de.einfo.moduleEventName));
}
moduleEventList.add(new DebugEventInfo(de.einfo, ret));
}
}
}
return moduleEventList;
}
@Override
public List<DebugEventInfo> getModuleEventHistory(String moduleName) {
if (!moduleEvents.containsKey(moduleName)) return Collections.emptyList();
List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>();
for (int eventId : moduleEvents.get(moduleName).values()) {
DebugEventHistory de = allEvents[eventId];
if (de != null) {
List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
for (Event e : de.eventBuffer) {
ret.add(e.getFormattedEvent(de.einfo.eventClass,
de.einfo.moduleEventName));
}
moduleEventList.add(new DebugEventInfo(de.einfo, ret));
}
}
return moduleEventList;
}
@Override
public DebugEventInfo getSingleEventHistory(String moduleName, String eventName,
int last) {
if (!moduleEvents.containsKey(moduleName)) return null;
Integer eventId = moduleEvents.get(moduleName).get(eventName);
if (eventId == null) return null;
DebugEventHistory de = allEvents[eventId];
if (de != null) {
int num = 1;
List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
for (Event e : de.eventBuffer) {
if (num > last)
break;
Map<String, String> temp = e.getFormattedEvent(de.einfo.eventClass,
de.einfo.moduleEventName);
temp.put("#", String.valueOf(num++));
ret.add(temp);
}
return new DebugEventInfo(de.einfo, ret);
}
return null;
}
@Override
public void resetAllEvents() {
for (Map<String, Integer> eventMap : moduleEvents.values()) {
for (Integer evId : eventMap.values()) {
allEvents[evId].eventBuffer.clear();
}
}
}
@Override
public void resetAllModuleEvents(String moduleName) {
if (!moduleEvents.containsKey(moduleName)) return;
Map<String, Integer> modEvents = moduleEvents.get(moduleName);
for (Integer evId : modEvents.values()) {
allEvents[evId].eventBuffer.clear();
}
}
@Override
public void resetSingleEvent(String moduleName, String eventName) {
if (!moduleEvents.containsKey(moduleName)) return;
Integer eventId = moduleEvents.get(moduleName).get(eventName);
if (eventId == null) return;
DebugEventHistory de = allEvents[eventId];
if (de != null) {
de.eventBuffer.clear();
}
}
@Override
public List<String> getModuleList() {
List<String> el = new ArrayList<String>();
el.addAll(moduleEvents.keySet());
return el;
}
@Override
public List<String> getModuleEventList(String moduleName) {
if (!moduleEvents.containsKey(moduleName))
return Collections.emptyList();
List<String> el = new ArrayList<String>();
el.addAll(moduleEvents.get(moduleName).keySet());
return el;
}
//*******************************
// IFloodlightModule
//*******************************
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IDebugEventService.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(IDebugEventService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
ArrayList<Class<? extends IFloodlightService>> deps =
new ArrayList<Class<? extends IFloodlightService>>();
deps.add(IRestApiService.class);
return deps;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
}
@Override
public void startUp(FloodlightModuleContext context)
throws FloodlightModuleException {
IRestApiService restService =
context.getServiceImpl(IRestApiService.class);
restService.addRestletRoutable(new DebugEventRoutable());
DebugEventAppender.setDebugEventServiceImpl(this);
}
}