Refeactor the Topology Events and Intent Events Metrics modules:
 * Use the new class EventMetric to cleanup and simplify the implementation
 * Replaced the single metric for Topology Events with four metrics
   (last event timestamp and event rate):
   - Device Event metrics
   - Host Event metrics
   - Link Event metrics
   - Topology Graph metrics

Change-Id: I2acc06ab84ef3ca06d0d383983dfcff9774ff341
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
index 38366b6..cfc88b8 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetrics.java
@@ -5,8 +5,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
 import com.google.common.collect.ImmutableList;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -14,8 +12,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.metrics.MetricsComponent;
-import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.EventMetric;
 import org.onlab.metrics.MetricsService;
 import org.onlab.onos.net.intent.IntentEvent;
 import org.onlab.onos.net.intent.IntentListener;
@@ -33,56 +30,32 @@
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentService intentService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MetricsService metricsService;
+
     private LinkedList<IntentEvent> lastEvents = new LinkedList<>();
     private static final int LAST_EVENTS_MAX_N = 100;
 
     //
     // Metrics
     //
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MetricsService metricsService;
-    //
     private static final String COMPONENT_NAME = "Intent";
     private static final String FEATURE_SUBMITTED_NAME = "Submitted";
     private static final String FEATURE_INSTALLED_NAME = "Installed";
     private static final String FEATURE_WITHDRAW_REQUESTED_NAME =
         "WithdrawRequested";
     private static final String FEATURE_WITHDRAWN_NAME = "Withdrawn";
-    private static final String GAUGE_TIMESTAMP_NAME = "Timestamp.EpochMs";
-    private static final String METER_RATE_NAME = "Rate";
     //
-    private MetricsComponent metricsComponent;
-    private MetricsFeature metricsFeatureSubmitted;
-    private MetricsFeature metricsFeatureInstalled;
-    private MetricsFeature metricsFeatureWithdrawRequested;
-    private MetricsFeature metricsFeatureWithdrawn;
+    // Event metrics:
+    //  - Intent Submitted API operation
+    //  - Intent Installed operation completion
+    //  - Intent Withdraw Requested API operation
+    //  - Intent Withdrawn operation completion
     //
-    // Timestamps:
-    //  - Intent Submitted API operation (ms from the Epoch)
-    //  - Intent Installed operation completion (ms from the Epoch)
-    //  - Intent Withdraw Requested API operation (ms from the Epoch)
-    //  - Intent Withdrawn operation completion (ms from the Epoch)
-    //
-    private volatile long intentSubmittedTimestampEpochMs = 0;
-    private volatile long intentInstalledTimestampEpochMs = 0;
-    private volatile long intentWithdrawRequestedTimestampEpochMs = 0;
-    private volatile long intentWithdrawnTimestampEpochMs = 0;
-    //
-    private Gauge<Long> intentSubmittedTimestampEpochMsGauge;
-    private Gauge<Long> intentInstalledTimestampEpochMsGauge;
-    private Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge;
-    private Gauge<Long> intentWithdrawnTimestampEpochMsGauge;
-    //
-    // Rate meters:
-    //  - Rate of the Submitted Intent API operations
-    //  - Rate of the Installed Intent operations
-    //  - Rate of the Withdrawn Requested Intent API operations
-    //  - Rate of the Withdrawn Intent operations
-    //
-    private Meter intentSubmittedRateMeter;
-    private Meter intentInstalledRateMeter;
-    private Meter intentWithdrawRequestedRateMeter;
-    private Meter intentWithdrawnRateMeter;
+    private EventMetric intentSubmittedEventMetric;
+    private EventMetric intentInstalledEventMetric;
+    private EventMetric intentWithdrawRequestedEventMetric;
+    private EventMetric intentWithdrawnEventMetric;
 
     @Activate
     protected void activate() {
@@ -108,43 +81,23 @@
     }
 
     @Override
