KryoFactory serialization/deserialization test.

- Tests to confirm change in serialized size, etc.
- Output simple benchmark result as CSV.
  - KryoFactoryTest_size.csv
  - KryoFactoryTest_tput.csv

Change-Id: Ide4083df449e76297124f19b7c2730d23f5aad11
diff --git a/src/test/java/net/onrc/onos/core/util/serializers/KryoFactoryTest.java b/src/test/java/net/onrc/onos/core/util/serializers/KryoFactoryTest.java
new file mode 100644
index 0000000..42d6a6a
--- /dev/null
+++ b/src/test/java/net/onrc/onos/core/util/serializers/KryoFactoryTest.java
@@ -0,0 +1,293 @@
+package net.onrc.onos.core.util.serializers;
+
+import static org.junit.Assert.*;
+
+import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.core.topology.HostEvent;
+import net.onrc.onos.core.topology.LinkEvent;
+import net.onrc.onos.core.topology.PortEvent;
+import net.onrc.onos.core.topology.TopologyElement;
+import net.onrc.onos.core.topology.TopologyEvent;
+import net.onrc.onos.core.topology.SwitchEvent;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+
+/**
+ * Tests to capture Kryo serialization characteristics.
+ * <p/>
+ * <ul>
+ *  <li>Serialization/deserialization throughput</li>
+ *  <li>Serialized size</li>
+ *  <li>Equality of object before and after serializaton,deserialization</li>
+ *  <li>TODO bit by bit comparison of serialized bytes</li>
+ * </ul>
+ */
+public class KryoFactoryTest {
+
+    private static final int NUM_ITERATIONS = Integer.parseInt(
+                                    System.getProperty("iterations", "100"));
+
+    private static final Dpid DPID_A = new Dpid(0x1234L);
+    private static final Dpid DPID_B = new Dpid(Long.MAX_VALUE);
+    private static final PortNumber PORT_NO_A = new PortNumber((short) 42);
+    private static final PortNumber PORT_NO_B = new PortNumber((short) 65534);
+
+    private static final double SEC_IN_NANO = 1000 * 1000 * 1000.0;
+
+    private KryoFactory kryoFactory;
+
+    @Before
+    public void setUp() throws Exception {
+        kryoFactory = new KryoFactory(1);
+    }
+
+    /**
+     * Benchmark result.
+     */
+    private static final class Result {
+        /**
+         * Serialized type name.
+         */
+        String type;
+        /**
+         * Serialized size.
+         */
+        int size;
+        /**
+         * Serialization throughput (ops/sec).
+         */
+        double ser;
+        /**
+         * Deserialization throughput (ops/sec).
+         */
+        double deser;
+
+        public Result(String type, int size, double ser, double deser) {
+            this.type = type;
+            this.size = size;
+            this.ser = ser;
+            this.deser = deser;
+        }
+    }
+
+    private static enum EqualityCheck {
+        /**
+         * No way to compare equality provided.
+         */
+        IMPOSSIBLE,
+        /**
+         * custom equals method is defined.
+         */
+        EQUALS,
+        /**
+         * Can be compared using toString() result.
+         */
+        TO_STRING,
+    }
+
+    /**
+     * Benchmark serialization of specified object.
+     *
+     * @param obj the object to benchmark
+     * @param equalityCheck how to check equality of deserialized object.
+     * @return benchmark {@link Result}
+     */
+    private Result benchType(Object obj, EqualityCheck equalityCheck) {
+
+        Kryo kryo = kryoFactory.newKryo();
+        try {
+            byte[] buffer = new byte[1 * 1000 * 1000];
+            Output output = new Output(buffer);
+
+            // Measurement: serialization size
+            kryo.writeClassAndObject(output, obj);
+            int serializedBytes = output.toBytes().length;
+
+            // Measurement: serialization throughput
+            byte[] result = null;
+
+            long t1 = System.nanoTime();
+            for (int j = 0; j < NUM_ITERATIONS; j++) {
+                output.clear();
+                kryo.writeClassAndObject(output, obj);
+                result = output.toBytes();
+            }
+            long t2 = System.nanoTime();
+            double serTput = NUM_ITERATIONS * SEC_IN_NANO / (t2 - t1);
+
+            // Measurement: deserialization throughput
+            Object objOut = null;
+            Input input = new Input(result);
+            long t3 = System.nanoTime();
+            for (int j = 0; j < NUM_ITERATIONS; j++) {
+                input.setBuffer(result);
+                objOut = kryo.readClassAndObject(input);
+            }
+            long t4 = System.nanoTime();
+
+            switch (equalityCheck) {
+            case IMPOSSIBLE:
+                break;
+            case EQUALS:
+                assertEquals(obj, objOut);
+                break;
+            case TO_STRING:
+                assertEquals(obj.toString(), objOut.toString());
+                break;
+            default:
+                break;
+            }
+            double deserTput = NUM_ITERATIONS * SEC_IN_NANO / (t4 - t3);
+
+            return new Result(obj.getClass().getSimpleName(),
+                    serializedBytes, serTput, deserTput);
+        } finally {
+            kryoFactory.deleteKryo(kryo);
+        }
+    }
+
+    /**
+     * Benchmark serialization of types registered to KryoFactory.
+     */
+    @Test
+    public void benchmark() throws Exception {
+
+        List<Result> results = new ArrayList<>();
+
+        // To be more strict, we should be checking serialized byte[].
+        { // CHECKSTYLE IGNORE THIS LINE
+            HostEvent obj = new HostEvent(MACAddress.valueOf(0x12345678));
+            obj.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER);
+            obj.addAttachmentPoint(new SwitchPort(DPID_A, PORT_NO_A));
+            // avoid using System.currentTimeMillis() var-int size may change
+            obj.setLastSeenTime(392860800000L);
+            obj.freeze();
+            Result result = benchType(obj, EqualityCheck.EQUALS);
+            results.add(result);
+            // update me if serialized form is expected to change
+            assertEquals(43, result.size);
+        }
+
+        { // CHECKSTYLE IGNORE THIS LINE
+            LinkEvent obj = new LinkEvent(DPID_A, PORT_NO_A, DPID_B, PORT_NO_B);
+            obj.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER);
+            obj.freeze();
+            Result result = benchType(obj, EqualityCheck.EQUALS);
+            results.add(result);
+            // update me if serialized form is expected to change
+            assertEquals(49, result.size);
+        }
+
+        { // CHECKSTYLE IGNORE THIS LINE
+            PortEvent obj = new PortEvent(DPID_A, PORT_NO_A);
+            obj.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER);
+            obj.freeze();
+            Result result = benchType(obj, EqualityCheck.EQUALS);
+            results.add(result);
+            // update me if serialized form is expected to change
+            assertEquals(24, result.size);
+        }
+
+        { // CHECKSTYLE IGNORE THIS LINE
+            SwitchEvent obj = new SwitchEvent(DPID_A);
+            obj.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER);
+            obj.freeze();
+            Result result = benchType(obj, EqualityCheck.EQUALS);
+            results.add(result);
+            // update me if serialized form is expected to change
+            assertEquals(21, result.size);
+        }
+
+        { // CHECKSTYLE IGNORE THIS LINE
+            SwitchEvent evt = new SwitchEvent(DPID_A);
+            evt.createStringAttribute(TopologyElement.TYPE, TopologyElement.TYPE_PACKET_LAYER);
+            evt.freeze();
+
+            // using the back door to access package-scoped constructor
+            Constructor<TopologyEvent> swConst
+                = TopologyEvent.class.getDeclaredConstructor(SwitchEvent.class);
+            swConst.setAccessible(true);
+            TopologyEvent obj = swConst.newInstance(evt);
+
+            Result result = benchType(obj, EqualityCheck.TO_STRING);
+            results.add(result);
+            // update me if serialized form is expected to change
+            assertEquals(26, result.size);
+        }
+
+        // TODO Add registered classes we still use.
+
+
+        // Output for plot plugin
+        List<String> slabels = new ArrayList<>();
+        List<Number> svalues = new ArrayList<>();
+        List<String> tlabels = new ArrayList<>();
+        List<Number> tvalues = new ArrayList<>();
+
+        // Type, size, serialize T-put, deserialize T-put, N
+        System.out.println("Type, size, serialize T-put, deserialize T-put, N");
+
+        for (Result result : results) {
+            System.out.printf("%s, %d, %f, %f, %d\n",
+                    result.type, result.size, result.ser, result.deser,
+                    NUM_ITERATIONS);
+
+            // Output for plot plugin
+            // <Type>_size, <Type>_ser, <Type>_deser
+            slabels.addAll(Arrays.asList(
+                    result.type + "_size"
+                    ));
+            svalues.addAll(Arrays.asList(
+                    result.size
+                    ));
+            tlabels.addAll(Arrays.asList(
+                    result.type + "_ser",
+                    result.type + "_deser"
+                    ));
+            tvalues.addAll(Arrays.asList(
+                    result.ser,
+                    result.deser
+                    ));
+        }
+
+        // Output for plot plugin
+        PrintStream size = new PrintStream("target/KryoFactoryTest_size.csv");
+        PrintStream tput = new PrintStream("target/KryoFactoryTest_tput.csv");
+
+        for (String label : slabels) {
+                size.print(label);
+                size.print(", ");
+        }
+        size.println();
+        for (Number value : svalues) {
+            size.print(value);
+            size.print(", ");
+        }
+        size.close();
+
+        for (String label : tlabels) {
+            tput.print(label);
+            tput.print(", ");
+        }
+        tput.println();
+        for (Number value : tvalues) {
+            tput.print(value);
+            tput.print(", ");
+        }
+        tput.close();
+    }
+}