changeset 1932:9e860337de11

Backport StackTraceProfiler review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-June/019712.html reviewwed-by: jerboaa PR3037
author Mario Torre <neugens.limasoftware@gmail.com>
date Thu, 23 Jun 2016 14:51:53 +0200
parents d62943ef182a
children c5dc33321342
files experimental/components/pom.xml thread/client-common/pom.xml thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/StackTraceProfilerView.java thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.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/SessionCheckingAction.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/StackTraceProfilerController.java thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/SessionCheckingActionTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/StackTraceProfilerControllerTest.java thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java thread/client-swing/pom.xml thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingStackTraceProfilerView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java thread/distribution/thermostat-plugin.xml
diffstat 16 files changed, 838 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/experimental/components/pom.xml	Thu Jun 23 14:51:52 2016 +0200
+++ b/experimental/components/pom.xml	Thu Jun 23 14:51:53 2016 +0200
@@ -103,8 +103,7 @@
                 <configuration>
                     <instructions>
                         <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-                        <Bundle-SymbolicName>com.redhat.thermostat.experimental.components
-                        </Bundle-SymbolicName>
+                        <Bundle-SymbolicName>com.redhat.thermostat.experimental.components</Bundle-SymbolicName>
                         <Export-Package>
                             com.redhat.thermostat.experimental.components,
                             com.redhat.thermostat.experimental.components.graph,
--- a/thread/client-common/pom.xml	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-common/pom.xml	Thu Jun 23 14:51:53 2016 +0200
@@ -101,7 +101,20 @@
       <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
-        
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-experimental-collections</artifactId>
+      <version>1.5.8-SNAPSHOT</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-experimental-components</artifactId>
+      <version>${parent.version}</version>
+    </dependency>
+
   </dependencies>
 
   <build>
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/locale/LocaleResources.java	Thu Jun 23 14:51:53 2016 +0200
@@ -84,6 +84,8 @@
     ZOOM_IN,
     ZOOM_OUT,
 
+    STACK_TRACE_PROFILER,
+
     ;
 
     public static final String RESOURCE_BUNDLE =
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/StackTraceProfilerView.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012-2016 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.view;
+
+import com.redhat.thermostat.client.core.views.BasicView;
+import com.redhat.thermostat.experimental.model.Trace;
+
+/**
+ */
+public abstract class StackTraceProfilerView extends BasicView {
+    public abstract void createModel(String modelID);
+    public abstract void rebuild();
+    public abstract void addTrace(Trace trace);
+    public abstract void clear();
+}
--- a/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.java	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-common/src/main/java/com/redhat/thermostat/thread/client/common/view/ThreadView.java	Thu Jun 23 14:51:53 2016 +0200
@@ -125,5 +125,6 @@
 
     public abstract VmDeadLockView createDeadLockView();
     public abstract void displayTimelineSessionList(List<ThreadSession> threadSessions);
+
+    public abstract StackTraceProfilerView createStackTraceProfilerView();
 }
-
--- a/thread/client-common/src/main/resources/com/redhat/thermostat/thread/client/common/locale/strings.properties	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-common/src/main/resources/com/redhat/thermostat/thread/client/common/locale/strings.properties	Thu Jun 23 14:51:53 2016 +0200
@@ -43,3 +43,5 @@
 THREAD_MONITOR_DISPLAY_SESSIONS = List Recordings
 RECORDING_LIST = RECORDING_LIST = List of Recorded Thread Timelines
 RECORDING_LIST_HINT = Open the list of Recoded Thread Timeline Sessions
