changeset 627:7252a5f21644

Timeline view (part I) review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-September/003272.html reviewed-by: rkennke
author Mario Torre <neugens.limasoftware@gmail.com>
date Wed, 19 Sep 2012 10:08:17 +0200
parents dacf6c5ea49f
children b3f8bfcea78b
files client/swing-components/src/main/java/com/redhat/thermostat/swing/ChartPanel.java client/swing-components/src/main/java/com/redhat/thermostat/swing/GradientRoundBorder.java client/swing-components/src/main/java/com/redhat/thermostat/swing/GraphicsUtils.java client/swing-components/src/main/java/com/redhat/thermostat/swing/HeaderPanel.java client/swing-components/src/main/java/com/redhat/thermostat/swing/models/LongRange.java client/swing-components/src/main/java/com/redhat/thermostat/swing/models/LongRangeNormalizer.java client/swing-components/src/test/java/com/redhat/thermostat/swing/models/LongRangeNormalizerTest.java distribution/config/osgi-export.properties thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineBean.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineView.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadView.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java thread/client-common/src/main/resources/com/redhat/thermostat/thread/client/common/locale/strings.properties thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/CommonController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInfoHelper.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInfoHelperTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineControllerTest.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadDetailsView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadTimelineChart.java
diffstat 28 files changed, 1442 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing-components/src/main/java/com/redhat/thermostat/swing/ChartPanel.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/ChartPanel.java	Wed Sep 19 10:08:17 2012 +0200
@@ -51,7 +51,7 @@
     private ChartRenderingInfo info;
     
     public ChartPanel(JFreeChart chart) {
-        this.chart = chart;
+        this(chart, new ChartRenderingInfo());
     }
     
     public ChartPanel(JFreeChart chart, ChartRenderingInfo info) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/GradientRoundBorder.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.swing;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.io.Serializable;
