Implement updateMetric and getLoad methods of ControlPlaneMonitor

- Add rrd4j jar, and wrap it as a bundle for karaf
- Implement updateMetric and getLoad methods
- Add unit test for two methods
- Revise the DefaultMetricDatabase to make it generate unique in
  memory storage space
- Revise the ControlPlaneMonitor interface
- Rename percentage to ratio, due to long string unsupport
  issue in RRD

Change-Id: Ia9d56f8e4f4bcd7ef7a29732668caa9c6a885ecf
diff --git a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetric.java b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetric.java
index fc1640f..3dde40c 100644
--- a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetric.java
+++ b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetric.java
@@ -28,11 +28,11 @@
         this.metricValue = metricValue;
     }
 
-    ControlMetricType metricType() {
+    public ControlMetricType metricType() {
         return metricType;
     }
 
-    MetricValue metricValue() {
+    public MetricValue metricValue() {
         return metricValue;
     }
 }
diff --git a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetricType.java b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetricType.java
index a5dc81c..b4626f9 100644
--- a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetricType.java
+++ b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlMetricType.java
@@ -62,11 +62,11 @@
     /* CPU Idle Time. **/
     CPU_IDLE_TIME,
 
-    /* Percentage of Used Memory Amount. */
-    MEMORY_USED_PERCENTAGE,
+    /* Ratio of Used Memory Amount. */
+    MEMORY_USED_RATIO,
 
-    /* Percentage of Free Memory Amount. **/
-    MEMORY_FREE_PERCENTAGE,
+    /* Ratio of Free Memory Amount. **/
+    MEMORY_FREE_RATIO,
 
     /* Used Memory Amount. **/
     MEMORY_USED,
diff --git a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlPlaneMonitorService.java b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlPlaneMonitorService.java
index d4e6bc5..64222e8 100644
--- a/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlPlaneMonitorService.java
+++ b/apps/cpman/api/src/main/java/org/onosproject/cpman/ControlPlaneMonitorService.java
@@ -19,7 +19,6 @@
 import org.onosproject.net.DeviceId;
 
 import java.util.Optional;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Control Plane Statistics Service Interface.
@@ -46,6 +45,8 @@
 
     /**
      * Obtains the control plane load of a specific device.
+     * The metrics range from control messages and system metrics
+     * (e.g., CPU and memory info)
      *
      * @param nodeId   node id {@link org.onosproject.cluster.NodeId}
      * @param type     control metric type
@@ -55,15 +56,13 @@
     ControlLoad getLoad(NodeId nodeId, ControlMetricType type, Optional<DeviceId> deviceId);
 
     /**
-     * Obtains the control plane load of a specific device with a specific time duration.
+     * Obtains the control plane load of a specific device.
+     * The metrics range from I/O device metrics (e.g., disk and network interface)
      *
-     * @param nodeId   node id {@link org.onosproject.cluster.NodeId}
-     * @param type     control metric type
-     * @param duration time duration
-     * @param unit     time unit
-     * @param deviceId device id {@link org.onosproject.net.Device}
+     * @param nodeId        node id {@link org.onosproject.cluster.NodeId}
+     * @param type          control metric type
+     * @param resourceName  resource name
      * @return control plane load
      */
-    ControlLoad getLoad(NodeId nodeId, ControlMetricType type, Optional<DeviceId> deviceId,
-                        int duration, TimeUnit unit);
+    ControlLoad getLoad(NodeId nodeId, ControlMetricType type, String resourceName);
 }
\ No newline at end of file
diff --git a/apps/cpman/app/features.xml b/apps/cpman/app/features.xml
index 81a25cd..1eac967 100644
--- a/apps/cpman/app/features.xml
+++ b/apps/cpman/app/features.xml
@@ -20,5 +20,6 @@
         <feature>onos-api</feature>
         <bundle>mvn:${project.groupId}/onos-app-cpman-api/${project.version}</bundle>
         <bundle>mvn:${project.groupId}/onos-app-cpman/${project.version}</bundle>
+        <bundle>wrap:mvn:org.rrd4j/rrd4j/2.2$Bundle-SymbolicName=rrd4j&amp;Bundle-Version=2.2</bundle>
     </feature>
 </features>
