Detangling incubator: virtual nets, tunnels, resource labels, oh my

- virtual networking moved to /apps/virtual; with CLI & REST API
- tunnels and labels moved to /apps/tunnel; with CLI & REST API; UI disabled for now
- protobuf/models moved to /core/protobuf/models
- defunct grpc/rpc registry stuff left under /graveyard
- compile dependencies on /incubator moved to respective modules for compilation
- run-time dependencies will need to be re-tested for dependent apps

- /graveyard will be removed in not-too-distant future

Change-Id: I0a0b995c635487edcf95a352f50dd162186b0b39
diff --git a/apps/dpistats/BUILD b/apps/dpistats/BUILD
new file mode 100644
index 0000000..a6d4490
--- /dev/null
+++ b/apps/dpistats/BUILD
@@ -0,0 +1,13 @@
+BUNDLES = [
+    "//apps/dpistats/api:onos-apps-dpistats-api",
+    "//apps/dpistats/app:onos-apps-dpistats-app",
+]
+
+onos_app(
+    app_name = "org.onosproject.dpistats",
+    category = "Traffic Engineering",
+    description = "dpistats Network Subsystem extension",
+    included_bundles = BUNDLES,
+    title = "dpistats Network Subsystem",
+    url = "http://onosproject.org",
+)
diff --git a/apps/dpistats/api/BUILD b/apps/dpistats/api/BUILD
new file mode 100644
index 0000000..c6ca52a
--- /dev/null
+++ b/apps/dpistats/api/BUILD
@@ -0,0 +1,9 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON
+
+TEST_DEPS = TEST_ADAPTERS
+
+osgi_jar_with_tests(
+    test_deps = TEST_DEPS,
+    visibility = ["//visibility:public"],
+    deps = COMPILE_DEPS,
+)
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfo.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfo.java
new file mode 100644
index 0000000..afa61f0
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfo.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import java.util.List;
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * DPI statistic information.
+ */
+public class DpiStatInfo {
+    TrafficStatInfo trafficStatistics;
+    List<ProtocolStatInfo> detectedProtos;
+    List<FlowStatInfo> knownFlows;
+    List<FlowStatInfo> unknownFlows;
+
+    /**
+     * Constructor for default DpiStatInfo class.
+     */
+    public DpiStatInfo() {
+        this.trafficStatistics = null;
+        this.detectedProtos = null;
+        this.knownFlows = null;
+        this.unknownFlows = null;
+    }
+
+    /**
+     * Constructor for DpiStatistics class specified with trafficStatInfo.
+     *
+     * @param trafficStatistics traffic statistic information
+     */
+    public DpiStatInfo(TrafficStatInfo trafficStatistics) {
+        this.trafficStatistics = trafficStatistics;
+        this.detectedProtos = null;
+        this.knownFlows = null;
+        this.unknownFlows = null;
+    }
+
+    /**
+     * Constructor for DpiStatistics class specified with trafficStatInfo and detectedProtos.
+     *
+     * @param trafficStatistics traffic statistic information
+     * @param detectedProtos detected protocols statistic information
+     */
+    public DpiStatInfo(TrafficStatInfo trafficStatistics,
+                       List<ProtocolStatInfo> detectedProtos) {
+        this.trafficStatistics = trafficStatistics;
+        this.detectedProtos = detectedProtos;
+        this.knownFlows = null;
+        this.unknownFlows = null;
+    }
+
+    /**
+     * Constructor for DpiStatistics class specified with trafficStatInfo, detectedProtos and knownFlows.
+     *
+     * @param trafficStatistics traffic statistic information
+     * @param detectedProtos detected protocols statistic information
+     * @param knownFlows known flows
+     */
+    public DpiStatInfo(TrafficStatInfo trafficStatistics,
+                       List<ProtocolStatInfo> detectedProtos,
+                       List<FlowStatInfo> knownFlows) {
+        this.trafficStatistics = trafficStatistics;
+        this.detectedProtos = detectedProtos;
+        this.knownFlows = knownFlows;
+        this.unknownFlows = null;
+    }
+
+    /**
+     * Constructor for DpiStatistics class specified with trafficStatInfo, detectedProtos, knownFlows and unknownFlows.
+     *
+     * @param trafficStatistics traffic statistic information
+     * @param detectedProtos detected protocols statistic information
+     * @param knownFlows known flows
+     * @param unknownFlows unknown flows
+     */
+    public DpiStatInfo(TrafficStatInfo trafficStatistics,
+                       List<ProtocolStatInfo> detectedProtos,
+                       List<FlowStatInfo> knownFlows,
+                       List<FlowStatInfo> unknownFlows) {
+        this.trafficStatistics = trafficStatistics;
+        this.detectedProtos = detectedProtos;
+        this.knownFlows = knownFlows;
+        this.unknownFlows = unknownFlows;
+    }
+
+    /**
+     * Returns DPI traffic statistic information.
+     *
+     * @return trafficStatistics
+     */
+    public TrafficStatInfo trafficStatistics() {
+        return trafficStatistics;
+    }
+
+    /**
+     * Returns DPI detected protocols statistic information.
+     *
+     * @return detectedProtos
+     */
+    public List<ProtocolStatInfo> detectedProtos() {
+        return detectedProtos;
+    }
+
+    /**
+     * Returns DPI known flows.
+     *
+     * @return knownFlows
+     */
+    public List<FlowStatInfo> knownFlows() {
+        return knownFlows;
+    }
+
+    /**
+     * Returns DPI unknown flows.
+     *
+     * @return unknownFlows
+     */
+    public List<FlowStatInfo> unknownFlows() {
+        return unknownFlows;
+    }
+
+    /**
+     * Sets the traffic statistic information.
+     *
+     * @param trafficStatistics traffic statistics
+     */
+    public void setTrafficStatistics(TrafficStatInfo trafficStatistics) {
+        this.trafficStatistics = trafficStatistics;
+    }
+
+    /**
+     * Sets the detected protocols statistic information.
+     *
+     * @param detectedProtos detected protocols statistics
+     */
+    public void setDetectedProtos(List<ProtocolStatInfo> detectedProtos) {
+        this.detectedProtos = detectedProtos;
+    }
+
+    /**
+     * Sets the known flows information.
+     *
+     * @param knownFlows known flows
+     */
+    public void setKnownFlows(List<FlowStatInfo> knownFlows) {
+        this.knownFlows = knownFlows;
+    }
+
+    /**
+     * Sets the unknown flows information.
+     *
+     * @param unknownFlows unknown flows
+     */
+    public void setUnknownFlows(List<FlowStatInfo> unknownFlows) {
+        this.unknownFlows = unknownFlows;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("trafficStatistics", trafficStatistics)
+                .add("detectedProtos", detectedProtos)
+                .add("knownFlows", knownFlows)
+                .add("unknownFlows", unknownFlows)
+                .toString();
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfoCodec.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfoCodec.java
new file mode 100644
index 0000000..34e246a
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatInfoCodec.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of encoder for DpiStatInfo codec.
+ */
+public final class DpiStatInfoCodec extends JsonCodec<DpiStatInfo> {
+
+    private static final String TRAFFIC_STATISTICS = "trafficStatistics";
+    private static final String DETECTED_PROTOS = "detectedProtos";
+    private static final String KNOWN_FLOWS = "knownFlows";
+    private static final String UNKNOWN_FLOWS = "unknownFlows";
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public ObjectNode encode(DpiStatInfo dsi, CodecContext context) {
+        checkNotNull(dsi, "DpiStatInfo cannot be null");
+
+        final ObjectNode result = context.mapper().createObjectNode();
+
+        final JsonCodec<TrafficStatInfo> trafficStatInfoCodec =
+                context.codec(TrafficStatInfo.class);
+
+
+        final TrafficStatInfo tsi = dsi.trafficStatistics();
+        if (tsi != null) {
+            final ObjectNode jsonTrafficStatistics = trafficStatInfoCodec.encode(tsi, context);
+            result.set(TRAFFIC_STATISTICS, jsonTrafficStatistics);
+        }
+
+
+        final List<ProtocolStatInfo> psi = dsi.detectedProtos();
+        if (psi != null) {
+            final ArrayNode jsonDetectedProtos = result.putArray(DETECTED_PROTOS);
+            final JsonCodec<ProtocolStatInfo> protocolStatInfoCodec =
+                    context.codec(ProtocolStatInfo.class);
+
+            for (final ProtocolStatInfo protocolStatInfo : psi) {
+                jsonDetectedProtos.add(protocolStatInfoCodec.encode(protocolStatInfo, context));
+            }
+        }
+
+        List<FlowStatInfo> fsi = dsi.knownFlows();
+        if (fsi != null) {
+            final ArrayNode jsonKnownFlows = result.putArray(KNOWN_FLOWS);
+            final JsonCodec<FlowStatInfo> flowStatInfoCodec =
+                    context.codec(FlowStatInfo.class);
+
+            for (final FlowStatInfo flowStatInfo : fsi) {
+                jsonKnownFlows.add(flowStatInfoCodec.encode(flowStatInfo, context));
+            }
+        }
+
+        fsi = dsi.unknownFlows();
+        if (fsi != null) {
+            final ArrayNode jsonUnknownFlows = result.putArray(UNKNOWN_FLOWS);
+            final JsonCodec<FlowStatInfo> flowStatInfoCodec =
+                    context.codec(FlowStatInfo.class);
+
+            for (final FlowStatInfo flowStatInfo : fsi) {
+                jsonUnknownFlows.add(flowStatInfoCodec.encode(flowStatInfo, context));
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    public DpiStatInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        log.debug("trafficStatistics={}, full json={} ", json.get("trafficStatistics"), json);
+        TrafficStatInfo trafficStatInfo = null;
+        ObjectNode tsJson = get(json, TRAFFIC_STATISTICS);
+        if (tsJson != null) {
+            JsonCodec<TrafficStatInfo> trafficStatInfoJsonCodec =
+                    context.codec(TrafficStatInfo.class);
+            trafficStatInfo = trafficStatInfoJsonCodec.decode(tsJson, context);
+        }
+
+        final JsonCodec<ProtocolStatInfo> protocolStatInfoCodec =
+                context.codec(ProtocolStatInfo.class);
+
+        List<ProtocolStatInfo> detectedProtos = new ArrayList<>();
+        JsonNode dpJson = json.get(DETECTED_PROTOS);
+        if (dpJson != null) {
+            IntStream.range(0, dpJson.size())
+                    .forEach(i -> detectedProtos.add(
+                            protocolStatInfoCodec.decode(get(dpJson, i),
+                                                     context)));
+        }
+
+        final JsonCodec<FlowStatInfo> flowStatInfoCodec =
+                context.codec(FlowStatInfo.class);
+
+        List<FlowStatInfo> knownFlows = new ArrayList<>();
+        JsonNode kfJson = json.get(KNOWN_FLOWS);
+        if (kfJson != null) {
+            IntStream.range(0, kfJson.size())
+                    .forEach(i -> knownFlows.add(
+                            flowStatInfoCodec.decode(get(kfJson, i),
+                                                         context)));
+        }
+
+        List<FlowStatInfo> unknownFlows = new ArrayList<>();
+        JsonNode ufJson = json.get(UNKNOWN_FLOWS);
+        if (ufJson != null) {
+            IntStream.range(0, ufJson.size())
+                    .forEach(i -> unknownFlows.add(
+                            flowStatInfoCodec.decode(get(ufJson, i),
+                                                     context)));
+        }
+
+        return new DpiStatInfo(trafficStatInfo,
+                               detectedProtos,
+                               knownFlows,
+                               unknownFlows);
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatistics.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatistics.java
new file mode 100644
index 0000000..e6bb618
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatistics.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * DPI statistics with received time.
+ */
+public class DpiStatistics {
+    private final String receivedTime;
+    private final DpiStatInfo dpiStatInfo;
+
+    /**
+     * Constructor for DpiStatistics class.
+     *
+     * @param receivedTime dpiStatInfo received time
+     * @param dpiStatInfo the dpi statistics info
+     */
+    public DpiStatistics(final String receivedTime, final DpiStatInfo dpiStatInfo) {
+        checkNotNull(receivedTime, "Must specify receivedTime");
+        checkNotNull(dpiStatInfo, "Must specify DpiStatInfo");
+
+        this.receivedTime = receivedTime;
+        this.dpiStatInfo = dpiStatInfo;
+    }
+
+    /**
+     * Returns DPI statistics received time.
+     *
+     * @return receivedTime
+     */
+    public String receivedTime() {
+        return receivedTime;
+    }
+
+    /**
+     * Returns DPI statistics information.
+     *
+     * @return dpiStatInfo
+     */
+    public DpiStatInfo dpiStatInfo() {
+        return dpiStatInfo;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(receivedTime, dpiStatInfo);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final DpiStatistics other = (DpiStatistics) obj;
+        if (!Objects.equals(this.receivedTime, other.receivedTime)) {
+            return false;
+        }
+        if (!Objects.equals(this.dpiStatInfo, other.dpiStatInfo)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("receivedTime", receivedTime)
+                .add("dpiStatInfo", dpiStatInfo)
+                .toString();
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsCodec.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsCodec.java
new file mode 100644
index 0000000..c448277
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsCodec.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of encoder for DpiStatistics codec.
+ */
+public final class DpiStatisticsCodec extends JsonCodec<DpiStatistics> {
+
+    private static final String RECEIVED_TIME = "receivedTime";
+    private static final String DPI_STATISTICS = "dpiStatistics";
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public ObjectNode encode(DpiStatistics ds, CodecContext context) {
+        checkNotNull(ds, "DpiStatistics cannot be null");
+
+        final ObjectNode result = context.mapper().createObjectNode();
+
+        result.put(RECEIVED_TIME, ds.receivedTime());
+
+        final JsonCodec<DpiStatInfo> dpiStatInfoCodec =
+                context.codec(DpiStatInfo.class);
+
+        final ObjectNode jsonDpiStatInfo = dpiStatInfoCodec.encode(ds.dpiStatInfo(), context);
+        result.set(DPI_STATISTICS, jsonDpiStatInfo);
+
+        return result;
+    }
+
+    @Override
+    public DpiStatistics decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        log.debug("receivedTime={}, full json={} ", json.get("receivedTime"), json);
+        JsonNode receivedTimeJson = json.get(RECEIVED_TIME);
+        String receivedTime = receivedTimeJson == null ? "" : receivedTimeJson.asText();
+
+        final JsonCodec<DpiStatInfo> dpiStatInfoCodec =
+                context.codec(DpiStatInfo.class);
+
+        DpiStatInfo dpiStatInfo =
+                dpiStatInfoCodec.decode(get(json, DPI_STATISTICS), context);
+
+        return new DpiStatistics(receivedTime, dpiStatInfo);
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsManagerService.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsManagerService.java
new file mode 100644
index 0000000..5d03536
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/DpiStatisticsManagerService.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import java.util.List;
+
+/**
+ * Service for DPI Statistics Service Manager.
+ */
+public interface DpiStatisticsManagerService {
+    /**
+     * Get the latest DpiStatistics in the Store list.
+     *
+     * @return the DpiStatistics object class or null if not exist
+     */
+    DpiStatistics getDpiStatisticsLatest();
+
+    /**
+     * Get the latest DpiStatistics in the Store list.
+     *
+     * @param topnProtocols detected topn protocols, default = 100
+     * @param topnFlows detected topn known and unknown flows , default = 100
+     *
+     * @return the DpiStatistics object class or null if not exist
+     */
+    DpiStatistics getDpiStatisticsLatest(int topnProtocols, int topnFlows);
+
+    /**
+     * Gets the last N(Max = 100) DpiStatistics in the Store list.
+     *
+     * @param lastN maximum number to fetch
+     * @return the List of DpiStatistics object class
+     */
+    List<DpiStatistics> getDpiStatistics(int lastN);
+
+    /**
+     * Gets the last N(Max = 100) DpiStatistics in the Store list.
+     *
+     * @param lastN latest N entries
+     * @param topnProtocols detected topn protocols, default = 100
+     * @param topnFlows detected topn known and unknown flows , default = 100
+     * @return the List of DpiStatistics object class
+     */
+    List<DpiStatistics> getDpiStatistics(int lastN, int topnProtocols, int topnFlows);
+
+    /**
+     * Get the specified receivedTime DpiStatistics in the Store list.
+     *
+     * @param receivedTime receivedTime string with format "yyyy-MM-dd HH:mm:ss"
+     * @return the DpiStatistics object class or null if not exist
+     */
+    DpiStatistics getDpiStatistics(String receivedTime);
+
+    /**
+     * Get the specified receivedTime DpiStatistics in the Store list.
+     *
+     * @param receivedTime receivedTime string with format "yyyy-MM-dd HH:mm:ss"
+     * @param topnProtocols detected topn protocols, default = 100
+     * @param topnFlows detected topn known and unknown flows , default = 100
+     * @return the DpiStatistics object class or null if not exist
+     */
+    DpiStatistics getDpiStatistics(String receivedTime, int topnProtocols, int topnFlows);
+
+    /**
+     * Adds DpiStatistics at the end of the Store list.
+     *
+     * @param ds statistics to add
+     * @return the added DpiStatistics object class
+     */
+    DpiStatistics addDpiStatistics(DpiStatistics ds);
+
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfo.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfo.java
new file mode 100644
index 0000000..bb409db
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfo.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Flow statistic information.
+ */
+public class FlowStatInfo {
+    String protocol;
+    String hostAName;
+    int hostAPort;
+    String hostBName;
+    int hostBPort;
+    int detectedProtocol;
+    String detectedProtocolName;
+    long packets;
+    long bytes;
+    String hostServerName;
+
+    /**
+     * Constructor for default FlowStatInfo class.
+     */
+    public FlowStatInfo() {
+        protocol = "";
+        hostAName = "::";
+        hostAPort = 0;
+        hostBName = "";
+        hostBPort = 0;
+        detectedProtocol = 0;
+        detectedProtocolName = "";
+        packets = 0;
+        bytes = 0;
+
+        hostServerName = "";
+    }
+
+    /**
+     * Constructor for FlowStatInfo class specified with flow statistic parameters.
+     *
+     * @param protocol protocol
+     * @param hostAName host A name
+     * @param hostAPort host A port
+     * @param hostBName host B name
+     * @param hostBPort host B port
+     * @param detectedProtocol detected protocol
+     * @param detectedProtocolName detected protocol name
+     * @param packets packet count
+     * @param bytes byte count
+     */
+    public FlowStatInfo(String protocol, String hostAName, int hostAPort, String hostBName, int hostBPort,
+                        int detectedProtocol, String detectedProtocolName, long packets, long bytes) {
+        this.protocol = protocol;
+        this.hostAName = hostAName;
+        this.hostAPort = hostAPort;
+        this.hostBName = hostBName;
+        this.hostBPort = hostBPort;
+        this.detectedProtocol = detectedProtocol;
+        this.detectedProtocolName = detectedProtocolName;
+        this.packets = packets;
+        this.bytes = bytes;
+
+        hostServerName = "";
+    }
+
+    /**
+     * Constructor for FlowStatInfo class specified with flow statistic parameters and hostServerName.
+     *
+     * @param protocol protocol
+     * @param hostAName host A name
+     * @param hostAPort host A port
+     * @param hostBName host B name
+     * @param hostBPort host B port
+     * @param detectedProtocol detected protocol
+     * @param detectedProtocolName detected protocol name
+     * @param packets packet count
+     * @param bytes byte count
+     * @param hostServerName host server name
+     */
+    public FlowStatInfo(String protocol, String hostAName, int hostAPort, String hostBName, int hostBPort,
+                        int detectedProtocol, String detectedProtocolName, long packets, long bytes,
+                        String hostServerName) {
+        this(protocol, hostAName, hostAPort, hostBName, hostBPort, detectedProtocol, detectedProtocolName,
+             packets, bytes);
+
+        this.hostServerName = hostServerName;
+    }
+
+    /**
+     * Returns DPI flow protocol.
+     *
+     * @return protocol
+     */
+    public String protocol() {
+        return protocol;
+    }
+
+    /**
+     * Returns DPI flow host A name.
+     *
+     * @return hostAName
+     */
+    public String hostAName() {
+        return hostAName;
+    }
+
+    /**
+     * Returns DPI flow host A port.
+     *
+     * @return hostAPort
+     */
+    public int hostAPort() {
+        return hostAPort;
+    }
+
+
+    /**
+     * Returns DPI flow host B name.
+     *
+     * @return hostBName
+     */
+    public String hostBName() {
+        return hostBName;
+    }
+
+    /**
+     * Returns DPI flow host B Port.
+     *
+     * @return hostBPort
+     */
+    public int hostBPort() {
+        return hostBPort;
+    }
+
+    /**
+     * Returns DPI flow detected protocol.
+     *
+     * @return detectedProtocol
+     */
+    public int detectedProtocol() {
+        return detectedProtocol;
+    }
+
+    /**
+     * Returns DPI flow detected protocol name.
+     *
+     * @return detectedProtocolName
+     */
+    public String detectedProtocolName() {
+        return detectedProtocolName;
+    }
+
+    /**
+     * Returns DPI flow packets.
+     *
+     * @return packets
+     */
+    public long packets() {
+        return packets;
+    }
+
+    /**
+     * Returns DPI flow bytes.
+     *
+     * @return bytes
+     */
+    public long bytes() {
+        return bytes;
+    }
+
+    /**
+     * Returns DPI flow host server name.
+     *
+     * @return hostServerName
+     */
+    public String hostServerName() {
+        return hostServerName;
+    }
+
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public void setHostAName(String hostAName) {
+        this.hostAName = hostAName;
+    }
+
+    public void setHostAPort(int hostAPort) {
+        this.hostAPort = hostAPort;
+    }
+
+    public void setHostBName(String hostBName) {
+        this.hostBName = hostBName;
+    }
+
+    public void setHostBPort(int hostBPort) {
+        this.hostBPort = hostBPort;
+    }
+
+    public void setDetectedProtocol(int detectedProtocol) {
+        this.detectedProtocol = detectedProtocol;
+    }
+
+    public void setDetectedProtocolName(String detectedProtocolName) {
+        this.detectedProtocolName = detectedProtocolName;
+    }
+
+    public void setPackets(long packets) {
+        this.packets = packets;
+    }
+
+    public void setBytes(long bytes) {
+        this.bytes = bytes;
+    }
+
+    public void setHostServerName(String hostServerName) {
+        this.hostServerName = hostServerName;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("protocol", protocol)
+                .add("hostAName", hostAName)
+                .add("hostAPort", hostAPort)
+                .add("hostBName", hostBName)
+                .add("hostBPort", hostBPort)
+                .add("detectedProtocol", detectedProtocol)
+                .add("detectedProtocolName", detectedProtocolName)
+                .add("packets", packets)
+                .add("bytes", bytes)
+                .add("hostServerName", hostServerName)
+                .toString();
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfoCodec.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfoCodec.java
new file mode 100644
index 0000000..57a3bad
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/FlowStatInfoCodec.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of encoder for FlowStatInfo codec.
+ */
+public final class FlowStatInfoCodec extends JsonCodec<FlowStatInfo> {
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public ObjectNode encode(FlowStatInfo fsi, CodecContext context) {
+        checkNotNull(fsi, "FlowStatInfo cannot be null");
+
+        return context.mapper().createObjectNode()
+                .put("protocol", fsi.protocol())
+                .put("hostAName", fsi.hostAName())
+                .put("hostAPort", fsi.hostAPort())
+                .put("hostBName", fsi.hostBName())
+                .put("hostBPort", fsi.hostBPort())
+                .put("detectedProtocol", fsi.detectedProtocol())
+                .put("detectedProtocolName", fsi.detectedProtocolName())
+                .put("packets", fsi.packets())
+                .put("bytes", fsi.bytes())
+                .put("hostServerName", fsi.hostServerName());
+    }
+
+    @Override
+    public FlowStatInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        log.debug("protocol={}, full json={} ", json.get("protocol"), json);
+        final String protocol = json.get("protocol").asText();
+        final String hostAName = json.get("hostAName").asText();
+        final int hostAPort = json.get("hostAPort").asInt();
+        final String hostBName = json.get("hostBName").asText();
+        final int hostBPort = json.get("hostBPort").asInt();
+        final int detectedProtocol = json.get("detectedProtocol").asInt();
+        final String detectedProtocolName = json.get("detectedProtocolName").asText();
+        final long packets = json.get("packets").asLong();
+        final long bytes = json.get("bytes").asLong();
+        final String hostServerName = json.get("hostServerName").asText();
+
+        return new FlowStatInfo(protocol,
+                                hostAName, hostAPort, hostBName, hostBPort,
+                                detectedProtocol, detectedProtocolName,
+                                packets, bytes, hostServerName);
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfo.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfo.java
new file mode 100644
index 0000000..22ac9a1
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Protocol statistic information.
+ */
+public class ProtocolStatInfo {
+    String name;
+    String breed;
+    long packets;
+    long bytes;
+    int flows;
+
+    /**
+     * Constructor for default ProtocolStatInfo class.
+     */
+    public ProtocolStatInfo() {
+        name = "";
+        breed = "";
+        packets = 0;
+        bytes = 0;
+        flows = 0;
+    }
+
+    /**
+     * Constructor for ProtocolStatInfo class specified with protocol statistic parameters.
+     *
+     * @param name protocol name
+     * @param breed protocol breed
+     * @param packets protocol packets
+     * @param bytes protocol bytes
+     * @param flows protocol flows
+     */
+    public ProtocolStatInfo(String name, String breed, long packets, long bytes, int flows) {
+        this.name = name;
+        this.breed = breed;
+        this.packets = packets;
+        this.bytes = bytes;
+        this.flows = flows;
+    }
+
+    /**
+     * Returns DPI protocol name.
+     *
+     * @return name
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Returns DPI protocol breed.
+     *
+     * @return breed
+     */
+    public String breed() {
+        return breed;
+    }
+
+    /**
+     * Returns DPI protocol packets.
+     *
+     * @return packets
+     */
+    public long packets() {
+        return packets;
+    }
+
+    /**
+     * Returns DPI protocol bytes.
+     *
+     * @return bytes
+     */
+    public long bytes() {
+        return bytes;
+    }
+
+    /**
+     * Returns DPI protocol flows.
+     *
+     * @return flows
+     */
+    public int flows() {
+        return flows;
+    }
+
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setBreed(String breed) {
+        this.breed = breed;
+    }
+
+    public void setPackets(long packets) {
+        this.packets = packets;
+    }
+
+    public void setBytes(long bytes) {
+        this.bytes = bytes;
+    }
+
+    public void setFlows(int flows) {
+        this.flows = flows;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("name", name)
+                .add("breed", breed)
+                .add("packets", packets)
+                .add("bytes", bytes)
+                .add("flows", flows)
+                .toString();
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfoCodec.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfoCodec.java
new file mode 100644
index 0000000..9469f6c
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/ProtocolStatInfoCodec.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of encoder for ProtocolStatInfo codec.
+ */
+public final class ProtocolStatInfoCodec extends JsonCodec<ProtocolStatInfo> {
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public ObjectNode encode(ProtocolStatInfo psi, CodecContext context) {
+        checkNotNull(psi, "ProtocolStatInfo cannot be null");
+
+        return context.mapper().createObjectNode()
+                .put("name", psi.name())
+                .put("breed", psi.breed())
+                .put("packets", psi.packets())
+                .put("bytes", psi.bytes())
+                .put("flows", psi.flows());
+    }
+
+    @Override
+    public ProtocolStatInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        log.debug("name={}, full json={} ", json.get("name"), json);
+        final String name = json.get("name").asText();
+        final String breed = json.get("breed").asText();
+        final long packets = json.get("packets").asLong();
+        final long bytes = json.get("bytes").asLong();
+        final int flows = json.get("flows").asInt();
+
+        return new ProtocolStatInfo(name, breed, packets, bytes, flows);
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfo.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfo.java
new file mode 100644
index 0000000..57cce19
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfo.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Traffic statistic information.
+ */
+public class TrafficStatInfo {
+    long ethernetBytes;
+    long discardedBytes;
+    long ipPackets;
+    long totalPackets;
+    long ipBytes;
+    int avgPktSize;
+    int uniqueFlows;
+    long tcpPackets;
+    long udpPackets;
+
+    double dpiThroughputPps;
+    double dpiThroughputBps;
+    double trafficThroughputPps;
+    double trafficThroughputBps;
+    double trafficDurationSec;
+    int guessedFlowProtos;
+
+    static final String PPS_STRING = "pps";
+    static final String BPS_STRING = "bps";
+    static final String SEC_STRING = "sec";
+
+    /**
+     * Constructor for default TrafficStatInfo class.
+     */
+    public TrafficStatInfo() {
+        ethernetBytes = 0;
+        discardedBytes = 0;
+        ipPackets = 0;
+        totalPackets = 0;
+        ipBytes = 0;
+        avgPktSize = 0;
+        uniqueFlows = 0;
+        tcpPackets = 0;
+        udpPackets = 0;
+        dpiThroughputPps = 0;
+        dpiThroughputBps = 0;
+        trafficThroughputPps = 0;
+        trafficThroughputBps = 0;
+        trafficDurationSec = 0;
+        guessedFlowProtos = 0;
+    }
+
+    /**
+     * Constructor for TrafficStatInfo class specified with traffic statistic parameters.
+     *
+     * @param ethernetBytes ethernet byte count
+     * @param discardedBytes discarded byte count
+     * @param ipPackets IP packet count
+     * @param totalPackets total packet count
+     * @param ipBytes total IP byte count
+     * @param avgPktSize average packet size
+     * @param uniqueFlows unique flows count
+     * @param tcpPackets TCP packet count
+     * @param udpPackets UDP packet count
+     * @param trafficThroughputPps traffic throughput PPS
+     * @param trafficThroughputBps traffic throughput BPS
+     * @param dpiThroughputPps DPI throughput PPS
+     * @param dpiThroughputBps DPI throughput BPS
+     * @param trafficDurationSec traffic duration in seconds
+     * @param guessedFlowProtos guess flow protocols
+     */
+    public TrafficStatInfo(long ethernetBytes, long discardedBytes, long ipPackets, long totalPackets,
+                           long ipBytes, int avgPktSize, int uniqueFlows, long tcpPackets, long udpPackets,
+                           double dpiThroughputPps, double dpiThroughputBps,
+                           double trafficThroughputPps, double trafficThroughputBps,
+                           double trafficDurationSec, int guessedFlowProtos) {
+        this.ethernetBytes = ethernetBytes;
+        this.discardedBytes = discardedBytes;
+        this.ipPackets = ipPackets;
+        this.totalPackets = totalPackets;
+        this.ipBytes = ipBytes;
+        this.avgPktSize = avgPktSize;
+        this.uniqueFlows = uniqueFlows;
+        this.tcpPackets = tcpPackets;
+        this.udpPackets = udpPackets;
+        this.dpiThroughputPps = dpiThroughputPps;
+        this.dpiThroughputBps = dpiThroughputBps;
+        this.trafficThroughputPps = trafficThroughputPps;
+        this.trafficThroughputBps = trafficThroughputBps;
+        this.trafficDurationSec = trafficDurationSec;
+        this.guessedFlowProtos = guessedFlowProtos;
+    }
+
+    /**
+     * Returns DPI traffic ethernet bytes.
+     *
+     * @return ethernetBytes
+     */
+    public long ethernetBytes() {
+        return ethernetBytes;
+    }
+
+    /**
+     * Returns DPI traffic discarded bytes.
+     *
+     * @return discardedBytes
+     */
+    public long discardedBytes() {
+        return discardedBytes;
+    }
+
+    /**
+     * Returns DPI traffic ip packets.
+     *
+     * @return ipPackets
+     */
+    public long ipPackets() {
+        return ipPackets;
+    }
+
+    /**
+     * Returns DPI traffic total packets.
+     *
+     * @return totalPackets
+     */
+    public long totalPackets() {
+        return totalPackets;
+    }
+
+    /**
+     * Returns DPI traffic ip bytes.
+     *
+     * @return ipBytes
+     */
+    public long ipBytes() {
+        return ipBytes;
+    }
+
+    /**
+     * Returns DPI traffic average packet size.
+     *
+     * @return avgPktSize
+     */
+    public int avgPktSize() {
+        return avgPktSize;
+    }
+
+    /**
+     * Returns DPI traffic the number of unique flows.
+     *
+     * @return uniqueFlows
+     */
+    public int uniqueFlows() {
+        return uniqueFlows;
+    }
+
+    /**
+     * Returns DPI traffic TCP packets.
+     *
+     * @return tcpPackets
+     */
+    public long tcpPackets() {
+        return tcpPackets;
+    }
+
+    /**
+     * Returns DPI traffic UDP packets.
+     *
+     * @return udpPackets
+     */
+    public long udpPackets() {
+        return udpPackets;
+    }
+
+    /**
+     * Returns DPI traffic throughput Pps(Packet per second).
+     *
+     * @return dpiThroughputPps
+     */
+    public double dpiThroughputPps() {
+        return dpiThroughputPps;
+    }
+
+    /**
+     * Returns DPI traffic throughput Bps(Byte per second).
+     *
+     * @return dpiThroughputBps
+     */
+    public double dpiThroughputBps() {
+        return dpiThroughputBps;
+    }
+
+    /**
+     * Returns total traffic throughput Pps(Packet per second).
+     *
+     * @return trafficThroughputPps
+     */
+    public double trafficThroughputPps() {
+        return trafficThroughputPps;
+    }
+
+    /**
+     * Returns total traffic throughput Bps(Byte per second).
+     *
+     * @return trafficThroughputBps
+     */
+    public double trafficThroughputBps() {
+        return trafficThroughputBps;
+    }
+
+    /**
+     * Returns DPI traffic duration second.
+     *
+     * @return trafficDurationSec
+     */
+    public double trafficDurationSec() {
+        return trafficDurationSec;
+    }
+
+    /**
+     * Returns DPI traffic the number of guessed flow protocols.
+     *
+     * @return guessedFlowProtos
+     */
+    public int guessedFlowProtos() {
+        return guessedFlowProtos;
+    }
+
+
+    public void setEthernetBytes(long ethernetBytes) {
+        this.ethernetBytes = ethernetBytes;
+    }
+
+    public void setDiscardedBytes(long discardedBytes) {
+        this.discardedBytes = discardedBytes;
+    }
+
+    public void setIpPackets(long ipPackets) {
+        this.ipPackets = ipPackets;
+    }
+
+    public void setTotalPackets(long totalPackets) {
+        this.totalPackets = totalPackets;
+    }
+
+    public void setIpBytes(long ipBytes) {
+        this.ipBytes = ipBytes;
+    }
+
+    public void setAvgPktSize(int avgPktSize) {
+        this.avgPktSize = avgPktSize;
+    }
+
+    public void setUniqueFlows(int uniqueFlows) {
+        this.uniqueFlows = uniqueFlows;
+    }
+
+    public void setTcpPackets(long tcpPackets) {
+        this.tcpPackets = tcpPackets;
+    }
+
+    public void setUdpPackets(long udpPackets) {
+        this.udpPackets = udpPackets;
+    }
+
+    public void setDpiThroughputPps(double dpiThroughputPps) {
+        this.dpiThroughputPps = dpiThroughputPps;
+    }
+
+    public void setDpiThroughputBps(double dpiThroughputBps) {
+        this.dpiThroughputBps = dpiThroughputBps;
+    }
+
+    public void setTrafficThroughputPps(double trafficThroughputPps) {
+        this.trafficThroughputPps = trafficThroughputPps;
+    }
+
+    public void setTrafficThroughputBps(double trafficThroughputBps) {
+        this.trafficThroughputBps = trafficThroughputBps;
+    }
+
+    public void setTrafficDurationSec(double trafficDurationSec) {
+        this.trafficDurationSec = trafficDurationSec;
+    }
+
+    public void setGuessedFlowProtos(int guessedFlowProtos) {
+        this.guessedFlowProtos = guessedFlowProtos;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("ethernetBytes", ethernetBytes)
+                .add("discardedBytes", discardedBytes)
+                .add("ipPackets", ipPackets)
+                .add("totalPackets", totalPackets)
+                .add("ipBytes", ipBytes)
+                .add("avgPktSize", avgPktSize)
+                .add("uniqueFlows", uniqueFlows)
+                .add("tcpPackets", tcpPackets)
+                .add("udpPackets", udpPackets)
+                .add("dpiThroughputPps", dpiThroughputPps + " " + PPS_STRING)
+                .add("dpiThroughputBps", dpiThroughputBps + " " + BPS_STRING)
+                .add("trafficThroughputPps", trafficThroughputPps + " " + PPS_STRING)
+                .add("trafficThroughputBps", trafficThroughputBps + " " + BPS_STRING)
+                .add("trafficDurationSec", trafficDurationSec + " " + SEC_STRING)
+                .add("guessedFlowProtos", guessedFlowProtos)
+                .toString();
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfoCodec.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfoCodec.java
new file mode 100644
index 0000000..dafe523
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/TrafficStatInfoCodec.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.codec.CodecContext;
+import org.onosproject.codec.JsonCodec;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of encoder for TrafficStatInfo codec.
+ */
+public final class TrafficStatInfoCodec extends JsonCodec<TrafficStatInfo> {
+
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public ObjectNode encode(TrafficStatInfo tsi, CodecContext context) {
+        checkNotNull(tsi, "TrafficStatInfo cannot be null");
+
+        return context.mapper().createObjectNode()
+                .put("ethernetBytes", tsi.ethernetBytes())
+                .put("discardedBytes", tsi.discardedBytes())
+                .put("ipPackets", tsi.ipPackets())
+                .put("totalPackets", tsi.totalPackets())
+                .put("ipBytes", tsi.ipBytes())
+                .put("avgPktSize", tsi.avgPktSize())
+                .put("uniqueFlows", tsi.uniqueFlows())
+                .put("tcpPackets", tsi.tcpPackets())
+                .put("udpPackets", tsi.udpPackets())
+                .put("dpiThroughputPps", tsi.dpiThroughputPps())
+                .put("dpiThroughputBps", tsi.dpiThroughputBps())
+                .put("trafficThroughputPps", tsi.trafficThroughputPps())
+                .put("trafficThroughputBps", tsi.trafficThroughputBps())
+                .put("trafficDurationSec", tsi.trafficDurationSec())
+                .put("guessedFlowProtos", tsi.guessedFlowProtos());
+    }
+
+    @Override
+    public TrafficStatInfo decode(ObjectNode json, CodecContext context) {
+        if (json == null || !json.isObject()) {
+            return null;
+        }
+
+        log.debug("ethernetBytes={}, full json={} ", json.get("ethernetBytes"), json);
+        final Long ethernetBytes = json.get("ethernetBytes").asLong();
+        final Long discardedBytes = json.get("discardedBytes").asLong();
+        final Long ipPackets = json.get("ipPackets").asLong();
+        final Long totalPackets = json.get("totalPackets").asLong();
+        final Long ipBytes = json.get("ipBytes").asLong();
+        final int avgPktSize = json.get("avgPktSize").asInt();
+        final int uniqueFlows = json.get("uniqueFlows").asInt();
+        final Long tcpPackets = json.get("tcpPackets").asLong();
+        final Long udpPackets = json.get("udpPackets").asLong();
+        final double dpiThroughputPps = json.get("dpiThroughputPps").asDouble();
+        final double dpiThroughputBps = json.get("dpiThroughputBps").asDouble();
+        final double trafficThroughputPps = json.get("trafficThroughputPps").asDouble();
+        final double trafficThroughputBps = json.get("trafficThroughputBps").asDouble();
+        final double trafficDurationSec = json.get("trafficDurationSec").asDouble();
+        final int guessedFlowProtos = json.get("guessedFlowProtos").asInt();
+
+        return new TrafficStatInfo(ethernetBytes,
+                                   discardedBytes,
+                                   ipPackets, totalPackets,
+                                   ipBytes, avgPktSize,
+                                   uniqueFlows,
+                                   tcpPackets, udpPackets,
+                                   dpiThroughputPps, dpiThroughputBps,
+                                   trafficThroughputPps, trafficThroughputBps,
+                                   trafficDurationSec,
+                                   guessedFlowProtos);
+    }
+}
diff --git a/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/package-info.java b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/package-info.java
new file mode 100644
index 0000000..3f769b8
--- /dev/null
+++ b/apps/dpistats/api/src/main/java/org/onosproject/incubator/net/dpi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Subsystem for dpi statistics service.
+ */
+package org.onosproject.incubator.net.dpi;
\ No newline at end of file
diff --git a/apps/dpistats/app/BUILD b/apps/dpistats/app/BUILD
new file mode 100644
index 0000000..f075daa
--- /dev/null
+++ b/apps/dpistats/app/BUILD
@@ -0,0 +1,16 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + REST + CLI + [
+    "//apps/dpistats/api:onos-apps-dpistats-api",
+]
+
+TEST_DEPS = TEST_ADAPTERS
+
+osgi_jar_with_tests(
+    api_description = "REST API for DPI Stats",
+    api_package = "org.onosproject.incubator.net.dpi",
+    api_title = "DPI Stats",
+    api_version = "1.0",
+    karaf_command_packages = ["org.onosproject.incubator.net.dpi"],
+    test_deps = TEST_DEPS,
+    visibility = ["//visibility:public"],
+    deps = COMPILE_DEPS,
+)
diff --git a/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpiStatisticsManager.java b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpiStatisticsManager.java
new file mode 100644
index 0000000..f1fb721
--- /dev/null
+++ b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpiStatisticsManager.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.incubator.net.dpi.DpiStatInfo;
+import org.onosproject.incubator.net.dpi.DpiStatistics;
+import org.onosproject.incubator.net.dpi.DpiStatisticsManagerService;
+import org.onosproject.incubator.net.dpi.FlowStatInfo;
+import org.onosproject.incubator.net.dpi.ProtocolStatInfo;
+import org.onosproject.incubator.net.dpi.TrafficStatInfo;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.SortedMap;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static java.lang.Thread.sleep;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * DPI Statistics Manager.
+ */
+@Component(immediate = true, service = DpiStatisticsManagerService.class)
+public class DpiStatisticsManager implements DpiStatisticsManagerService {
+
+    private ServerSocket serverSocket;
+    private static int port = 11990; // socket server listening port
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected CoreService coreService;
+
+    private ApplicationId appId;
+
+    private final ExecutorService dpiListenerThread =
+            Executors.newSingleThreadExecutor(groupedThreads("onos/apps/dpi", "dpi-listener"));
+
+    DpiStatisticsListener dpiStatisticsListener = null;
+
+    // 31*2(month)*24(hour)*3600(second)/5(second)
+    private static final int MAX_DPI_STATISTICS_ENTRY = 1071360;
+
+    private SortedMap<String, DpiStatistics> dpiStatisticsMap =
+            new TreeMap<>(new MapComparator());
+
+    private long convertTimeToLong(String timeString) {
+        long timeLong = 0;
+
+        try {
+            // Time format: yyyy-MM-dd HH:mm:ss, Time Zone: GMT
+            SimpleDateFormat df = new SimpleDateFormat(DATE_FMT, Locale.KOREA);
+            df.setTimeZone(TimeZone.getTimeZone(TIME_ZONE));
+
+             timeLong = df.parse(timeString).getTime();
+        } catch (ParseException e) {
+            log.error("Time parse error! Exception={}", e.toString());
+        }
+
+        return timeLong;
+    }
+
+    private static final String DATE_FMT = "yyyy-MM-dd HH:mm:ss";
+    private static final String TIME_ZONE = "GMT";
+
+    public static final int MAX_DPI_STATISTICS_REQUEST = 100;
+    public static final int MAX_DPI_STATISTICS_TOPN = 100;
+
+    @Activate
+    public void activate(ComponentContext context) {
+        appId = coreService.registerApplication("org.onosproject.dpi");
+
+//        registerCodec(DpiStatistics.class, new DpiStatisticsCodec());
+//        registerCodec(DpiStatInfo.class, new DpiStatInfoCodec());
+//        registerCodec(TrafficStatInfo.class, new TrafficStatInfoCodec());
+//        registerCodec(ProtocolStatInfo.class, new ProtocolStatInfoCodec());
+//        registerCodec(FlowStatInfo.class, new FlowStatInfoCodec());
+
+        dpiStatisticsListener = new DpiStatisticsListener();
+        dpiListenerThread.execute(dpiStatisticsListener);
+
+        log.info("Started", appId.id());
+    }
+
+    @Deactivate
+    public void deactivate() {
+        log.info("Deactivated...");
+        dpiListenerThread.shutdownNow();
+        log.info("Stopped");
+    }
+
+    @Override
+    public DpiStatistics getDpiStatisticsLatest() {
+        if (dpiStatisticsMap.size() > 0) {
+            return dpiStatisticsMap.get(dpiStatisticsMap.firstKey());
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public DpiStatistics getDpiStatisticsLatest(int topnProtocols, int topnFlows) {
+        DpiStatistics ds, topnDs;
+
+        ds = getDpiStatisticsLatest();
+        topnDs = processTopn(ds, topnProtocols, topnFlows);
+
+        return topnDs;
+    }
+
+    @Override
+    public List<DpiStatistics> getDpiStatistics(int lastN) {
+        List<DpiStatistics> dsList = new ArrayList<>();
+        DpiStatistics ds;
+
+        if (lastN > MAX_DPI_STATISTICS_REQUEST) {
+            lastN = MAX_DPI_STATISTICS_REQUEST;
+        }
+
+        SortedMap tempMap = new TreeMap(new MapComparator());
+        tempMap.putAll(dpiStatisticsMap);
+
+        for (int i = 0; i < lastN && i < tempMap.size(); i++) {
+            ds = (DpiStatistics) tempMap.get(tempMap.firstKey());
+            dsList.add(i, new DpiStatistics(ds.receivedTime(), ds.dpiStatInfo()));
+
+            tempMap.remove(tempMap.firstKey());
+        }
+
+        return dsList;
+    }
+
+    @Override
+    public List<DpiStatistics> getDpiStatistics(int lastN, int topnProtocols, int topnFlows) {
+        List<DpiStatistics> dsList;
+        List<DpiStatistics> topnDsList = new ArrayList<>();
+        DpiStatistics ds, topnDs;
+
+        dsList = getDpiStatistics(lastN);
+        for (int i = 0; i < dsList.size(); i++) {
+            ds = dsList.get(i);
+            topnDs = processTopn(ds, topnProtocols, topnFlows);
+            topnDsList.add(i, topnDs);
+        }
+
+        return topnDsList;
+    }
+
+    @Override
+    public DpiStatistics getDpiStatistics(String receivedTime) {
+        DpiStatistics ds;
+
+        if (receivedTime == null) {
+            return null;
+        }
+
+        if (!dpiStatisticsMap.containsKey(receivedTime)) {
+            return null;
+        }
+
+        ds = dpiStatisticsMap.get(receivedTime);
+
+        return ds;
+    }
+
+    @Override
+    public DpiStatistics getDpiStatistics(String receivedTime, int topnProtocols, int topnFlows) {
+        DpiStatistics ds, topnDs;
+
+        ds = getDpiStatistics(receivedTime);
+
+        topnDs = processTopn(ds, topnProtocols, topnFlows);
+
+        return topnDs;
+    }
+
+    @Override
+    public DpiStatistics addDpiStatistics(DpiStatistics ds) {
+        if (ds == null) {
+            return ds;
+        }
+
+        // check the time. The firstKey is lastTime because of descending sorted order
+        if (dpiStatisticsMap.size() > 0) {
+            String lastTime = dpiStatisticsMap.get(dpiStatisticsMap.firstKey()).receivedTime();
+            String inputTime = ds.receivedTime();
+
+            long lastTimeLong = convertTimeToLong(lastTime);
+            long inputTimeLong = convertTimeToLong(inputTime);
+
+            if (lastTimeLong >= inputTimeLong) {
+                return null;
+            }
+        }
+
+        if (dpiStatisticsMap.size() >= MAX_DPI_STATISTICS_ENTRY) {
+            // remove the last (oldest) entry
+            dpiStatisticsMap.remove(dpiStatisticsMap.lastKey());
+        }
+
+        if (dpiStatisticsMap.containsKey(ds.receivedTime())) {
+            log.warn("addDpiStatistics(), {} dpiStatistics is already existing!",
+                      ds.receivedTime());
+            return null;
+        }
+
+        dpiStatisticsMap.put(ds.receivedTime(), ds);
+        log.debug("addDpiStatistics: dpiResultJson data[time={}] is added " +
+                          "into DpiStatisticsMap size={}.",
+                  ds.receivedTime(), dpiStatisticsMap.size());
+
+        return ds;
+    }
+
+    private class MapComparator implements Comparator<String> {
+        @Override
+        public int compare(String rt1, String rt2) {
+            long rt1Long = convertTimeToLong(rt1);
+            long rt2Long = convertTimeToLong(rt2);
+
+            // Descending order
+            if (rt1Long > rt2Long) {
+                return -1;
+            } else if (rt1Long < rt2Long) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    private class ProtocolComparator implements Comparator<ProtocolStatInfo> {
+        @Override
+        public int compare(ProtocolStatInfo p1, ProtocolStatInfo p2) {
+            //Descending order
+            if (p1.bytes() > p2.bytes()) {
+                return -1;
+            } else if (p1.bytes() < p2.bytes()) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    private class FlowComparator implements Comparator<FlowStatInfo> {
+        @Override
+        public int compare(FlowStatInfo f1, FlowStatInfo f2) {
+            // Descending order
+            if (f1.bytes() > f2.bytes()) {
+                return -1;
+            } else if (f1.bytes() < f2.bytes()) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+    private DpiStatistics processTopn(DpiStatistics ds, int topnProtocols, int topnFlows) {
+        if (ds == null) {
+            return null;
+        }
+
+        if (topnProtocols <= 0) {
+            // displays all entries
+            topnProtocols = 0;
+        } else if (topnProtocols > MAX_DPI_STATISTICS_TOPN) {
+            topnProtocols = MAX_DPI_STATISTICS_TOPN;
+        }
+
+        if (topnFlows <= 0) {
+            // displays all entries
+            topnFlows = 0;
+        } else if (topnFlows > MAX_DPI_STATISTICS_TOPN) {
+            topnFlows = MAX_DPI_STATISTICS_TOPN;
+        }
+
+        if (topnProtocols == 0 && topnFlows == 0) {
+            return ds;
+        }
+
+        TrafficStatInfo tsi = ds.dpiStatInfo().trafficStatistics();
+        List<ProtocolStatInfo> psiList;
+        List<FlowStatInfo> kfList;
+        List<FlowStatInfo> ufList;
+
+        List<ProtocolStatInfo> pList = ds.dpiStatInfo().detectedProtos();
+        Collections.sort(pList, new ProtocolComparator());
+        if (topnProtocols > 0 && topnProtocols < pList.size()) {
+            psiList = pList.subList(0, topnProtocols);
+        } else {
+            psiList = pList;
+        }
+
+
+        List<FlowStatInfo> fList = ds.dpiStatInfo().knownFlows();
+        Collections.sort(fList, new FlowComparator());
+        if (topnFlows > 0 && topnFlows < fList.size()) {
+            kfList = fList.subList(0, topnFlows);
+        } else {
+            kfList = fList;
+        }
+
+        fList = ds.dpiStatInfo().unknownFlows();
+        Collections.sort(fList, new FlowComparator());
+        if (topnFlows > 0 && topnFlows < fList.size()) {
+            ufList = fList.subList(0, topnFlows);
+        } else {
+            ufList = fList;
+        }
+
+        DpiStatInfo dsi = new DpiStatInfo();
+        dsi.setTrafficStatistics(tsi);
+        dsi.setDetectedProtos(psiList);
+        dsi.setKnownFlows(kfList);
+        dsi.setUnknownFlows(ufList);
+
+        DpiStatistics retDs = new DpiStatistics(ds.receivedTime(), dsi);
+        return retDs;
+    }
+
+    /**
+     * Receiving DPI Statistics result thread.
+     */
+    private class DpiStatisticsListener implements Runnable {
+        Socket clientSocket = null;
+        BufferedReader in = null;
+        PrintWriter out = null;
+
+        String resultJsonString = null;
+
+        static final int MAX_SLEEP_COUNT = 10;
+        int sleepCount = 0;
+
+        @Override
+        public void run() {
+            log.info("DpiStatisticsListener: Receiving thread started...");
+            receiveDpiResult();
+        }
+
+        private void receiveDpiResult() {
+            try {
+                serverSocket = new ServerSocket(port);
+            } catch (Exception e) {
+                log.error("DpiStatisticsListener: ServerSocket listening error from port={} in localhost, exception={}",
+                          port, e.toString());
+                return;
+            }
+
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    if (clientSocket == null) {
+                        log.info("DpiStatisticsListener: Waiting for accepting from dpi client...");
+                        clientSocket = serverSocket.accept();
+                        log.info("DpiStatisticsListener: Accepted from dpi client={}",
+                                 clientSocket.getRemoteSocketAddress().toString());
+
+                        in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
+                        out = new PrintWriter(clientSocket.getOutputStream(), true); // For disconnecting check!
+
+                        resultJsonString = null;
+                    }
+
+                    sleepCount = 0;
+                    while (!in.ready()) {
+                        sleep(1000); // sleep one second.
+                        if (out.checkError() || ++sleepCount >= MAX_SLEEP_COUNT) {
+                            log.debug("DpiStatisticsListener: server and socket connect is lost...");
+                            in.close();
+                            in = null;
+                            out.close();
+                            out = null;
+                            clientSocket.close();
+                            clientSocket = null;
+
+                            break;
+                        }
+                    }
+
+                    if (in != null) {
+                        resultJsonString = in.readLine();
+
+                        // process the result
+                        log.trace("DpiStatisticsListener: resultJsonString={}", resultJsonString);
+                        processResultJson(resultJsonString);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("DpiStatisticsListener: Exception = {}", e.toString());
+                return;
+            } finally {
+                try {
+                    if (serverSocket != null) {
+                        if (clientSocket != null) {
+                            if (in != null) {
+                                in.close();
+                            }
+                            if (out != null) {
+                                out.close();
+                            }
+                            clientSocket.close();
+                            //log.debug("DpiResultListener: stop(): Socket close() is done...");
+                        }
+                        serverSocket.close();
+                        //log.debug("DpiResultListener: stop(): Server close() is done...");
+                    }
+                } catch (Exception e) {
+                    log.error("DpiStatisticsListener: stop(): Server Socket closing error, exception={}",
+                            e.toString());
+                }
+            }
+        }
+
+        private void processResultJson(String resultJsonString) {
+            Date tr = new Date(System.currentTimeMillis());
+            SimpleDateFormat df = new SimpleDateFormat(DATE_FMT, Locale.KOREA);
+            df.setTimeZone(TimeZone.getTimeZone(TIME_ZONE));
+
+            String curReceivedTime = new String(df.format(tr));
+            String curResultJson = new String(resultJsonString);
+
+            DpiStatInfo dpiStatInfo;
+            ObjectMapper mapper = new ObjectMapper();
+            try {
+                dpiStatInfo = mapper.readValue(curResultJson, DpiStatInfo.class);
+            } catch (IOException e) {
+                log.error("DpiStatisticsListener: ObjectMapper Exception = {}", e.toString());
+                return;
+            }
+
+            DpiStatistics dpiStatistics = new DpiStatistics(curReceivedTime, dpiStatInfo);
+
+            addDpiStatistics(dpiStatistics);
+        }
+    }
+}
diff --git a/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisListCommand.java b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisListCommand.java
new file mode 100644
index 0000000..33b8396
--- /dev/null
+++ b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisListCommand.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi.impl;
+
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.action.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.incubator.net.dpi.DpiStatInfo;
+import org.onosproject.incubator.net.dpi.DpiStatistics;
+import org.onosproject.incubator.net.dpi.DpiStatisticsManagerService;
+import org.onosproject.incubator.net.dpi.FlowStatInfo;
+import org.onosproject.incubator.net.dpi.ProtocolStatInfo;
+import org.onosproject.incubator.net.dpi.TrafficStatInfo;
+
+import java.util.List;
+
+import static java.lang.Thread.sleep;
+
+/**
+ * Fetches DPI statistics list.
+ */
+@Service
+@Command(scope = "onos", name = "dpis",
+        description = "Fetches the DPI result entries that is received from DPI engine server")
+public class DpisListCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "receivedTime", description = "received time: format 'yyyy-MM-dd HH:mm:ss', "
+            + "ex:'2016-08-30 10:31:20', default = null(latest time)",
+            required = false, multiValued = false)
+    String receivedTime = null; // default is latest time
+
+    @Option(name = "-l", aliases = "--latest",
+            description = "Show the latest dpi stats result entry",
+            required = false, multiValued = false)
+    boolean latest = true; // default
+
+    @Option(name = "-d", aliases = "--detectedProtocols",
+            description = "Show the detected protocols only for each statistic entry",
+            required = false, multiValued = false)
+    boolean dProtocols = false; // default
+
+    @Option(name = "-k", aliases = "--knownFlows",
+            description = "Show the known flows only for each statistic entry",
+            required = false, multiValued = false)
+    boolean kFlows = false; // default
+
+    @Option(name = "-u", aliases = "--unknownFlows",
+            description = "Show the unknown flows only for each statistic entry",
+            required = false, multiValued = false)
+    boolean uFlows = false; // default
+
+    @Option(name = "-a", aliases = "--all",
+            description = "Show the all statistics information in detail for each statistic entry",
+            required = false, multiValued = false)
+    boolean all = false; // default is traffic statistics only display
+
+    @Option(name = "-p", aliases = "--permanent",
+            description = "Show the latest dpi stats result entry permanently at 5 second, use Ctrl+C for quitting",
+            required = false, multiValued = false)
+    boolean permanent = false;
+
+    @Option(name = "-n", aliases = "--lastn",
+            description = "Show the last N Dpi stats result entries, MAX_REQUEST_ENTRY = 100",
+            required = false, multiValued = false)
+    String lastn = null;
+
+    @Option(name = "-P", aliases = "--topnProtocols",
+            description = "Show the topn detected Protocol result entries, MAX_PROTOCOLS_TOPN = 100",
+            required = false, multiValued = false)
+    String topnProtocols = null;
+
+    @Option(name = "-F", aliases = "--topnFlows",
+            description = "Show the topn known and unknown Flows result entries, MAX_FLOWS_TOPN = 100",
+            required = false, multiValued = false)
+    String topnFlows = null;
+
+    private static final int DEFAULT_LASTN = 100;
+    private static final int DEFAULT_TOPNP = -1;
+    private static final int DEFAULT_TOPNF = -1;
+    private static final String NO_DPI_ENTRY_ERROR_MSG = "No DPI statistic entry,"
+                                                        + " please check remote DPI engine is running";
+    private static final String RECEIVED_TIME_ERROR_MSG = NO_DPI_ENTRY_ERROR_MSG + "\n"
+                    + " or correct receivedTime format: 'yyyy-MM-dd HH:mm:ss', ex:'2016-08-30 10:31:20'";
+
+    @Override
+    protected void doExecute() {
+        DpiStatisticsManagerService dsms = get(DpiStatisticsManagerService.class);
+
+        DpiStatistics ds;
+
+        int topnP = DEFAULT_TOPNP;
+        int topnF = DEFAULT_TOPNF;
+
+        if (topnProtocols != null) {
+            topnP = parseIntWithDefault(topnProtocols, DEFAULT_TOPNP);
+            if (topnP <= 0) {
+                print("Invalid detected protocol topn number: 0 < valid number <= 100");
+                return;
+            }
+        }
+
+        if (topnFlows != null) {
+            topnF = parseIntWithDefault(topnFlows, DEFAULT_TOPNF);
+            if (topnF <= 0) {
+                print("Invalid known or unknown flows topn number: 0 < valid number <= 100");
+                return;
+            }
+        }
+
+        boolean isTopn = (topnP > 0 || topnF > 0);
+
+        if (all) {
+            dProtocols = true;
+            kFlows = true;
+            uFlows = true;
+        }
+
+        if (receivedTime != null) {
+            if (isTopn) {
+                ds = dsms.getDpiStatistics(receivedTime, topnP, topnF);
+            } else {
+                ds = dsms.getDpiStatistics(receivedTime);
+            }
+            if (ds == null) {
+                print(RECEIVED_TIME_ERROR_MSG);
+                return;
+            }
+
+            printDpiStatistics(0, ds);
+        } else if (lastn != null) {
+            int lastN = parseIntWithDefault(lastn, DEFAULT_LASTN);
+
+            List<DpiStatistics> dsList;
+            if (isTopn) {
+                dsList = dsms.getDpiStatistics(lastN, topnP, topnF);
+
+            } else {
+                dsList = dsms.getDpiStatistics(lastN);
+            }
+
+            printDpiStatisticsList(dsList);
+        } else if (permanent) {
+            int i = 0;
+            while (true) {
+                try {
+                    if (isTopn) {
+                        ds = dsms.getDpiStatisticsLatest(topnP, topnF);
+                    } else {
+                        ds = dsms.getDpiStatisticsLatest();
+                    }
+                    if (ds == null) {
+                        print(NO_DPI_ENTRY_ERROR_MSG);
+                        return;
+                    }
+
+                    printDpiStatistics(i++, ds);
+                    sleep(5000);
+                } catch (Exception e) {
+                    return;
+                }
+            }
+        } else { // latest == true
+            if (isTopn) {
+                ds = dsms.getDpiStatisticsLatest(topnP, topnF);
+            } else {
+                ds = dsms.getDpiStatisticsLatest();
+            }
+            if (ds == null) {
+                print(NO_DPI_ENTRY_ERROR_MSG);
+                return;
+            }
+
+            printDpiStatistics(0, ds);
+        }
+    }
+
+
+    /**
+     * Parse unsigned integer from input lastn string.
+     *
+     * @param lastN string lastn number
+     * @param defaultN integer default lastn number = 100
+     * @return integer lastN number, defaultN if input format is not a number
+     */
+    private int parseIntWithDefault(String lastN, int defaultN) {
+        try {
+            lastN = lastN.trim();
+            return Integer.parseUnsignedInt(lastN);
+        } catch (NumberFormatException e) {
+            return defaultN;
+        }
+    }
+
+    private void printDpiStatistics(int number, DpiStatistics ds) {
+        if (outputJson()) {
+            printDpiStatisticsJson(number, ds);
+        } else {
+            printDpiStatisticsClass(number, ds);
+        }
+    }
+
+    private void printDpiStatisticsJson(int number, DpiStatistics ds) {
+        String index = number < 0 ? "  -  " : String.format("%5d", number);
+        if ("".equals(ds.receivedTime())) {
+            print("ReceivedTime is null, No valid DPI Statistics!");
+            return;
+        }
+
+        print("<--- (%s) DPI Statistics Time [%s] --->", index, ds.receivedTime());
+        print("      %s", ds.toString());
+        print("<--------------------------------------------------------->");
+    }
+
+    private void printDpiStatisticsClass(int number, DpiStatistics ds) {
+        String printLine = "";
+        String index = number < 0 ? "  -  " : String.format("%5d", number);
+
+        DpiStatInfo dsi = ds.dpiStatInfo();
+        if (dsi == null) {
+            return;
+        }
+
+        if ("".equals(ds.receivedTime())) {
+            print("ReceivedTime is null, No valid DPI Statistics!");
+            return;
+        }
+
+        print("<--- (%s) DPI Statistics Time [%s] --->", index, ds.receivedTime());
+
+        print("Traffic Statistics:");
+        TrafficStatInfo tsi = dsi.trafficStatistics();
+
+        printLine = String.format("        %-30s %-30s", "ethernet.bytes:" + ":", tsi.ethernetBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "discarded.bytes" + ":", tsi.discardedBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "ip.packets" + ":", tsi.ipPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "total.packets" + ":", tsi.totalPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "ip.bytes" + ":", tsi.ipBytes());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "avg.pkt.size" + ":", tsi.avgPktSize());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "unique.flows" + ":", tsi.uniqueFlows());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "tcp.packets" + ":", tsi.tcpPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "udp.packets" + ":", tsi.tcpPackets());
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "dpi.throughput.pps" + ":",
+                                  tsi.dpiThroughputPps() + " pps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "dpi.throughput.bps" + ":",
+                                  tsi.dpiThroughputBps() + " bps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.throughput.pps" + ":",
+                                  tsi.trafficThroughputPps() + " pps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.throughput.bps" + ":",
+                                  tsi.trafficThroughputBps() + " bps");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "traffic.duration.sec" + ":",
+                                  tsi.trafficDurationSec() + " sec");
+        print("%s", printLine);
+        printLine = String.format("        %-30s %-30s", "guessed.flow.protos" + ":", tsi.guessedFlowProtos());
+        print("%s", printLine);
+
+        if (dProtocols || topnProtocols != null) {
+            print("");
+            print("Detected Protocols:");
+            List<ProtocolStatInfo> psiList = dsi.detectedProtos();
+            if (psiList != null) {
+                psiList.forEach(psi -> print(makeProtocolString(psi)));
+            }
+        }
+
+        List<FlowStatInfo> fsiList;
+        if (kFlows || topnFlows != null) {
+            print("");
+            print("Known Flows:");
+            fsiList = dsi.knownFlows();
+            if (fsiList != null) {
+                for (int i = 0; i < fsiList.size(); i++) {
+                    print(makeFlowString(fsiList.get(i), i));
+                }
+            }
+        }
+
+        if (uFlows || topnFlows != null) {
+            print("");
+            print("Unknown Flows:");
+            fsiList = dsi.unknownFlows();
+            if (fsiList != null) {
+                for (int i = 0; i < fsiList.size(); i++) {
+                    print(makeFlowString(fsiList.get(i), i));
+                }
+            }
+        }
+
+        print("<--------------------------------------------------------->");
+    }
+
+    private void printDpiStatisticsList(List<DpiStatistics> dsList) {
+        if (outputJson()) {
+            printDpiStatisticsListJson(dsList);
+        } else {
+            printDpiStatisticsListClass(dsList);
+        }
+    }
+
+    private void printDpiStatisticsListJson(List<DpiStatistics> dsList) {
+        for (int i = 0; i < dsList.size(); i++) {
+            printDpiStatisticsJson(i, dsList.get(i));
+        }
+    }
+
+    private void printDpiStatisticsListClass(List<DpiStatistics> dsList) {
+        for (int i = 0; i < dsList.size(); i++) {
+            printDpiStatisticsClass(i, dsList.get(i));
+        }
+    }
+
+    private String makeProtocolString(ProtocolStatInfo psi) {
+        StringBuffer sb = new StringBuffer("        ");
+
+        sb.append(String.format("%-20s", psi.name()));
+        sb.append(String.format(" %s: %-20s", "packets", psi.packets()));
+        sb.append(String.format(" %s: %-20s", "bytes", psi.bytes()));
+        sb.append(String.format(" %s: %-20s", "flows", psi.flows()));
+
+        return sb.toString();
+    }
+
+    private String makeFlowString(FlowStatInfo fsi, int index) {
+        StringBuffer sb = new StringBuffer("        ");
+
+        sb.append(String.format("%-8d ", index));
+        sb.append(String.format("%s ", fsi.protocol()));
+        sb.append(String.format("%s", fsi.hostAName()));
+        sb.append(String.format(":%s", fsi.hostAPort()));
+        sb.append(String.format(" <-> %s", fsi.hostBName()));
+        sb.append(String.format(":%s", fsi.hostBPort()));
+        sb.append(String.format(" [proto: %d", fsi.detectedProtocol()));
+        sb.append(String.format("/%s]", fsi.detectedProtocolName()));
+        sb.append(String.format(" [%s pkts/", fsi.packets()));
+        sb.append(String.format("%s bytes]", fsi.bytes()));
+        String serverHostName = fsi.hostServerName();
+        if (serverHostName != null && !"".equals(serverHostName)) {
+            sb.append(String.format("[Host: %s]", serverHostName));
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebApplication.java b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebApplication.java
new file mode 100644
index 0000000..b90b85d
--- /dev/null
+++ b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebApplication.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi.impl;
+
+import org.onlab.rest.AbstractWebApplication;
+
+import java.util.Set;
+
+/**
+ * DPI Stats REST APIs web application.
+ */
+public class DpisWebApplication extends AbstractWebApplication {
+    @Override
+    public Set<Class<?>> getClasses() {
+        return getClasses(
+                DpisWebResource.class
+        );
+    }
+}
diff --git a/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebResource.java b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebResource.java
new file mode 100644
index 0000000..51d2eb6
--- /dev/null
+++ b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/DpisWebResource.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.dpi.impl;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.incubator.net.dpi.DpiStatInfo;
+import org.onosproject.incubator.net.dpi.DpiStatistics;
+import org.onosproject.incubator.net.dpi.DpiStatisticsManagerService;
+import org.onosproject.incubator.net.dpi.FlowStatInfo;
+import org.onosproject.incubator.net.dpi.ProtocolStatInfo;
+import org.onosproject.rest.AbstractWebResource;
+import org.slf4j.Logger;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.onlab.util.Tools.readTreeFromStream;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Query the latest DPI statistics info.
+ */
+
+@Path("dpis")
+public class DpisWebResource extends AbstractWebResource {
+
+    private final Logger log = getLogger(getClass());
+
+    private static final int MAX_TOPN = 100;
+
+    private final DpiStatisticsManagerService service = get(DpiStatisticsManagerService.class);
+
+    public static final Comparator<ProtocolStatInfo> PROTOCOL_STAT_INFO_COMPARATOR =
+        new Comparator<ProtocolStatInfo>() {
+            @Override
+            public int compare(ProtocolStatInfo psi1, ProtocolStatInfo psi2) {
+                long delta = psi1.bytes() - psi2.bytes();
+                return delta == 0 ? 0 : (delta > 0 ? -1 : +1);
+            }
+        };
+
+    public static final Comparator<FlowStatInfo> FLOW_STAT_INFO_COMPARATOR =
+            new Comparator<FlowStatInfo>() {
+                @Override
+                public int compare(FlowStatInfo fsi1, FlowStatInfo fsi2) {
+                    long delta = fsi1.bytes() - fsi2.bytes();
+                    return delta == 0 ? 0 : (delta > 0 ? -1 : +1);
+                }
+            };
+
+    /**
+     * Gets the latest dpi statistics.
+     *
+     * @param topn max size
+     * @return 200 OK with a dpi statistics
+     * @onos.rsModel DpiStatistics
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response getDpisLatest(@QueryParam("topn") int topn) {
+        log.debug("getDpisLatest request with topn={}", topn);
+
+        DpiStatistics ds = service.getDpiStatisticsLatest();
+        DpiStatistics retDs;
+
+        if (ds == null) {
+            retDs = new DpiStatistics("", new DpiStatInfo());
+        } else if (topn <= 0) {
+            retDs = ds;
+        } else {
+            if (topn > MAX_TOPN) {
+                topn = MAX_TOPN;
+            }
+            retDs = new DpiStatistics(ds.receivedTime(),
+                                      new DpiStatInfo(ds.dpiStatInfo().trafficStatistics()));
+            List<ProtocolStatInfo> psiList = ds.dpiStatInfo().detectedProtos();
+            if (psiList != null) {
+                // sorts protocol list with descending order based on bytes within topn
+                List<ProtocolStatInfo> psiListSorted =
+                        psiList.stream().sorted(PROTOCOL_STAT_INFO_COMPARATOR).
+                        limit(topn).collect(Collectors.toList());
+                retDs.dpiStatInfo().setDetectedProtos(psiListSorted);
+            }
+            List<FlowStatInfo> fsiList = ds.dpiStatInfo().knownFlows();
+            if (fsiList != null) {
+                // sorts known flow list with descending order based on bytes within topn
+                List<FlowStatInfo> fsiListSorted =
+                        fsiList.stream().sorted(FLOW_STAT_INFO_COMPARATOR).
+                                limit(topn).collect(Collectors.toList());
+                retDs.dpiStatInfo().setKnownFlows(fsiListSorted);
+            }
+            fsiList = ds.dpiStatInfo().unknownFlows();
+            if (fsiList != null) {
+                // sorts unknown flow list with descending order based on bytes within topn
+                List<FlowStatInfo> fsiListSorted =
+                        fsiList.stream().sorted(FLOW_STAT_INFO_COMPARATOR).
+                                limit(topn).collect(Collectors.toList());
+                retDs.dpiStatInfo().setUnknownFlows(fsiListSorted);
+            }
+        }
+
+        ObjectNode result = codec(DpiStatistics.class).encode(retDs, this);
+        return ok(result).build();
+
+    }
+
+    /**
+     * Gets the latest traffic statistics only.
+     *
+     * @return 200 OK with a traffic statistics
+     * @onos.rsModel TrafficStatistics
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("traffic")
+    public Response getTrafficStatistics() {
+        log.debug("getTrafficStatistics request");
+
+        DpiStatistics ds = service.getDpiStatisticsLatest();
+        if (ds == null) {
+            ds = new DpiStatistics("", new DpiStatInfo());
+        }
+
+        DpiStatInfo dsi = new DpiStatInfo();
+        dsi.setTrafficStatistics(ds.dpiStatInfo().trafficStatistics());
+        DpiStatistics dsTraffic = new DpiStatistics(ds.receivedTime(), dsi);
+
+        ObjectNode result = codec(DpiStatistics.class).encode(dsTraffic, this);
+        return ok(result).build();
+    }
+
+    /**
+     * Gets the latest detected protocol statistics only.
+     *
+     * @param topn max size
+     * @return 200 OK with a protocol statistics
+     * @onos.rsModel ProtocolStatistics
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("protocols")
+    public Response getDetectedProtocols(@QueryParam("topn") int topn) {
+        log.debug("getDetectedProtocols request with topn={}", topn);
+
+        DpiStatistics ds = service.getDpiStatisticsLatest();
+        DpiStatistics dsProtocol;
+
+        if (ds == null) {
+            dsProtocol = new DpiStatistics("", new DpiStatInfo());
+        } else if (topn <= 0) {
+            DpiStatInfo dsi = new DpiStatInfo();
+            dsi.setDetectedProtos(ds.dpiStatInfo().detectedProtos());
+            dsProtocol = new DpiStatistics(ds.receivedTime(), dsi);
+        } else {
+            if (topn > MAX_TOPN) {
+                topn = MAX_TOPN;
+            }
+            dsProtocol = new DpiStatistics(ds.receivedTime(), new DpiStatInfo());
+            List<ProtocolStatInfo> psiList = ds.dpiStatInfo().detectedProtos();
+            if (psiList != null) {
+                // sorts protocol list with descending order based on bytes within topn
+                List<ProtocolStatInfo> psiListSorted =
+                        psiList.stream().sorted(PROTOCOL_STAT_INFO_COMPARATOR).
+                                limit(topn).collect(Collectors.toList());
+                dsProtocol.dpiStatInfo().setDetectedProtos(psiListSorted);
+            }
+        }
+
+        ObjectNode result = codec(DpiStatistics.class).encode(dsProtocol, this);
+        return ok(result).build();
+    }
+
+    /**
+     * Gets the latest known flows statistics only.
+     *
+     * @param topn max size
+     * @return 200 OK with a known flow statistics
+     * @onos.rsModel KnownFlowStatistics
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("knownFlows")
+    public Response getKnownFlows(@QueryParam("topn") int topn) {
+        log.debug("getKnownFlows request with topn={}", topn);
+
+        DpiStatistics ds = service.getDpiStatisticsLatest();
+        DpiStatistics dsKnownFlows;
+
+        if (ds == null) {
+            dsKnownFlows = new DpiStatistics("", new DpiStatInfo());
+        } else if (topn <= 0) {
+            DpiStatInfo dsi = new DpiStatInfo();
+            dsi.setKnownFlows(ds.dpiStatInfo().knownFlows());
+            dsKnownFlows = new DpiStatistics(ds.receivedTime(), dsi);
+        } else {
+            if (topn > MAX_TOPN) {
+                topn = MAX_TOPN;
+            }
+            dsKnownFlows = new DpiStatistics(ds.receivedTime(), new DpiStatInfo());
+            List<FlowStatInfo> fsiList = ds.dpiStatInfo().knownFlows();
+            if (fsiList != null) {
+                // sorts known flow list with descending order based on bytes within topn
+                List<FlowStatInfo> fsiListSorted =
+                        fsiList.stream().sorted(FLOW_STAT_INFO_COMPARATOR).
+                                limit(topn).collect(Collectors.toList());
+                dsKnownFlows.dpiStatInfo().setKnownFlows(fsiListSorted);
+            }
+        }
+
+        ObjectNode result = codec(DpiStatistics.class).encode(dsKnownFlows, this);
+        return ok(result).build();
+    }
+
+    /**
+     * Gets the latest unknown flows statistics only.
+     *
+     * @param topn max size
+     * @return 200 OK with an unknown flow statistics
+     * @onos.rsModel UnknownFlowStatistics
+     */
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("unknownFlows")
+    public Response getUnknownFlows(@QueryParam("topn") int topn) {
+        log.debug("getUnknownFlows request with topn={}", topn);
+
+        DpiStatistics ds = service.getDpiStatisticsLatest();
+        DpiStatistics dsUnknownFlows;
+
+        if (ds == null) {
+            dsUnknownFlows = new DpiStatistics("", new DpiStatInfo());
+        } else if (topn <= 0) {
+            DpiStatInfo dsi = new DpiStatInfo();
+            dsi.setUnknownFlows(ds.dpiStatInfo().unknownFlows());
+            dsUnknownFlows = new DpiStatistics(ds.receivedTime(), dsi);
+        } else {
+            if (topn > 100) {
+                topn = 100;
+            }
+            dsUnknownFlows = new DpiStatistics(ds.receivedTime(), new DpiStatInfo());
+            List<FlowStatInfo> fsiList = ds.dpiStatInfo().unknownFlows();
+            if (fsiList != null) {
+                // sorts unknown flow list with descending order based on bytes within topn
+                List<FlowStatInfo> fsiListSorted =
+                        fsiList.stream().sorted(FLOW_STAT_INFO_COMPARATOR).
+                                limit(topn).collect(Collectors.toList());
+                dsUnknownFlows.dpiStatInfo().setUnknownFlows(fsiListSorted);
+            }
+        }
+
+        ObjectNode result = codec(DpiStatistics.class).encode(dsUnknownFlows, this);
+        return ok(result).build();
+    }
+
+    /**
+     * Add new dpi statistics entry at the end of list.
+     *
+     * @param stream dpi statistics JSON
+     * @return status of the request - CREATED if the JSON is correct,
+     * BAD_REQUEST if the JSON is invalid
+     * @onos.rsModel DpiStatisticsPost
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response addDpiStatistics(InputStream stream) {
+        ObjectNode result;
+
+        try {
+            ObjectNode jsonTree = readTreeFromStream(mapper(), stream);
+            log.debug("jsonTree={}", jsonTree);
+
+            DpiStatistics ds = codec(DpiStatistics.class).decode(jsonTree, this);
+            if (ds == null) {
+                log.error("Wrong DpiStatistics json format error");
+            }
+
+            // TODO: check the validity of dpi statistics values, specially receivedTime format
+            DpiStatistics added = service.addDpiStatistics(ds);
+
+            result = codec(DpiStatistics.class).encode(added, this);
+        } catch (IOException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+        return ok(result).build();
+    }
+}
diff --git a/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/package-info.java b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/package-info.java
new file mode 100644
index 0000000..52f9652
--- /dev/null
+++ b/apps/dpistats/app/src/main/java/org/onosproject/incubator/net/dpi/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of the dpi statistics service.
+ */
+package org.onosproject.incubator.net.dpi.impl;
\ No newline at end of file
diff --git a/apps/dpistats/app/src/main/resources/definitions/DpiStatistics.json b/apps/dpistats/app/src/main/resources/definitions/DpiStatistics.json
new file mode 100644
index 0000000..d9f3010
--- /dev/null
+++ b/apps/dpistats/app/src/main/resources/definitions/DpiStatistics.json
@@ -0,0 +1,307 @@
+{
+  "type": "object",
+  "title": "dpiStatistics",
+  "required": [
+    "receivedTime",
+    "dpiStatInfo"
+  ],
+  "properties": {
+    "receivedTime": {
+      "type": "string",
+      "example": "2016-06-12 04:05:05"
+    },
+    "dpiStatInfo": {
+      "type": "object",
+      "title": "dpiStatInfo",
+      "required": [
+        "trafficStatistics",
+        "detectedProtos",
+        "knownFlows",
+        "unknownFlow"
+      ],
+      "properties": {
+        "trafficStatistics": {
+          "type": "object",
+          "title": "trafficStatistics",
+          "required": [
+            "ethernetBytes",
+            "discardedBytes",
+            "ipPackets",
+            "totalPackets",
+            "ipBytes",
+            "avgPktSize",
+            "uniqueFlows",
+            "tcpPackets",
+            "udpPackets",
+            "dpiThroughputPps",
+            "dpiThroughputBps",
+            "trafficThroughputPps",
+            "trafficThroughputBps",
+            "trafficDurationSec",
+            "guessedFlowProtos"
+          ],
+          "properties": {
+            "ethernetBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "discardedBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "ipPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "totalPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "ipBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "avgPktSize": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            },
+            "uniqueFlows": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            },
+            "tcpPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "udpPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "dpiThroughputPps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "dpiThroughputBps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficThroughputPps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficThroughputBps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficDurationSec": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "guessedFlowProtos": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            }
+          }
+        },
+        "detectedProtos": {
+          "type": "array",
+          "xml": {
+            "name": "detectedProtos",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "protos",
+            "required": [
+              "name",
+              "breed",
+              "packets",
+              "bytes",
+              "flows"
+            ],
+            "properties": {
+              "name": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "breed": {
+                "type": "string",
+                "example": "Acceptable"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "flows": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              }
+            }
+          }
+        },
+        "knownFlows": {
+          "type": "array",
+          "xml": {
+            "name": "knownFlows",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "knownFlows",
+            "required": [
+              "protocol",
+              "hostAName",
+              "hostAPort",
+              "hostBName",
+              "hostBPort",
+              "detectedProtocol",
+              "detectedProtocolName",
+              "packets",
+              "bytes",
+              "hostServerName"
+            ],
+            "properties": {
+              "protocol": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "hostAName": {
+                "type": "string",
+                "example": "10.0.20.50"
+              },
+              "hostAPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              },
+              "hostBName": {
+                "type": "string",
+                "example": "10.0.20.10"
+              },
+              "hostBPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 8181
+              },
+              "detectedProtocol": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80
+              },
+              "detectedProtocolName": {
+                "type": "string",
+                "example": "HTTP"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "hostSeverName": {
+                "type": "string",
+                "example": "raptor"
+              }
+            }
+          }
+        },
+        "unknownFlows": {
+          "type": "array",
+          "xml": {
+            "name": "unknownFlows",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "unknownFlows",
+            "required": [
+              "protocol",
+              "hostAName",
+              "hostAPort",
+              "hostBName",
+              "hostBPort",
+              "detectedProtocol",
+              "detectedProtocolName",
+              "packets",
+              "bytes",
+              "hostServerName"
+            ],
+            "properties": {
+              "protocol": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "hostAName": {
+                "type": "string",
+                "example": "10.0.20.50"
+              },
+              "hostAPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              },
+              "hostBName": {
+                "type": "string",
+                "example": "10.0.20.10"
+              },
+              "hostBPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 8181
+              },
+              "detectedProtocol": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80
+              },
+              "detectedProtocolName": {
+                "type": "string",
+                "example": "HTTP"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "hostSeverName": {
+                "type": "string",
+                "example": "raptor"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apps/dpistats/app/src/main/resources/definitions/DpiStatisticsPost.json b/apps/dpistats/app/src/main/resources/definitions/DpiStatisticsPost.json
new file mode 100644
index 0000000..d9f3010
--- /dev/null
+++ b/apps/dpistats/app/src/main/resources/definitions/DpiStatisticsPost.json
@@ -0,0 +1,307 @@
+{
+  "type": "object",
+  "title": "dpiStatistics",
+  "required": [
+    "receivedTime",
+    "dpiStatInfo"
+  ],
+  "properties": {
+    "receivedTime": {
+      "type": "string",
+      "example": "2016-06-12 04:05:05"
+    },
+    "dpiStatInfo": {
+      "type": "object",
+      "title": "dpiStatInfo",
+      "required": [
+        "trafficStatistics",
+        "detectedProtos",
+        "knownFlows",
+        "unknownFlow"
+      ],
+      "properties": {
+        "trafficStatistics": {
+          "type": "object",
+          "title": "trafficStatistics",
+          "required": [
+            "ethernetBytes",
+            "discardedBytes",
+            "ipPackets",
+            "totalPackets",
+            "ipBytes",
+            "avgPktSize",
+            "uniqueFlows",
+            "tcpPackets",
+            "udpPackets",
+            "dpiThroughputPps",
+            "dpiThroughputBps",
+            "trafficThroughputPps",
+            "trafficThroughputBps",
+            "trafficDurationSec",
+            "guessedFlowProtos"
+          ],
+          "properties": {
+            "ethernetBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "discardedBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "ipPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "totalPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "ipBytes": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "avgPktSize": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            },
+            "uniqueFlows": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            },
+            "tcpPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "udpPackets": {
+              "type": "integer",
+              "format": "int64",
+              "example": 69889
+            },
+            "dpiThroughputPps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "dpiThroughputBps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficThroughputPps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficThroughputBps": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "trafficDurationSec": {
+              "type": "number",
+              "format": "double",
+              "example": 69889.12
+            },
+            "guessedFlowProtos": {
+              "type": "integer",
+              "format": "int32",
+              "example": 9889
+            }
+          }
+        },
+        "detectedProtos": {
+          "type": "array",
+          "xml": {
+            "name": "detectedProtos",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "protos",
+            "required": [
+              "name",
+              "breed",
+              "packets",
+              "bytes",
+              "flows"
+            ],
+            "properties": {
+              "name": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "breed": {
+                "type": "string",
+                "example": "Acceptable"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "flows": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              }
+            }
+          }
+        },
+        "knownFlows": {
+          "type": "array",
+          "xml": {
+            "name": "knownFlows",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "knownFlows",
+            "required": [
+              "protocol",
+              "hostAName",
+              "hostAPort",
+              "hostBName",
+              "hostBPort",
+              "detectedProtocol",
+              "detectedProtocolName",
+              "packets",
+              "bytes",
+              "hostServerName"
+            ],
+            "properties": {
+              "protocol": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "hostAName": {
+                "type": "string",
+                "example": "10.0.20.50"
+              },
+              "hostAPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              },
+              "hostBName": {
+                "type": "string",
+                "example": "10.0.20.10"
+              },
+              "hostBPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 8181
+              },
+              "detectedProtocol": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80
+              },
+              "detectedProtocolName": {
+                "type": "string",
+                "example": "HTTP"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "hostSeverName": {
+                "type": "string",
+                "example": "raptor"
+              }
+            }
+          }
+        },
+        "unknownFlows": {
+          "type": "array",
+          "xml": {
+            "name": "unknownFlows",
+            "wrapped": true
+          },
+          "items": {
+            "type": "object",
+            "title": "unknownFlows",
+            "required": [
+              "protocol",
+              "hostAName",
+              "hostAPort",
+              "hostBName",
+              "hostBPort",
+              "detectedProtocol",
+              "detectedProtocolName",
+              "packets",
+              "bytes",
+              "hostServerName"
+            ],
+            "properties": {
+              "protocol": {
+                "type": "string",
+                "example": "TCP"
+              },
+              "hostAName": {
+                "type": "string",
+                "example": "10.0.20.50"
+              },
+              "hostAPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 9889
+              },
+              "hostBName": {
+                "type": "string",
+                "example": "10.0.20.10"
+              },
+              "hostBPort": {
+                "type": "integer",
+                "format": "int32",
+                "example": 8181
+              },
+              "detectedProtocol": {
+                "type": "integer",
+                "format": "int32",
+                "example": 80
+              },
+              "detectedProtocolName": {
+                "type": "string",
+                "example": "HTTP"
+              },
+              "packets": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "bytes": {
+                "type": "integer",
+                "format": "int64",
+                "example": 69889
+              },
+              "hostSeverName": {
+                "type": "string",
+                "example": "raptor"
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}