Update the TopologyMetrics module to listen for all topology-related events:
Devices, Hosts, Links, TopologyEvent

Now the semantics for updating the metrics are:
 * Any topology-related event (DeviceEvent, HostEvent, LinkEvent,
   TopologyEvent) will update the Lost Topology Event Timestamp
 * Only the DeviceEvent, HostEvent and LinkEvent will be counted in
   measuring the event rate; TopologyEvent is excluded, because it
   is generated as a result of some of those events

Also, increased the number of saved events from 10 to 100.

Change-Id: Ie759ee69869cddc617d7ad5b8b75a622e2571620
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
index e2a4532..32cf0cf 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetrics.java
@@ -18,6 +18,15 @@
 import org.onlab.metrics.MetricsFeature;
 import org.onlab.metrics.MetricsService;
 import org.onlab.onos.event.Event;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
+import org.onlab.onos.net.host.HostEvent;
+import org.onlab.onos.net.host.HostListener;
+import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.link.LinkListener;
+import org.onlab.onos.net.link.LinkService;
 import org.onlab.onos.net.topology.TopologyEvent;
 import org.onlab.onos.net.topology.TopologyListener;
 import org.onlab.onos.net.topology.TopologyService;
@@ -28,14 +37,26 @@
  */
 @Component(immediate = true)
 @Service
