Added Job timeline view

git-svn-id: https://hyracks.googlecode.com/svn/branches/hyracks_dev_next@897 123451ca-8445-de46-9d55-352943316053
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/ActivityId.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/ActivityId.java
index c6ca51c..32a3afd 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/ActivityId.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/ActivityId.java
@@ -52,6 +52,16 @@
     }
 
     public String toString() {
-        return "ANID:[" + odId + "]:" + id;
+        return "ANID:" + odId + ":" + id;
+    }
+
+    public static ActivityId parse(String str) {
+        if (str.startsWith("ANID:")) {
+            str = str.substring(5);
+            int idIdx = str.lastIndexOf(':');
+            return new ActivityId(OperatorDescriptorId.parse(str.substring(0, idIdx)), Integer.parseInt(str
+                    .substring(idIdx + 1)));
+        }
+        throw new IllegalArgumentException();
     }
 }
\ No newline at end of file
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/OperatorDescriptorId.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/OperatorDescriptorId.java
index b858736..330e3a2 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/OperatorDescriptorId.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/OperatorDescriptorId.java
@@ -49,4 +49,12 @@
     public String toString() {
         return "ODID:" + id;
     }
+
+    public static OperatorDescriptorId parse(String str) {
+        if (str.startsWith("ODID:")) {
+            str = str.substring(5);
+            return new OperatorDescriptorId(Integer.parseInt(str));
+        }
+        throw new IllegalArgumentException();
+    }
 }
\ No newline at end of file
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskAttemptId.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskAttemptId.java
index d4e6972..96437bf 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskAttemptId.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskAttemptId.java
@@ -52,6 +52,15 @@
 
     @Override
     public String toString() {
-        return "TAID:[" + taskId + "]:" + attempt;
+        return "TAID:" + taskId + ":" + attempt;
+    }
+
+    public static TaskAttemptId parse(String str) {
+        if (str.startsWith("TAID:")) {
+            str = str.substring(5);
+            int idIdx = str.lastIndexOf(':');
+            return new TaskAttemptId(TaskId.parse(str.substring(0, idIdx)), Integer.parseInt(str.substring(idIdx + 1)));
+        }
+        throw new IllegalArgumentException();
     }
 }
\ No newline at end of file
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskId.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskId.java
index ee63355..78abaa2 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskId.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/dataflow/TaskId.java
@@ -52,6 +52,15 @@
 
     @Override
     public String toString() {
-        return "TID:[" + activityId + "]:" + partition;
+        return "TID:" + activityId + ":" + partition;
+    }
+
+    public static TaskId parse(String str) {
+        if (str.startsWith("TID:")) {
+            str = str.substring(4);
+            int idIdx = str.lastIndexOf(':');
+            return new TaskId(ActivityId.parse(str.substring(0, idIdx)), Integer.parseInt(str.substring(idIdx + 1)));
+        }
+        throw new IllegalArgumentException();
     }
 }
\ No newline at end of file
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobActivityGraph.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobActivityGraph.java
index 930d299..4577f4c 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobActivityGraph.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobActivityGraph.java
@@ -209,13 +209,11 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject jplan = new JSONObject();
 