-    public Gauge<Long> intentSubmittedTimestampEpochMsGauge() {
-        return intentSubmittedTimestampEpochMsGauge;
+    public EventMetric intentSubmittedEventMetric() {
+        return intentSubmittedEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentInstalledTimestampEpochMsGauge() {
-        return intentInstalledTimestampEpochMsGauge;
+    public EventMetric intentInstalledEventMetric() {
+        return intentInstalledEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge() {
-        return intentWithdrawRequestedTimestampEpochMsGauge;
+    public EventMetric intentWithdrawRequestedEventMetric() {
+        return intentWithdrawRequestedEventMetric;
     }
 
     @Override
-    public Gauge<Long> intentWithdrawnTimestampEpochMsGauge() {
-        return intentWithdrawnTimestampEpochMsGauge;
-    }
-
-    @Override
-    public Meter intentSubmittedRateMeter() {
-        return intentSubmittedRateMeter;
-    }
-
-    @Override
-    public Meter intentInstalledRateMeter() {
-        return intentInstalledRateMeter;
-    }
-
-    @Override
-    public Meter intentWithdrawRequestedRateMeter() {
-        return intentWithdrawRequestedRateMeter;
-    }
-
-    @Override
-    public Meter intentWithdrawnRateMeter() {
-        return intentWithdrawnRateMeter;
+    public EventMetric intentWithdrawnEventMetric() {
+        return intentWithdrawnEventMetric;
     }
 
     @Override
@@ -156,26 +109,21 @@
             //
             switch (event.type()) {
             case SUBMITTED:
-                intentSubmittedTimestampEpochMs = System.currentTimeMillis();
-                intentSubmittedRateMeter.mark(1);
+                intentSubmittedEventMetric.eventReceived();
                 break;
             case INSTALLED:
-                intentInstalledTimestampEpochMs = System.currentTimeMillis();
-                intentInstalledRateMeter.mark(1);
+                intentInstalledEventMetric.eventReceived();
                 break;
             case FAILED:
                 // TODO: Just ignore?
                 break;
                 /*
             case WITHDRAW_REQUESTED:
-                intentWithdrawRequestedTimestampEpochMs =
-                    System.currentTimeMillis();
-                    intentWithdrawRequestedRateMeter.mark(1);
+                intentWithdrawRequestedEventMetric.eventReceived();
                 break;
                 */
             case WITHDRAWN:
-                intentWithdrawnTimestampEpochMs = System.currentTimeMillis();
-                intentWithdrawnRateMeter.mark(1);
+                intentWithdrawnEventMetric.eventReceived();
                 break;
             default:
                 break;
@@ -199,10 +147,6 @@
      */
     private void clear() {
         synchronized (lastEvents) {
-            intentSubmittedTimestampEpochMs = 0;
-            intentInstalledTimestampEpochMs = 0;
-            intentWithdrawRequestedTimestampEpochMs = 0;
-            intentWithdrawnTimestampEpochMs = 0;
             lastEvents.clear();
         }
     }
@@ -211,109 +155,32 @@
      * Registers the metrics.
      */
     private void registerMetrics() {
-        metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
-        //
-        metricsFeatureSubmitted =
-            metricsComponent.registerFeature(FEATURE_SUBMITTED_NAME);
-        metricsFeatureInstalled =
-            metricsComponent.registerFeature(FEATURE_INSTALLED_NAME);
-        metricsFeatureWithdrawRequested =
-            metricsComponent.registerFeature(FEATURE_WITHDRAW_REQUESTED_NAME);
-        metricsFeatureWithdrawn =
-            metricsComponent.registerFeature(FEATURE_WITHDRAWN_NAME);
-        //
-        intentSubmittedTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureSubmitted,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentSubmittedTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentInstalledTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureInstalled,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentInstalledTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentWithdrawRequestedTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureWithdrawRequested,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentWithdrawRequestedTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentWithdrawnTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureWithdrawn,
-                                          GAUGE_TIMESTAMP_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return intentWithdrawnTimestampEpochMs;
-                                              }
-                                          });
-        //
-        intentSubmittedRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureSubmitted,
-                                       METER_RATE_NAME);
-        //
-        intentInstalledRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureInstalled,
-                                       METER_RATE_NAME);
-        //
-        intentWithdrawRequestedRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureWithdrawRequested,
-                                       METER_RATE_NAME);
-        //
-        intentWithdrawnRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureWithdrawn,
-                                       METER_RATE_NAME);
+        intentSubmittedEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_SUBMITTED_NAME);
+        intentInstalledEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_INSTALLED_NAME);
+        intentWithdrawRequestedEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_WITHDRAW_REQUESTED_NAME);
+        intentWithdrawnEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_WITHDRAWN_NAME);
+
+        intentSubmittedEventMetric.registerMetrics();
+        intentInstalledEventMetric.registerMetrics();
+        intentWithdrawRequestedEventMetric.registerMetrics();
+        intentWithdrawnEventMetric.registerMetrics();
     }
 
     /**
      * Removes the metrics.
      */
     private void removeMetrics() {
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureSubmitted,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureInstalled,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawRequested,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawn,
-                                    GAUGE_TIMESTAMP_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureSubmitted,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureInstalled,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawRequested,
-                                    METER_RATE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureWithdrawn,
-                                    METER_RATE_NAME);
+        intentSubmittedEventMetric.removeMetrics();
+        intentInstalledEventMetric.removeMetrics();
+        intentWithdrawRequestedEventMetric.removeMetrics();
+        intentWithdrawnEventMetric.removeMetrics();
     }
 }
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
index 4acd00f..73df4bc 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/IntentMetricsService.java
@@ -1,9 +1,7 @@
 package org.onlab.onos.metrics.intent;
 
 import java.util.List;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.net.intent.IntentEvent;
 
 /**
@@ -18,68 +16,32 @@
     public List<IntentEvent> getEvents();
 
     /**
-     * Gets the Metrics' Gauge for the intent SUBMITTED event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent SUBMITTED events.
      *
-     * @return the Metrics' Gauge for the intent SUBMITTED event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent SUBMITTED events.
      */