+
+STACK_TRACE_PROFILER = Stack Trace Profiler
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/SessionCheckingAction.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2012-2016 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 com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.thread.model.SessionID;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ */
+public abstract class SessionCheckingAction implements Runnable {
+
+    private SessionID lastSession;
+    private long lastUpdate;
+
+    public SessionCheckingAction() {
+        lastUpdate  = getTimeDeltaOnNewSession();
+    }
+
+    protected long getTimeDeltaOnNewSession() {
+        return System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1);
+    }
+
+    /**
+     * Returns the sessions ID the user wants to track, which can be null.
+     */
+    protected abstract SessionID getCurrentSessionID();
+
+    /**
+     * Returns the last available sessions ID. This is typically the sessions
+     * with the most recent timestamp from a set of sessions.
+     */
+    protected abstract SessionID getLastAvailableSessionID() ;
+
+    /**
+     * Returns the sessions id to track by this action, which is either the
+     * session returned by {@link #getCurrentSessionID()} or, if null,
+     * the one returned by {@link #getLastAvailableSessionID()}.
+     */
+    protected SessionID getSessionID() {
+        SessionID session = getCurrentSessionID();
+        if (session == null) {
+            // no session selected, but let's try to default to the last
+            // available
+            session = getLastAvailableSessionID();
+        }
+        return session;
+    }
+
+
+    /**
+     * The actual action to perform.
+     */
+    protected abstract void actionPerformed(SessionID session,
+                                            Range<Long> range,
+                                            Range<Long> totalRange);
+
+    protected abstract Range<Long> getTotalRange(SessionID session);
+
+    protected Range<Long> getRange(SessionID session, Range<Long> totalRange) {
+        if (totalRange == null) {
+            // there is range to check
+            return null;
+        }
+
+        if (lastUpdate == totalRange.getMax()) {
+            // we already covered this range
+            return null;
+        }
+
+        Range<Long> result = new Range<>(lastUpdate, totalRange.getMax());
+        lastUpdate = totalRange.getMax();
+        return result;
+    }
+
+    protected boolean isRangeValid(Range<Long> range, Range<Long> totalRange) {
+        return totalRange != null && range != null;
+    }
+
+    protected boolean isSessionValid(SessionID session) {
+        return session != null;
+    }
+
+    /**
+     * Determine if the session in input is a new session. By default the
+     * implementation checks the sessions against the last session.
+     */
+    protected boolean isNewSession(SessionID session) {
+        boolean result = (lastSession == null || !session.get().equals(lastSession.get()));
+        lastSession = session;
+        return result;
+    }
+
+    /**
+     * Called when a new session is detected, as defined by
+     * {@link #isNewSession(SessionID)}.
+     */
+    protected void onNewSession() {}
+
+    @Override
+    public void run() {
+        SessionID session = getSessionID();
+        if (!isSessionValid(session)) {
+            return;
+        }
+
+        if (isNewSession(session)) {
+            lastUpdate = getTimeDeltaOnNewSession();
+            onNewSession();
+        }
+
+        Range<Long> totalRange = getTotalRange(session);
+        Range<Long> range = getRange(session, totalRange);
+        if (isRangeValid(range, totalRange)) {
+            actionPerformed(session, range, totalRange);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/StackTraceProfilerController.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012-2016 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 com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.experimental.model.Trace;
+import com.redhat.thermostat.experimental.model.TraceElement;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.core.experimental.statement.ResultHandler;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.view.StackTraceProfilerView;
+import com.redhat.thermostat.thread.model.SessionID;
+import com.redhat.thermostat.thread.model.StackFrame;
+import com.redhat.thermostat.thread.model.StackTrace;
+import com.redhat.thermostat.thread.model.ThreadState;
+
+import java.util.List;
+
+/**
+ */
+public class StackTraceProfilerController extends CommonController {
+
+    private ThreadCollector collector;
+    private StackTraceProfilerView stackTraceProfilerView;
+    public StackTraceProfilerController(StackTraceProfilerView view,
+                                        ThreadCollector collector,
+                                        Timer timer,
+                                        VmRef ref)
+    {
+        super(timer, view);
+        this.stackTraceProfilerView = view;
+        this.collector = collector;
+
+        view.createModel(ref.getName());
+
+        timer.setAction(new StackTraceProfilerControllerAction());
+    }
+
+    private class StackTraceProfilerControllerAction extends SessionCheckingAction {
+
+        private ThreadStateResultHandler threadStateResultHandler;
+
+        public StackTraceProfilerControllerAction() {
+            threadStateResultHandler = new ThreadStateResultHandler();
+            resetState();
+        }
+
+        private void resetState() {
+            stackTraceProfilerView.clear();
+        }
+
+        @Override
+        protected SessionID getCurrentSessionID() {
+            return session;
+        }
+
+        @Override
+        protected SessionID getLastAvailableSessionID() {
+            return collector.getLastThreadSession();
+        }
+
+        @Override
+        protected Range<Long> getTotalRange(SessionID session) {
+            return collector.getThreadRange(session);
+        }
+
+        @Override
+        protected void actionPerformed(SessionID session, Range<Long> range,
+                                       Range<Long> totalRange)
+        {
+            collector.getThreadStates(session,  threadStateResultHandler, range);
+            stackTraceProfilerView.rebuild();
+        }
+
+        @Override
+        protected void onNewSession() {
+            resetState();
+        }
+    }
+
+    // for testing
+    StackTrace getStackTrace(ThreadState state) {
+        return StackTrace.fromJson(state.getStackTrace());
+    }
+
+    private volatile boolean stopLooping;
+
+    @Override
+    protected void onViewVisible() {
+        stopLooping = false;
+    }
+
+    @Override
+    protected void onViewHidden() {
+        stopLooping = true;
+    }
+
+    private class ThreadStateResultHandler implements ResultHandler<ThreadState> {
+
+        @Override
+        public boolean onResult(ThreadState state) {
+
+            Thread.State threadState = Thread.State.valueOf(state.getState());
+            switch (threadState) {
+                // all other cases, we want to track this thread
+                case BLOCKED:
+                case WAITING:
+                case TIMED_WAITING:
+                case TERMINATED:
+                    return true;
+            }
+
+            StackTrace stackTrace = getStackTrace(state);
+
+            Trace trace = new Trace(state.getName());
+            List<StackFrame> frames = stackTrace.getFrames();
+            int frameID = frames.size() - 1;
+
+            while (frameID >= 0) {
+                StackFrame frame = frames.get(frameID);
+                frameID--;
+
+                TraceElement element =
+                        new TraceElement(frame.getClassName()  + "." +
+                                                 frame.getMethodName() + ":" +
+                                                 frame.getLineNumber());
+                trace.add(element);
+            }
+
+            stackTraceProfilerView.addTrace(trace);
+
+            boolean _stopLooping = stopLooping;
+            return !_stopLooping;
+        }
+    }
+}
--- a/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-controllers/src/main/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationController.java	Thu Jun 23 14:51:53 2016 +0200
@@ -86,6 +86,7 @@
     private CommonController threadTableController;
     private CommonController threadCountController;
     private VmDeadLockController deadLockController;
+    private CommonController stackTraceProfilerController;
 
     public ThreadInformationController(VmRef ref, ApplicationService appService,
                                        VmInfoDAO vmInfoDao,
@@ -152,6 +153,7 @@
                     if (session != null) {
                         threadTimeline.setSession(session.getSessionID());
                         threadTableController.setSession(session.getSessionID());
+                        stackTraceProfilerController.setSession(session.getSessionID());
                     }
             } break;
 
@@ -273,6 +275,14 @@
                                                       tf.createTimer());
         threadTimeline.setSession(lastThreadSession);
         threadTimeline.initialize();
+
+        stackTraceProfilerController =
+                new StackTraceProfilerController(view.createStackTraceProfilerView(),
+                                                 collector,
+                                                 tf.createTimer(),
+                                                 ref);
+        stackTraceProfilerController.initialize();
+        stackTraceProfilerController.setSession(lastThreadSession);
     }
 
     void ___injectControllersForTesting(ThreadTimelineController timeline,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/SessionCheckingActionTest.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012-2016 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 com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.thread.model.SessionID;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ */
+public class SessionCheckingActionTest {
+
+    private static final long ONE_HOUR = TimeUnit.HOURS.toMillis(1);
+
+    @Test
+    public void getRangeOnlyReturnsDelta() throws Exception {
+
+        final long[] delta = new long[1];
+        final Range<Long> [] results = new Range [2];
+
+        SessionCheckingAction action = new SessionCheckingAction() {
+
+            @Override
+            protected Range<Long> getTotalRange(SessionID session) {
+                Range<Long> range = new Range<>(0l, delta[0] + ONE_HOUR);
+                return range;
+            }
+
+            @Override
+            protected boolean isNewSession(SessionID session) {
+                return false;
+            }
+
+            @Override
+            protected boolean isSessionValid(SessionID session) {
+                return true;
+            }
+
+            @Override
+            protected long getTimeDeltaOnNewSession() {
+                return 0;
+            }
+
+            @Override
+            protected SessionID getCurrentSessionID() {
+                return null;
+            }
+
+            @Override
+            protected SessionID getLastAvailableSessionID() {
+                return null;
+            }
+
+            @Override
+            protected void actionPerformed(SessionID session, Range<Long> range, Range<Long> totalRange) {
+                results[0] = range;
+                results[1] = totalRange;
+            }
+        };
+
+        action.run();
+
+        Range<Long> range = results[0];
+        Range<Long> totalRange = results[1];
+
+        // the first run, we should get the full range
+        assertEquals(range, totalRange);
+
+        // simulate an elapsed second
+        delta[0] = 1000;
+        long lastMax = totalRange.getMax();
+
+        action.run();
+
+        range = results[0];
+        totalRange = results[1];
+
+        assertEquals(lastMax, (long) range.getMin());
+        assertEquals((long) totalRange.getMax(), (long) range.getMax());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/StackTraceProfilerControllerTest.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012-2016 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 com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.Timer;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.experimental.model.Trace;
+import com.redhat.thermostat.storage.core.VmRef;
+import com.redhat.thermostat.storage.core.experimental.statement.ResultHandler;
+import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
+import com.redhat.thermostat.thread.client.common.view.StackTraceProfilerView;
+import com.redhat.thermostat.thread.model.SessionID;
+import com.redhat.thermostat.thread.model.StackFrame;
+import com.redhat.thermostat.thread.model.StackTrace;
+import com.redhat.thermostat.thread.model.ThreadState;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ */
+public class StackTraceProfilerControllerTest {
+    private Timer timer;
+    private Runnable threadAction;
+    private StackTraceProfilerView view;
+    private ThreadCollector collector;
+
+    private ActionListener<StackTraceProfilerView.Action> actionListener;
+
+    private VmRef ref;
+
+    private static final String VM_ID = "42";
+
+    private SessionID session;
+
+    @Before
+    public void setUp() {
+        timer = mock(Timer.class);
+        view = mock(StackTraceProfilerView.class);
+        collector = mock(ThreadCollector.class);
+        ref = mock(VmRef.class);
+        when(ref.getName()).thenReturn(VM_ID);
+
+        session = mock(SessionID.class);
+    }
+
+    @Test
+    public void testModelCreated() {
+        StackTraceProfilerController profiler =
+                new StackTraceProfilerController(view, collector, timer, ref);
+
+        verify(view).createModel(VM_ID);
+    }
+
+    @Test
+    public void testGetThreadTraces() {
+
+        final ThreadState state0 = mock(ThreadState.class);
+        when(state0.getName()).thenReturn("state0");
+        when(state0.getId()).thenReturn(0l);
+        when(state0.getState()).thenReturn("NEW");
+        final StackTrace trace0 = mock(StackTrace.class);
+
+        ThreadState state1 = mock(ThreadState.class);
+        when(state1.getName()).thenReturn("state1");
+        when(state1.getId()).thenReturn(1l);
+        when(state1.getState()).thenReturn("BLOCKED");
+        StackTrace trace1 = mock(StackTrace.class);
+
+        ThreadState state2 = mock(ThreadState.class);
+        when(state2.getName()).thenReturn("state2");
+        when(state2.getId()).thenReturn(2l);
+        when(state2.getState()).thenReturn("WAITING");
+        StackTrace trace2 = mock(StackTrace.class);
+
+        ArgumentCaptor<SessionCheckingAction> captor = ArgumentCaptor.forClass(SessionCheckingAction.class);
+        doNothing().when(timer).setAction(captor.capture());
+        ArgumentCaptor<ResultHandler> resultHandlerCaptor = ArgumentCaptor.forClass(ResultHandler.class);
+
+        Range<Long> range = new Range<>(0l, 10l);
+        when(collector.getThreadRange(session)).thenReturn(range);
+
+        StackTraceProfilerController profiler =
+                new StackTraceProfilerController(view, collector, timer, ref) {
+                    @Override
+                    StackTrace getStackTrace(ThreadState state) {
+                        // the other are blocked or waiting
+                        assertEquals(state, state0);
+                        return trace0;
+                    }
+                };
+
+        SessionCheckingAction action = captor.getValue();
+        assertNotNull(action);
+
+        action.actionPerformed(session, range, range);
+
+        verify(view).rebuild();
+        verify(collector).getThreadStates(any(SessionID.class), resultHandlerCaptor.capture(), any(Range.class));
+
+        StackFrame frame0 = new StackFrame("file", "package", "class", "method0", 1, false);
+        StackFrame frame1 = new StackFrame("file", "package", "class", "method1", 2, false);
+
+        List<StackFrame> frames = new ArrayList<>();
+        frames.add(frame0);
+        frames.add(frame1);
+
+        when(trace0.getFrames()).thenReturn(frames);
+
+        ArgumentCaptor<Trace> traceCaptor = ArgumentCaptor.forClass(Trace.class);
+
+        ResultHandler handler = resultHandlerCaptor.getValue();
+        handler.onResult(state0);
+        handler.onResult(state1);
+        handler.onResult(state2);
+
+        verify(view).addTrace(traceCaptor.capture());
+
+        Trace trace = traceCaptor.getValue();
+        assertEquals("state0", trace.getName());
+        assertEquals(2, trace.getTrace().size());
+        assertEquals("class.method1:2", trace.getTrace().get(0).getName());
+        assertEquals("class.method0:1", trace.getTrace().get(1).getName());
+    }
+}
--- a/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-controllers/src/test/java/com/redhat/thermostat/thread/client/controller/impl/ThreadInformationControllerTest.java	Thu Jun 23 14:51:53 2016 +0200
@@ -52,6 +52,7 @@
 import com.redhat.thermostat.thread.client.common.ThreadViewProvider;
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollector;
 import com.redhat.thermostat.thread.client.common.collector.ThreadCollectorFactory;
+import com.redhat.thermostat.thread.client.common.view.StackTraceProfilerView;
 import com.redhat.thermostat.thread.client.common.view.ThreadCountView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTableView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTableView.ThreadSelectionAction;
@@ -92,11 +93,14 @@
     private VmInfoDAO vmInfoDao;
 
     private ThreadTableView threadTableView;
+    private StackTraceProfilerView stackTraceProfilerView;
     private VmDeadLockView deadLockView;
     private ThreadTimelineView threadTimelineView;
     private ThreadCountView threadCountView;
     private ExecutorService appExecutor;
 
+
+
     @Before
     public void setUp() {
 
@@ -111,6 +115,7 @@
     }
 
     private void setUpView() {
+        stackTraceProfilerView = mock(StackTraceProfilerView.class);
         deadLockView = mock(VmDeadLockView.class);
         threadTableView = mock(ThreadTableView.class);
         threadTimelineView = mock(ThreadTimelineView.class);
@@ -124,7 +129,7 @@
         when(view.createThreadTableView()).thenReturn(threadTableView);
         when(view.createThreadTimelineView()).thenReturn(threadTimelineView);
         when(view.createThreadCountView()).thenReturn(threadCountView);
-
+        when(view.createStackTraceProfilerView()).thenReturn(stackTraceProfilerView);
     }
 
     private void setupExecutor() {
--- a/thread/client-swing/pom.xml	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-swing/pom.xml	Thu Jun 23 14:51:53 2016 +0200
@@ -101,6 +101,12 @@
         <version>${project.version}</version>
     </dependency>
 
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-experimental-components</artifactId>
+      <version>${parent.version}</version>
+    </dependency>
+
   </dependencies>
 
   <build>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingStackTraceProfilerView.java	Thu Jun 23 14:51:53 2016 +0200
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2012-2016 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 com.redhat.thermostat.client.swing.SwingComponent;
+import com.redhat.thermostat.client.swing.components.ThermostatScrollPane;
+import com.redhat.thermostat.client.swing.experimental.ComponentVisibilityNotifier;
+import com.redhat.thermostat.experimental.components.graph.GraphContainer;
+import com.redhat.thermostat.experimental.model.Trace;
+import com.redhat.thermostat.experimental.model.graph.GraphModel;
+import com.redhat.thermostat.thread.client.common.view.StackTraceProfilerView;
+
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import java.awt.Component;
+
+/**
+ */
+public class SwingStackTraceProfilerView extends StackTraceProfilerView implements SwingComponent {
+
+    private ComponentVisibilityNotifier visibilityNotifier;
+
+    private ThermostatScrollPane contentPane;
+    private GraphModel graphModel;
+    private GraphContainer graphContainer;
+
+    public SwingStackTraceProfilerView() {
+        graphContainer = new GraphContainer();
+
+        contentPane = new ThermostatScrollPane(graphContainer);
+        contentPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
+        contentPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+
+        visibilityNotifier = new ComponentVisibilityNotifier();
+        visibilityNotifier.initialize(graphContainer, notifier);
+    }
+
+    @Override
+    public void createModel(final String modelID) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                graphModel = new GraphModel(modelID);
+                graphContainer.setModel(graphModel);
+            }
+        });
+    }
+
+    @Override
+    public void rebuild() {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                graphModel.rebuild();
+            }
+        });
+    }
+
+    @Override
+    public void addTrace(final Trace trace) {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                graphModel.addTrace(trace);
+            }
+        });
+    }
+
+    @Override
+    public void clear() {
+        SwingUtilities.invokeLater(new Runnable() {
+            @Override
+            public void run() {
+                graphModel.clear();
+            }
+        });
+    }
+
+    @Override
+    public Component getUiComponent() {
+        return contentPane;
+    }
+}
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadView.java	Thu Jun 23 14:51:53 2016 +0200
@@ -45,6 +45,7 @@
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.thread.client.common.ThreadTableBean;
 import com.redhat.thermostat.thread.client.common.locale.LocaleResources;
