Skip to content

Commit

Permalink
GH-439 group threads by state
Browse files Browse the repository at this point in the history
  • Loading branch information
thurka committed Sep 5, 2024
1 parent ebd3781 commit 463bf12
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,22 @@ public class ThreadNode extends InstanceNode implements CCTNode.DoNotSortChildre

private final String name;
private final boolean isOOME;
private final Thread.State state;


public ThreadNode(String name, Instance instance) {
this(name, false, instance);
this(name, null, false, instance);
}

public ThreadNode(String name, Thread.State state, Instance instance) {
this(name, state, false, instance);
}

public ThreadNode(String name, boolean isOOME, Instance instance) {
public ThreadNode(String name, Thread.State state, boolean isOOME, Instance instance) {
super(instance);
this.name = name;
this.isOOME = isOOME;
this.state = state;
}


Expand All @@ -63,11 +69,14 @@ public boolean isOOMEThread() {
return isOOME;
}

public Thread.State getState() {
return state;
}

public static class Unknown extends ThreadNode {

public Unknown() {
super(Bundle.ThreadNode_UnknownThread(), null);
super(Bundle.ThreadNode_UnknownThread(), null, null);
}

public boolean equals(Object o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.visualvm.heapviewer.java;

import java.util.List;
import org.graalvm.visualvm.heapviewer.model.DataType;
import org.graalvm.visualvm.heapviewer.model.HeapViewerNode;
import org.graalvm.visualvm.heapviewer.model.NodesCache;
import org.graalvm.visualvm.lib.jfluid.heap.Heap;

/**
*
* @author Tomas Hurka
*/
public class ThreadStateNode extends HeapViewerNode {
private final Thread.State state;

public ThreadStateNode(Thread.State state, List<HeapViewerNode> children) {
this.state = state;
setChildren(children.toArray(NO_NODES));
}

public String getName() {
if (state == null) return "Undefined"; // NOI18N
return state.toString();
}

public Thread.State getState() {
return state;
}

public String toString() {
return getName();
}

protected void resetChildren() {}

public void forgetChildren(NodesCache cache) {}

protected Object getValue(DataType type, Heap heap) {
if (type == DataType.NAME) return getName();
if (type == DataType.COUNT) return DataType.COUNT.getUnsupportedValue();
if (type == DataType.OWN_SIZE) return DataType.OWN_SIZE./*getNoValue()*/getUnsupportedValue();
if (type == DataType.RETAINED_SIZE) return DataType.RETAINED_SIZE./*getNoValue()*/getUnsupportedValue();

if (type == DataType.INSTANCE) return DataType.INSTANCE./*getNoValue()*/getUnsupportedValue();
if (type == DataType.CLASS) return DataType.CLASS./*getNoValue()*/getUnsupportedValue();

if (type == DataType.LOGICAL_VALUE) return DataType.LOGICAL_VALUE./*getNoValue()*/getUnsupportedValue();

return super.getValue(type, heap);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.graalvm.visualvm.heapviewer.java;

import java.awt.Font;
import java.util.HashMap;
import java.util.Map;
import javax.swing.Icon;
import org.graalvm.visualvm.heapviewer.ui.HeapViewerRenderer;
import org.graalvm.visualvm.lib.jfluid.global.CommonConstants;
import org.graalvm.visualvm.lib.profiler.api.icons.Icons;
import org.graalvm.visualvm.lib.profiler.api.icons.ProfilerIcons;
import org.graalvm.visualvm.lib.ui.swing.renderer.LabelRenderer;
import org.graalvm.visualvm.lib.ui.threads.ThreadStateIcon;

/**
*
* @author Tomas Hurka
*/
public class ThreadStateNodeRenderer extends LabelRenderer implements HeapViewerRenderer {

private static final Icon ICON = Icons.getIcon(ProfilerIcons.THREAD);

public ThreadStateNodeRenderer() {
setIcon(ICON);
setFont(getFont().deriveFont(Font.BOLD));
}


public void setValue(Object value, int row) {
Icon i;
ThreadStateNode node = (ThreadStateNode)value;
setText(node.getName());
setIcon(getIcon(node.getState()));
}

public String getShortName() {
return getText();
}

private static final int THREAD_ICON_SIZE = 9;
private static final Map<Thread.State, Icon> STATE_ICONS_CACHE = new HashMap();

private static Icon getIcon(Thread.State state) {
Icon icon = STATE_ICONS_CACHE.get(state);

if (icon == null) {
int pState;
switch (state) {
case RUNNABLE:
pState = CommonConstants.THREAD_STATUS_RUNNING;
break;
case BLOCKED:
pState = CommonConstants.THREAD_STATUS_MONITOR;
break;
case WAITING:
pState = CommonConstants.THREAD_STATUS_WAIT;
break;
case TIMED_WAITING:
pState = CommonConstants.THREAD_STATUS_SLEEPING;
break;
default:
pState = CommonConstants.THREAD_STATUS_UNKNOWN;
break;
}
icon = new ThreadStateIcon(pState, THREAD_ICON_SIZE, THREAD_ICON_SIZE);
STATE_ICONS_CACHE.put(state, icon);
}
return icon;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
import org.graalvm.visualvm.heapviewer.java.StackFrameNodeRenderer;
import org.graalvm.visualvm.heapviewer.java.ThreadNode;
import org.graalvm.visualvm.heapviewer.java.ThreadNodeRenderer;
import org.graalvm.visualvm.heapviewer.java.ThreadStateNode;
import org.graalvm.visualvm.heapviewer.java.ThreadStateNodeRenderer;
import org.graalvm.visualvm.heapviewer.model.HeapViewerNode;
import org.graalvm.visualvm.heapviewer.ui.HeapViewerRenderer;
import org.graalvm.visualvm.lib.jfluid.heap.Heap;
Expand Down Expand Up @@ -89,6 +91,9 @@ public void registerRenderers(Map<Class<? extends HeapViewerNode>, HeapViewerRen

// stack frames
renderers.put(StackFrameNode.class, new StackFrameNodeRenderer());

// thread state
renderers.put(ThreadStateNode.class, new ThreadStateNodeRenderer());

// local variables
renderers.put(LocalObjectNode.class, new LocalObjectNodeRenderer(heap));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.graalvm.visualvm.heapviewer.java.LocalObjectNode;
import org.graalvm.visualvm.heapviewer.java.StackFrameNode;
import org.graalvm.visualvm.heapviewer.java.ThreadNode;
import org.graalvm.visualvm.heapviewer.java.ThreadStateNode;
import org.graalvm.visualvm.heapviewer.model.HeapViewerNode;
import org.graalvm.visualvm.heapviewer.model.RootNode;
import org.graalvm.visualvm.heapviewer.utils.HeapUtils;
Expand Down Expand Up @@ -69,31 +70,57 @@ class JavaThreadsProvider {
private static final String LOCAL_VARIABLE = Bundle.JavaThreadsProvider_LocalVariable();
private static final String JNI_LOCAL = Bundle.JavaThreadsProvider_JniLocal();


static String getThreadName(JavaClass vtClass, Instance instance) {
if (isVirtualThread(vtClass, instance)) {
return "Virtual Thread "+DetailsSupport.getDetailsString(instance); // NOI18N
private static class ThreadInfo {

String threadName;
Long threadId;
Boolean daemon;
Integer priority;
Thread.State threadState;
String virtualName;

ThreadInfo(JavaClass vtClass, Instance instance) {
if (isVirtualThread(vtClass, instance)) {
virtualName = "Virtual Thread "+DetailsSupport.getDetailsString(instance); // NOI18N
return;
}
threadName = getThreadInstanceName(instance);
threadId = (Long) instance.getValueOfField("tid"); // NOI18N
daemon = (Boolean) instance.getValueOfField("daemon"); // NOI18N
priority = (Integer) instance.getValueOfField("priority"); // NOI18N
Integer threadStatus = (Integer) instance.getValueOfField("threadStatus"); // NOI18N

if (daemon == null) {
Instance holder = (Instance) instance.getValueOfField("holder"); // NOI18N
if (holder != null) {
daemon = (Boolean) holder.getValueOfField("daemon"); // NOI18N
priority = (Integer) holder.getValueOfField("priority"); // NOI18N
threadStatus = (Integer) holder.getValueOfField("threadStatus"); // NOI18N
}
}
if (threadStatus != null) {
threadState = toThreadState(threadStatus.intValue());
}
}

Thread.State getThreadState() {
return threadState;
}
String threadName = getThreadInstanceName(instance);
Long threadId = (Long)instance.getValueOfField("tid"); // NOI18N
Boolean daemon = (Boolean)instance.getValueOfField("daemon"); // NOI18N
Integer priority = (Integer)instance.getValueOfField("priority"); // NOI18N
Integer threadStatus = (Integer)instance.getValueOfField("threadStatus"); // NOI18N

if (daemon == null) {
Instance holder = (Instance)instance.getValueOfField("holder"); // NOI18N
if (holder != null) {
daemon = (Boolean)holder.getValueOfField("daemon"); // NOI18N
priority = (Integer)holder.getValueOfField("priority"); // NOI18N
threadStatus = (Integer)holder.getValueOfField("threadStatus"); // NOI18N
public String toString() {
if (virtualName != null) {
return virtualName;
}
String tName = "\"" + threadName + "\"" + (daemon.booleanValue() ? " daemon" : "") + " prio=" + priority; // NOI18N
if (threadId != null) tName += " tid=" + threadId; // NOI18N
if (threadState != null) tName += " " + threadState; // NOI18N

return tName;
}
}

String tName = "\"" + threadName + "\"" + (daemon.booleanValue() ? " daemon" : "") + " prio=" + priority; // NOI18N
if (threadId != null) tName += " tid=" + threadId; // NOI18N
if (threadStatus != null) tName += " " + toThreadState(threadStatus.intValue()); // NOI18N

return tName;
static String getThreadName(JavaClass vtClass, Instance instance) {
return new ThreadInfo(vtClass, instance).toString();
}

static ThreadObjectGCRoot getOOMEThread(Heap heap) {
Expand Down Expand Up @@ -132,6 +159,28 @@ static HeapViewerNode getNode(URL url, HeapContext context) {
return null;
}

static HeapViewerNode[] getStateNodes(RootNode root, Heap heap) throws InterruptedException {
HeapViewerNode[] stateNodes;
HeapViewerNode[] threadsNodes = getThreadsNodes(root, heap);
Map<Thread.State,List<HeapViewerNode>> states = new HashMap<>();
for (HeapViewerNode n : threadsNodes) {
Thread.State s = ((ThreadNode)n).getState();
List<HeapViewerNode> nodes = states.get(s);
if (nodes == null) {
nodes = new ArrayList<>();
states.put(s, nodes);
}
nodes.add(n);
}
int i = 0;
stateNodes = new HeapViewerNode[states.size()];
for (Map.Entry<Thread.State, List<HeapViewerNode>> stateEntry : states.entrySet()) {
Thread.State state = stateEntry.getKey();
ThreadStateNode stateNode = new ThreadStateNode(state, stateEntry.getValue());
stateNodes[i++] = stateNode;
}
return stateNodes;
}

static HeapViewerNode[] getThreadsNodes(RootNode rootNode, Heap heap) throws InterruptedException {
List<HeapViewerNode> threadNodes = new ArrayList();
Expand All @@ -151,10 +200,9 @@ static HeapViewerNode[] getThreadsNodes(RootNode rootNode, Heap heap) throws Int
StackTraceElement stack[] = threadRoot.getStackTrace();
Map<Integer,List<GCRoot>> localsMap = javaFrameMap.get(threadRoot);

String tName = JavaThreadsProvider.getThreadName(vtClass, threadInstance);

ThreadInfo ti = new ThreadInfo(vtClass, threadInstance);
final List<HeapViewerNode> stackFrameNodes = new ArrayList();
ThreadNode threadNode = new ThreadNode(tName, threadRoot.equals(oome), threadInstance) {
ThreadNode threadNode = new ThreadNode(ti.toString(), ti.getThreadState(), threadRoot.equals(oome), threadInstance) {
protected HeapViewerNode[] computeChildren(RootNode root) {
return stackFrameNodes.toArray(HeapViewerNode.NO_NODES);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private JavaThreadsSummary(Instance oomeInstance, HeapContext context, HeapViewe

JavaClass vtClass = oomeInstance.getJavaClass().getHeap().getJavaClassByName("java.lang.VirtualThread"); // NOI18N
String threadName = JavaThreadsProvider.getThreadName(vtClass, oomeInstance);
threadData = new Object[][] {{ new ThreadNode(threadName, true, oomeInstance) }};
threadData = new Object[][] {{ new ThreadNode(threadName, null, true, oomeInstance) }};
}


Expand Down
Loading

0 comments on commit 463bf12

Please sign in to comment.