-    public Gauge<Long> intentSubmittedTimestampEpochMsGauge();
+    public EventMetric intentSubmittedEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent INSTALLED event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent INSTALLED events.
      *
-     * @return the Metrics' Gauge for the intent INSTALLED event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent INSTALLED events.
      */
-    public Gauge<Long> intentInstalledTimestampEpochMsGauge();
+    public EventMetric intentInstalledEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
-     * timestamp (ms from the epoch).
+     * Gets the Event Metric for the intent WITHDRAW_REQUESTED events.
      *
      * TODO: This intent event is not implemented yet.
      *
-     * @return the Metrics' Gauge for the intent WITHDRAW_REQUESTED event
-     * timestamp (ms from the epoch)
+     * @return the Event Metric for the intent WITHDRAW_REQUESTED events.
      */
-    public Gauge<Long> intentWithdrawRequestedTimestampEpochMsGauge();
+    public EventMetric intentWithdrawRequestedEventMetric();
 
     /**
-     * Gets the Metrics' Gauge for the intent WITHDRAWN event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the intent WITHDRAWN events.
      *
-     * @return the Metrics' Gauge for the intent WITHDRAWN event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the intent WITHDRAWN events.
      */
-    public Gauge<Long> intentWithdrawnTimestampEpochMsGauge();
-
-    /**
-     * Gets the Metrics' Meter for the submitted intents event rate.
-     *
-     * @return the Metrics' Meter for the submitted intents event rate
-     */
-    public Meter intentSubmittedRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the installed intents event rate.
-     *
-     * @return the Metrics' Meter for the installed intent event rate
-     */
-    public Meter intentInstalledRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the withdraw requested intents event rate.
-     *
-     * @return the Metrics' Meter for the withdraw requested intents event rate
-     */
-    public Meter intentWithdrawRequestedRateMeter();
-
-    /**
-     * Gets the Metrics' Meter for the withdraw completed intents event rate.
-     *
-     * @return the Metrics' Meter for the withdraw completed intents event rate
-     */
-    public Meter intentWithdrawnRateMeter();
+    public EventMetric intentWithdrawnEventMetric();
 }
