Add durational agent event types
For better handling in Web

commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java
commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java web/src/main/java/com/navercorp/pinpoint/web/vo/DurationalAgentEvent.java
diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java b/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java
index 7c254f1ffa4f..1d650b6ea81d 100644
--- a/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java
+++ b/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventType.java
@@ -29,12 +29,12 @@
  * @author HyunGil Jeong
  */
 public enum AgentEventType {
-    AGENT_CONNECTED(10100, "Agent connected", Void.class, AGENT_LIFECYCLE),
+    AGENT_CONNECTED(10100, "Agent connected", Void.class, DURATIONAL, AGENT_LIFECYCLE),
     AGENT_PING(10199, "Agent ping", Void.class, AGENT_LIFECYCLE),
-    AGENT_SHUTDOWN(10200, "Agent shutdown", Void.class, AGENT_LIFECYCLE),
-    AGENT_UNEXPECTED_SHUTDOWN(10201, "Agent unexpected shutdown", Void.class, AGENT_LIFECYCLE),
-    AGENT_CLOSED_BY_SERVER(10300, "Agent connection closed by server", Void.class, AGENT_LIFECYCLE),
-    AGENT_UNEXPECTED_CLOSE_BY_SERVER(10301, "Agent connection unexpectedly closed by server", Void.class, AGENT_LIFECYCLE),
+    AGENT_SHUTDOWN(10200, "Agent shutdown", Void.class, DURATIONAL, AGENT_LIFECYCLE),
+    AGENT_UNEXPECTED_SHUTDOWN(10201, "Agent unexpected shutdown", Void.class, DURATIONAL, AGENT_LIFECYCLE),
+    AGENT_CLOSED_BY_SERVER(10300, "Agent connection closed by server", Void.class, DURATIONAL, AGENT_LIFECYCLE),
+    AGENT_UNEXPECTED_CLOSE_BY_SERVER(10301, "Agent connection unexpectedly closed by server", Void.class, DURATIONAL, AGENT_LIFECYCLE),
     USER_THREAD_DUMP(20100, "Thread dump by user", TCommandThreadDumpResponse.class, USER_REQUEST, THREAD_DUMP),
     OTHER(-1, "Other event", String.class, AgentEventTypeCategory.OTHER);
     
diff --git a/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java b/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java
index 1fabb4cbded4..6f7b8d869018 100644
--- a/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java
+++ b/commons/src/main/java/com/navercorp/pinpoint/common/util/AgentEventTypeCategory.java
@@ -20,6 +20,7 @@
  * @author HyunGil Jeong
  */
 public enum AgentEventTypeCategory {
+    DURATIONAL,
     AGENT_LIFECYCLE,
     USER_REQUEST,
     THREAD_DUMP,
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java b/web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java
index c3dc4e5f672b..c7e28415a114 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/service/AgentEventServiceImpl.java
@@ -19,8 +19,12 @@
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
+import java.util.PriorityQueue;
 
+import com.navercorp.pinpoint.common.util.AgentEventTypeCategory;
+import com.navercorp.pinpoint.web.vo.DurationalAgentEvent;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,13 +58,8 @@ public List<AgentEvent> getAgentEvents(String agentId, Range range) {
         }
         final boolean includeEventMessage = false;
         List<AgentEventBo> agentEventBos = this.agentEventDao.getAgentEvents(agentId, range);
-        List<AgentEvent> agentEvents = new ArrayList<>(agentEventBos.size());
-        for (AgentEventBo agentEventBo : agentEventBos) {
-            if (agentEventBo != null) {
-                agentEvents.add(createAgentEvent(agentEventBo, includeEventMessage));
-            }
-        }
-        Collections.sort(agentEvents, AgentEvent.EVENT_TIMESTAMP_DESC_COMPARATOR);
+        List<AgentEvent> agentEvents = createAgentEvents(agentEventBos, includeEventMessage);
+        Collections.sort(agentEvents, AgentEvent.EVENT_TIMESTAMP_ASC_COMPARATOR);
         return agentEvents;
     }
 
@@ -84,21 +83,54 @@ public AgentEvent getAgentEvent(String agentId, long eventTimestamp, int eventTy
         return null;
     }
 
+    private List<AgentEvent> createAgentEvents(List<AgentEventBo> agentEventBos, boolean includeEventMessage) {
+        List<AgentEvent> agentEvents = new ArrayList<>(agentEventBos.size());
+        PriorityQueue<DurationalAgentEvent> durationalAgentEvents = new PriorityQueue<>(agentEventBos.size(), AgentEvent.EVENT_TIMESTAMP_ASC_COMPARATOR);
+        for (AgentEventBo agentEventBo : agentEventBos) {
+            if (agentEventBo.getEventType().isCategorizedAs(AgentEventTypeCategory.DURATIONAL)) {
+                durationalAgentEvents.add(createDurationalAgentEvent(agentEventBo, includeEventMessage));
+            } else {
+                agentEvents.add(createAgentEvent(agentEventBo, includeEventMessage));
+            }
+        }
+        long durationStartTimestamp = DurationalAgentEvent.UNKNOWN_TIMESTAMP;
+        while (!durationalAgentEvents.isEmpty()) {
+            DurationalAgentEvent currentEvent = durationalAgentEvents.remove();
+            currentEvent.setDurationStartTimestamp(durationStartTimestamp);
+            DurationalAgentEvent nextEvent = durationalAgentEvents.peek();
+            if (nextEvent != null) {
+                long nextEventTimestamp = nextEvent.getEventTimestamp();
+                currentEvent.setDurationEndTimestamp(nextEventTimestamp);
+                durationStartTimestamp = nextEventTimestamp;
+            }
+            agentEvents.add(currentEvent);
+        }
+        return agentEvents;
+    }
+
     private AgentEvent createAgentEvent(AgentEventBo agentEventBo, boolean includeEventMessage) {
-        final String agentId = agentEventBo.getAgentId();
-        final long eventTimestamp = agentEventBo.getEventTimestamp();
-        final AgentEventType eventType = agentEventBo.getEventType();
-        AgentEvent agentEvent = new AgentEvent(agentId, eventTimestamp, eventType);
-        agentEvent.setStartTimestamp(agentEventBo.getStartTimestamp());
+        AgentEvent agentEvent = new AgentEvent(agentEventBo);
         if (includeEventMessage) {
-            try {
-                agentEvent.setEventMessage(this.agentEventMessageDeserializer.deserialize(eventType,
-                        agentEventBo.getEventBody()));
-            } catch (UnsupportedEncodingException e) {
-                logger.warn("error deserializing event message", e);
-            }
+            agentEvent.setEventMessage(deserializeEventMessage(agentEventBo));
         }
         return agentEvent;
     }
 
+    private DurationalAgentEvent createDurationalAgentEvent(AgentEventBo agentEventBo, boolean includeEventMessage) {
+        DurationalAgentEvent durationalAgentEvent = new DurationalAgentEvent(agentEventBo);
+        if (includeEventMessage) {
+            durationalAgentEvent.setEventMessage(deserializeEventMessage(agentEventBo));
+        }
+        return durationalAgentEvent;
+    }
+
+    private Object deserializeEventMessage(AgentEventBo agentEventBo) {
+        try {
+            return this.agentEventMessageDeserializer.deserialize(agentEventBo.getEventType(), agentEventBo.getEventBody());
+        } catch (UnsupportedEncodingException e) {
+            logger.warn("error deserializing event message", e);
+            return null;
+        }
+    }
+
 }
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java b/web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java
index 91737a998c6e..da9630207d00 100644
--- a/web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java
+++ b/web/src/main/java/com/navercorp/pinpoint/web/vo/AgentEvent.java
@@ -21,6 +21,7 @@
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.navercorp.pinpoint.common.bo.AgentEventBo;
 import com.navercorp.pinpoint.common.util.AgentEventType;
 
 /**
@@ -29,6 +30,17 @@
 @JsonInclude(Include.NON_NULL)
 public class AgentEvent {
 
+    public static final Comparator<AgentEvent> EVENT_TIMESTAMP_ASC_COMPARATOR = new Comparator<AgentEvent>() {
+        @Override
+        public int compare(AgentEvent o1, AgentEvent o2) {
+            int eventTimestampComparison = Long.compare(o1.eventTimestamp, o2.eventTimestamp);
+            if (eventTimestampComparison == 0) {
+                return o2.eventTypeCode - o1.eventTypeCode;
+            }
+            return eventTimestampComparison;
+        }
+    };
+
     public static final Comparator<AgentEvent> EVENT_TIMESTAMP_DESC_COMPARATOR = new Comparator<AgentEvent>() {
         @Override
         public int compare(AgentEvent o1, AgentEvent o2) {
@@ -56,23 +68,16 @@ public int compare(AgentEvent o1, AgentEvent o2) {
     private final boolean hasEventMessage;
 
     @JsonProperty
-    private long startTimestamp;
+    private final long startTimestamp;
 
     @JsonProperty
     private Object eventMessage;
 
-    public AgentEvent(String agentId, long eventTimestamp, AgentEventType eventType) {
-        if (agentId == null) {
-            throw new NullPointerException("agentId must not be null");
-        }
-        if (eventTimestamp < 0) {
-            throw new IllegalArgumentException("eventTimestamp must not be null");
-        }
-        if (eventType == null) {
-            throw new NullPointerException("eventType must not be null");
-        }
-        this.agentId = agentId;
-        this.eventTimestamp = eventTimestamp;
+    public AgentEvent(AgentEventBo agentEventBo) {
+        this.agentId = agentEventBo.getAgentId();
+        this.eventTimestamp = agentEventBo.getEventTimestamp();
+        this.startTimestamp = agentEventBo.getStartTimestamp();
+        AgentEventType eventType = agentEventBo.getEventType();
         this.eventTypeCode = eventType.getCode();
         this.eventTypeDesc = eventType.getDesc();
         this.hasEventMessage = eventType.getMessageType() != Void.class;
@@ -102,10 +107,6 @@ public long getStartTimestamp() {
         return startTimestamp;
     }
 
-    public void setStartTimestamp(long startTimestamp) {
-        this.startTimestamp = startTimestamp;
-    }
-
     public Object getEventMessage() {
         return eventMessage;
     }
@@ -161,6 +162,6 @@ public boolean equals(Object obj) {
     public String toString() {
         return "AgentEvent [agentId=" + agentId + ", eventTimestamp=" + eventTimestamp + ", eventTypeCode="
                 + eventTypeCode + ", eventTypeDesc=" + eventTypeDesc + ", hasEventMessage=" + hasEventMessage
-                + ", startTimestamp=" + startTimestamp + ", eventMessage=" + eventMessage + "]";
+                + ", eventMessage=" + eventMessage + ", startTimestamp=" + startTimestamp + "]";
     }
 }
diff --git a/web/src/main/java/com/navercorp/pinpoint/web/vo/DurationalAgentEvent.java b/web/src/main/java/com/navercorp/pinpoint/web/vo/DurationalAgentEvent.java
new file mode 100644
index 000000000000..1300f0320f64
--- /dev/null
+++ b/web/src/main/java/com/navercorp/pinpoint/web/vo/DurationalAgentEvent.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 NAVER Corp.
+ *
+ * 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 com.navercorp.pinpoint.web.vo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.navercorp.pinpoint.common.bo.AgentEventBo;
+import com.navercorp.pinpoint.common.util.AgentEventType;
+
+/**
+ * @author HyunGil Jeong
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class DurationalAgentEvent extends AgentEvent {
+
+    public static final long UNKNOWN_TIMESTAMP = -1;
+
+    @JsonProperty
+    private long durationStartTimestamp = UNKNOWN_TIMESTAMP;
+
+    @JsonProperty
+    private long durationEndTimestamp = UNKNOWN_TIMESTAMP;
+
+    public DurationalAgentEvent(AgentEventBo agentEventBo) {
+        super(agentEventBo);
+    }
+
+    public long getDurationStartTimestamp() {
+        return this.durationStartTimestamp;
+    }
+
+    public void setDurationStartTimestamp(long durationStartTimestamp) {
+        this.durationStartTimestamp = durationStartTimestamp;
+    }
+
+    public long getDurationEndTimestamp() {
+        return this.durationEndTimestamp;
+    }
+
+    public void setDurationEndTimestamp(long durationEndTimestamp) {
+        this.durationEndTimestamp = durationEndTimestamp;
+    }
+
+    @Override
+    public String toString() {
+        return "DurationalAgentEvent{" +
+                "agentId=" + super.getAgentId() +
+                ", eventTimestamp=" + super.getEventTimestamp() +
+                ", eventTypeCode=" + super.getEventTypeCode() +
+                ", eventTypeDesc=" + super.getEventTypeDesc() +
+                ", hasEventMessage=" + super.hasEventMessage() +
+                ", eventMessage=" + super.getEventMessage() +
+                ", startTimestamp=" + super.getStartTimestamp() +
+                ", durationStartTimestamp=" + durationStartTimestamp +
+                ", durationEndTimestamp=" + durationEndTimestamp +
+                '}';
+    }
+}