diff --git a/apps/cpman/app/pom.xml b/apps/cpman/app/pom.xml
index 78d88bf..8a3bc9a 100644
--- a/apps/cpman/app/pom.xml
+++ b/apps/cpman/app/pom.xml
@@ -108,6 +108,7 @@
             <groupId>org.rrd4j</groupId>
             <artifactId>rrd4j</artifactId>
             <version>2.2</version>
+            <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>com.sun.jersey</groupId>
@@ -184,7 +185,8 @@
                             org.onlab.rest.*,
                             org.onosproject.*,
                             org.onlab.util.*,
-                            org.jboss.netty.util.*
+                            org.jboss.netty.util.*,
+                            org.rrd4j.*
                         </Import-Package>
                         <Web-ContextPath>${web.context}</Web-ContextPath>
                     </instructions>
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlMetricsFactory.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlMetricsFactory.java
index 3becf63..2460d6c 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlMetricsFactory.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlMetricsFactory.java
@@ -48,8 +48,8 @@
     private MetricsAggregator cpuIdleTime;
     private MetricsAggregator memoryUsed;
     private MetricsAggregator memoryFree;
-    private MetricsAggregator memoryUsedPercentage;
-    private MetricsAggregator memoryFreePercentage;
+    private MetricsAggregator memoryUsedRatio;
+    private MetricsAggregator memoryFreeRatio;
     private Map<String, MetricsAggregator> diskReadBytes;
     private Map<String, MetricsAggregator> diskWriteBytes;
     private Map<String, MetricsAggregator> nwIncomingBytes;
@@ -283,10 +283,10 @@
         /* Memory */
         memoryFree = new MetricsAggregator(metricsService, ControlMetricType.MEMORY_FREE);
         memoryUsed = new MetricsAggregator(metricsService, ControlMetricType.MEMORY_USED);
-        memoryFreePercentage = new MetricsAggregator(metricsService,
-                                        ControlMetricType.MEMORY_FREE_PERCENTAGE);
-        memoryUsedPercentage = new MetricsAggregator(metricsService,
-                                        ControlMetricType.MEMORY_USED_PERCENTAGE);
+        memoryFreeRatio = new MetricsAggregator(metricsService,
+                                        ControlMetricType.MEMORY_FREE_RATIO);
+        memoryUsedRatio = new MetricsAggregator(metricsService,
+                                        ControlMetricType.MEMORY_USED_RATIO);
 
         /* Disk I/O */
         diskReadBytes = new ConcurrentHashMap<>();
@@ -350,12 +350,12 @@
         return cpuIdleTime;
     }
 