diff --git a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
index 204cfd6..6f8d013 100644
--- a/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
+++ b/apps/metrics/intent/src/main/java/org/onlab/onos/metrics/intent/cli/IntentEventsMetricsCommand.java
@@ -11,6 +11,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.metrics.intent.IntentMetricsService;
 
@@ -29,8 +30,6 @@
     @Override
     protected void execute() {
         IntentMetricsService service = get(IntentMetricsService.class);
-        Gauge<Long> gauge;
-        Meter meter;
 
         if (outputJson()) {
             ObjectMapper mapper = new ObjectMapper()
@@ -38,49 +37,49 @@
                                                   TimeUnit.MILLISECONDS,
                                                   false));
             ObjectNode result = mapper.createObjectNode();
-            //
-            gauge = service.intentSubmittedTimestampEpochMsGauge();
-            result.put("intentSubmittedTimestamp", json(mapper, gauge));
-            gauge = service.intentInstalledTimestampEpochMsGauge();
-            result.put("intentInstalledTimestamp", json(mapper, gauge));
-            gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
-            result.put("intentWithdrawRequestedTimestamp",
-                       json(mapper, gauge));
-            gauge = service.intentWithdrawnTimestampEpochMsGauge();
-            result.put("intentWithdrawnTimestamp", json(mapper, gauge));
-            //
-            meter = service.intentSubmittedRateMeter();
-            result.put("intentSubmittedRate", json(mapper, meter));
-            meter = service.intentInstalledRateMeter();
-            result.put("intentInstalledRate", json(mapper, meter));
-            meter = service.intentWithdrawRequestedRateMeter();
-            result.put("intentWithdrawRequestedRate", json(mapper, meter));
-            meter = service.intentWithdrawnRateMeter();
-            result.put("intentWithdrawnRate", json(mapper, meter));
-            //
+            result = json(mapper, result, "intentSubmitted",
+                          service.intentSubmittedEventMetric());
+            result = json(mapper, result, "intentInstalled",
+                          service.intentInstalledEventMetric());
+            result = json(mapper, result, "intentWithdrawRequested",
+                          service.intentWithdrawRequestedEventMetric());
+            result = json(mapper, result, "intentWithdrawn",
+                          service.intentWithdrawnEventMetric());
             print("%s", result);
         } else {
-            gauge = service.intentSubmittedTimestampEpochMsGauge();
-            printGauge("Submitted", gauge);
-            gauge = service.intentInstalledTimestampEpochMsGauge();
-            printGauge("Installed", gauge);
-            gauge = service.intentWithdrawRequestedTimestampEpochMsGauge();
-            printGauge("Withdraw Requested", gauge);
-            gauge = service.intentWithdrawnTimestampEpochMsGauge();
-            printGauge("Withdrawn", gauge);
-            //
-            meter = service.intentSubmittedRateMeter();
-            printMeter("Submitted", meter);
-            meter = service.intentInstalledRateMeter();
-            printMeter("Installed", meter);
-            meter = service.intentWithdrawRequestedRateMeter();
-            printMeter("Withdraw Requested", meter);
-            meter = service.intentWithdrawnRateMeter();
-            printMeter("Withdrawn", meter);
+            printEventMetric("Submitted",
+                             service.intentSubmittedEventMetric());
+            printEventMetric("Installed",
+                             service.intentInstalledEventMetric());
+            printEventMetric("Withdraw Requested",
+                             service.intentWithdrawRequestedEventMetric());
+            printEventMetric("Withdrawn",
+                             service.intentWithdrawnEventMetric());
         }
     }
 
     /**
+     * Produces JSON node for an Event Metric.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param objectNode the JSON object node to use
+     * @param propertyPrefix the property prefix to use
+     * @param eventMetric the Event Metric with the data
+     * @return JSON object node for the Event Metric
+     */
+    private ObjectNode json(ObjectMapper mapper, ObjectNode objectNode,
+                            String propertyPrefix, EventMetric eventMetric) {
+        String gaugeName = propertyPrefix + "Timestamp";
+        String meterName = propertyPrefix + "Rate";
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+
+        objectNode.put(gaugeName, json(mapper, gauge));
+        objectNode.put(meterName, json(mapper, meter));
+        return objectNode;
+    }
+
+    /**
      * Produces JSON node for an Object.
      *
      * @param mapper the JSON object mapper to use
@@ -94,8 +93,8 @@
         //
         try {
             final String objectJson = mapper.writeValueAsString(object);
-            JsonNode objectNode = mapper.readTree(objectJson);
-            return objectNode;
+            JsonNode jsonNode = mapper.readTree(objectJson);
+            return jsonNode;
         } catch (JsonProcessingException e) {
             log.error("Error writing value as JSON string", e);
         } catch (IOException e) {
@@ -105,28 +104,26 @@
     }
 
     /**
-     * Prints a Gauge.
+     * Prints an Event Metric.
      *
      * @param operationStr the string with the intent operation to print
-     * @param gauge the Gauge to print
+     * @param eventMetric the Event Metric to print
      */
