Skip to content

Commit 5f09a8b

Browse files
committed
Output call tree analysis as CSV for use in graph database
* Methods and virtual methods are represented with graph nodes. * Direct, virtual and overriden-by relationships have been mapped. * Bytecode indexes are part of the relationships. * A method can interact with others multiple times, with each bytecode index indicating the origin of the call with the origin method.
1 parent 806ee9e commit 5f09a8b

File tree

2 files changed

+291
-0
lines changed

2 files changed

+291
-0
lines changed

docs/reference-manual/native-image/Reports.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,16 @@ The call tree report name has the structure `call_tree_<image_name>_<date_time>.
151151
The object tree report name has the structure: `object_tree_<image_name>_<date_time>.txt`.
152152
The image name is the name of the generated image, which can be set with the `-H:Name=<name>` option.
153153
The `<date_time>` is in the `yyyyMMdd_HHmmss` format.
154+
155+
#### CSV files
156+
157+
The reports include a number of CSV files containing raw data for methods and their relationships.
158+
The aim of these files is to make it enable this raw data to be easily imported into graph databases.
159+
Graph databases can provide the following functionality:
160+
161+
* Sophisticated graphical visualization of the call tree graph that provide a different perspective compared to text-based formats.
162+
* Ability to execute complex queries that can for example show a subset of the tree that causes certain code path to be included in the call tree analysis.
163+
This querying functionality is crucial in making big analysis call trees manageable.
164+
165+
The process to import the files into graph databases is specific to each database.
166+
Please follow the instructions provided by the graph database providers to find out how to import them.

substratevm/src/com.oracle.graal.pointsto/src/com/oracle/graal/pointsto/reports/CallTreePrinter.java

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,36 @@
3535
import java.util.ArrayDeque;
3636
import java.util.ArrayList;
3737
import java.util.Arrays;
38+
import java.util.Collection;
3839
import java.util.Deque;
40+
import java.util.HashMap;
3941
import java.util.HashSet;
4042
import java.util.Iterator;
4143
import java.util.LinkedHashMap;
4244
import java.util.List;
4345
import java.util.Map;
46+
import java.util.Objects;
4447
import java.util.Set;
48+
import java.util.concurrent.atomic.AtomicInteger;
49+
import java.util.regex.Matcher;
50+
import java.util.regex.Pattern;
4551
import java.util.stream.Collectors;
52+
import java.util.stream.Stream;
4653

4754
import com.oracle.graal.pointsto.BigBang;
4855
import com.oracle.graal.pointsto.flow.InvokeTypeFlow;
4956
import com.oracle.graal.pointsto.meta.AnalysisMethod;
5057

5158
import jdk.vm.ci.code.BytecodePosition;
59+
import jdk.vm.ci.meta.JavaKind;
5260
import jdk.vm.ci.meta.ResolvedJavaMethod;
61+
import jdk.vm.ci.meta.ResolvedJavaType;
5362

5463
public final class CallTreePrinter {
5564

65+
public static final Pattern CAMEL_CASE_PATTERN = Pattern.compile(
66+
"\\b[a-zA-Z]|[A-Z]|\\.");
67+
5668
public static void print(BigBang bigbang, String reportsPath, String reportName) {
5769
CallTreePrinter printer = new CallTreePrinter(bigbang);
5870
printer.buildCallTree();
@@ -65,6 +77,8 @@ public static void print(BigBang bigbang, String reportsPath, String reportName)
6577
writer -> printer.printClasses(writer, false));
6678
ReportUtils.report("list of used packages", reportsPath, "used_packages_" + reportName, "txt",
6779
writer -> printer.printClasses(writer, true));
80+
81+
printCsvFiles(printer.methodToNode, reportsPath, reportName);
6882
}
6983

7084
interface Node {
@@ -299,4 +313,268 @@ private static String packagePrefix(String name) {
299313
}
300314
return name.substring(0, lastDot);
301315
}
316+
317+
private static void printCsvFiles(Map<AnalysisMethod, MethodNode> methodToNode, String reportsPath, String reportName) {
318+
// Set virtual node at next available method id
319+
final AtomicInteger virtualNodeId = new AtomicInteger(MethodNode.methodId);
320+
321+
Set<Integer> entryPointIds = new HashSet<>();
322+
Set<MethodNode> nonVirtualNodes = new HashSet<>();
323+
Map<List<String>, Integer> virtualNodes = new HashMap<>();
324+
325+
Map<Integer, Set<BciEndEdge>> directEdges = new HashMap<>();
326+
Map<Integer, Set<BciEndEdge>> virtualEdges = new HashMap<>();
327+
Map<Integer, Set<Integer>> overridenByEdges = new HashMap<>();
328+
329+
final Iterator<MethodNode> iterator = methodToNode.values().stream().filter(n -> n.isEntryPoint).iterator();
330+
while (iterator.hasNext()) {
331+
final MethodNode node = iterator.next();
332+
entryPointIds.add(node.id);
333+
walkNodes(node, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes, virtualNodeId);
334+
}
335+
336+
ReportUtils.report("call tree for vm entry point", reportsPath, "csv_call_tree_vm_" + reportName, "csv",
337+
CallTreePrinter::printVMEntryPoint);
338+
339+
ReportUtils.report("call tree for methods", reportsPath, "csv_call_tree_methods_" + reportName, "csv",
340+
writer -> printMethodNodes(methodToNode.values(), writer));
341+
342+
ReportUtils.report("call tree for virtual methods", reportsPath, "csv_call_tree_virtual_methods_" + reportName, "csv",
343+
writer -> printVirtualNodes(virtualNodes, writer));
344+
345+
ReportUtils.report("call tree for entry points", reportsPath, "csv_call_tree_entry_points_" + reportName, "csv",
346+
writer -> printEntryPointIds(entryPointIds, writer));
347+
348+
ReportUtils.report("call tree for direct edges", reportsPath, "csv_call_tree_direct_edges_" + reportName, "csv",
349+
writer -> printBciEdges(directEdges, writer));
350+
351+
ReportUtils.report("call tree for overriden by edges", reportsPath, "csv_call_tree_override_by_edges_" + reportName, "csv",
352+
writer -> printNonBciEdges(overridenByEdges, writer));
353+
354+
ReportUtils.report("call tree for virtual edges", reportsPath, "csv_call_tree_virtual_edges_" + reportName, "csv",
355+
writer -> printBciEdges(virtualEdges, writer));
356+
}
357+
358+
private static void printVMEntryPoint(PrintWriter writer) {
359+
writer.println(convertToCSV("Id", "Name"));
360+
writer.println(convertToCSV("0", "VM"));
361+
}
362+
363+
private static void printMethodNodes(Collection<MethodNode> methods, PrintWriter writer) {
364+
writer.println(convertToCSV("Id", "Name", "Type", "Parameters", "Return", "Display"));
365+
methods.stream()
366+
.map(CallTreePrinter::methodNodeInfo)
367+
.map(CallTreePrinter::convertToCSV)
368+
.forEach(writer::println);
369+
}
370+
371+
private static List<String> methodNodeInfo(MethodNode method) {
372+
return resolvedJavaMethodInfo(method.id, method.method);
373+
}
374+
375+
private static void walkNodes(MethodNode methodNode, Map<Integer, Set<BciEndEdge>> directEdges, Map<Integer, Set<BciEndEdge>> virtualEdges, Map<Integer, Set<Integer>> overridenByEdges,
376+
Map<List<String>, Integer> virtualNodes, Set<MethodNode> nonVirtualNodes, AtomicInteger virtualNodeId) {
377+
for (InvokeNode invoke : methodNode.invokes) {
378+
if (invoke.isDirectInvoke) {
379+
if (invoke.callees.size() > 0) {
380+
Node calleeNode = invoke.callees.get(0);
381+
addDirectEdge(methodNode.id, invoke, calleeNode, directEdges, nonVirtualNodes);
382+
if (calleeNode instanceof MethodNode) {
383+
walkNodes((MethodNode) calleeNode, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes, virtualNodeId);
384+
}
385+
}
386+
} else {
387+
final int nodeId = addVirtualNode(invoke, virtualNodes, virtualNodeId);
388+
addVirtualMethodEdge(methodNode.id, invoke, nodeId, virtualEdges);
389+
for (Node calleeNode : invoke.callees) {
390+
addOverridenByEdge(nodeId, calleeNode, overridenByEdges, nonVirtualNodes);
391+
if (calleeNode instanceof MethodNode) {
392+
walkNodes((MethodNode) calleeNode, directEdges, virtualEdges, overridenByEdges, virtualNodes, nonVirtualNodes, virtualNodeId);
393+
}
394+
}
395+
}
396+
}
397+
}
398+
399+
private static void addDirectEdge(int nodeId, InvokeNode invoke, Node calleeNode, Map<Integer, Set<BciEndEdge>> edges, Set<MethodNode> nodes) {
400+
Set<BciEndEdge> nodeEdges = edges.computeIfAbsent(nodeId, k -> new HashSet<>());
401+
MethodNode methodNode = calleeNode instanceof MethodNode
402+
? (MethodNode) calleeNode
403+
: ((MethodNodeReference) calleeNode).methodNode;
404+
nodes.add(methodNode);
405+
nodeEdges.add(new BciEndEdge(methodNode.id, bytecodeIndexes(invoke)));
406+
}
407+
408+
private static List<Integer> bytecodeIndexes(InvokeNode node) {
409+
return Stream.of(node.sourceReferences)
410+
.map(source -> source.bci)
411+
.collect(Collectors.toList());
412+
}
413+
414+
private static int addVirtualNode(InvokeNode node, Map<List<String>, Integer> virtualNodes, AtomicInteger virtualNodeId) {
415+
final List<String> virtualMethodInfo = virtualMethodInfo(node.targetMethod);
416+
return virtualNodes.computeIfAbsent(virtualMethodInfo, k -> virtualNodeId.getAndIncrement());
417+
}
418+
419+
private static void addVirtualMethodEdge(int startId, InvokeNode invoke, int endId, Map<Integer, Set<BciEndEdge>> edges) {
420+
Set<BciEndEdge> nodeEdges = edges.computeIfAbsent(startId, k -> new HashSet<>());
421+
nodeEdges.add(new BciEndEdge(endId, bytecodeIndexes(invoke)));
422+
}
423+
424+
private static void printVirtualNodes(Map<List<String>, Integer> virtualNodes, PrintWriter writer) {
425+
writer.println(convertToCSV("Id", "Name", "Type", "Parameters", "Return", "Display"));
426+
virtualNodes.entrySet().stream()
427+
.map(CallTreePrinter::virtualMethodAndIdInfo)
428+
.map(CallTreePrinter::convertToCSV)
429+
.forEach(writer::println);
430+
}
431+
432+
private static List<String> virtualMethodAndIdInfo(Map.Entry<List<String>, Integer> entry) {
433+
final List<String> methodInfo = entry.getKey();
434+
final List<String> result = new ArrayList<>(methodInfo.size() + 1);
435+
result.add(String.valueOf(entry.getValue()));
436+
for (int i = 1; i < methodInfo.size(); i++) {
437+
result.add(i, methodInfo.get(i));
438+
}
439+
return result;
440+
}
441+
442+
private static void printEntryPointIds(Set<Integer> entryPoints, PrintWriter writer) {
443+
writer.println(convertToCSV("Id"));
444+
entryPoints.forEach(writer::println);
445+
}
446+
447+
private static void addOverridenByEdge(int nodeId, Node calleeNode, Map<Integer, Set<Integer>> edges, Set<MethodNode> nodes) {
448+
Set<Integer> nodeEdges = edges.computeIfAbsent(nodeId, k -> new HashSet<>());
449+
MethodNode methodNode = calleeNode instanceof MethodNode
450+
? (MethodNode) calleeNode
451+
: ((MethodNodeReference) calleeNode).methodNode;
452+
nodes.add(methodNode);
453+
nodeEdges.add(methodNode.id);
454+
}
455+
456+
private static void printBciEdges(Map<Integer, Set<BciEndEdge>> edges, PrintWriter writer) {
457+
final Set<BciEdge> idEdges = edges.entrySet().stream()
458+
.flatMap(entry -> entry.getValue().stream().map(endId -> new BciEdge(entry.getKey(), endId)))
459+
.collect(Collectors.toSet());
460+
461+
writer.println(convertToCSV("StartId", "EndId", "BytecodeIndexes"));
462+
idEdges.stream()
463+
.map(edge -> convertToCSV(String.valueOf(edge.startId), String.valueOf(edge.endEdge.id), showBytecodeIndexes(edge.endEdge.bytecodeIndexes)))
464+
.forEach(writer::println);
465+
}
466+
467+
private static String showBytecodeIndexes(List<Integer> bytecodeIndexes) {
468+
return bytecodeIndexes.stream()
469+
.map(String::valueOf)
470+
.collect(Collectors.joining("->"));
471+
}
472+
473+
private static void printNonBciEdges(Map<Integer, Set<Integer>> edges, PrintWriter writer) {
474+
final Set<NonBciEdge> idEdges = edges.entrySet().stream()
475+
.flatMap(entry -> entry.getValue().stream().map(endId -> new NonBciEdge(entry.getKey(), endId)))
476+
.collect(Collectors.toSet());
477+
478+
writer.println(convertToCSV("StartId", "EndId"));
479+
idEdges.stream()
480+
.map(edge -> convertToCSV(String.valueOf(edge.startId), String.valueOf(edge.endId)))
481+
.forEach(writer::println);
482+
}
483+
484+
private static List<String> virtualMethodInfo(AnalysisMethod method) {
485+
return resolvedJavaMethodInfo(null, method);
486+
}
487+
488+
private static List<String> resolvedJavaMethodInfo(Integer id, ResolvedJavaMethod method) {
489+
// TODO method parameter types are opaque, but could in the future be split out and link
490+
// together
491+
// e.g. each method could BELONG to a type, and a method could have PARAMETER relationships
492+
// with N types
493+
// see https://neo4j.com/developer/guide-import-csv/#_converting_data_values_with_load_csv
494+
// for examples
495+
final String parameters = method.getSignature().getParameterCount(false) > 0
496+
? method.format("%P").replace(",", "")
497+
: "empty";
498+
499+
return Arrays.asList(
500+
id == null ? null : Integer.toString(id),
501+
method.getName(),
502+
method.getDeclaringClass().toJavaName(true),
503+
parameters,
504+
method.getSignature().getReturnType(null).toJavaName(true),
505+
display(method));
506+
}
507+
508+
private static String display(ResolvedJavaMethod method) {
509+
final ResolvedJavaType type = method.getDeclaringClass();
510+
final String typeName = type.toJavaName(true);
511+
if (type.getJavaKind() == JavaKind.Object) {
512+
List<String> matchResults = new ArrayList<>();
513+
Matcher matcher = CAMEL_CASE_PATTERN.matcher(typeName);
514+
while (matcher.find()) {
515+
matchResults.add(matcher.toMatchResult().group());
516+
}
517+
518+
return String.join("", matchResults) + "." + method.getName();
519+
}
520+
521+
return typeName + "." + method.getName();
522+
}
523+
524+
private static String convertToCSV(String... data) {
525+
return String.join(",", data);
526+
}
527+
528+
private static String convertToCSV(List<String> data) {
529+
return String.join(",", data);
530+
}
531+
532+
private static final class NonBciEdge {
533+
534+
final int startId;
535+
final int endId;
536+
537+
private NonBciEdge(int startId, int endId) {
538+
this.startId = startId;
539+
this.endId = endId;
540+
}
541+
}
542+
543+
private static final class BciEdge {
544+
final int startId;
545+
final BciEndEdge endEdge;
546+
547+
private BciEdge(int startId, BciEndEdge endEdge) {
548+
this.startId = startId;
549+
this.endEdge = endEdge;
550+
}
551+
}
552+
553+
private static final class BciEndEdge {
554+
final int id;
555+
final List<Integer> bytecodeIndexes;
556+
557+
private BciEndEdge(int id, List<Integer> bytecodeIndexes) {
558+
this.id = id;
559+
this.bytecodeIndexes = bytecodeIndexes;
560+
}
561+
562+
@Override
563+
public boolean equals(Object o) {
564+
if (this == o) {
565+
return true;
566+
}
567+
if (o == null || getClass() != o.getClass()) {
568+
return false;
569+
}
570+
BciEndEdge endEdge = (BciEndEdge) o;
571+
return id == endEdge.id &&
572+
bytecodeIndexes.equals(endEdge.bytecodeIndexes);
573+
}
574+
575+
@Override
576+
public int hashCode() {
577+
return Objects.hash(id, bytecodeIndexes);
578+
}
579+
}
302580
}

0 commit comments

Comments
 (0)