+import com.redhat.thermostat.thread.client.common.view.StackTraceProfilerView;
 import com.redhat.thermostat.thread.client.common.view.ThreadCountView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTableView;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
@@ -78,6 +79,7 @@
     private SwingVmDeadLockView vmDeadLockView;
     private SwingThreadTimelineView threadTimelineView;
     private SwingThreadDetailsView threadDetailsView;
+    private SwingStackTraceProfilerView stackTraceProfilerView;
 
     private JTabbedPane topPane;
     private JTabbedPane bottomPane;
@@ -194,7 +196,12 @@
         comp = threadTimelineView.getUiComponent();
         comp.setName("timeline");
         topPane.addTab(t.localize(LocaleResources.TIMELINE).getContents(), comp);
-        
+
+        stackTraceProfilerView = new SwingStackTraceProfilerView();
+        comp = stackTraceProfilerView.getUiComponent();
+        comp.setName("stackTraceProfiler");
+        topPane.addTab(t.localize(LocaleResources.STACK_TRACE_PROFILER).getContents(), comp);
+
         panel.getSplitPane().setTopComponent(topPane);
     }
     
@@ -318,5 +325,10 @@
             }
         });
     }
+
+    @Override
+    public StackTraceProfilerView createStackTraceProfilerView() {
+        return stackTraceProfilerView;
+    }
 }
 
--- a/thread/distribution/thermostat-plugin.xml	Thu Jun 23 14:51:52 2016 +0200
+++ b/thread/distribution/thermostat-plugin.xml	Thu Jun 23 14:51:53 2016 +0200
@@ -47,6 +47,8 @@
         <bundle><symbolic-name>com.redhat.thermostat.thread.client.swing</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.thread.client.controller</symbolic-name><version>${project.version}</version></bundle>
         <bundle><symbolic-name>com.redhat.thermostat.thread.client.common</symbolic-name><version>${project.version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.experimental.collections</symbolic-name><version>${project.version}</version></bundle>
+        <bundle><symbolic-name>com.redhat.thermostat.experimental.components</symbolic-name><version>${project.version}</version></bundle>
       </bundles>
     </extension>
     <extension>