-    public MetricsAggregator memoryFreePercentage() {
-        return memoryFreePercentage;
+    public MetricsAggregator memoryFreeRatio() {
+        return memoryFreeRatio;
     }
 
-    public MetricsAggregator memoryUsedPercentage() {
-        return memoryUsedPercentage;
+    public MetricsAggregator memoryUsedRatio() {
+        return memoryUsedRatio;
     }
 
     public MetricsAggregator diskReadBytes(String partitionName) {
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
index ef11466..06f3298 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneManager.java
@@ -53,5 +53,4 @@
     protected void deactivate() {
         log.info("Stopped");
     }
-
 }
\ No newline at end of file
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
index 5ffe4a4..682b3705 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/ControlPlaneMonitor.java
@@ -15,22 +15,50 @@
  */
 package org.onosproject.cpman.impl;
 
+import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.cpman.ControlLoad;
 import org.onosproject.cpman.ControlMetric;
 import org.onosproject.cpman.ControlMetricType;
 import org.onosproject.cpman.ControlPlaneMonitorService;
+import org.onosproject.cpman.MetricsDatabase;
 import org.onosproject.net.DeviceId;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import java.util.Map;
 import java.util.Optional;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ConcurrentHashMap;
 
-import static org.slf4j.LoggerFactory.getLogger;
+import static org.onosproject.cpman.ControlMetricType.CPU_IDLE_TIME;
+import static org.onosproject.cpman.ControlMetricType.CPU_LOAD;
+import static org.onosproject.cpman.ControlMetricType.DISK_READ_BYTES;
+import static org.onosproject.cpman.ControlMetricType.DISK_WRITE_BYTES;
+import static org.onosproject.cpman.ControlMetricType.FLOW_MOD_PACKET;
+import static org.onosproject.cpman.ControlMetricType.FLOW_REMOVED_PACKET;
+import static org.onosproject.cpman.ControlMetricType.INBOUND_PACKET;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_FREE;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_FREE_RATIO;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_USED;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_USED_RATIO;
+import static org.onosproject.cpman.ControlMetricType.NW_INCOMING_BYTES;
+import static org.onosproject.cpman.ControlMetricType.NW_INCOMING_PACKETS;
+import static org.onosproject.cpman.ControlMetricType.NW_OUTGOING_BYTES;
+import static org.onosproject.cpman.ControlMetricType.NW_OUTGOING_PACKETS;
+import static org.onosproject.cpman.ControlMetricType.OUTBOUND_PACKET;
+import static org.onosproject.cpman.ControlMetricType.REPLY_PACKET;
+import static org.onosproject.cpman.ControlMetricType.REQUEST_PACKET;
+import static org.onosproject.cpman.ControlMetricType.SYS_CPU_TIME;
+import static org.onosproject.cpman.ControlMetricType.TOTAL_CPU_TIME;
+import static org.onosproject.cpman.ControlMetricType.USER_CPU_TIME;
 
 /**
  * Control plane monitoring service class.
@@ -39,37 +67,219 @@
 @Service
 public class ControlPlaneMonitor implements ControlPlaneMonitorService {
 
-    private final Logger log = getLogger(getClass());
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private MetricsDatabase cpuMetrics;
+    private MetricsDatabase memoryMetrics;
+    private Map<DeviceId, MetricsDatabase> controlMessageMap;
+    private Map<String, MetricsDatabase> diskMetricsMap;
+    private Map<String, MetricsDatabase> networkMetricsMap;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected ClusterService clusterService;
+
+    private static final String CPU = "Cpu";
+    private static final String MEMORY = "Memory";
+    private static final String CTRL_MSG = "ControlMessage";
+    private static final String DISK = "Disk";
+    private static final String NETWORK = "Network";
+
+    private static final ImmutableSet<ControlMetricType> CPU_METRICS =
+            ImmutableSet.of(CPU_IDLE_TIME, CPU_LOAD, SYS_CPU_TIME,
+                    USER_CPU_TIME, TOTAL_CPU_TIME);
+    private static final ImmutableSet<ControlMetricType> MEMORY_METRICS =
+            ImmutableSet.of(MEMORY_FREE, MEMORY_FREE_RATIO, MEMORY_USED,
+                    MEMORY_USED_RATIO);
+    private static final ImmutableSet<ControlMetricType> DISK_METRICS =
+            ImmutableSet.of(DISK_READ_BYTES, DISK_WRITE_BYTES);
+    private static final ImmutableSet<ControlMetricType> NETWORK_METRICS =
+            ImmutableSet.of(NW_INCOMING_BYTES, NW_OUTGOING_BYTES,
+                    NW_INCOMING_PACKETS, NW_OUTGOING_PACKETS);
+    private static final ImmutableSet<ControlMetricType> CTRL_MSGS =
+            ImmutableSet.of(INBOUND_PACKET, OUTBOUND_PACKET, FLOW_MOD_PACKET,
+                    FLOW_REMOVED_PACKET, REQUEST_PACKET, REPLY_PACKET);
+    private Map<ControlMetricType, Double> cpuBuf;
+    private Map<ControlMetricType, Double> memoryBuf;
+    private Map<String, Map<ControlMetricType, Double>> diskBuf;
+    private Map<String, Map<ControlMetricType, Double>> networkBuf;
+    private Map<DeviceId, Map<ControlMetricType, Double>> ctrlMsgBuf;
 
     @Activate
     public void activate() {
+        cpuMetrics = genMDbBuilder(CPU, CPU_METRICS);
+        memoryMetrics = genMDbBuilder(MEMORY, MEMORY_METRICS);
+        controlMessageMap = new ConcurrentHashMap<>();
+        diskMetricsMap = new ConcurrentHashMap<>();
+        networkMetricsMap = new ConcurrentHashMap<>();
+
+        cpuBuf = new ConcurrentHashMap<>();
+        memoryBuf = new ConcurrentHashMap<>();
+        diskBuf = new ConcurrentHashMap<>();
+        networkBuf = new ConcurrentHashMap<>();
+        ctrlMsgBuf = new ConcurrentHashMap<>();
+
+        log.info("Started");
     }
 
     @Deactivate
     public void deactivate() {
+
+        // TODO: need to handle the mdb close.
+        cpuBuf.clear();
+        memoryBuf.clear();
+        diskBuf.clear();
+        networkBuf.clear();
+        ctrlMsgBuf.clear();
+
+        log.info("Stopped");
     }
 
     @Override
-    public void updateMetric(ControlMetric cpm, Integer updateInterval,
+    public void updateMetric(ControlMetric cm, Integer updateInterval,
                              Optional<DeviceId> deviceId) {
+        if (deviceId.isPresent()) {
 
+            // insert a new device entry if we cannot find any
+            ctrlMsgBuf.putIfAbsent(deviceId.get(), new ConcurrentHashMap<>());
+
+            // update control message metrics
+            if (CTRL_MSGS.contains(cm.metricType())) {
+
+                // we will accumulate the metric value into buffer first
+                ctrlMsgBuf.get(deviceId.get()).putIfAbsent(cm.metricType(),
+                        (double) cm.metricValue().getLoad());
+
+                // if buffer contains all control message metrics,
+                // we simply set and update the values into MetricsDatabase.
+                if (ctrlMsgBuf.get(deviceId.get()).keySet().containsAll(CTRL_MSGS)) {
+                    updateControlMessages(ctrlMsgBuf.get(deviceId.get()), deviceId.get());
+                    ctrlMsgBuf.get(deviceId.get()).clear();
+                }
+            }
+        } else {
+
+            // update cpu metrics
+            if (CPU_METRICS.contains(cm.metricType())) {
+                cpuBuf.putIfAbsent(cm.metricType(),
+                        (double) cm.metricValue().getLoad());
+                if (cpuBuf.keySet().containsAll(CPU_METRICS)) {
+                    cpuMetrics.updateMetrics(convertMap(cpuBuf));
+                    cpuBuf.clear();
+                }
+            }
+
+            // update memory metrics
+            if (MEMORY_METRICS.contains(cm.metricType())) {
+                memoryBuf.putIfAbsent(cm.metricType(),
+                        (double) cm.metricValue().getLoad());
+                if (memoryBuf.keySet().containsAll(MEMORY_METRICS)) {
+                    memoryMetrics.updateMetrics(convertMap(memoryBuf));
+                    memoryBuf.clear();
+                }
+            }
+        }
     }
 
     @Override
-    public void updateMetric(ControlMetric controlMetric, Integer updateInterval,
+    public void updateMetric(ControlMetric cm, Integer updateInterval,
                              String resourceName) {
+        // update disk metrics
+        if (DISK_METRICS.contains(cm.metricType())) {
+            diskBuf.putIfAbsent(resourceName, new ConcurrentHashMap<>());
+            diskBuf.get(resourceName).putIfAbsent(cm.metricType(),
+                    (double) cm.metricValue().getLoad());
+            if (diskBuf.get(resourceName).keySet().containsAll(DISK_METRICS)) {
+                updateDiskMetrics(diskBuf.get(resourceName), resourceName);
+                diskBuf.clear();
+            }
+        }
 
+        // update network metrics
+        if (NETWORK_METRICS.contains(cm.metricType())) {
+            networkBuf.putIfAbsent(resourceName, new ConcurrentHashMap<>());
+            networkBuf.get(resourceName).putIfAbsent(cm.metricType(),
+                    (double) cm.metricValue().getLoad());
+            if (networkBuf.get(resourceName).keySet().containsAll(NETWORK_METRICS)) {
+                updateNetworkMetrics(networkBuf.get(resourceName), resourceName);
+                networkBuf.clear();
+            }
+        }
     }
 
     @Override
     public ControlLoad getLoad(NodeId nodeId, ControlMetricType type,
                                Optional<DeviceId> deviceId) {
+        ControllerNode node = clusterService.getNode(nodeId);
+        if (clusterService.getLocalNode().equals(node)) {
+
+            if (deviceId.isPresent()) {
+                if (CTRL_MSGS.contains(type)) {
+                    return new DefaultControlLoad(controlMessageMap.get(deviceId.get()), type);
+                }
+            } else {
+                // returns controlLoad of CPU metrics
+                if (CPU_METRICS.contains(type)) {
+                    return new DefaultControlLoad(cpuMetrics, type);
+                }
+
+                // returns memoryLoad of memory metrics
+                if (MEMORY_METRICS.contains(type)) {
+                    return new DefaultControlLoad(memoryMetrics, type);
+                }
+            }
+        } else {
+            // TODO: currently only query the metrics of local node
+            return null;
+        }
         return null;
     }
 
     @Override
     public ControlLoad getLoad(NodeId nodeId, ControlMetricType type,
-                               Optional<DeviceId> deviceId, int duration, TimeUnit unit) {
+                               String resourceName) {
+        if (clusterService.getLocalNode().id().equals(nodeId)) {
+            if (DISK_METRICS.contains(type)) {
+                return new DefaultControlLoad(diskMetricsMap.get(resourceName), type);
+            }
+
+            if (NETWORK_METRICS.contains(type)) {
+                return new DefaultControlLoad(networkMetricsMap.get(resourceName), type);
+            }
+        } else {
+            // TODO: currently only query the metrics of local node
+            return null;
+        }
         return null;
     }
+
+    private MetricsDatabase genMDbBuilder(String metricName,
+                                          ImmutableSet<ControlMetricType> metricTypes) {
+        MetricsDatabase.Builder builder = new DefaultMetricsDatabase.Builder();
+        builder.withMetricName(metricName);
+        metricTypes.forEach(type -> builder.addMetricType(type.toString()));
+        return builder.build();
+    }
+
+    private void updateNetworkMetrics(Map<ControlMetricType, Double> metricMap,
+                                      String resName) {
+        networkMetricsMap.putIfAbsent(resName, genMDbBuilder(NETWORK, NETWORK_METRICS));
+        networkMetricsMap.get(resName).updateMetrics(convertMap(metricMap));
+    }
+
+    private void updateDiskMetrics(Map<ControlMetricType, Double> metricMap,
+                                   String resName) {
+        diskMetricsMap.putIfAbsent(resName, genMDbBuilder(DISK, DISK_METRICS));
+        diskMetricsMap.get(resName).updateMetrics(convertMap(metricMap));
+    }
+
+    private void updateControlMessages(Map<ControlMetricType, Double> metricMap,
+                                       DeviceId devId) {
+        controlMessageMap.putIfAbsent(devId, genMDbBuilder(CTRL_MSG, CTRL_MSGS));
+        controlMessageMap.get(devId).updateMetrics(convertMap(metricMap));
+    }
+
+    private Map convertMap(Map<ControlMetricType, Double> map) {
+        Map newMap = new ConcurrentHashMap<>();
+        map.forEach((k, v) -> newMap.putIfAbsent(k.toString(), v));
+        return newMap;
+    }
 }
\ No newline at end of file
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/DefaultMetricsDatabase.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/DefaultMetricsDatabase.java
index f387dbf..f62a0ec 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/DefaultMetricsDatabase.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/impl/DefaultMetricsDatabase.java
@@ -243,10 +243,6 @@
         private String metricName;
 
         public Builder() {
-
-            // define the resolution of monitored metrics
-            rrdDef = new RrdDef(DB_PATH, RESOLUTION);
-
             // initialize data source definition list
             dsDefs = new ArrayList<>();
         }
@@ -254,6 +250,9 @@
         @Override
         public Builder withMetricName(String metricName) {
             this.metricName = metricName;
+
+            // define the resolution of monitored metrics
+            rrdDef = new RrdDef(DB_PATH + "_" + metricName, RESOLUTION);
             return this;
         }
 
diff --git a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java
index f7a60dc..f547866 100644
--- a/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java
+++ b/apps/cpman/app/src/main/java/org/onosproject/cpman/rest/ControlMetricsCollectorWebResource.java
@@ -122,20 +122,20 @@
         ControlMetric cm;
         try {
             ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
-            JsonNode memUsedPerc = jsonTree.get("memoryUsedPercentage");
-            JsonNode memFreePerc = jsonTree.get("memoryFreePercentage");
+            JsonNode memUsedRatio = jsonTree.get("memoryUsedRatio");
+            JsonNode memFreeRatio = jsonTree.get("memoryFreeRatio");
             JsonNode memUsed = jsonTree.get("memoryUsed");
             JsonNode memFree = jsonTree.get("memoryFree");
 
-            if (memUsedPerc != null) {
-                cm = new ControlMetric(ControlMetricType.MEMORY_USED_PERCENTAGE,
-                        new MetricValue.Builder().load(memUsedPerc.asLong()).add());
+            if (memUsedRatio != null) {
+                cm = new ControlMetric(ControlMetricType.MEMORY_USED_RATIO,
+                        new MetricValue.Builder().load(memUsedRatio.asLong()).add());
                 service.updateMetric(cm, UPDATE_INTERVAL, Optional.ofNullable(null));
             }
 
-            if (memFreePerc != null) {
-                cm = new ControlMetric(ControlMetricType.MEMORY_FREE_PERCENTAGE,
-                        new MetricValue.Builder().load(memFreePerc.asLong()).add());
+            if (memFreeRatio != null) {
+                cm = new ControlMetric(ControlMetricType.MEMORY_FREE_RATIO,
+                        new MetricValue.Builder().load(memFreeRatio.asLong()).add());
                 service.updateMetric(cm, UPDATE_INTERVAL, Optional.ofNullable(null));
             }
 
diff --git a/apps/cpman/app/src/main/resources/definitions/MemoryMetricsPost.json b/apps/cpman/app/src/main/resources/definitions/MemoryMetricsPost.json
index ab815da..229df64 100644
--- a/apps/cpman/app/src/main/resources/definitions/MemoryMetricsPost.json
+++ b/apps/cpman/app/src/main/resources/definitions/MemoryMetricsPost.json
@@ -1,18 +1,18 @@
 {
   "type": "object",
   "required": [
-    "memoryUsedPercentage",
-    "memoryFreePercentage",
+    "memoryUsedRatio",
+    "memoryFreeRatio",
     "memoryUsed",
     "memoryFree"
   ],
   "properties": {
-    "memoryUsedPercentage": {
+    "memoryUsedRatio": {
       "type": "integer",
       "format": "int64",
       "example": "30"
     },
-    "memoryFreePercentage": {
+    "memoryFreeRatio": {
       "type": "integer",
       "format": "int64",
       "example": "70"
diff --git a/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java
new file mode 100644
index 0000000..7c1afef
--- /dev/null
+++ b/apps/cpman/app/src/test/java/org/onosproject/cpman/impl/ControlPlaneMonitorTest.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2016 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onosproject.cpman.impl;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.ControllerNode;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.cpman.ControlMetric;
+import org.onosproject.cpman.ControlMetricType;
+import org.onosproject.cpman.MetricValue;
+import org.onosproject.net.DeviceId;
+
+import java.util.Optional;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+import static org.onosproject.cpman.ControlMetricType.CPU_IDLE_TIME;
+import static org.onosproject.cpman.ControlMetricType.CPU_LOAD;
+import static org.onosproject.cpman.ControlMetricType.DISK_READ_BYTES;
+import static org.onosproject.cpman.ControlMetricType.DISK_WRITE_BYTES;
+import static org.onosproject.cpman.ControlMetricType.FLOW_MOD_PACKET;
+import static org.onosproject.cpman.ControlMetricType.FLOW_REMOVED_PACKET;
+import static org.onosproject.cpman.ControlMetricType.INBOUND_PACKET;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_FREE;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_FREE_RATIO;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_USED;
+import static org.onosproject.cpman.ControlMetricType.MEMORY_USED_RATIO;
+import static org.onosproject.cpman.ControlMetricType.NW_INCOMING_BYTES;
+import static org.onosproject.cpman.ControlMetricType.NW_INCOMING_PACKETS;
+import static org.onosproject.cpman.ControlMetricType.NW_OUTGOING_BYTES;
+import static org.onosproject.cpman.ControlMetricType.NW_OUTGOING_PACKETS;
+import static org.onosproject.cpman.ControlMetricType.OUTBOUND_PACKET;
+import static org.onosproject.cpman.ControlMetricType.REPLY_PACKET;
+import static org.onosproject.cpman.ControlMetricType.REQUEST_PACKET;
+import static org.onosproject.cpman.ControlMetricType.SYS_CPU_TIME;
+import static org.onosproject.cpman.ControlMetricType.TOTAL_CPU_TIME;
+import static org.onosproject.cpman.ControlMetricType.USER_CPU_TIME;
+
+/**
+ * Unit test of control plane monitoring service.
+ */
+public class ControlPlaneMonitorTest {
+
+    private ControlPlaneMonitor monitor;
+    private static final Integer UPDATE_INTERVAL = 1;
+    private ClusterService mockClusterService;
+    private ControllerNode mockControllerNode;
+    private NodeId nodeId;
+    private static final ImmutableSet<ControlMetricType> CPU_METRICS =
+            ImmutableSet.of(CPU_IDLE_TIME, CPU_LOAD, SYS_CPU_TIME,
+                    USER_CPU_TIME, TOTAL_CPU_TIME);
+    private static final ImmutableSet<ControlMetricType> MEMORY_METRICS =
+            ImmutableSet.of(MEMORY_FREE, MEMORY_FREE_RATIO, MEMORY_USED,
+                    MEMORY_USED_RATIO);
+    private static final ImmutableSet<ControlMetricType> DISK_METRICS =
+            ImmutableSet.of(DISK_READ_BYTES, DISK_WRITE_BYTES);
+    private static final ImmutableSet<ControlMetricType> NETWORK_METRICS =
+            ImmutableSet.of(NW_INCOMING_BYTES, NW_OUTGOING_BYTES,
+                    NW_INCOMING_PACKETS, NW_OUTGOING_PACKETS);
+    private static final ImmutableSet<ControlMetricType> CTRL_MSGS =
+            ImmutableSet.of(INBOUND_PACKET, OUTBOUND_PACKET, FLOW_MOD_PACKET,
+                    FLOW_REMOVED_PACKET, REQUEST_PACKET, REPLY_PACKET);
+
+    @Before
+    public void setup() throws Exception {
+        monitor = new ControlPlaneMonitor();
+        monitor.activate();
+
+        nodeId = new NodeId("1");
+        mockControllerNode = new MockControllerNode(nodeId);
+        mockClusterService = createMock(ClusterService.class);
+        monitor.clusterService = mockClusterService;
+
+        expect(mockClusterService.getNode(anyObject()))
+                .andReturn(mockControllerNode).anyTimes();
+        expect(mockClusterService.getLocalNode())
+                .andReturn(mockControllerNode).anyTimes();
+        replay(mockClusterService);
+    }
+
+    /**
+     * Mock class for a controller node.
+     */
+    private static class MockControllerNode implements ControllerNode {
+        final NodeId id;
+
+        public MockControllerNode(NodeId id) {
+            this.id = id;
+        }
+
+        @Override
+        public NodeId id() {
+            return this.id;
+        }
+
+        @Override
+        public IpAddress ip() {
+            return null;
+        }
+
+        @Override
+        public int tcpPort() {
+            return 0;
+        }
+    }
+
+    private void testUpdateMetricWithoutId(ControlMetricType cmt, MetricValue mv) {
+        ControlMetric cm = new ControlMetric(cmt, mv);
+        monitor.updateMetric(cm, UPDATE_INTERVAL, Optional.ofNullable(null));
+    }
+
+    private void testLoadMetricWithoutId(ControlMetricType cmt, MetricValue mv) {
+        assertThat(monitor.getLoad(nodeId, cmt, Optional.ofNullable(null)).latest(), is(mv.getLoad()));
+    }
+
+    private void testUpdateMetricWithResource(ControlMetricType cmt, MetricValue mv, String resoureName) {
+        ControlMetric cm = new ControlMetric(cmt, mv);
+        monitor.updateMetric(cm, UPDATE_INTERVAL, resoureName);
+    }
+
+    private void testLoadMetricWithResource(ControlMetricType cmt, MetricValue mv, String resoureName) {
+        assertThat(monitor.getLoad(nodeId, cmt, resoureName).latest(), is(mv.getLoad()));
+    }
+
+    private void testUpdateMetricWithId(ControlMetricType cmt, MetricValue mv, DeviceId did) {
+        ControlMetric cm = new ControlMetric(cmt, mv);
+        monitor.updateMetric(cm, UPDATE_INTERVAL, Optional.of(did));
+    }
+
+    private void testLoadMetricWithId(ControlMetricType cmt, MetricValue mv, DeviceId did) {
+        assertThat(monitor.getLoad(nodeId, cmt, Optional.of(did)).latest(), is(mv.getLoad()));
+    }
+
+    @Test
+    public void testCpuMetric() {
+        MetricValue mv = new MetricValue.Builder().load(30).add();
+
+        CPU_METRICS.forEach(cmt -> testUpdateMetricWithoutId(cmt, mv));
+        CPU_METRICS.forEach(cmt -> testLoadMetricWithoutId(cmt, mv));
+    }
+
+    @Test
+    public void testMemoryMetric() {
+        MetricValue mv = new MetricValue.Builder().load(40).add();
+
+        MEMORY_METRICS.forEach(cmt -> testUpdateMetricWithoutId(cmt, mv));
+        MEMORY_METRICS.forEach(cmt -> testLoadMetricWithoutId(cmt, mv));
+    }
+
+    @Test
+    public void testDiskMetric() {
+        MetricValue mv = new MetricValue.Builder().load(50).add();
+
+        ImmutableSet<String> set = ImmutableSet.of("disk1", "disk2");
+
+        set.forEach(disk -> DISK_METRICS.forEach(cmt ->
+                testUpdateMetricWithResource(cmt, mv, disk)));
+
+        set.forEach(disk -> DISK_METRICS.forEach(cmt ->
+                testLoadMetricWithResource(cmt, mv, disk)));
+    }
+
+    @Test
+    public void testNetworkMetric() {
+        MetricValue mv = new MetricValue.Builder().load(10).add();
+
+        ImmutableSet<String> set = ImmutableSet.of("eth0", "eth1");
+
+        set.forEach(network -> NETWORK_METRICS.forEach(cmt ->
+                testUpdateMetricWithResource(cmt, mv, network)));
+
+        set.forEach(network -> NETWORK_METRICS.forEach(cmt ->
+                testLoadMetricWithResource(cmt, mv, network)));
+    }
+
+    @Test
+    public void testUpdateControlMessage() {
+        MetricValue mv = new MetricValue.Builder().load(10).add();
+
+        ImmutableSet<String> set = ImmutableSet.of("of:0000000000000001",
+                                                   "of:0000000000000002");
+
+        set.forEach(ctrlMsg -> CTRL_MSGS.forEach(cmt ->
+                testUpdateMetricWithId(cmt, mv, DeviceId.deviceId(ctrlMsg))));
+
+        set.forEach(ctrlMsg -> CTRL_MSGS.forEach(cmt ->
+                testLoadMetricWithId(cmt, mv, DeviceId.deviceId(ctrlMsg))));
+    }
+}
diff --git a/apps/cpman/app/src/test/resources/org/onosproject/cpman/rest/memory-metrics-post.json b/apps/cpman/app/src/test/resources/org/onosproject/cpman/rest/memory-metrics-post.json
index 8bce870..9057078 100644
--- a/apps/cpman/app/src/test/resources/org/onosproject/cpman/rest/memory-metrics-post.json
+++ b/apps/cpman/app/src/test/resources/org/onosproject/cpman/rest/memory-metrics-post.json
@@ -1,6 +1,6 @@
 {
-  "memoryUsedPercentage": 30,
-  "memoryFreePercentage": 70,
+  "memoryUsedRatio": 30,
+  "memoryFreeRatio": 70,
   "memoryUsed": 1024,
   "memoryFree": 2048
 }
\ No newline at end of file