-        jplan.put("type", "plan");
         jplan.put("flags", jobFlags.toString());
 
         JSONArray jans = new JSONArray();
         for (IActivity an : activityNodes.values()) {
             JSONObject jan = new JSONObject();
-            jan.put("type", "activity");
             jan.put("id", an.getActivityId().toString());
             jan.put("java-class", an.getClass().getName());
             jan.put("operator-id", an.getActivityId().getOperatorDescriptorId().toString());
@@ -225,7 +223,6 @@
                 JSONArray jInputs = new JSONArray();
                 for (int i = 0; i < inputs.size(); ++i) {
                     JSONObject jInput = new JSONObject();
-                    jInput.put("type", "activity-input");
                     jInput.put("input-port", i);
                     jInput.put("connector-id", inputs.get(i).getConnectorId().toString());
                     jInputs.put(jInput);
@@ -238,7 +235,6 @@
                 JSONArray jOutputs = new JSONArray();
                 for (int i = 0; i < outputs.size(); ++i) {
                     JSONObject jOutput = new JSONObject();
-                    jOutput.put("type", "activity-output");
                     jOutput.put("output-port", i);
                     jOutput.put("connector-id", outputs.get(i).getConnectorId().toString());
                     jOutputs.put(jOutput);
diff --git a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobSpecification.java b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobSpecification.java
index 23b33f0..cdea942 100644
--- a/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobSpecification.java
+++ b/hyracks-api/src/main/java/edu/uci/ics/hyracks/api/job/JobSpecification.java
@@ -264,8 +264,6 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject jjob = new JSONObject();
 
-        jjob.put("type", "job");
-
         JSONArray jopArray = new JSONArray();
         for (Map.Entry<OperatorDescriptorId, IOperatorDescriptor> e : opMap.entrySet()) {
             jopArray.put(e.getValue().toJSON());
@@ -277,7 +275,6 @@
             JSONObject conn = new JSONObject();
             Pair<Pair<IOperatorDescriptor, Integer>, Pair<IOperatorDescriptor, Integer>> connection = connectorOpMap
                     .get(e.getKey());
-            conn.put("type", "connector-info");
             if (connection != null) {
                 conn.put("in-operator-id", connection.first.first.getOperatorId().toString());
                 conn.put("in-operator-port", connection.first.second.intValue());
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.java
index 6b7e143..3a86c42 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.java
@@ -15,9 +15,12 @@
 package edu.uci.ics.hyracks.control.cc.adminconsole.pages;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.EmptyPanel;
@@ -26,6 +29,8 @@
 import org.json.JSONArray;
 import org.json.JSONObject;
 
+import edu.uci.ics.hyracks.api.dataflow.ActivityId;
+import edu.uci.ics.hyracks.api.dataflow.TaskId;
 import edu.uci.ics.hyracks.api.job.JobId;
 import edu.uci.ics.hyracks.control.cc.ClusterControllerService;
 import edu.uci.ics.hyracks.control.cc.work.GetJobActivityGraphJSONWork;
@@ -35,7 +40,7 @@
 public class JobDetailsPage extends AbstractPage {
     private static final long serialVersionUID = 1L;
 
-    private static final int HEIGHT = 9;
+    private static final int HEIGHT = 29;
     private static final int WIDTH = 9;
 
     public JobDetailsPage(PageParameters params) throws Exception {
@@ -47,18 +52,164 @@
 
         GetJobSpecificationJSONWork gjsw = new GetJobSpecificationJSONWork(ccs, jobId);
         ccs.getWorkQueue().scheduleAndSync(gjsw);
-        add(new Label("job-specification", gjsw.getJSON().toString()));
+        Label jobspec = new Label("job-specification", gjsw.getJSON().toString());
+        jobspec.setEscapeModelStrings(false);
+        add(jobspec);
 
         GetJobActivityGraphJSONWork gjagw = new GetJobActivityGraphJSONWork(ccs, jobId);
         ccs.getWorkQueue().scheduleAndSync(gjagw);
-        add(new Label("job-activity-graph", gjagw.getJSON().toString()));
+        Label jag = new Label("job-activity-graph", gjagw.getJSON().toString());
+        jag.setEscapeModelStrings(false);
+        add(jag);
+
+        JSONObject jagO = gjagw.getJSON();
+
+        Map<ActivityId, String> activityMap = new HashMap<ActivityId, String>();
+        if (jagO.has("activities")) {
+            JSONArray aArray = jagO.getJSONArray("activities");
+            for (int i = 0; i < aArray.length(); ++i) {
+                JSONObject aO = aArray.getJSONObject(i);
+                ActivityId aid = ActivityId.parse(aO.getString("id"));
+                String className = aO.getString("java-class");
+
+                activityMap.put(aid, className);
+            }
+        }
 
         GetJobRunJSONWork gjrw = new GetJobRunJSONWork(ccs, jobId);
         ccs.getWorkQueue().scheduleAndSync(gjrw);
-        add(new Label("job-run", gjrw.getJSON().toString()));
+        Label jobrun = new Label("job-run", gjrw.getJSON().toString());
+        jobrun.setEscapeModelStrings(false);
+        add(jobrun);
 
         JSONObject jrO = gjrw.getJSON();
 
+        List<TaskClusterAttempt[]> tcList = new ArrayList<TaskClusterAttempt[]>();
+        long minTime = Long.MAX_VALUE;
+        long maxTime = Long.MIN_VALUE;
+        if (jrO.has("activity-clusters")) {
+            JSONArray acA = jrO.getJSONArray("activity-clusters");
+            for (int i = 0; i < acA.length(); ++i) {
+                JSONObject acO = acA.getJSONObject(i);
+                if (acO.has("plan")) {
+                    JSONObject planO = acO.getJSONObject("plan");
+                    if (planO.has("task-clusters")) {
+                        JSONArray tcA = planO.getJSONArray("task-clusters");
+                        for (int j = 0; j < tcA.length(); ++j) {
+                            JSONObject tcO = tcA.getJSONObject(j);
+                            String tcId = tcO.getString("task-cluster-id");
+                            if (tcO.has("attempts")) {
+                                JSONArray tcaA = tcO.getJSONArray("attempts");
+                                TaskClusterAttempt[] tcAttempts = new TaskClusterAttempt[tcaA.length()];
+                                for (int k = 0; k < tcaA.length(); ++k) {
+                                    JSONObject tcaO = tcaA.getJSONObject(k);
+                                    int attempt = tcaO.getInt("attempt");
+                                    long startTime = tcaO.getLong("start-time");
+                                    long endTime = tcaO.getLong("end-time");
+
+                                    tcAttempts[k] = new TaskClusterAttempt(tcId, attempt, startTime, endTime);
+                                    if (startTime < minTime) {
+                                        minTime = startTime;
+                                    }
+                                    if (endTime > maxTime) {
+                                        maxTime = endTime;
+                                    }
+                                    if (tcaO.has("task-attempts")) {
+                                        JSONArray taArray = tcaO.getJSONArray("task-attempts");
+                                        tcAttempts[k].tasks = new TaskAttempt[taArray.length()];
+                                        for (int l = 0; l < taArray.length(); ++l) {
+                                            JSONObject taO = taArray.getJSONObject(l);
+                                            TaskAttempt ta = new TaskAttempt(taO.getString("task-id"),
+                                                    taO.getLong("start-time"), taO.getLong("end-time"));
+                                            tcAttempts[k].tasks[l] = ta;
+                                            TaskId tid = TaskId.parse(taO.getString("task-id"));
+                                            ta.name = activityMap.get(tid.getActivityId());
+                                            ta.partition = tid.getPartition();
+                                        }
+                                        Arrays.sort(tcAttempts[k].tasks, new Comparator<TaskAttempt>() {
+                                            @Override
+                                            public int compare(TaskAttempt o1, TaskAttempt o2) {
+                                                return o1.startTime < o2.startTime ? -1
+                                                        : (o1.startTime > o2.startTime ? 1 : 0);
+                                            }
+                                        });
+                                    }
+                                }
+                                Arrays.sort(tcAttempts, new Comparator<TaskClusterAttempt>() {
+                                    @Override
+                                    public int compare(TaskClusterAttempt o1, TaskClusterAttempt o2) {
+                                        return o1.startTime < o2.startTime ? -1 : (o1.startTime > o2.startTime ? 1 : 0);
+                                    }
+                                });
+                                tcList.add(tcAttempts);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!tcList.isEmpty()) {
+            Collections.sort(tcList, new Comparator<TaskClusterAttempt[]>() {
+                @Override
+                public int compare(TaskClusterAttempt[] o1, TaskClusterAttempt[] o2) {
+                    if (o1.length == 0) {
+                        return o2.length == 0 ? 0 : -1;
+                    } else if (o2.length == 0) {
+                        return 1;
+                    }
+                    return o1[0].startTime < o2[0].startTime ? -1 : (o1[0].startTime > o2[0].startTime ? 1 : 0);
+                }
+            });
+            long range = maxTime - minTime;
+
+            double leftOffset = 20;
+
+            int xWidth = 1024;
+            double width = ((double) xWidth) / range;
+            StringBuilder buffer = new StringBuilder();
+            int y = 0;
+            for (TaskClusterAttempt[] tcAttempts : tcList) {
+                for (int i = 0; i < tcAttempts.length; ++i) {
+                    TaskClusterAttempt tca = tcAttempts[i];
+                    long startTime = tca.startTime - minTime;
+                    long endTime = tca.endTime - minTime;
+                    buffer.append("<rect x=\"").append(startTime * width + leftOffset).append("\" y=\"")
+                            .append(y * (HEIGHT + 1)).append("\" width=\"").append(width * (endTime - startTime))
+                            .append("\" height=\"").append(HEIGHT).append("\"/>\n");
+                    buffer.append("<text x=\"").append(endTime * width + leftOffset + 20).append("\" y=\"")
+                            .append(y * (HEIGHT + 1) + HEIGHT * 3 / 4).append("\">")
+                            .append((endTime - startTime) + " ms").append("</text>\n");
+                    ++y;
+                    for (int j = 0; j < tca.tasks.length; ++j) {
+                        TaskAttempt ta = tca.tasks[j];
+                        long tStartTime = ta.startTime - minTime;
+                        long tEndTime = ta.endTime - minTime;
+                        buffer.append("<rect x=\"").append(tStartTime * width + leftOffset).append("\" y=\"")
+                                .append(y * (HEIGHT + 1) + HEIGHT / 4).append("\" width=\"")
+                                .append(width * (tEndTime - tStartTime)).append("\" height=\"").append(HEIGHT / 2)
+                                .append("\"/>\n");
+                        buffer.append("<text x=\"").append(tEndTime * width + leftOffset + 20).append("\" y=\"")
+                                .append(y * (HEIGHT + 1) + HEIGHT * 3 / 4).append("\">")
+                                .append((tEndTime - tStartTime) + " ms (" + ta.name + ":" + ta.partition + ")")
+                                .append("</text>\n");
+                        ++y;
+                    }
+                }
+            }
+            buffer.append("<rect x=\"").append(leftOffset).append("\" y=\"").append(0).append("\" width=\"").append(1)
+                    .append("\" height=\"").append((y + 2) * (HEIGHT + 1)).append("\"/>\n");
+            buffer.append("<rect x=\"").append(0).append("\" y=\"").append((y + 1) * (HEIGHT + 1))
+                    .append("\" width=\"").append(xWidth + 2 * leftOffset).append("\" height=\"").append(1)
+                    .append("\"/>\n");
+            buffer.append("</svg>");
+            Label markup = new Label("job-timeline",
+                    "<svg version=\"1.1\"\nxmlns=\"http://www.w3.org/2000/svg\" width=\"" + (xWidth * 1.5)
+                            + "\" height=\"" + ((y + 2) * (HEIGHT + 1)) + "\">\n" + buffer.toString());
+            markup.setEscapeModelStrings(false);
+            add(markup);
+        }
+
         List<TaskProfile> taskProfiles = new ArrayList<TaskProfile>();
         if (jrO.has("profile")) {
             JSONObject pO = jrO.getJSONObject("profile");
@@ -104,8 +255,8 @@
             long timeRange = taskProfiles.get(taskProfiles.size() - 1).closeTime - startTime;
             int n = taskProfiles.size();
             StringBuilder buffer = new StringBuilder();
-            buffer.append("<svg viewBox=\"0 0 ").append((timeRange + 1) * (WIDTH + 1))
-                    .append(' ').append((n + 1) * (HEIGHT + 1)).append("\" version=\"1.1\"\n");
+            buffer.append("<svg viewBox=\"0 0 ").append((timeRange + 1) * (WIDTH + 1)).append(' ')
+                    .append((n + 1) * (HEIGHT + 1)).append("\" version=\"1.1\"\n");
             buffer.append("xmlns=\"http://www.w3.org/2000/svg\">\n");
             for (int i = 0; i < n; ++i) {
                 TaskProfile tp = taskProfiles.get(i);
@@ -116,11 +267,11 @@
                 close(buffer, i, tp.closeTime - startTime);
             }
             buffer.append("</svg>");
-            Label markup = new Label("job-timeline", buffer.toString());
+            Label markup = new Label("job-timeline-profile", buffer.toString());
             markup.setEscapeModelStrings(false);
             add(markup);
         } else {
-            add(new EmptyPanel("job-timeline"));
+            add(new EmptyPanel("job-timeline-profile"));
         }
     }
 
@@ -139,6 +290,35 @@
                 .append("\" width=\"").append(WIDTH).append("\" height=\"").append(HEIGHT).append("\"/>\n");
     }
 
+    private static class TaskAttempt {
+        private String taskId;
+        private long startTime;
+        private long endTime;
+        private String name;
+        private int partition;
+
+        public TaskAttempt(String taskId, long startTime, long endTime) {
+            this.taskId = taskId;
+            this.startTime = startTime;
+            this.endTime = endTime;
+        }
+    }
+
+    private static class TaskClusterAttempt {
+        private String tcId;
+        private int attempt;
+        private long startTime;
+        private long endTime;
+        private TaskAttempt[] tasks;
+
+        public TaskClusterAttempt(String tcId, int attempt, long startTime, long endTime) {
+            this.tcId = tcId;
+            this.attempt = attempt;
+            this.startTime = startTime;
+            this.endTime = endTime;
+        }
+    }
+
     private static class TaskProfile {
         private String activityId;
         private int partition;
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
index df003b2..a72a866 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/JobRun.java
@@ -316,6 +316,8 @@
                             JSONObject attempt = new JSONObject();
                             attempt.put("attempt", tca.getAttempt());
                             attempt.put("status", tca.getStatus());
+                            attempt.put("start-time", tca.getStartTime());
+                            attempt.put("end-time", tca.getEndTime());
 
                             JSONArray taskAttempts = new JSONArray();
                             for (TaskAttempt ta : tca.getTaskAttempts()) {
@@ -324,6 +326,8 @@
                                 taskAttempt.put("task-attempt-id", ta.getTaskAttemptId());
                                 taskAttempt.put("status", ta.getStatus());
                                 taskAttempt.put("node-id", ta.getNodeId());
+                                taskAttempt.put("start-time", ta.getStartTime());
+                                taskAttempt.put("end-time", ta.getEndTime());
                                 String failureDetails = ta.getFailureDetails();
                                 if (failureDetails != null) {
                                     taskAttempt.put("failure-details", failureDetails);
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskAttempt.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskAttempt.java
index 43495db..7c0dd57 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskAttempt.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskAttempt.java
@@ -29,7 +29,7 @@
 
     private final TaskAttemptId taskId;
 
-    private final Task taskState;
+    private final Task task;
 
     private String nodeId;
 
@@ -37,10 +37,16 @@
 
     private String failureDetails;
 
-    public TaskAttempt(TaskClusterAttempt tcAttempt, TaskAttemptId taskId, Task taskState) {
+    private long startTime;
+
+    private long endTime;
+
+    public TaskAttempt(TaskClusterAttempt tcAttempt, TaskAttemptId taskId, Task task) {
         this.tcAttempt = tcAttempt;
         this.taskId = taskId;
-        this.taskState = taskState;
+        this.task = task;
+        startTime = -1;
+        endTime = -1;
     }
 
     public TaskClusterAttempt getTaskClusterAttempt() {
@@ -51,8 +57,8 @@
         return taskId;
     }
 
-    public Task getTaskState() {
-        return taskState;
+    public Task getTask() {
+        return task;
     }
 
     public String getNodeId() {
@@ -75,4 +81,20 @@
         this.status = status;
         this.failureDetails = details;
     }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
 }
\ No newline at end of file
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskClusterAttempt.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskClusterAttempt.java
index 9d33d12..84848bb 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskClusterAttempt.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/job/TaskClusterAttempt.java
@@ -32,9 +32,15 @@
 
     private int pendingTaskCounter;
 
+    private long startTime;
+
+    private long endTime;
+
     public TaskClusterAttempt(TaskCluster taskCluster, int attempt) {
         this.taskCluster = taskCluster;
         this.attempt = attempt;
+        startTime = -1;
+        endTime = -1;
     }
 
     public TaskCluster getTaskCluster() {
@@ -61,6 +67,22 @@
         return status;
     }
 
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
+
     public void initializePendingTaskCounter() {
         pendingTaskCounter = taskAttempts.length;
     }
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/scheduler/JobScheduler.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/scheduler/JobScheduler.java
index 3feb5a6..6933bf0 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/scheduler/JobScheduler.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/scheduler/JobScheduler.java
@@ -387,6 +387,7 @@
             }
             taskAttempt.setNodeId(nodeId);
             taskAttempt.setStatus(TaskAttempt.TaskStatus.RUNNING, null);
+            taskAttempt.setStartTime(System.currentTimeMillis());
             List<TaskAttemptDescriptor> tads = taskAttemptMap.get(nodeId);
             if (tads == null) {
                 tads = new ArrayList<TaskAttemptDescriptor>();
@@ -399,6 +400,7 @@
         tcAttempt.initializePendingTaskCounter();
         tcAttempts.add(tcAttempt);
         tcAttempt.setStatus(TaskClusterAttempt.TaskClusterStatus.RUNNING);
+        tcAttempt.setStartTime(System.currentTimeMillis());
         inProgressTaskClusters.add(tc);
     }
 
@@ -477,6 +479,7 @@
             LOGGER.info("Checking " + taId + ": " + ta.getStatus());
             if (status == TaskAttempt.TaskStatus.RUNNING || status == TaskAttempt.TaskStatus.COMPLETED) {
                 ta.setStatus(TaskAttempt.TaskStatus.ABORTED, null);
+                ta.setEndTime(System.currentTimeMillis());
                 List<TaskAttemptId> abortTaskAttempts = abortTaskAttemptMap.get(ta.getNodeId());
                 if (abortTaskAttempts == null) {
                     abortTaskAttempts = new ArrayList<TaskAttemptId>();
@@ -524,6 +527,7 @@
             TaskClusterAttempt tca = findLastTaskClusterAttempt(tc);
             if (tca != null) {
                 abortTaskCluster(tca);
+                tca.setEndTime(System.currentTimeMillis());
                 tca.setStatus(TaskClusterAttempt.TaskClusterStatus.ABORTED);
             }
         }
@@ -571,14 +575,16 @@
 
     public void notifyTaskComplete(TaskAttempt ta) throws HyracksException {
         TaskAttemptId taId = ta.getTaskAttemptId();
-        TaskCluster tc = ta.getTaskState().getTaskCluster();
+        TaskCluster tc = ta.getTask().getTaskCluster();
         TaskClusterAttempt lastAttempt = findLastTaskClusterAttempt(tc);
         if (lastAttempt != null && taId.getAttempt() == lastAttempt.getAttempt()) {
             TaskAttempt.TaskStatus taStatus = ta.getStatus();
             if (taStatus == TaskAttempt.TaskStatus.RUNNING) {
                 ta.setStatus(TaskAttempt.TaskStatus.COMPLETED, null);
+                ta.setEndTime(System.currentTimeMillis());
                 if (lastAttempt.decrementPendingTasksCounter() == 0) {
                     lastAttempt.setStatus(TaskClusterAttempt.TaskClusterStatus.COMPLETED);
+                    lastAttempt.setEndTime(System.currentTimeMillis());
                     inProgressTaskClusters.remove(tc);
                     startRunnableActivityClusters();
                 }
@@ -604,13 +610,14 @@
         try {
             LOGGER.info("Received failure notification for TaskAttempt " + ta.getTaskAttemptId());
             TaskAttemptId taId = ta.getTaskAttemptId();
-            TaskCluster tc = ta.getTaskState().getTaskCluster();
+            TaskCluster tc = ta.getTask().getTaskCluster();
             TaskClusterAttempt lastAttempt = findLastTaskClusterAttempt(tc);
             if (lastAttempt != null && taId.getAttempt() == lastAttempt.getAttempt()) {
                 LOGGER.info("Marking TaskAttempt " + ta.getTaskAttemptId() + " as failed");
                 ta.setStatus(TaskAttempt.TaskStatus.FAILED, details);
                 abortTaskCluster(lastAttempt);
                 lastAttempt.setStatus(TaskClusterAttempt.TaskClusterStatus.FAILED);
+                lastAttempt.setEndTime(System.currentTimeMillis());
                 abortDoomedTaskClusters();
                 if (lastAttempt.getAttempt() >= ac.getMaxTaskClusterAttempts()) {
                     abortJob(new HyracksException(details));
@@ -648,6 +655,7 @@
                                 assert (ta.getStatus() == TaskAttempt.TaskStatus.COMPLETED || ta.getStatus() == TaskAttempt.TaskStatus.RUNNING);
                                 if (deadNodes.contains(ta.getNodeId())) {
                                     ta.setStatus(TaskAttempt.TaskStatus.FAILED, "Node " + ta.getNodeId() + " failed");
+                                    ta.setEndTime(System.currentTimeMillis());
                                     abort = true;
                                 }
                             }
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskCompleteWork.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskCompleteWork.java
index 2838ed4..2694af4 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskCompleteWork.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskCompleteWork.java
@@ -39,7 +39,7 @@
     @Override
     protected void performEvent(TaskAttempt ta) {
         try {
-            ActivityCluster ac = ta.getTaskState().getTaskCluster().getActivityCluster();
+            ActivityCluster ac = ta.getTask().getTaskCluster().getActivityCluster();
             JobRun run = ac.getJobRun();
             if (statistics != null) {
                 JobProfile jobProfile = run.getJobProfile();
diff --git a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskFailureWork.java b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskFailureWork.java
index b80d928..3201bec 100644
--- a/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskFailureWork.java
+++ b/hyracks-control-cc/src/main/java/edu/uci/ics/hyracks/control/cc/work/TaskFailureWork.java
@@ -31,7 +31,7 @@
 
     @Override
     protected void performEvent(TaskAttempt ta) {
-        ActivityCluster ac = ta.getTaskState().getTaskCluster().getActivityCluster();
+        ActivityCluster ac = ta.getTask().getTaskCluster().getActivityCluster();
         ac.getJobRun().getScheduler().notifyTaskFailure(ta, ac, details);
     }
 
diff --git a/hyracks-control-cc/src/main/resources/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.html b/hyracks-control-cc/src/main/resources/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.html
index f11e480..8c5578d 100644
--- a/hyracks-control-cc/src/main/resources/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.html
+++ b/hyracks-control-cc/src/main/resources/edu/uci/ics/hyracks/control/cc/adminconsole/pages/JobDetailsPage.html
@@ -5,5 +5,6 @@
     </div>
     <div id="job-run" wicket:id="job-run" style="display: none;">
     </div>
-    <div wicket:id="job-timeline"></div>
+    <div wicket:id="job-timeline" style="overflow: auto;"></div>
+    <div wicket:id="job-timeline-profile"></div>
 </wicket:extend>
\ No newline at end of file
diff --git a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobProfile.java b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobProfile.java
index 5457ac5..46a964a 100644
--- a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobProfile.java
+++ b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobProfile.java
@@ -3,6 +3,7 @@
 import java.util.HashMap;
 import java.util.Map;
 
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -32,12 +33,13 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject json = new JSONObject();
 
-        json.put("type", "job-profile");
         json.put("job-id", jobId.toString());
         populateCounters(json);
+        JSONArray jobletsArray = new JSONArray();
         for (JobletProfile p : jobletProfiles.values()) {
-            json.accumulate("joblets", p.toJSON());
+            jobletsArray.put(p.toJSON());
         }
+        json.put("joblets", jobletsArray);
 
         return json;
     }
diff --git a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobletProfile.java b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobletProfile.java
index 78f885d..0c60006 100644
--- a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobletProfile.java
+++ b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/JobletProfile.java
@@ -47,7 +47,6 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject json = new JSONObject();
 
-        json.put("type", "joblet-profile");
         json.put("node-id", nodeId.toString());
         populateCounters(json);
         JSONArray tasks = new JSONArray();
diff --git a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/TaskProfile.java b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/TaskProfile.java
index 3764a01..139a985 100644
--- a/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/TaskProfile.java
+++ b/hyracks-control-common/src/main/java/edu/uci/ics/hyracks/control/common/job/profiling/om/TaskProfile.java
@@ -48,7 +48,6 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject json = new JSONObject();
 
-        json.put("type", "task-profile");
         json.put("activity-id", taskAttemptId.getTaskId().getActivityId().toString());
         json.put("partition", taskAttemptId.getTaskId().getPartition());
         json.put("attempt", taskAttemptId.getAttempt());
diff --git a/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractConnectorDescriptor.java b/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractConnectorDescriptor.java
index f3535f8..4ffc060 100644
--- a/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractConnectorDescriptor.java
+++ b/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractConnectorDescriptor.java
@@ -41,7 +41,6 @@
     public JSONObject toJSON() throws JSONException {
         JSONObject jconn = new JSONObject();
 
-        jconn.put("type", "connector");
         jconn.put("id", getConnectorId().getId());
         jconn.put("java-class", getClass().getName());
 
diff --git a/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractOperatorDescriptor.java b/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractOperatorDescriptor.java
index 15fc75d..1bb13ca 100644
--- a/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractOperatorDescriptor.java
+++ b/hyracks-dataflow-std/src/main/java/edu/uci/ics/hyracks/dataflow/std/base/AbstractOperatorDescriptor.java
@@ -75,7 +75,6 @@
     @Override
     public JSONObject toJSON() throws JSONException {
         JSONObject jop = new JSONObject();
-        jop.put("type", "operator");
         jop.put("id", getOperatorId().getId());
         jop.put("java-class", getClass().getName());
         jop.put("in-arity", getInputArity());