-    private void printGauge(String operationStr, Gauge<Long> gauge) {
-        print(FORMAT_GAUGE, operationStr, gauge.getValue());
-    }
+    private void printEventMetric(String operationStr,
+                                  EventMetric eventMetric) {
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+        TimeUnit rateUnit = TimeUnit.SECONDS;
+        double rateFactor = rateUnit.toSeconds(1);
 
-    /**
-     * Prints a Meter.
-     *
-     * @param operationStr the string with the intent operation to print
-     * @param meter the Meter to print
-     */
-    private void printMeter(String operationStr, Meter meter) {
-            TimeUnit rateUnit = TimeUnit.SECONDS;
-            double rateFactor = rateUnit.toSeconds(1);
-            print(FORMAT_METER, operationStr, meter.getCount(),
-                  meter.getMeanRate() * rateFactor,
-                  meter.getOneMinuteRate() * rateFactor,
-                  meter.getFiveMinuteRate() * rateFactor,
-                  meter.getFifteenMinuteRate() * rateFactor);
+        // Print the Gauge
+        print(FORMAT_GAUGE, operationStr, gauge.getValue());
+
+        // Print the Meter
+        print(FORMAT_METER, operationStr, meter.getCount(),
+              meter.getMeanRate() * rateFactor,
+              meter.getOneMinuteRate() * rateFactor,
+              meter.getFiveMinuteRate() * rateFactor,
+              meter.getFifteenMinuteRate() * rateFactor);
     }
 }
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 32cf0cf..814e178 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
@@ -5,8 +5,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
 import com.google.common.collect.ImmutableList;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -14,8 +12,7 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
-import org.onlab.metrics.MetricsComponent;
-import org.onlab.metrics.MetricsFeature;
+import org.onlab.metrics.EventMetric;
 import org.onlab.metrics.MetricsService;
 import org.onlab.onos.event.Event;
 import org.onlab.onos.net.device.DeviceEvent;
@@ -48,6 +45,8 @@
     protected LinkService linkService;
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected TopologyService topologyService;
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MetricsService metricsService;
 
     private LinkedList<Event> lastEvents = new LinkedList<>();
     private static final int LAST_EVENTS_MAX_N = 100;
@@ -61,22 +60,22 @@
     //
     // Metrics
     //
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected MetricsService metricsService;
-    //
     private static final String COMPONENT_NAME = "Topology";
-    private static final String FEATURE_NAME = "EventNotification";
-    private static final String GAUGE_NAME = "LastEventTimestamp.EpochMs";
-    private static final String METER_NAME = "EventRate";
+    private static final String FEATURE_DEVICE_NAME = "DeviceEvent";
+    private static final String FEATURE_HOST_NAME = "HostEvent";
+    private static final String FEATURE_LINK_NAME = "LinkEvent";
+    private static final String FEATURE_GRAPH_NAME = "GraphEvent";
     //
-    private MetricsComponent metricsComponent;
-    private MetricsFeature metricsFeatureEventNotification;
+    // Event metrics:
+    //  - Device events
+    //  - Host events
+    //  - Link events
+    //  - Topology Graph events
     //