-public class TopologyMetrics implements TopologyMetricsService,
-                                        TopologyListener {
+public class TopologyMetrics implements TopologyMetricsService {
     private static final Logger log = getLogger(TopologyMetrics.class);
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected TopologyService topologyService;
-    private LinkedList<TopologyEvent> lastEvents = new LinkedList<>();
-    private static final int LAST_EVENTS_MAX_N = 10;
+
+    private LinkedList<Event> lastEvents = new LinkedList<>();
+    private static final int LAST_EVENTS_MAX_N = 100;
+
+    private final DeviceListener deviceListener = new InnerDeviceListener();
+    private final HostListener hostListener = new InnerHostListener();
+    private final LinkListener linkListener = new InnerLinkListener();
+    private final TopologyListener topologyListener =
+        new InnerTopologyListener();
 
     //
     // Metrics
@@ -61,22 +82,33 @@
     protected void activate() {
         clear();
         registerMetrics();
-        topologyService.addListener(this);
+
+        // Register for all topology-related events
+        deviceService.addListener(deviceListener);
+        hostService.addListener(hostListener);
+        linkService.addListener(linkListener);
+        topologyService.addListener(topologyListener);
+
         log.info("ONOS Topology Metrics started.");
     }
 
     @Deactivate
     public void deactivate() {
-        topologyService.removeListener(this);
+        // De-register from all topology-related events
+        deviceService.removeListener(deviceListener);
+        hostService.removeListener(hostListener);
+        linkService.removeListener(linkListener);
+        topologyService.removeListener(topologyListener);
+
         removeMetrics();
         clear();
         log.info("ONOS Topology Metrics stopped.");
     }
 
     @Override
-    public List<TopologyEvent> getEvents() {
+    public List<Event> getEvents() {
         synchronized (lastEvents) {
-            return ImmutableList.<TopologyEvent>copyOf(lastEvents);
+            return ImmutableList.<Event>copyOf(lastEvents);
         }
     }
 
@@ -90,27 +122,22 @@
         return eventRateMeter;
     }
 
-    @Override
-    public void event(TopologyEvent event) {
-        lastEventTimestampEpochMs = System.currentTimeMillis();
-        //
-        // NOTE: If we want to count each "reason" as a separate event,
-        // then we should use 'event.reason().size()' instead of '1' to
-        // mark the meter below.
-        //
-        eventRateMeter.mark(1);
-
-        log.debug("Topology Event: time = {} type = {} subject = {}",
-                  event.time(), event.type(), event.subject());
-        for (Event reason : event.reasons()) {
-            log.debug("Topology Event Reason: time = {} type = {} subject = {}",
-                      reason.time(), reason.type(), reason.subject());
-        }
-
-        //
-        // Keep only the last N events, where N = LAST_EVENTS_MAX_N
-        //
+    /**
+     * Records an event.
+     *
+     * @param event the event to record
+     * @param updateEventRateMeter if true, update the Event Rate Meter
+     */
+    private void recordEvent(Event event, boolean updateEventRateMeter) {
         synchronized (lastEvents) {
+            lastEventTimestampEpochMs = System.currentTimeMillis();
+            if (updateEventRateMeter) {
+                eventRateMeter.mark(1);
+            }
+
+            //
+            // Keep only the last N events, where N = LAST_EVENTS_MAX_N
+            //
             while (lastEvents.size() >= LAST_EVENTS_MAX_N) {
                 lastEvents.remove();
             }
@@ -119,11 +146,67 @@
     }
 
     /**
+     * Inner Device Event Listener class.
+     */
+    private class InnerDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            recordEvent(event, true);
+            log.debug("Device Event: time = {} type = {} event = {}",
+                      event.time(), event.type(), event);
+        }
+    }
+
+    /**
+     * Inner Host Event Listener class.
+     */
+    private class InnerHostListener implements HostListener {
+        @Override
+        public void event(HostEvent event) {
+            recordEvent(event, true);
+            log.debug("Host Event: time = {} type = {} event = {}",
+                      event.time(), event.type(), event);
+        }
+    }
+
+    /**
+     * Inner Link Event Listener class.
+     */
+    private class InnerLinkListener implements LinkListener {
+        @Override
+        public void event(LinkEvent event) {
+            recordEvent(event, true);
+            log.debug("Link Event: time = {} type = {} event = {}",
+                      event.time(), event.type(), event);
+        }
+    }
+
+    /**
+     * Inner Topology Event Listener class.
+     */
+    private class InnerTopologyListener implements TopologyListener {
+        @Override
+        public void event(TopologyEvent event) {
+            //
+            // NOTE: Don't update the eventRateMeter, because the real
+            // events are already captured/counted.
+            //
+            recordEvent(event, false);
+            log.debug("Topology Event: time = {} type = {} event = {}",
+                      event.time(), event.type(), event);
+            for (Event reason : event.reasons()) {
+                log.debug("Topology Event Reason: time = {} type = {} event = {}",
+                          reason.time(), reason.type(), reason);
+            }
+        }
+    }
+
+    /**
      * Clears the internal state.
      */
     private void clear() {
-        lastEventTimestampEpochMs = 0;
         synchronized (lastEvents) {
+            lastEventTimestampEpochMs = 0;
             lastEvents.clear();
         }
     }
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
index cc370fa..aeb2e32 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/TopologyMetricsService.java
@@ -4,7 +4,7 @@
 
 import com.codahale.metrics.Gauge;
 import com.codahale.metrics.Meter;
-import org.onlab.onos.net.topology.TopologyEvent;
+import org.onlab.onos.event.Event;
 
 /**
  * Service interface exported by TopologyMetrics.
@@ -15,7 +15,7 @@
      *
      * @return the last saved topology events.
      */
-    public List<TopologyEvent> getEvents();
+    public List<Event> getEvents();
 
     /**
      * Gets the Metrics' Gauge for the last topology event timestamp
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
index 8bab4d0..f8d0c1a 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsListCommand.java
@@ -19,10 +19,8 @@
          description = "Lists the last topology events")
 public class TopologyEventsListCommand extends AbstractShellCommand {
 
-    private static final String FORMAT_EVENT =
-        "Topology Event time=%d type=%s subject=%s";
-    private static final String FORMAT_REASON =
-        "    Reason time=%d type=%s subject=%s";
+    private static final String FORMAT_EVENT =  "Event=%s";
+    private static final String FORMAT_REASON = "    Reason=%s";
 
     @Override
     protected void execute() {
@@ -31,12 +29,13 @@
         if (outputJson()) {
             print("%s", json(service.getEvents()));
         } else {
-            for (TopologyEvent event : service.getEvents()) {
-                print(FORMAT_EVENT, event.time(), event.type(),
-                      event.subject());
-                for (Event reason : event.reasons()) {
-                    print(FORMAT_REASON, reason.time(), reason.type(),
-                          reason.subject());
+            for (Event event : service.getEvents()) {
+                print(FORMAT_EVENT, event);
+                if (event instanceof TopologyEvent) {
+                    TopologyEvent topologyEvent = (TopologyEvent) event;
+                    for (Event reason : topologyEvent.reasons()) {
+                        print(FORMAT_REASON, reason);
+                    }
                 }
                 print("");          // Extra empty line for clarity
             }
@@ -46,14 +45,14 @@
     /**
      * Produces a JSON array of topology events.
      *
-     * @param topologyEvents the topology events with the data
+     * @param events the topology events with the data
      * @return JSON array with the topology events
      */
-    private JsonNode json(List<TopologyEvent> topologyEvents) {
+    private JsonNode json(List<Event> events) {
         ObjectMapper mapper = new ObjectMapper();
         ArrayNode result = mapper.createArrayNode();
 
-        for (TopologyEvent event : topologyEvents) {
+        for (Event event : events) {
             result.add(json(mapper, event));
         }
         return result;
@@ -66,32 +65,23 @@
      * @param topologyEvent the topology event with the data
      * @return JSON object for the topology event
      */
-    private ObjectNode json(ObjectMapper mapper, TopologyEvent topologyEvent) {
-        ObjectNode result = mapper.createObjectNode();
-        ArrayNode reasons = mapper.createArrayNode();
-
-        for (Event reason : topologyEvent.reasons()) {
-            reasons.add(json(mapper, reason));
-        }
-        result.put("time", topologyEvent.time())
-            .put("type", topologyEvent.type().toString())
-            .put("subject", topologyEvent.subject().toString())
-            .put("reasons", reasons);
-        return result;
-    }
-
-    /**
-     * Produces JSON object for a generic event.
-     *
-     * @param event the generic event with the data
-     * @return JSON object for the generic event
-     */
     private ObjectNode json(ObjectMapper mapper, Event event) {
         ObjectNode result = mapper.createObjectNode();
 
         result.put("time", event.time())
             .put("type", event.type().toString())
-            .put("subject", event.subject().toString());
+            .put("event", event.toString());
+
+        // Add the reasons if a TopologyEvent
+        if (event instanceof TopologyEvent) {
+            TopologyEvent topologyEvent = (TopologyEvent) event;
+            ArrayNode reasons = mapper.createArrayNode();
+            for (Event reason : topologyEvent.reasons()) {
+                reasons.add(json(mapper, reason));
+            }
+            result.put("reasons", reasons);
+        }
+
         return result;
     }
 }