+
+import javax.swing.UIManager;
+import javax.swing.border.AbstractBorder;
+import javax.swing.plaf.UIResource;
+
+@SuppressWarnings("serial")
+public class GradientRoundBorder extends AbstractBorder implements UIResource, Serializable {
+
+    @Override
+    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+        GraphicsUtils utils = GraphicsUtils.getInstance();
+        Graphics2D graphics = utils.createAAGraphics(g);
+        
+        Color highlight = UIManager.getColor("textHighlight");
+        if (highlight == null) {
+            highlight = Palette.EGYPTIAN_BLUE.getColor();
+        }
+        Paint paint = new GradientPaint(x, y, highlight, 0, height, c.getBackground());
+        graphics.setPaint(paint);
+        
+        graphics.translate(x, y);
+        
+        Shape shape = utils.getRoundShape(width, height);
+        graphics.draw(shape);
+        
+        graphics.dispose();
+    }
+}
--- a/client/swing-components/src/main/java/com/redhat/thermostat/swing/GraphicsUtils.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/GraphicsUtils.java	Wed Sep 19 10:08:17 2012 +0200
@@ -85,7 +85,7 @@
     }
     
     public Shape getRoundShape(int width, int height) {
-        return new RoundRectangle2D.Double(0, 0, width - 2, height - 1, 4, 4);
+        return new RoundRectangle2D.Double(0, 0, width, height, 4, 4);
     }
     
     public void setGradientPaint(Graphics2D g, int x, int height, Color start, Color stop) {
--- a/client/swing-components/src/main/java/com/redhat/thermostat/swing/HeaderPanel.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/HeaderPanel.java	Wed Sep 19 10:08:17 2012 +0200
@@ -43,8 +43,6 @@
 import java.lang.reflect.InvocationTargetException;
 
 import javax.swing.BoxLayout;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
 import javax.swing.JComponent;
 import javax.swing.JFrame;
 import javax.swing.JLabel;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/models/LongRange.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.swing.models;
+
+public class LongRange {
+
+    long min;
+    long max;
+    
+    public void setMax(long max) {
+        this.max = max;
+    }
+    
+    public long getMax() {
+        return max;
+    }
+    
+    public void setMin(long min) {
+        this.min = min;
+    }
+    
+    
+    public long getMin() {
+        return min;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing-components/src/main/java/com/redhat/thermostat/swing/models/LongRangeNormalizer.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.swing.models;
+
+public class LongRangeNormalizer {
+
+    private long minNormalized;
+    
+    private long maxNormalized;
+ 
+    private long value;
+
+    private LongRange range;
+    
+    public LongRangeNormalizer(LongRange range) {
+        this.range = range;
+    }
+
+    public void setMaxNormalized(long maxNormalized) {
+        this.maxNormalized = maxNormalized;
+    }
+    
+    public void setMinNormalized(long minNormalized) {
+        this.minNormalized = minNormalized;
+    }
+    
+    public long getValue() {
+        return value;
+    }
+
+    public void setValue(long newValue) {
+        this.value = newValue;
+    }
+    
+    public long getMaxNormalized() {
+        return maxNormalized;
+    }
+    
+    public long getMinNormalized() {
+        return minNormalized;
+    }
+    
+    public long getValueNormalized() {
+        double normalized = ((value - range.min) * (double)(maxNormalized - minNormalized)/(range.max - range.min)) + minNormalized;
+        return Math.round(normalized);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing-components/src/test/java/com/redhat/thermostat/swing/models/LongRangeNormalizerTest.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.swing.models;
+
+import static org.junit.Assert.*;
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class LongRangeNormalizerTest {
+
+    @Test
+    public void testSameRange() {
+        
+        LongRange range = new LongRange();
+        range.setMax(10);
+        range.setMin(0);
+        
+        LongRangeNormalizer model = new LongRangeNormalizer(range);
+        
+        model.setValue(5);
+        
+        model.setMaxNormalized(10);
+        model.setMinNormalized(0);
+        
+        
+        Assert.assertEquals((int) model.getValue(), model.getValueNormalized());
+    }
+
+    @Test
+    public void testDoubleRange() {
+        
+        LongRange range = new LongRange();
+        range.setMax(10);
+        range.setMin(0);
+        
+        LongRangeNormalizer model = new LongRangeNormalizer(range);
+        
+        model.setValue(5);
+        
+        model.setMaxNormalized(20);
+        model.setMinNormalized(0);
+        
+        
+        Assert.assertEquals(10, model.getValueNormalized());
+    }
+    
+    @Test
+    public void testRanges() {
+        
+        LongRange range = new LongRange();
+        range.setMax(10);
+        range.setMin(0);
+        
+        LongRangeNormalizer model = new LongRangeNormalizer(range);
+        
+        model.setValue(5);
+        
+        model.setMaxNormalized(40);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(20, model.getValueNormalized());
+        
+        model.setMaxNormalized(60);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(30, model.getValueNormalized());
+                
+        model.setMaxNormalized(200);
+        model.setMinNormalized(100);
+                
+        Assert.assertEquals(150, model.getValueNormalized());
+        
+        range.setMax(100);
+        range.setMin(0);
+        model.setValue(50);
+        
+        model.setMaxNormalized(1);
+        model.setMinNormalized(0);
+                
+        Assert.assertEquals(1, model.getValueNormalized());
+        
+        model.setValue(49);
+        Assert.assertEquals(0, model.getValueNormalized());
+    }
+}
--- a/distribution/config/osgi-export.properties	Tue Sep 18 17:38:29 2012 -0400
+++ b/distribution/config/osgi-export.properties	Wed Sep 19 10:08:17 2012 +0200
@@ -50,6 +50,7 @@
 org.jfree.data.time
 org.jfree.data.xy
 org.jfree.ui
+org.jfree.data.gantt
 sun.swing
 sun.swing.table
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineBean.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common;
+
+import java.util.Date;
+
+public class ThreadTimelineBean {
+
+    private long startTime;
+    private long stopTime;
+    private String name;
+    private Thread.State state;
+    
+    public Thread.State getState() {
+        return state;
+    }
+    
+    public void setState(Thread.State state) {
+        this.state = state;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public long getStartTime() {
+        return startTime;
+    }
+    
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+    
+    public long getStopTime() {
+        return stopTime;
+    }
+    
+    public void setStopTime(long stopTime) {
+        this.stopTime = stopTime;
+    }
+
+    @Override
+    public String toString() {
+        return "ThreadTimelineBean [name=" + name + ", state=" + state
+                + ", startTime=" + startTime + " (" + new Date(startTime) + ")"
+                + ", stopTime=" + stopTime + " (" + new Date(stopTime) + ")]";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadTimelineView.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.common;
+
+import java.util.List;
+import java.util.Map;
+
+import com.redhat.thermostat.client.osgi.service.BasicView;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public abstract class ThreadTimelineView extends BasicView {
+
+    public abstract void displayStats(Map<ThreadInfoData, List<ThreadTimelineBean>> timelines, long start, long stop);
+
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadView.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/ThreadView.java	Wed Sep 19 10:08:17 2012 +0200
@@ -74,6 +74,7 @@
     
     public abstract VMThreadCapabilitiesView createVMThreadCapabilitiesView();
     public abstract ThreadTableView createThreadTableView();
+    public abstract ThreadTimelineView createThreadTimelineView();
     
     public abstract void displayWarning(String warning);
 
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java	Wed Sep 19 10:08:17 2012 +0200
@@ -57,6 +57,7 @@
     VM_CAPABILITIES,
     TABLE,
     DETAILS,
+    TIMELINE,
     
     LIVE_THREADS,
     DAEMON_THREADS,
--- a/thread/client-common/src/main/resources/com/redhat/thermostat/thread/client/common/locale/strings.properties	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-common/src/main/resources/com/redhat/thermostat/thread/client/common/locale/strings.properties	Wed Sep 19 10:08:17 2012 +0200
@@ -16,6 +16,7 @@
 VM_CAPABILITIES = VM Capabilities
 TABLE = Table
 DETAILS = Details
+TIMELINE = Timeline
 
 LIVE_THREADS = Live Threads
 DAEMON_THREADS = Daemon Threads
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/CommonController.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/CommonController.java	Wed Sep 19 10:08:17 2012 +0200
@@ -36,6 +36,47 @@
 
 package com.redhat.thermostat.thread.client.controller.impl;
 
-public interface CommonController {
-    void initialize();
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.osgi.service.BasicView;
+import com.redhat.thermostat.client.osgi.service.BasicView.Action;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.Timer.SchedulingType;
+
+public abstract class CommonController {
+    
+    protected Timer timer;
+    protected BasicView view;
+    
+    public CommonController(Timer timer, BasicView view) {
+        this.view = view;
+        this.timer = timer;
+    }
+    
+    void initialize() {
+        timer.setInitialDelay(0);
+        timer.setDelay(1000);
+        timer.setTimeUnit(TimeUnit.MILLISECONDS);
+        timer.setSchedulingType(SchedulingType.FIXED_RATE);
+        
+        view.addActionListener(new ActionListener<Action>() {
+            @Override
+            public void actionPerformed(ActionEvent<Action> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case VISIBLE:
+                    timer.start();
+                    break;
+
+                case HIDDEN:
+                    timer.stop();
+                    break;
+
+                default:
+                    break;
+                }
+            }
+        });
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInfoHelper.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public class ThreadInfoHelper {
+
+    /**
+     * Creates a {@link Map} whose keys are {@link ThreadInfoData} in the input
+     * list and whose values are all the {@link ThreadInfoData} equals to the
+     * key.
+     *
+     * <br /><br />
+     * 
+     * Preserves the order of the input list.
+     * 
+     * <br /><br />
+     * 
+     * <strong>NOTE</strong>: The current invariant is that
+     * {@link ThreadInfoData} are equals if they have same thread id and name.
+     */
+    public static Map<ThreadInfoData, List<ThreadInfoData>> getThreadInfoDataMap(List<ThreadInfoData> infos) {
+        Map<ThreadInfoData, List<ThreadInfoData>> stats = new HashMap<>();
+        for (ThreadInfoData info : infos) {
+            List<ThreadInfoData> beanList = stats.get(info);
+            if (beanList == null) {
+                beanList = new ArrayList<ThreadInfoData>();
+                stats.put(info, beanList);
+            }                    
+            beanList.add(info);
+        }
+        return stats;
+    }
+}
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Wed Sep 19 10:08:17 2012 +0200
@@ -197,7 +197,8 @@
     }
     
     private void initControllers() {
-        CommonController capsController =
+        
+        VMThreadCapabilitiesController capsController =
                 new VMThreadCapabilitiesController(view.createVMThreadCapabilitiesView(), collector);
         capsController.initialize();
         
@@ -207,5 +208,10 @@
                 new ThreadTableController(threadTableView, collector,
                                           ApplicationContext.getInstance().getTimerFactory().createTimer());
         threadTableController.initialize();
+        
+        CommonController threadTimeline =
+                new ThreadTimelineController(view.createThreadTimelineView(), collector,
+                                             ApplicationContext.getInstance().getTimerFactory().createTimer());
+        threadTimeline.initialize();
     }
 }
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableController.java	Wed Sep 19 10:08:17 2012 +0200
@@ -41,60 +41,29 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
-import com.redhat.thermostat.client.osgi.service.BasicView.Action;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.Timer;
-import com.redhat.thermostat.common.Timer.SchedulingType;
 import com.redhat.thermostat.thread.client.common.ThreadTableBean;
 import com.redhat.thermostat.thread.client.common.ThreadTableView;
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
 import com.redhat.thermostat.thread.model.ThreadInfoData;
 
-public class ThreadTableController implements CommonController {
+public class ThreadTableController extends CommonController {
     
     private ThreadTableView threadTableView;
     private ThreadCollector collector;
-    private Timer timer;
     
     public ThreadTableController(ThreadTableView threadTableView,
                                  ThreadCollector collector,
                                  Timer timer)
     {
+        super(timer, threadTableView);
+        timer.setAction(new ThreadTableControllerAction());
+
         this.collector = collector;
         this.threadTableView = threadTableView;
-        this.timer = timer;
     }
 
-    @Override
-    public void initialize() {
-        
-        timer.setInitialDelay(0);
-        timer.setDelay(1000);
-        timer.setTimeUnit(TimeUnit.MILLISECONDS);
-        timer.setSchedulingType(SchedulingType.FIXED_RATE);
-        timer.setAction(new ThreadTableControllerAction());
-        
-        threadTableView.addActionListener(new ActionListener<Action>() {
-            @Override
-            public void actionPerformed(ActionEvent<Action> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case VISIBLE:
-                    timer.start();
-                    break;
-
-                case HIDDEN:
-                    timer.stop();
-                    break;
-
-                default:
-                    break;
-                }
-            }
-        });
-    }
     
     private class ThreadTableControllerAction implements Runnable {
         @Override
@@ -110,15 +79,8 @@
                 // first, get a map of all threads with the respective info
                 // the list will contain an ordered-by-timestamp list
                 // with the known history for each thread
-                Map<ThreadInfoData, List<ThreadInfoData>> stats = new HashMap<>();
-                for (ThreadInfoData info : infos) {
-                    List<ThreadInfoData> beanList = stats.get(info);
-                    if (beanList == null) {
-                        beanList = new ArrayList<ThreadInfoData>();
-                        stats.put(info, beanList);
-                    }                    
-                    beanList.add(info);
-                }
+                Map<ThreadInfoData, List<ThreadInfoData>> stats =
+                        ThreadInfoHelper.getThreadInfoDataMap(infos);
                 
                 List<ThreadTableBean> tableBeans = new ArrayList<>();
                 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineController.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineView;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public class ThreadTimelineController extends CommonController {
+
+    private ThreadTimelineView view;
+    private ThreadCollector collector;
+    
+    public ThreadTimelineController(ThreadTimelineView view, ThreadCollector collector, Timer timer) {
+        super(timer, view);
+        timer.setAction(new ThreadTimelineControllerAction());
+        this.view = view;
+        this.collector = collector;
+    }
+
+    private class ThreadTimelineControllerAction implements Runnable {
+        @Override
+        public void run() {
+            List<ThreadInfoData> infos = collector.getThreadInfo();
+            if(infos.size() > 0) {
+                
+                Map<ThreadInfoData, List<ThreadTimelineBean>> timelines = new HashMap<>();
+                
+                Map<ThreadInfoData, List<ThreadInfoData>> stats =
+                        ThreadInfoHelper.getThreadInfoDataMap(infos);
+                for (List<ThreadInfoData> beanList : stats.values()) {
+                    
+                    // the list is ordered in most recent first
+                    // the first element is the latest sample we have of this
+                    // thread, so we use it as stop time. 
+                    
+                    ThreadInfoData lastThreadInfo = beanList.get(beanList.size() - 1);
+                    Thread.State lastState = lastThreadInfo.getState();
+                    
+                    ThreadTimelineBean timeline = new ThreadTimelineBean();
+                    timeline.setName(lastThreadInfo.getThreadName());
+                    timeline.setState(lastThreadInfo.getState());
+                    timeline.setStartTime(lastThreadInfo.getTimeStamp()); 
+                    
+                    long stopTime = beanList.get(0).getTimeStamp();
+                    timeline.setStopTime(stopTime);
+                    
+                    Stack<ThreadTimelineBean> threadTimelines = new Stack<ThreadTimelineBean>();
+                    timelines.put(lastThreadInfo, threadTimelines);
+                    threadTimelines.push(timeline);
+                    
+                    for (int i = beanList.size() - 1; i >= 0; i--) {
+                        ThreadInfoData threadInfo = beanList.get(i);
+                        
+                        Thread.State currentState = threadInfo.getState();
+                        if (currentState != lastState) {
+                            lastState = currentState;
+                            
+                            timeline = threadTimelines.pop();
+                            timeline.setStopTime(threadInfo.getTimeStamp());
+                            
+                            threadTimelines.push(timeline);
+                            
+                            timeline = new ThreadTimelineBean();
+                            timeline.setName(threadInfo.getThreadName());
+                            timeline.setState(threadInfo.getState());
+                            timeline.setStartTime(threadInfo.getTimeStamp());
+                            timeline.setStopTime(stopTime);
+
+                            lastThreadInfo = threadInfo;
+                            lastState = currentState;
+                            
+                            // add the new thread stat
+                            threadTimelines.push(timeline);
+                        }
+                    }
+                }
+                
+                view.displayStats(timelines, infos.get(infos.size() - 1).getTimeStamp(), infos.get(0).getTimeStamp());
+            }
+        }
+    }
+}
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/VMThreadCapabilitiesController.java	Wed Sep 19 10:08:17 2012 +0200
@@ -43,7 +43,7 @@
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
 import com.redhat.thermostat.thread.model.VMThreadCapabilities;
 
-public class VMThreadCapabilitiesController implements CommonController {
+public class VMThreadCapabilitiesController {
 
     private ThreadCollector collector;
     private VMThreadCapabilitiesView view;
@@ -53,7 +53,6 @@
         this.collector = collector;
     }
     
-    @Override
     public void initialize() {
         view.addActionListener(new ActionListener<Action>() {
             @Override
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInfoHelperTest.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public class ThreadInfoHelperTest {
+
+    @Test
+    public void verifyMap() {
+        ThreadInfoData data1 = new ThreadInfoData();
+        data1.setThreadName("test1");
+        data1.setThreadId(1);
+        data1.setState(Thread.State.RUNNABLE);
+        
+        ThreadInfoData data2 = new ThreadInfoData();
+        data2.setThreadName("test2");
+        data2.setThreadId(2);
+        data2.setState(Thread.State.BLOCKED);
+        
+        ThreadInfoData data3 = new ThreadInfoData();
+        data3.setThreadName("test1");
+        data3.setThreadId(1);
+        data3.setState(Thread.State.TIMED_WAITING);
+        
+        ThreadInfoData data4 = new ThreadInfoData();
+        data4.setThreadName("test2");
+        data4.setThreadId(2);
+        data4.setState(Thread.State.RUNNABLE);
+        
+        List<ThreadInfoData> infos = new ArrayList<>();
+        infos.add(data1);
+        infos.add(data2);
+        infos.add(data3);
+        infos.add(data4);
+        
+        Map<ThreadInfoData, List<ThreadInfoData>> result = ThreadInfoHelper.getThreadInfoDataMap(infos);
+        assertEquals(2, result.size());
+        
+        assertTrue(result.containsKey(data1));
+        assertTrue(result.containsKey(data2));
+        
+        assertEquals(2, result.get(data1).size());
+        assertEquals(2, result.get(data2).size());
+        
+        assertEquals(Thread.State.RUNNABLE, result.get(data1).get(0).getState());
+        assertEquals(Thread.State.TIMED_WAITING, result.get(data1).get(1).getState());       
+
+        assertEquals(Thread.State.BLOCKED, result.get(data2).get(0).getState());
+        assertEquals(Thread.State.RUNNABLE, result.get(data2).get(1).getState());
+    }
+}
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Wed Sep 19 10:08:17 2012 +0200
@@ -60,6 +60,7 @@
 import com.redhat.thermostat.thread.client.common.ThreadTableBean;
 import com.redhat.thermostat.thread.client.common.ThreadTableView;
 import com.redhat.thermostat.thread.client.common.ThreadTableView.ThreadSelectionAction;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineView;
 import com.redhat.thermostat.thread.client.common.ThreadView;
 import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
@@ -80,6 +81,7 @@
 
     private ThreadTableView threadTableView;
     private VMThreadCapabilitiesView threadCapsView;
+    private ThreadTimelineView threadTimelineView;
     
     @Before
     public void setUp() {
@@ -96,13 +98,15 @@
     private void setUpView() {
         threadCapsView = mock(VMThreadCapabilitiesView.class);
         threadTableView = mock(ThreadTableView.class);
-
+        threadTimelineView = mock(ThreadTimelineView.class);
+        
         view = mock(ThreadView.class);
         viewFactory = mock(ThreadViewProvider.class);
         when(viewFactory.createView()).thenReturn(view);
         
         when(view.createVMThreadCapabilitiesView()).thenReturn(threadCapsView);
         when(view.createThreadTableView()).thenReturn(threadTableView);
+        when(view.createThreadTimelineView()).thenReturn(threadTimelineView);
     }
     
     private void setUpTimers() {
@@ -152,6 +156,7 @@
         
         verify(view).createThreadTableView();
         verify(view).createVMThreadCapabilitiesView();
+        verify(view).createThreadTimelineView();
     }
     
     @Test
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTableControllerTest.java	Wed Sep 19 10:08:17 2012 +0200
@@ -68,7 +68,7 @@
         collector = mock(ThreadCollector.class);
         
         timer = mock(Timer.class);
-                
+
         view = mock(ThreadTableView.class);
         
         setUpTimers();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadTimelineControllerTest.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.controller.impl;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.osgi.service.BasicView;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.appctx.ApplicationContextUtil;
+import com.redhat.thermostat.thread.client.common.ThreadTableView;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineView;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public class ThreadTimelineControllerTest {
+
+    private ThreadTimelineView view;
+    private ThreadCollector collector;
+    
+    private Timer timer;
+    
+    private ActionListener<ThreadTableView.Action> actionListener;
+    ArgumentCaptor<Runnable> timerActionCaptor;
+    
+    @Before
+    public void setUp() {
+        ApplicationContextUtil.resetApplicationContext();
+        collector = mock(ThreadCollector.class);
+        
+        timer = mock(Timer.class);
+        
+        view = mock(ThreadTimelineView.class);
+        
+        setUpTimers();
+    }
+    
+    private void setUpTimers() {
+        timer = mock(Timer.class);
+        timerActionCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(timerActionCaptor.capture());
+    }
+    
+    @Test
+    public void testDisplayStats() {
+        
+        ThreadInfoData data1 = new ThreadInfoData();
+        data1.setThreadName("test1");
+        data1.setThreadId(1);
+        data1.setState(Thread.State.RUNNABLE);
+        data1.setTimeStamp(100);
+        
+        ThreadInfoData data2 = new ThreadInfoData();
+        data2.setThreadName("test2");
+        data2.setThreadId(2);
+        data2.setTimeStamp(1000);
+        data2.setState(Thread.State.BLOCKED);
+        
+        ThreadInfoData data3 = new ThreadInfoData();
+        data3.setThreadName("test1");
+        data3.setThreadId(1);
+        data3.setState(Thread.State.TIMED_WAITING);
+        data3.setTimeStamp(200);
+        
+        ThreadInfoData data4 = new ThreadInfoData();
+        data4.setThreadName("test2");
+        data4.setThreadId(2);
+        data4.setState(Thread.State.BLOCKED);
+        data4.setTimeStamp(2000);
+        
+        ThreadInfoData data5 = new ThreadInfoData();
+        data5.setThreadName("test2");
+        data5.setThreadId(2);
+        data5.setState(Thread.State.RUNNABLE);
+        data5.setTimeStamp(3000);
+        
+        List<ThreadInfoData> infos = new ArrayList<>();
+        // descending order
+        infos.add(data5);
+        infos.add(data4);
+        infos.add(data2);
+        infos.add(data3);
+        infos.add(data1);
+
+        when(collector.getThreadInfo()).thenReturn(infos);
+        
+        ArgumentCaptor<Runnable> timerCaptor = ArgumentCaptor.forClass(Runnable.class);
+        doNothing().when(timer).setAction(timerCaptor.capture());
+        
+        ThreadTimelineController controller = new ThreadTimelineController(view, collector, timer);
+        controller.initialize();
+        
+        Runnable action = timerCaptor.getValue();
+        action.run();
+        
+        ArgumentCaptor<Map> mapCaptor = ArgumentCaptor.forClass(Map.class);
+        
+        verify(view).displayStats(mapCaptor.capture(), anyLong(), anyLong());
+        
+        Map viewResult = mapCaptor.getValue();
+        assertEquals(2, viewResult.size());
+        
+        List<ThreadTimelineBean> beanList = (List<ThreadTimelineBean>) viewResult.get(data1);
+        
+        assertEquals(2, beanList.size());
+        
+        assertEquals("test1", beanList.get(0).getName());
+        assertEquals("test1", beanList.get(1).getName());
+
+        beanList = (List<ThreadTimelineBean>) viewResult.get(data2);
+        assertEquals(2, beanList.size());
+        
+        assertEquals("test2", beanList.get(0).getName());
+        assertEquals("test2", beanList.get(1).getName());
+
+        assertEquals(1000, beanList.get(0).getStartTime());
+        assertEquals(3000, beanList.get(0).getStopTime());
+        assertEquals(Thread.State.BLOCKED, beanList.get(0).getState());
+
+        assertEquals(3000, beanList.get(1).getStartTime());
+        assertEquals(Thread.State.BLOCKED, beanList.get(0).getState());        
+    }
+
+    @Test
+    public void testStartThreadTimelineController() {
+        
+        ArgumentCaptor<ActionListener> viewArgumentCaptor = ArgumentCaptor.forClass(ActionListener.class);
+        doNothing().when(view).addActionListener(viewArgumentCaptor.capture());
+        
+        ThreadTimelineController controller = new ThreadTimelineController(view, collector, timer);
+        controller.initialize();
+
+        actionListener = viewArgumentCaptor.getValue();
+        actionListener.actionPerformed(new ActionEvent<>(view, BasicView.Action.VISIBLE));
+        
+        verify(timer).start();
+        
+        actionListener.actionPerformed(new ActionEvent<>(view, BasicView.Action.HIDDEN));
+
+        verify(timer).stop();
+    }
+}
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadDetailsView.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadDetailsView.java	Wed Sep 19 10:08:17 2012 +0200
@@ -36,8 +36,11 @@
 
 package com.redhat.thermostat.thread.client.swing.impl;
 
+import java.awt.BorderLayout;
 import java.awt.Component;
 
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
 import javax.swing.JPanel;
 
 import com.redhat.thermostat.client.ui.SwingComponent;
@@ -48,15 +51,6 @@
 import com.redhat.thermostat.thread.client.common.chart.ThreadDeatailsPieChart;
 import com.redhat.thermostat.thread.client.common.locale.LocaleResources;
 
-import javax.swing.GroupLayout;
-import javax.swing.GroupLayout.Alignment;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
-import java.awt.GridBagLayout;
-import java.awt.GridBagConstraints;
-import java.awt.BorderLayout;
-import java.awt.GridLayout;
-
 public class SwingThreadDetailsView extends ThreadDetailsView implements SwingComponent {
 
     private JPanel details;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineChart.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Shape;
+import java.awt.geom.Area;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.swing.GradientRoundBorder;
+import com.redhat.thermostat.swing.GraphicsUtils;
+import com.redhat.thermostat.swing.Palette;
+import com.redhat.thermostat.swing.models.LongRange;
+import com.redhat.thermostat.swing.models.LongRangeNormalizer;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+
+@SuppressWarnings("serial")
+public class SwingThreadTimelineChart extends JPanel {
+
+    private List<ThreadTimelineBean> timeline;
+    
+    private LongRangeNormalizer normalizer;
+
+    public SwingThreadTimelineChart(List<ThreadTimelineBean> timeline, long rangeStart, long rangeStop) {
+        this.timeline = timeline;
+    
+        LongRange range = new LongRange();
+        range.setMin(rangeStart);
+        range.setMax(rangeStop);
+        
+        setBorder(new GradientRoundBorder());
+        normalizer = new LongRangeNormalizer(range);
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        
+        normalizer.setMinNormalized(0);
+        normalizer.setMaxNormalized(getWidth() - 3);
+        
+        GraphicsUtils utils = GraphicsUtils.getInstance();
+        
+        Graphics2D graphics = utils.createAAGraphics(g);
+        
+        int y = getHeight()/3;
+        graphics.clearRect(0, 0, getWidth(), getHeight());
+        graphics.drawString(timeline.get(0).getName(), 2, y);
+        
+        y = getHeight()/2;
+        
+        int w = getWidth() - 4;
+        int h = 10;
+        
+        Shape shape = utils.getRoundShape(w, h);
+        
+        graphics.translate(2, y);
+
+        Paint paint = new GradientPaint(0, 0, Palette.DARK_GRAY.getColor(), 0, h, Palette.WHITE.getColor());
+        graphics.setPaint(paint);        
+        graphics.fill(shape);        
+
+        for (ThreadTimelineBean thread : timeline) {
+            normalizer.setValue(thread.getStartTime());
+            int x0 = (int) normalizer.getValueNormalized();
+
+            normalizer.setValue(thread.getStopTime());
+            int x1 = (int) normalizer.getValueNormalized();
+
+            graphics.setColor(getColor(thread));
+            graphics.fillRect(x0, 1, x1 - x0, h - 1);
+        }
+        
+        graphics.setColor(getColor(timeline.get(timeline.size() - 1)));
+        graphics.draw(shape);        
+        graphics.dispose();
+    }
+
+    private Color getColor(ThreadTimelineBean thread) {
+        Color result = null;
+        
+        switch (thread.getState()) {
+        case TIMED_WAITING:
+            result = Palette.PALE_RED.getColor();
+            break;
+            
+        case NEW:
+            result = Palette.POMP_AND_POWER_VIOLET.getColor();
+            break;
+
+        case RUNNABLE:
+            result = Palette.PRUSSIAN_BLUE.getColor();
+            break;
+
+        case TERMINATED:
+            result = Palette.GRAY.getColor();
+            break;
+
+        case BLOCKED:
+            result = Palette.RED.getColor();            
+            break;
+
+        case WAITING:
+            result = Palette.GRANITA_ORANGE.getColor();            
+            break;
+
+        default:
+            result = Color.BLACK;            
+            break;
+        }
+        return result;
+    }
+    
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
+            
+            @Override
+            public void run() {
+                JFrame frame = new JFrame();
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                
+                ThreadTimelineBean bean1 = new ThreadTimelineBean();
+                bean1.setName("test");
+                bean1.setStartTime(0);
+                bean1.setStopTime(1000);
+                bean1.setState(Thread.State.BLOCKED);
+                
+                ThreadTimelineBean bean2 = new ThreadTimelineBean();
+                bean2.setName("test");
+                bean2.setStartTime(1000);
+                bean2.setStopTime(2000);
+                bean2.setState(Thread.State.RUNNABLE);
+                
+                ThreadTimelineBean bean3 = new ThreadTimelineBean();
+                bean3.setName("test");
+                bean3.setStartTime(2000);
+                bean3.setStopTime(2100);
+                bean3.setState(Thread.State.TIMED_WAITING);
+                
+                List<ThreadTimelineBean> timeline = new ArrayList<>();
+                timeline.add(bean1);
+                timeline.add(bean2);
+                timeline.add(bean3);
+
+                SwingThreadTimelineChart chart = new SwingThreadTimelineChart(timeline, 0, 2500);
+                
+                frame.add(chart);
+                frame.setSize(800, 150);
+                
+                frame.setVisible(true);
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+
+import com.redhat.thermostat.client.ui.ComponentVisibleListener;
+import com.redhat.thermostat.client.ui.SwingComponent;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineBean;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineView;
+import com.redhat.thermostat.thread.model.ThreadInfoData;
+
+public class SwingThreadTimelineView extends ThreadTimelineView  implements SwingComponent  {
+
+    private JPanel timeLinePanel;
+    private DefaultListModel<SwingThreadTimelineChart> model;
+    
+    public SwingThreadTimelineView() {
+        timeLinePanel = new JPanel();
+        timeLinePanel.addHierarchyListener(new ComponentVisibleListener() {
+            @Override
+            public void componentShown(Component component) {
+                SwingThreadTimelineView.this.notify(Action.VISIBLE);
+            }
+            
+            @Override
+            public void componentHidden(Component component) {
+                SwingThreadTimelineView.this.notify(Action.HIDDEN);
+            }
+        });
+        
+        timeLinePanel.setLayout(new BorderLayout(0, 0));
+        model = new DefaultListModel<>();
+        JList<SwingThreadTimelineChart> chartList = new JList<>(model);
+        
+        chartList.setLayoutOrientation(JList.VERTICAL);
+        chartList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+        JScrollPane scrollPane = new JScrollPane(chartList);
+        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+        timeLinePanel.add(scrollPane);
+        chartList.setCellRenderer(new ListCellRenderer<SwingThreadTimelineChart>() {
+            @Override
+            public Component getListCellRendererComponent(
+                    JList<? extends SwingThreadTimelineChart> list, SwingThreadTimelineChart value,
+                    int index, boolean isSelected, boolean cellHasFocus) {
+                return value;
+            }
+        });
+    }
+    
+    @Override
+    public void displayStats(final Map<ThreadInfoData, List<ThreadTimelineBean>> timelines, final long start, final long stop) {
+
+        SwingUtilities.invokeLater(new Runnable() {
+            
+            @Override
+            public void run() {
+                model.clear();
+                for (List<ThreadTimelineBean> timeline : timelines.values()) {
+                    SwingThreadTimelineChart panel = new SwingThreadTimelineChart(timeline, start, stop);
+                    panel.setPreferredSize(new Dimension(timeLinePanel.getWidth(), 50));
+                    model.addElement(panel);
+                }
+            }
+        });
+    }
+    
+    @Override
+    public Component getUiComponent() {
+        return timeLinePanel;
+    }
+}
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Tue Sep 18 17:38:29 2012 -0400
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Wed Sep 19 10:08:17 2012 +0200
@@ -56,6 +56,7 @@
 import com.redhat.thermostat.swing.ChartPanel;
 import com.redhat.thermostat.thread.client.common.ThreadTableBean;
 import com.redhat.thermostat.thread.client.common.ThreadTableView;
+import com.redhat.thermostat.thread.client.common.ThreadTimelineView;
 import com.redhat.thermostat.thread.client.common.ThreadView;
 import com.redhat.thermostat.thread.client.common.VMThreadCapabilitiesView;
 import com.redhat.thermostat.thread.client.common.chart.LivingDaemonThreadDifferenceChart;
@@ -70,8 +71,9 @@
     
     private SwingThreadTableView threadTableView;
     private SwingVMThreadCapabilitiesView vmCapsView;
+    private SwingThreadTimelineView threadTimelineView;
     private SwingThreadDetailsView threadDetailsView;
-    
+
     private JTabbedPane pane;
     
     private static final Translate t = LocaleResources.createLocalizer();
@@ -152,6 +154,9 @@
         pane.addTab(t.localize(LocaleResources.DETAILS), threadDetailsView.getUiComponent());
         threadDetailsPaneID = 2;
         
+        threadTimelineView = new SwingThreadTimelineView();
+        pane.addTab(t.localize(LocaleResources.TIMELINE), threadTimelineView.getUiComponent());
+        
         panel.getSplitPane().setBottomComponent(pane);
     }
     
@@ -261,4 +266,9 @@
             }
         });
     }
+    
+    @Override
+    public ThreadTimelineView createThreadTimelineView() {
+        return threadTimelineView;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/ThreadTimelineChart.java	Wed Sep 19 10:08:17 2012 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.thread.client.swing.impl;
+
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.plaf.ColorUIResource;
+
+import com.redhat.thermostat.swing.GraphicsUtils;
+
+@SuppressWarnings("serial")
+public class ThreadTimelineChart extends JPanel {
+
+    private static final ColorUIResource TICK_COLOR = new ColorUIResource(0xa8aca8);
+
+    public ThreadTimelineChart() {
+    }
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
+        
+        graphics.setColor(TICK_COLOR);
+        
+        int x = 1;
+        int y = 2;
+        int w = getWidth() - 2;
+        int h = getHeight();
+        
+        graphics.drawLine(x, y, w, y);
+        
+        graphics.dispose();
+    }
+    
+    public static void main(String[] args) {
+        SwingUtilities.invokeLater(new Runnable() {
+            
+            @Override
+            public void run() {
+                JFrame frame = new JFrame();
+                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                
+                ThreadTimelineChart chart = new ThreadTimelineChart();
+                
+                frame.add(chart);
+                frame.setSize(500, 500);
+                frame.setVisible(true);
+            }
+        });
+    }
+}