Skip to content

Commit c4de3ae

Browse files
committed
[GR-41037] Compute native image heap graph
PullRequest: graal/13030
2 parents 00cf640 + f7caa3d commit c4de3ae

File tree

9 files changed

+971
-69
lines changed

9 files changed

+971
-69
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/image/ImageHeap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
public interface ImageHeap {
3030
Collection<? extends ImageHeapObject> getObjects();
3131

32-
ImageHeapObject addLateToImageHeap(Object object, String reason);
32+
ImageHeapObject addLateToImageHeap(Object object, Object reason);
3333

3434
ImageHeapObject addFillerObject(int size);
3535

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted;
26+
27+
public class ByteFormattingUtil {
28+
private static final double BYTES_TO_KiB = 1024d;
29+
private static final double BYTES_TO_MiB = 1024d * 1024d;
30+
private static final double BYTES_TO_GiB = 1024d * 1024d * 1024d;
31+
32+
public static String bytesToHuman(long bytes) {
33+
return bytesToHuman("%4.2f", bytes);
34+
}
35+
36+
public static String bytesToHuman(String format, long bytes) {
37+
if (bytes < BYTES_TO_KiB) {
38+
return String.format(format, (double) bytes) + "B";
39+
} else if (bytes < BYTES_TO_MiB) {
40+
return String.format(format, bytesToKiB(bytes)) + "KB";
41+
} else if (bytes < BYTES_TO_GiB) {
42+
return String.format(format, bytesToMiB(bytes)) + "MB";
43+
} else {
44+
return String.format(format, bytesToGiB(bytes)) + "GB";
45+
}
46+
}
47+
48+
static double bytesToKiB(long bytes) {
49+
return bytes / BYTES_TO_KiB;
50+
}
51+
52+
static double bytesToGiB(long bytes) {
53+
return bytes / BYTES_TO_GiB;
54+
}
55+
56+
static double bytesToMiB(long bytes) {
57+
return bytes / BYTES_TO_MiB;
58+
}
59+
}

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ProgressReporter.java

Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public void printInitializeEnd() {
267267
String gcName = Heap.getHeap().getGC().getName();
268268
recordJsonMetric(GeneralInfo.GC, gcName);
269269
long maxHeapSize = SubstrateGCOptions.MaxHeapSize.getValue();
270-
String maxHeapValue = maxHeapSize == 0 ? "unlimited" : Utils.bytesToHuman(maxHeapSize);
270+
String maxHeapValue = maxHeapSize == 0 ? "unlimited" : ByteFormattingUtil.bytesToHuman(maxHeapSize);
271271
l().a(" ").doclink("Garbage collector", "#glossary-gc").a(": ").a(gcName).a(" (").doclink("max heap size", "#glossary-gc-max-heap-size").a(": ").a(maxHeapValue).a(")").println();
272272
}
273273

@@ -414,16 +414,17 @@ public void printCreationEnd(int imageFileSize, int numHeapObjects, long imageHe
414414
stagePrinter.end(imageTimer.getTotalTime() + writeTimer.getTotalTime());
415415
creationStageEndCompleted = true;
416416
String format = "%9s (%5.2f%%) for ";
417-
l().a(format, Utils.bytesToHuman(codeAreaSize), codeAreaSize / (double) imageFileSize * 100)
417+
l().a(format, ByteFormattingUtil.bytesToHuman(codeAreaSize), codeAreaSize / (double) imageFileSize * 100)
418418
.doclink("code area", "#glossary-code-area").a(":%,10d compilation units", numCompilations).println();
419419
EconomicMap<Pair<String, String>, ResourceStorageEntry> resources = Resources.singleton().resources();
420420
int numResources = resources.size();
421421
recordJsonMetric(ImageDetailKey.IMAGE_HEAP_RESOURCE_COUNT, numResources);
422-
l().a(format, Utils.bytesToHuman(imageHeapSize), imageHeapSize / (double) imageFileSize * 100)
422+
l().a(format, ByteFormattingUtil.bytesToHuman(imageHeapSize), imageHeapSize / (double) imageFileSize * 100)
423423
.doclink("image heap", "#glossary-image-heap").a(":%,9d objects and %,d resources", numHeapObjects, numResources).println();
424424
if (debugInfoSize > 0) {
425425
recordJsonMetric(ImageDetailKey.DEBUG_INFO_SIZE, debugInfoSize); // Optional metric
426-
DirectPrinter l = l().a(format, Utils.bytesToHuman(debugInfoSize), debugInfoSize / (double) imageFileSize * 100)
426+
DirectPrinter l = l().a(format, ByteFormattingUtil.bytesToHuman(debugInfoSize), debugInfoSize / (double) imageFileSize * 100)
427+
427428
.doclink("debug info", "#glossary-debug-info");
428429
if (debugInfoTimer != null) {
429430
l.a(" generated in %.1fs", Utils.millisToSeconds(debugInfoTimer.getTotalTime()));
@@ -435,9 +436,9 @@ public void printCreationEnd(int imageFileSize, int numHeapObjects, long imageHe
435436
recordJsonMetric(ImageDetailKey.TOTAL_SIZE, imageFileSize);
436437
recordJsonMetric(ImageDetailKey.CODE_AREA_SIZE, codeAreaSize);
437438
recordJsonMetric(ImageDetailKey.NUM_COMP_UNITS, numCompilations);
438-
l().a(format, Utils.bytesToHuman(otherBytes), otherBytes / (double) imageFileSize * 100)
439+
l().a(format, ByteFormattingUtil.bytesToHuman(otherBytes), otherBytes / (double) imageFileSize * 100)
439440
.doclink("other data", "#glossary-other-data").println();
440-
l().a("%9s in total", Utils.bytesToHuman(imageFileSize)).println();
441+
l().a("%9s in total", ByteFormattingUtil.bytesToHuman(imageFileSize)).println();
441442
printBreakdowns();
442443
}
443444

@@ -568,7 +569,7 @@ private void printBreakdowns() {
568569
if (packagesBySize.hasNext()) {
569570
Entry<String, Long> e = packagesBySize.next();
570571
String className = Utils.truncateClassOrPackageName(e.getKey());
571-
codeSizePart = String.format("%9s %s", Utils.bytesToHuman(e.getValue()), className);
572+
codeSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(e.getValue()), className);
572573
printedCodeBytes += e.getValue();
573574
printedCodeItems++;
574575
}
@@ -581,7 +582,7 @@ private void printBreakdowns() {
581582
if (!className.startsWith(BREAKDOWN_BYTE_ARRAY_PREFIX)) {
582583
className = Utils.truncateClassOrPackageName(className);
583584
}
584-
heapSizePart = String.format("%9s %s", Utils.bytesToHuman(e.getValue()), className);
585+
heapSizePart = String.format("%9s %s", ByteFormattingUtil.bytesToHuman(e.getValue()), className);
585586
printedHeapBytes += e.getValue();
586587
printedHeapItems++;
587588
}
@@ -595,9 +596,10 @@ private void printBreakdowns() {
595596
int numHeapItems = heapBreakdown.size();
596597
long totalCodeBytes = codeBreakdown.values().stream().collect(Collectors.summingLong(Long::longValue));
597598
long totalHeapBytes = heapBreakdown.values().stream().collect(Collectors.summingLong(Long::longValue));
598-
p.l().a(String.format("%9s for %s more ", Utils.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems)).doclink("origins", "#glossary-code-area-origins")
599+
600+
p.l().a(String.format("%9s for %s more packages", ByteFormattingUtil.bytesToHuman(totalCodeBytes - printedCodeBytes), numCodeItems - printedCodeItems))
599601
.jumpToMiddle()
600-
.a(String.format("%9s for %s more object types", Utils.bytesToHuman(totalHeapBytes - printedHeapBytes), numHeapItems - printedHeapItems)).flushln();
602+
.a(String.format("%9s for %s more object types", ByteFormattingUtil.bytesToHuman(totalHeapBytes - printedHeapBytes), numHeapItems - printedHeapItems)).flushln();
601603
}
602604

603605
public void printEpilog(Optional<String> optionalImageName, Optional<NativeImageGenerator> optionalGenerator, ImageClassLoader classLoader, Optional<Throwable> optionalError,
@@ -702,11 +704,11 @@ private static Path reportImageBuildStatistics(String imageName, BigBang bb) {
702704
String description = "image build statistics";
703705
if (ImageBuildStatistics.Options.ImageBuildStatisticsFile.hasBeenSet(bb.getOptions())) {
704706
final File file = new File(ImageBuildStatistics.Options.ImageBuildStatisticsFile.getValue(bb.getOptions()));
705-
return ReportUtils.report(description, file.getAbsoluteFile().toPath(), statsReporter, false);
707+
return com.oracle.graal.pointsto.reports.ReportUtils.report(description, file.getAbsoluteFile().toPath(), statsReporter, false);
706708
} else {
707-
String name = "image_build_statistics_" + ReportUtils.extractImageName(imageName);
709+
String name = "image_build_statistics_" + com.oracle.graal.pointsto.reports.ReportUtils.extractImageName(imageName);
708710
String path = SubstrateOptions.Path.getValue() + File.separatorChar + "reports";
709-
return ReportUtils.report(description, path, name, "json", statsReporter, false);
711+
return com.oracle.graal.pointsto.reports.ReportUtils.report(description, path, name, "json", statsReporter, false);
710712
}
711713
}
712714

@@ -721,7 +723,7 @@ private void printResourceStatistics() {
721723
.doclink("GCs", "#glossary-garbage-collections");
722724
long peakRSS = ProgressReporterCHelper.getPeakRSS();
723725
if (peakRSS >= 0) {
724-
p.a(" | ").doclink("Peak RSS", "#glossary-peak-rss").a(": ").a("%.2fGB", Utils.bytesToGiB(peakRSS));
726+
p.a(" | ").doclink("Peak RSS", "#glossary-peak-rss").a(": ").a("%.2fGB", ByteFormattingUtil.bytesToGiB(peakRSS));
725727
}
726728
recordJsonMetric(ResourceUsageKey.PEAK_RSS, (peakRSS >= 0 ? peakRSS : UNAVAILABLE_METRIC));
727729
OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
@@ -747,7 +749,7 @@ private void checkForExcessiveGarbageCollection() {
747749
.a(": %.1fs spent in %d GCs during the last stage, taking up %.2f%% of the time.",
748750
Utils.millisToSeconds(gcTimeDeltaMillis), currentGCStats.totalCount - lastGCStats.totalCount, ratio * 100)
749751
.println();
750-
l().a(" Please ensure more than %.2fGB of memory is available for Native Image", Utils.bytesToGiB(ProgressReporterCHelper.getPeakRSS())).println();
752+
l().a(" Please ensure more than %.2fGB of memory is available for Native Image", ByteFormattingUtil.bytesToGiB(ProgressReporterCHelper.getPeakRSS())).println();
751753
l().a(" to reduce GC overhead and improve image build time.").println();
752754
}
753755
lastGCStats = currentGCStats;
@@ -770,40 +772,8 @@ private static Timer getTimer(TimerCollection.Registry type) {
770772
private static class Utils {
771773
private static final double MILLIS_TO_SECONDS = 1000d;
772774
private static final double NANOS_TO_SECONDS = 1000d * 1000d * 1000d;
773-
private static final double BYTES_TO_KiB = 1024d;
774-
private static final double BYTES_TO_MiB = 1024d * 1024d;
775-
private static final double BYTES_TO_GiB = 1024d * 1024d * 1024d;
776-
777775
private static final Field STRING_VALUE = ReflectionUtil.lookupField(String.class, "value");
778776

779-
private static String bytesToHuman(long bytes) {
780-
return bytesToHuman("%4.2f", bytes);
781-
}
782-
783-
private static String bytesToHuman(String format, long bytes) {
784-
if (bytes < BYTES_TO_KiB) {
785-
return String.format(format, (double) bytes) + "B";
786-
} else if (bytes < BYTES_TO_MiB) {
787-
return String.format(format, bytesToKiB(bytes)) + "KB";
788-
} else if (bytes < BYTES_TO_GiB) {
789-
return String.format(format, bytesToMiB(bytes)) + "MB";
790-
} else {
791-
return String.format(format, bytesToGiB(bytes)) + "GB";
792-
}
793-
}
794-
795-
private static double bytesToKiB(long bytes) {
796-
return bytes / BYTES_TO_KiB;
797-
}
798-
799-
private static double bytesToGiB(long bytes) {
800-
return bytes / BYTES_TO_GiB;
801-
}
802-
803-
private static double bytesToMiB(long bytes) {
804-
return bytes / BYTES_TO_MiB;
805-
}
806-
807777
private static double millisToSeconds(double millis) {
808778
return millis / MILLIS_TO_SECONDS;
809779
}
@@ -821,7 +791,7 @@ private static int getInternalByteArrayLength(String string) {
821791
}
822792

823793
private static double getUsedMemory() {
824-
return bytesToGiB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
794+
return ByteFormattingUtil.bytesToGiB(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
825795
}
826796

827797
private static String stringFilledWith(int size, String fill) {
@@ -862,6 +832,10 @@ private static String truncateClassOrPackageName(String classOrPackageName) {
862832
}
863833
}
864834

835+
private static void resetANSIMode() {
836+
NativeImageSystemIOWrappers.singleton().getOut().print(ANSI.RESET);
837+
}
838+
865839
private static class GCStats {
866840
private final long totalCount;
867841
private final long totalTimeMillis;

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/HeapHistogram.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package com.oracle.svm.hosted.image;
2626

27+
import java.io.PrintWriter;
2728
import java.util.Arrays;
2829
import java.util.Comparator;
2930
import java.util.HashMap;
@@ -39,6 +40,7 @@ public class HeapHistogram {
3940
protected static boolean PrintStrings = false;
4041

4142
private final Map<HostedClass, HistogramEntry> data = new HashMap<>();
43+
private final PrintWriter out;
4244

4345
static class HistogramEntry {
4446
protected final HostedClass clazz;
@@ -50,6 +52,14 @@ static class HistogramEntry {
5052
}
5153
}
5254

55+
public HeapHistogram() {
56+
this.out = new PrintWriter(System.out);
57+
}
58+
59+
public HeapHistogram(PrintWriter out) {
60+
this.out = out;
61+
}
62+
5363
private static final Comparator<HistogramEntry> SIZE_COMPARATOR = (o1, o2) -> {
5464
// Larger sizes first
5565
int result = Long.compare(o2.size, o1.size);
@@ -77,18 +87,18 @@ public void add(ObjectInfo objectInfo, long size) {
7787
entry.size += size;
7888

7989
if (PrintStrings && objectInfo.getObject() instanceof String) {
80-
String reason = String.valueOf(objectInfo.reason);
90+
String reason = String.valueOf(objectInfo.getMainReason());
8191
String value = ((String) objectInfo.getObject()).replace("\n", "");
8292
if (!reason.startsWith("com.oracle.svm.core.hub.DynamicHub")) {
83-
System.out.format("%120s ::: %s\n", value, reason);
93+
out.format("%120s ::: %s\n", value, reason);
8494
}
8595
}
8696
}
8797

8898
public void printHeadings(final String title) {
8999
assert NativeImageOptions.PrintHeapHistogram.getValue();
90-
System.out.format("\n%s\n", title);
91-
System.out.format(headerFormat, "Count", "Size", "Size%", "Cum%", "Class");
100+
out.format("%s\n", title);
101+
out.format(headerFormat, "Count", "Size", "Size%", "Cum%", "Class");
92102
}
93103

94104
public void print() {
@@ -101,7 +111,7 @@ public void print() {
101111
long printedSize = 0;
102112
for (HistogramEntry entry : entries) {
103113
printedSize += entry.size;
104-
System.out.format(entryFormat, entry.count, entry.size, entry.size * 100d / totalSize, printedSize * 100d / totalSize, entry.clazz.toJavaName());
114+
out.format(entryFormat, entry.count, entry.size, entry.size * 100d / totalSize, printedSize * 100d / totalSize, entry.clazz.toJavaName());
105115
}
106116
}
107117

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.hosted.image;
26+
27+
import com.oracle.graal.pointsto.reports.ReportUtils;
28+
import com.oracle.svm.core.SubstrateOptions;
29+
import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
30+
import com.oracle.svm.core.feature.InternalFeature;
31+
import com.oracle.svm.core.option.HostedOptionKey;
32+
import com.oracle.svm.hosted.FeatureImpl;
33+
import org.graalvm.compiler.options.Option;
34+
import org.graalvm.compiler.options.OptionType;
35+
36+
import java.io.File;
37+
import java.io.PrintWriter;
38+
import java.nio.file.Path;
39+
import java.util.function.Consumer;
40+
41+
@AutomaticallyRegisteredFeature
42+
public class ImageHeapConnectedComponentsFeature implements InternalFeature {
43+
public static class Options {
44+
@Option(help = {"file:doc-files/PrintImageHeapConnectedComponents.md"}, type = OptionType.Debug)//
45+
static final HostedOptionKey<Boolean> PrintImageHeapConnectedComponents = new HostedOptionKey<>(false);
46+
}
47+
48+
private AbstractImage image;
49+
private NativeImageHeap heap;
50+
51+
@Override
52+
public boolean isInConfiguration(IsInConfigurationAccess access) {
53+
return Options.PrintImageHeapConnectedComponents.getValue();
54+
}
55+
56+
@Override
57+
public void afterHeapLayout(AfterHeapLayoutAccess a) {
58+
FeatureImpl.AfterHeapLayoutAccessImpl access = (FeatureImpl.AfterHeapLayoutAccessImpl) a;
59+
this.heap = access.getHeap();
60+
}
61+
62+
@Override
63+
public void beforeImageWrite(BeforeImageWriteAccess access) {
64+
this.image = ((FeatureImpl.BeforeImageWriteAccessImpl) access).getImage();
65+
}
66+
67+
@Override
68+
public void afterImageWrite(AfterImageWriteAccess a) {
69+
FeatureImpl.AfterImageWriteAccessImpl access = (FeatureImpl.AfterImageWriteAccessImpl) a;
70+
Path imagePath = access.getImagePath().getFileName();
71+
String imageName = imagePath != null ? imagePath.toString() : "native-image";
72+
ImageHeapConnectedComponentsPrinter printer = new ImageHeapConnectedComponentsPrinter(heap, access.getUniverse().getBigBang(), image, imageName);
73+
printReport("connected_components_" + imageName, "txt", printer::printConnectedComponentsObjectHistogramReport);
74+
printReport("summary_info_for_every_object_in_connected_components_" + imageName, "json", printer::printSummaryInfoForEveryObjectInConnectedComponents);
75+
printReport("access_points_for_connected_components_" + imageName, "json", printer::printAccessPointsForConnectedComponents);
76+
heap.objectReachabilityInfo.clear();
77+
}
78+
79+
private static void printReport(String reportName, String extension, Consumer<PrintWriter> writer) {
80+
File file = ReportUtils.reportFile(SubstrateOptions.reportsPath(), reportName, extension);
81+
ReportUtils.report(reportName, file.toPath(), writer);
82+
}
83+
}

0 commit comments

Comments
 (0)