-    // Timestamp of the last Topology event (ms from the Epoch)
-    private volatile long lastEventTimestampEpochMs = 0;
-    private Gauge<Long> lastEventTimestampEpochMsGauge;
-    // Rate of the Topology events published to the Topology listeners
-    private Meter eventRateMeter;
+    private EventMetric topologyDeviceEventMetric;
+    private EventMetric topologyHostEventMetric;
+    private EventMetric topologyLinkEventMetric;
+    private EventMetric topologyGraphEventMetric;
 
     @Activate
     protected void activate() {
@@ -113,27 +112,34 @@
     }
 
     @Override
-    public Gauge<Long> lastEventTimestampEpochMsGauge() {
-        return lastEventTimestampEpochMsGauge;
+    public EventMetric topologyDeviceEventMetric() {
+        return topologyDeviceEventMetric;
     }
 
     @Override
-    public Meter eventRateMeter() {
-        return eventRateMeter;
+    public EventMetric topologyHostEventMetric() {
+        return topologyHostEventMetric;
+    }
+
+    @Override
+    public EventMetric topologyLinkEventMetric() {
+        return topologyLinkEventMetric;
+    }
+
+    @Override
+    public EventMetric topologyGraphEventMetric() {
+        return topologyGraphEventMetric;
     }
 
     /**
      * Records an event.
      *
      * @param event the event to record
-     * @param updateEventRateMeter if true, update the Event Rate Meter
+     * @param eventMetric the Event Metric to use
      */
-    private void recordEvent(Event event, boolean updateEventRateMeter) {
+    private void recordEvent(Event event, EventMetric eventMetric) {
         synchronized (lastEvents) {
-            lastEventTimestampEpochMs = System.currentTimeMillis();
-            if (updateEventRateMeter) {
-                eventRateMeter.mark(1);
-            }
+            eventMetric.eventReceived();
 
             //
             // Keep only the last N events, where N = LAST_EVENTS_MAX_N
@@ -151,7 +157,7 @@
     private class InnerDeviceListener implements DeviceListener {
         @Override
         public void event(DeviceEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyDeviceEventMetric);
             log.debug("Device Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -163,7 +169,7 @@
     private class InnerHostListener implements HostListener {
         @Override
         public void event(HostEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyHostEventMetric);
             log.debug("Host Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -175,7 +181,7 @@
     private class InnerLinkListener implements LinkListener {
         @Override
         public void event(LinkEvent event) {
-            recordEvent(event, true);
+            recordEvent(event, topologyLinkEventMetric);
             log.debug("Link Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
         }
@@ -187,11 +193,7 @@
     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);
+            recordEvent(event, topologyGraphEventMetric);
             log.debug("Topology Event: time = {} type = {} event = {}",
                       event.time(), event.type(), event);
             for (Event reason : event.reasons()) {
@@ -206,7 +208,6 @@
      */
     private void clear() {
         synchronized (lastEvents) {
-            lastEventTimestampEpochMs = 0;
             lastEvents.clear();
         }
     }
@@ -215,35 +216,32 @@
      * Registers the metrics.
      */
     private void registerMetrics() {
-        metricsComponent = metricsService.registerComponent(COMPONENT_NAME);
-        metricsFeatureEventNotification =
-            metricsComponent.registerFeature(FEATURE_NAME);
-        lastEventTimestampEpochMsGauge =
-            metricsService.registerMetric(metricsComponent,
-                                          metricsFeatureEventNotification,
-                                          GAUGE_NAME,
-                                          new Gauge<Long>() {
-                                              @Override
-                                              public Long getValue() {
-                                                  return lastEventTimestampEpochMs;
-                                              }
-                                          });
-        eventRateMeter =
-            metricsService.createMeter(metricsComponent,
-                                       metricsFeatureEventNotification,
-                                       METER_NAME);
+        topologyDeviceEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_DEVICE_NAME);
+        topologyHostEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_HOST_NAME);
+        topologyLinkEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_LINK_NAME);
+        topologyGraphEventMetric =
+            new EventMetric(metricsService, COMPONENT_NAME,
+                            FEATURE_GRAPH_NAME);
 
+        topologyDeviceEventMetric.registerMetrics();
+        topologyHostEventMetric.registerMetrics();
+        topologyLinkEventMetric.registerMetrics();
+        topologyGraphEventMetric.registerMetrics();
     }
 
     /**
      * Removes the metrics.
      */
     private void removeMetrics() {
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureEventNotification,
-                                    GAUGE_NAME);
-        metricsService.removeMetric(metricsComponent,
-                                    metricsFeatureEventNotification,
-                                    METER_NAME);
+        topologyDeviceEventMetric.removeMetrics();
+        topologyHostEventMetric.removeMetrics();
+        topologyLinkEventMetric.removeMetrics();
+        topologyGraphEventMetric.removeMetrics();
     }
 }
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 aeb2e32..9203d9b 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
@@ -1,9 +1,7 @@
 package org.onlab.onos.metrics.topology;
 
 import java.util.List;
-
-import com.codahale.metrics.Gauge;
-import com.codahale.metrics.Meter;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.event.Event;
 
 /**
@@ -18,18 +16,30 @@
     public List<Event> getEvents();
 
     /**
-     * Gets the Metrics' Gauge for the last topology event timestamp
-     * (ms from the epoch).
+     * Gets the Event Metric for the Device Events.
      *
-     * @return the Metrics' Gauge for the last topology event timestamp
-     * (ms from the epoch)
+     * @return the Event Metric for the Device Events.
      */
-    public Gauge<Long> lastEventTimestampEpochMsGauge();
+    public EventMetric topologyDeviceEventMetric();
 
     /**
-     * Gets the Metrics' Meter for the topology events rate.
+     * Gets the Event Metric for the Host Events.
      *
-     * @return the Metrics' Meter for the topology events rate
+     * @return the Event Metric for the Host Events.
      */
-    public Meter eventRateMeter();
+    public EventMetric topologyHostEventMetric();
+
+    /**
+     * Gets the Event Metric for the Link Events.
+     *
+     * @return the Event Metric for the Link Events.
+     */
+    public EventMetric topologyLinkEventMetric();
+
+    /**
+     * Gets the Event Metric for the Topology Graph Events.
+     *
+     * @return the Event Metric for the Topology Graph Events.
+     */
+    public EventMetric topologyGraphEventMetric();
 }
diff --git a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
index 54d3a95..b7e0401 100644
--- a/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
+++ b/apps/metrics/topology/src/main/java/org/onlab/onos/metrics/topology/cli/TopologyEventsMetricsCommand.java
@@ -11,6 +11,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.karaf.shell.commands.Command;
+import org.onlab.metrics.EventMetric;
 import org.onlab.onos.cli.AbstractShellCommand;
 import org.onlab.onos.metrics.topology.TopologyMetricsService;
 
@@ -22,15 +23,13 @@
 public class TopologyEventsMetricsCommand extends AbstractShellCommand {
 
     private static final String FORMAT_GAUGE =
-        "Last Topology Event Timestamp (ms from epoch)=%d";
+        "Topology %s Event Timestamp (ms from epoch)=%d";
     private static final String FORMAT_METER =
-        "Topology Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
+        "Topology %s Events count=%d rate(events/sec) mean=%f m1=%f m5=%f m15=%f";
 
     @Override
     protected void execute() {
         TopologyMetricsService service = get(TopologyMetricsService.class);
-        Gauge<Long> gauge = service.lastEventTimestampEpochMsGauge();
-        Meter meter = service.eventRateMeter();
 
         if (outputJson()) {
             ObjectMapper mapper = new ObjectMapper()
@@ -38,32 +37,89 @@
                                                   TimeUnit.MILLISECONDS,
                                                   false));
             ObjectNode result = mapper.createObjectNode();
-            try {
-                //
-                // NOTE: The API for custom serializers is incomplete,
-                // hence we have to parse the JSON string to create JsonNode.
-                //
-                final String gaugeJson = mapper.writeValueAsString(gauge);
-                final String meterJson = mapper.writeValueAsString(meter);
-                JsonNode gaugeNode = mapper.readTree(gaugeJson);
-                JsonNode meterNode = mapper.readTree(meterJson);
-                result.put("lastTopologyEventTimestamp", gaugeNode);
-                result.put("topologyEventRate", meterNode);
-            } catch (JsonProcessingException e) {
-                log.error("Error writing value as JSON string", e);
-            } catch (IOException e) {
-                log.error("Error writing value as JSON string", e);
-            }
+            result = json(mapper, result, "topologyDeviceEvent",
+                          service.topologyDeviceEventMetric());
+            result = json(mapper, result, "topologyHostEvent",
+                          service.topologyHostEventMetric());
+            result = json(mapper, result, "topologyLinkEvent",
+                          service.topologyLinkEventMetric());
+            result = json(mapper, result, "topologyGraphEvent",
+                          service.topologyGraphEventMetric());
             print("%s", result);
         } else {
-            TimeUnit rateUnit = TimeUnit.SECONDS;
-            double rateFactor = rateUnit.toSeconds(1);
-            print(FORMAT_GAUGE, gauge.getValue());
-            print(FORMAT_METER, meter.getCount(),
-                  meter.getMeanRate() * rateFactor,
-                  meter.getOneMinuteRate() * rateFactor,
-                  meter.getFiveMinuteRate() * rateFactor,
-                  meter.getFifteenMinuteRate() * rateFactor);
+            printEventMetric("Device", service.topologyDeviceEventMetric());
+            printEventMetric("Host", service.topologyHostEventMetric());
+            printEventMetric("Link", service.topologyLinkEventMetric());
+            printEventMetric("Graph", service.topologyGraphEventMetric());
         }
     }
+
+    /**
+     * Produces JSON node for an Event Metric.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param objectNode the JSON object node to use
+     * @param propertyPrefix the property prefix to use
+     * @param eventMetric the Event Metric with the data
+     * @return JSON object node for the Event Metric
+     */
+    private ObjectNode json(ObjectMapper mapper, ObjectNode objectNode,
+                            String propertyPrefix, EventMetric eventMetric) {
+        String gaugeName = propertyPrefix + "Timestamp";
+        String meterName = propertyPrefix + "Rate";
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+
+        objectNode.put(gaugeName, json(mapper, gauge));
+        objectNode.put(meterName, json(mapper, meter));
+        return objectNode;
+    }
+
+    /**
+     * Produces JSON node for an Object.
+     *
+     * @param mapper the JSON object mapper to use
+     * @param object the Object with the data
+     * @return JSON node for the Object
+     */
+    private JsonNode json(ObjectMapper mapper, Object object) {
+        //
+        // NOTE: The API for custom serializers is incomplete,
+        // hence we have to parse the JSON string to create JsonNode.
+        //
+        try {
+            final String objectJson = mapper.writeValueAsString(object);
+            JsonNode jsonNode = mapper.readTree(objectJson);
+            return jsonNode;
+        } catch (JsonProcessingException e) {
+            log.error("Error writing value as JSON string", e);
+        } catch (IOException e) {
+            log.error("Error writing value as JSON string", e);
+        }
+        return null;
+    }
+
+    /**
+     * Prints an Event Metric.
+     *
+     * @param operationStr the string with the intent operation to print
+     * @param eventMetric the Event Metric to print
+     */
+    private void printEventMetric(String operationStr,
+                                  EventMetric eventMetric) {
+        Gauge<Long> gauge = eventMetric.lastEventTimestampGauge();
+        Meter meter = eventMetric.eventRateMeter();
+        TimeUnit rateUnit = TimeUnit.SECONDS;
+        double rateFactor = rateUnit.toSeconds(1);
+
+        // Print the Gauge
+        print(FORMAT_GAUGE, operationStr, gauge.getValue());
+
+        // Print the Meter
+        print(FORMAT_METER, operationStr, meter.getCount(),
+              meter.getMeanRate() * rateFactor,
+              meter.getOneMinuteRate() * rateFactor,
+              meter.getFiveMinuteRate() * rateFactor,
+              meter.getFifteenMinuteRate() * rateFactor);
+    }
 }