-
Notifications
You must be signed in to change notification settings - Fork 100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: more backend capabilities for heapdump #233
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,10 +20,7 @@ | |
import org.eclipse.mat.snapshot.ISnapshot; | ||
|
||
import java.lang.ref.SoftReference; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.*; | ||
|
||
public class AnalysisContext { | ||
|
||
|
@@ -54,14 +51,23 @@ static class ClassLoaderExplorerData { | |
} | ||
|
||
static class DirectByteBufferData { | ||
static final String OQL = | ||
"SELECT s.@displayName as label, s.position as position, s.limit as limit, s.capacity as " + | ||
"capacity FROM java.nio.DirectByteBuffer s where s.cleaner != null"; | ||
static final String JDK_MANAGED_BUFFER_OQL = | ||
"SELECT s.@displayName as label, s.position as position, s.limit as limit, s.capacity as " + | ||
"capacity FROM java.nio.DirectByteBuffer s where s.cleaner != null"; | ||
static final String JNI_ALLOCATED_BUFFER_OQL = | ||
"SELECT s.@displayName as label, s.position as position, s.limit as limit, s.capacity as " + | ||
"capacity FROM java.nio.DirectByteBuffer s where s.cleaner = null and s.att = null"; | ||
static final String ALL_BUFFER_OQL = | ||
"SELECT s.@displayName as label, s.position as position, s.limit as limit, s.capacity as " + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this break the existing default behaviour now? Previously the default was equivalent to JDK_MANAGED_BUFFER_OQL, but below we default to ALL. I'm not sure which is intended. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The frontend will be synced later by @D-D-H The existing default behavior is problematic because actual direct buffer(and related off-heap memory) is less than presented, that why we provide these fine-grained options to inspect direct buffer. |
||
"capacity FROM java.nio.DirectByteBuffer s"; | ||
|
||
static final Map<String, Object> JDK_MANAGED_BUFFER_ARGS = | ||
Collections.singletonMap("queryString", JDK_MANAGED_BUFFER_OQL); | ||
static final Map<String, Object> JNI_ALLOC_BUFFER_ARGS = | ||
Collections.singletonMap("queryString", JNI_ALLOCATED_BUFFER_OQL); | ||
static final Map<String, Object> ALL_BUFFER_ARGS = | ||
Collections.singletonMap("queryString", ALL_BUFFER_OQL); | ||
|
||
static final Map<String, Object> ARGS = new HashMap<>(1); | ||
static { | ||
ARGS.put("queryString", OQL); | ||
} | ||
|
||
RefinedTable resultContext; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -496,6 +496,40 @@ private ClassLoaderExplorerData queryClassLoader(AnalysisContext context) throws | |
} | ||
} | ||
|
||
@Override | ||
public Model.VMBoard.Summary getSummaryOfVMBoard() { | ||
Map<String, Object> queryVMOptions = new HashMap<>(); | ||
Map<String, Object> queryGc = new HashMap<>(); | ||
queryVMOptions.put("queryString", "SELECT toString(x) AS VMOptions FROM OBJECTS (SELECT OBJECTS s.vmArgs.list.a[0:-1] FROM sun.management.VMManagementImpl s) x"); | ||
queryGc.put("queryString", "SELECT toString(g.name) AS GC FROM sun.management.GarbageCollectorImpl g"); | ||
return $(() -> { | ||
List<String> options = new ArrayList<>(); | ||
List<String> gc = new ArrayList<>(); | ||
IResultTable result = null; | ||
IResult r = queryByCommand(context, "oql", queryVMOptions); | ||
if (r instanceof IResultTable) { | ||
// In case we get IResultText, i.e. empty result | ||
result = (IResultTable) r; | ||
for (int i = 0; i < result.getRowCount(); i++) { | ||
Object row = result.getRow(i); | ||
options.add((String) result.getColumnValue(row, 0)); | ||
} | ||
} | ||
r = queryByCommand(context, "oql", queryGc); | ||
if (r instanceof IResultTable) { | ||
result = (IResultTable) r; | ||
for (int i = 0; i < result.getRowCount(); i++) { | ||
Object row = result.getRow(i); | ||
gc.add((String) result.getColumnValue(row, 0)); | ||
} | ||
} | ||
Model.VMBoard.Summary vmBoard = new Model.VMBoard.Summary(); | ||
vmBoard.setVmOptions(options); | ||
vmBoard.setGc(gc); | ||
return vmBoard; | ||
}); | ||
} | ||
|
||
@Override | ||
public Model.ClassLoader.Summary getSummaryOfClassLoaders() { | ||
return $(() -> { | ||
|
@@ -603,56 +637,55 @@ public PageView<UnreachableObject.Item> getUnreachableObjects(int page, int page | |
} | ||
|
||
private DirectByteBufferData queryDirectByteBufferData( | ||
AnalysisContext context) throws SnapshotException { | ||
DirectByteBufferData data = context.directByteBufferData.get(); | ||
if (data != null) { | ||
return data; | ||
AnalysisContext context, String mode) throws SnapshotException { | ||
DirectByteBufferData data = new DirectByteBufferData(); | ||
final Map<String, Object> queryArg; | ||
switch (mode) { | ||
case "jniAlloc": | ||
queryArg = DirectByteBufferData.JNI_ALLOC_BUFFER_ARGS; | ||
break; | ||
case "jvmManaged": | ||
queryArg = DirectByteBufferData.JDK_MANAGED_BUFFER_ARGS; | ||
break; | ||
case "all": | ||
default: | ||
queryArg = DirectByteBufferData.ALL_BUFFER_ARGS; | ||
break; | ||
} | ||
|
||
//noinspection SynchronizationOnLocalVariableOrMethodParameter | ||
synchronized (context) { | ||
data = context.directByteBufferData.get(); | ||
if (data != null) { | ||
return data; | ||
IResult result = queryByCommand(context, "oql", queryArg); | ||
IResultTable table; | ||
if (result instanceof IResultTable) { | ||
table = (IResultTable) result; | ||
|
||
RefinedResultBuilder builder = | ||
new RefinedResultBuilder(new SnapshotQueryContext(context.snapshot), table); | ||
builder.setSortOrder(3, Column.SortDirection.DESC); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is sort order important for summary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we want to sort by retaiend heap |
||
data.resultContext = (RefinedTable) builder.build(); | ||
DirectByteBuffer.Summary summary = new DirectByteBuffer.Summary(); | ||
summary.totalSize = data.resultContext.getRowCount(); | ||
|
||
for (int i = 0; i < summary.totalSize; i++) { | ||
Object row = data.resultContext.getRow(i); | ||
summary.position += data.position(row); | ||
summary.limit += data.limit(row); | ||
summary.capacity += data.capacity(row); | ||
} | ||
|
||
data = new DirectByteBufferData(); | ||
IResult result = queryByCommand(context, "oql", DirectByteBufferData.ARGS); | ||
IResultTable table; | ||
if (result instanceof IResultTable) { | ||
table = (IResultTable) result; | ||
|
||
RefinedResultBuilder builder = | ||
new RefinedResultBuilder(new SnapshotQueryContext(context.snapshot), table); | ||
builder.setSortOrder(3, Column.SortDirection.DESC); | ||
data.resultContext = (RefinedTable) builder.build(); | ||
DirectByteBuffer.Summary summary = new DirectByteBuffer.Summary(); | ||
summary.totalSize = data.resultContext.getRowCount(); | ||
|
||
for (int i = 0; i < summary.totalSize; i++) { | ||
Object row = data.resultContext.getRow(i); | ||
summary.position += data.position(row); | ||
summary.limit += data.limit(row); | ||
summary.capacity += data.capacity(row); | ||
} | ||
data.summary = summary; | ||
} else { | ||
data.summary = new DirectByteBuffer.Summary(); | ||
} | ||
context.directByteBufferData = new SoftReference<>(data); | ||
return data; | ||
data.summary = summary; | ||
} else { | ||
data.summary = new DirectByteBuffer.Summary(); | ||
} | ||
return data; | ||
} | ||
|
||
@Override | ||
public DirectByteBuffer.Summary getSummaryOfDirectByteBuffers() { | ||
return $(() -> queryDirectByteBufferData(context).summary); | ||
public DirectByteBuffer.Summary getSummaryOfDirectByteBuffers(String mode) { | ||
return $(() -> queryDirectByteBufferData(context, mode).summary); | ||
} | ||
|
||
@Override | ||
public PageView<DirectByteBuffer.Item> getDirectByteBuffers(int page, int pageSize) { | ||
public PageView<DirectByteBuffer.Item> getDirectByteBuffers(String mode, int page, int pageSize) { | ||
return $(() -> { | ||
DirectByteBufferData data = queryDirectByteBufferData(context); | ||
DirectByteBufferData data = queryDirectByteBufferData(context, mode); | ||
RefinedTable resultContext = data.resultContext; | ||
return PageViewBuilder.build(new PageViewBuilder.Callback<Object>() { | ||
@Override | ||
|
@@ -702,6 +735,69 @@ private PageView<JavaObject> queryIOBoundsOfObject(AnalysisContext context, int | |
}); | ||
} | ||
|
||
@Override | ||
public PageView<JavaObject> getListObjects(String objectLabel, int objectId, int type, int page, int pageSize) { | ||
Map<String, Object> args = new HashMap<>(); | ||
if (type == JavaObject.ARRAY_TYPE || type == JavaObject.NORMAL_TYPE) { | ||
objectLabel = String.valueOf(objectId); | ||
} | ||
String query = "list_objects " + objectLabel; | ||
if (type == Model.Histogram.ItemType.SUPER_CLASS) { | ||
query += " -include_subclasses"; | ||
} | ||
if (type == Model.Histogram.ItemType.CLASS_LOADER) { | ||
query = "oql"; | ||
String oql = "SELECT * FROM (SELECT * FROM java.lang.Class c WHERE c implements org.eclipse.mat.snapshot.model.IClass and c.@classLoaderId = " + objectId + ")"; | ||
args.put("queryString", oql); | ||
} | ||
String finalQuery = query; | ||
|
||
return $(() -> { | ||
if(type == Model.Histogram.ItemType.PACKAGE){ | ||
IResult result = queryByCommand(context, "histogram -groupBy BY_PACKAGE"); | ||
Histogram.PackageTree pt = (Histogram.PackageTree) result; | ||
Object targetParentNode = new ExoticTreeFinder(pt) | ||
.setGetChildrenCallback(node -> { | ||
Map<String, ?> subPackages = ReflectionUtil.getFieldValueOrNull(node, "subPackages"); | ||
if (subPackages != null) { | ||
return new ArrayList<>(subPackages.values()); | ||
} else { | ||
return null; | ||
} | ||
}) | ||
.setPredicate((theTree, theNode) -> { | ||
if (!(theNode instanceof XClassHistogramRecord)) { | ||
try { | ||
java.lang.reflect.Field | ||
field = theNode.getClass().getSuperclass().getDeclaredField("label"); | ||
field.setAccessible(true); | ||
String labelName = (String) field.get(theNode); | ||
return labelName.hashCode(); | ||
} catch (Throwable e) { | ||
e.printStackTrace(); | ||
} | ||
} | ||
return null; | ||
}) | ||
.findTargetParentNode(objectId); | ||
IContextObject c = pt.getContext(targetParentNode); | ||
if (c instanceof IContextObjectSet) { | ||
int[] objectIds = ((IContextObjectSet) c).getObjectIds(); | ||
return PageViewBuilder.build(objectIds, new PagingRequest(page, pageSize), this::getObjectInfo); | ||
} | ||
return PageView.empty(); | ||
}else{ | ||
IResultTree tree = queryByCommand(context, finalQuery, args); | ||
List<?> objectIds = tree.getElements(); | ||
|
||
return PageViewBuilder.build(objectIds, new PagingRequest(page, pageSize), node -> { | ||
int id = tree.getContext(node).getObjectId(); | ||
return getObjectInfo(id); | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public PageView<JavaObject> getOutboundOfObject(int objectId, int page, int pageSize) { | ||
return $(() -> queryIOBoundsOfObject(context, objectId, page, pageSize, true)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making 'mode' optional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See queryDirectByteBufferData, default to query all byte buffers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional here means that front-end doesn't have to pass the parameter.
see @ApiParameterMeta.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making mode as an Enum type like 'grouping'?