graal icon indicating copy to clipboard operation
graal copied to clipboard

[GR-41269] Build output: "other data" is miscalculated

Open fniephaus opened this issue 2 years ago • 11 comments

It seems that https://github.com/oracle/graal/pull/4707 has introduced a reporting issue. when debug info is enabled, "other data" is usually negative. An example:

[7/7] Creating image...                                                                                 (24.5s @ 2.14GB)
   6.08MB ( 9.26%) for code area:    10,189 compilation units
   9.59MB (14.62%) for image heap:  138,041 objects and 85 resources
  80.62MB (122.88%) for debug info generated in 15.0s
-32170672.00B (-46.76%) for other data
  65.61MB in total

Build: https://github.com/oracle/graal/runs/7838086740?check_suite_focus=true#step:10:844

If you could prepare a fix, @jerboaa, that'd be great. Thanks!

fniephaus avatar Aug 15 '22 16:08 fniephaus

https://github.com/oracle/graal/pull/4707 only indirectly introduced this issue. The determined debuginfo size seems to exceed the actual on-disk size (as determined by objcopy --only-keep-debug <binary> <binary>.debuginfo -> ls -l the size of the <binary>.debuginfo) by about the same size the calculation of debuginfo + heap size + code area exceeds the actual on-disk image size. For example, for an hello world with -H:+SourceLevelDebug I'm seeing these numbers:

(d) debuginfo-bytes: 11577272
(h) heap-bytes:       7266304
(c) code-area-bytes:  4573232
------------------------------------
    total size:      22135952
    sum (d+h+c):     23416808
    delta:           -1280856

vs. debuginfo size when using on-disk size of the file generated with objcopy --only-keep-debug <binary> <binary>.debuginfo:

(d) debuginfo-bytes: 10189568
(h) heap-bytes:       7266304
(c) code-area-bytes:  4573232
------------------------------------
    total size:      22135952
    sum (d+h+c):     22029104
    delta:             106848

The former debuginfo size is 11577272 and the latter debuginfo size is 10189568 with a delta of ~1.2 MB. That yields me to believe that the debuginfo size reporting is similarly incorrect as the total image size was prior to #4707.

jerboaa avatar Aug 16 '22 12:08 jerboaa

Thanks for investigating... yeah, sounds like we need a similar fix for the debuginfo.

fniephaus avatar Aug 16 '22 13:08 fniephaus

That yields me to believe that the debuginfo size reporting is similarly incorrect as the total image size was prior to https://github.com/oracle/graal/pull/4707.

Hmm, how is that reported debuginfo size actually calculated?

There is not much room for doubt as to exactly how much generated debuginfo data is written into the file. The generator hands over a byte[] as the contents to be written into each section. So, the amount produced can easily be computed as a sum of the individual byte array sizes (possibly rounded-up to conform with any equirement for section alignment).

That said, comparison of the generated byte count with final image byte count is not so straightforward. The image can include debuginfo merged in from compiled C code during final linking. I don't know if there is any easy way to compute how much this will increase the final size (other than linking and seeing what turns up, obviously).

So, the on-disk debuginfo size could be significantly increased relative to the generated content size. I'd like to understand how we have got to the point where the on-disk size is reported as smaller?

adinn avatar Aug 16 '22 13:08 adinn

That yields me to believe that the debuginfo size reporting is similarly incorrect as the total image size was prior to #4707.

Hmm, how is that reported debuginfo size actually calculated?

There is not much room for doubt as to exactly how much generated debuginfo data is written into the file. The generator hands over a byte[] as the contents to be written into each section. So, the amount produced can easily be computed as a sum of the individual byte array sizes (possibly rounded-up to conform with any equirement for section alignment).

That said, comparison of the generated byte count with final image byte count is not so straightforward. The image can include debuginfo merged in from compiled C code during final linking. I don't know if there is any easy way to compute how much this will increase the final size (other than linking and seeing what turns up, obviously).

So, the on-disk debuginfo size could be significantly increased relative to the generated content size. I'd like to understand how we have got to the point where the on-disk size is reported as smaller?

The comparison I've done is:

  1. Generate a native-image with debuginfo: native-image -H:BuildOutputJSONFile=build_stats.json -H:+SourceLevelDebug -g HelloWorld hello-debug
  2. Get the number what the native image generator thinks the debuginfo size is: cat build_stats.json | jq .image_details.debug_info.bytes => 11582272
  3. Strip the debuginfo from the hello-debug binary using objcopy:
$ objcopy --only-keep-debug hello-debug hello-debug.debuginfo
$ ls -l hello-debug.debuginfo
-rwxr-xr-x. 1 sgehwolf sgehwolf 10194576 Aug 16 15:38 hello-debug.debuginfo
  1. Compare that number to the one the native image generator reports. See that it's larger. The native image reported number is 11582272 vs. 10194576 on-disk.

jerboaa avatar Aug 16 '22 13:08 jerboaa

Hmm, how is that reported debuginfo size actually calculated?

AFAIK it's this:

        debugInfoSize = 0;
        String debugIdentifier = OS.getCurrent() == OS.DARWIN ? "__debug" : ".debug";
        for (Element e : objectFile.getElements()) {
            if (e.getName().contains(debugIdentifier)) {
                debugInfoSize += e.getMemSize(objectFile.getDecisionsByElement());
            }
        }

jerboaa avatar Aug 16 '22 13:08 jerboaa

Looking at e.getName().contains(debugIdentifier), I wonder if that's correct. I see this on readelf --sections on hello-debug:

  [29] .debug_aranges    PROGBITS         0000000000000000  00b65480
       0000000000029290  0000000000000000           0     0     8
  [30] .debug_info       PROGBITS         0000000000000000  00b8e710
       000000000014d304  0000000000000000           0     0     8
  [31] .debug_abbrev     PROGBITS         0000000000000000  00cdba18
       0000000000000f3b  0000000000000000           0     0     8
  [32] .debug_line       PROGBITS         0000000000000000  00cdc958
       00000000000d0e49  0000000000000000           0     0     8
  [33] .debug_frame      PROGBITS         0000000000000000  00dad7a8
       000000000004a6a8  0000000000000000           0     0     8
  [34] .debug_str        PROGBITS         0000000000000000  00df7e50
       00000000000ef07e  0000000000000000           0     0     8
  [35] .debug_loc        PROGBITS         0000000000000000  00ee6ed0
       00000000006308d1  0000000000000000           0     0     8
  [36] .debug_ranges     PROGBITS         0000000000000000  015177a1
       0000000000001600  0000000000000000           0     0     1

Should it really count all .debug* sections?

jerboaa avatar Aug 16 '22 13:08 jerboaa

Ah, ok. I think I may have the answer to this then.

Several of the standard .debug_<xxx> sections include fields that are relocatable addresses. For example, a method location record includes a low and high pc. Each of these is specified using a relocation, computed by adding a constant offset to the virtual memory base address (VMA) of the .text section.

Any section with name .debug_xxx that requires relocs has a corresponding section with name .rela.debug_xxx wher edetails of the reloc operation and parameters are written. So, the above code is counting the bytes in the reloc sections as well as the bytes in the debug sections. That's a problem because the reloc section data is squeezed out of the picture during final linking. It is only the bytes for the non-reloc sections that make it into the final image.

A solution which would work for the current ELF/Linux generator is this

    debugInfoSize = 0;
    String debugIdentifier = OS.getCurrent() == OS.DARWIN ? "__debug" : ".debug";
    for (Element e : objectFile.getElements()) {
        String name = e.getName()
        if (name.contains(debugIdentifier) &&
            !name.startsWith(".rela")) {
            debugInfoSize += e.getMemSize(objectFile.getDecisionsByElement());
        }
    }

That will not count the bytes in the reloc sections.

Someone with better knowledge of the object file code needs to deal with DARWIN.

There is also a remaining issue on Linux. The prefix ".rela" will work with the current debug code but that is only modulo the current definition of ObjectFile.useExplicitAddend(int). The prefix might need to be specified as ".rel". A proper solution means the code above really ought to check what that method returns and construct the prefix accordingly. When creating the debug info reloc sections the int argument is set to 0 but only because 1) I had no idea what value to pass and 2) it currently makes no difference. A proper solution fro this bit of code should at the very least call useExplicitAddend with argument 0 or alternatively fix the reloc section create to use a sensible value and adjust the call here accordingly.

adinn avatar Aug 16 '22 14:08 adinn

Should it really count all .debug* sections?

Yes, the ones you list all go into the final image.

adinn avatar Aug 16 '22 14:08 adinn

@fniephaus I'm not sure I'm the right assignee for this. Unassigning myself.

jerboaa avatar Aug 16 '22 14:08 jerboaa

@olpaw Might you be able to fix this?

adinn avatar Aug 16 '22 14:08 adinn

I've tested this patch on Linux and it seems promising (for that platform):

commit 67c631e234be7db506eb3ab7995d929e6164c29a (HEAD -> debuginfo-neg-size)
Author: Severin Gehwolf <[email protected]>
Date:   Tue Aug 16 16:21:27 2022 +0200

    Test debuginfo size fix

diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java
index 4d9d577d8b2a..2ec21434a97d 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImage.java
@@ -184,7 +184,8 @@ public abstract class NativeImage extends AbstractImage {
         debugInfoSize = 0;
         String debugIdentifier = OS.getCurrent() == OS.DARWIN ? "__debug" : ".debug";
         for (Element e : objectFile.getElements()) {
-            if (e.getName().contains(debugIdentifier)) {
+            String name = e.getName();
+            if (name.contains(debugIdentifier) && !name.startsWith(".rela")) {
                 debugInfoSize += e.getMemSize(objectFile.getDecisionsByElement());
             }
         }

Build output now seems better:

native-image -H:BuildOutputJSONFile=build_stats.json -H:+SourceLevelDebug -g -cp mandrel-build HelloWorld hello-debug
========================================================================================================================
GraalVM Native Image: Generating 'hello-debug' (executable)...
========================================================================================================================
[1/7] Initializing...                                                                                    (3.2s @ 0.21GB)
 Version info: 'GraalVM 21.3.3-dev Java 17 Mandrel Distribution'
 Java version info: '17.0.4-beta+7-202206282316'
 C compiler: gcc (redhat, x86_64, 12.1.1)
 Garbage collector: Serial GC
[2/7] Performing analysis...  [*****]                                                                   (10.5s @ 0.82GB)
   2,848 (73.42%) of  3,879 classes reachable
   3,441 (51.22%) of  6,718 fields reachable
  12,575 (43.27%) of 29,064 methods reachable
      28 classes,     0 fields, and   317 methods registered for reflection
      58 classes,    58 fields, and    52 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                               (1.7s @ 0.48GB)
[4/7] Parsing methods...      [*]                                                                        (1.4s @ 0.46GB)
[5/7] Inlining methods...     [***]                                                                      (1.3s @ 1.25GB)
[6/7] Compiling methods...    [***]                                                                     (10.8s @ 0.96GB)
[7/7] Creating image...                                                                                  (6.9s @ 4.06GB)
   4.36MB (20.66%) for code area:     7,239 compilation units
   6.93MB (32.82%) for image heap:   97,196 objects and 5 resources
   9.57MB (45.32%) for debug info generated in 3.4s
 258.26KB ( 1.19%) for other data
  21.11MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
 700.53KB java.util                                          908.44KB java.lang.String
 352.26KB java.lang                                          904.57KB byte[] for code metadata
 279.51KB java.text                                          843.52KB byte[] for general heap data
 229.59KB java.util.regex                                    625.16KB java.lang.Class
 204.38KB java.util.concurrent                               526.50KB byte[] for java.lang.String
 157.08KB java.math                                          430.88KB java.util.HashMap$Node
 126.58KB com.oracle.svm.core.genscavenge                    222.50KB com.oracle.svm.core.hub.DynamicHubCompanion
 123.00KB java.lang.invoke                                   206.36KB java.util.HashMap$Node[]
 100.75KB com.oracle.svm.core.code                           158.89KB java.lang.String[]
 100.71KB java.util.stream                                   155.81KB java.util.concurrent.ConcurrentHashMap$Node
   1.99MB for 120 more packages                                1.55MB for 776 more object types
------------------------------------------------------------------------------------------------------------------------
                        0.9s (2.3% of total time) in 20 GCs | Peak RSS: 4.39GB | CPU load: 5.99
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /disk/graal/upstream-sources/graal/build_stats.json (json)
 /disk/graal/upstream-sources/graal/hello-debug (executable, debug_info)
 /disk/graal/upstream-sources/graal/hello-debug.build_artifacts.txt (txt)
 /disk/graal/upstream-sources/graal/sources (debug_info)
========================================================================================================================
Finished generating 'hello-debug' in 37.3s.

Compare to a non-debuginfo build:

native-image -H:BuildOutputJSONFile=build_stats.json -cp mandrel-build HelloWorld hello-debug
========================================================================================================================
GraalVM Native Image: Generating 'hello-debug' (executable)...
========================================================================================================================
[1/7] Initializing...                                                                                    (3.1s @ 0.21GB)
 Version info: 'GraalVM 21.3.3-dev Java 17 Mandrel Distribution'
 Java version info: '17.0.4-beta+7-202206282316'
 C compiler: gcc (redhat, x86_64, 12.1.1)
 Garbage collector: Serial GC
[2/7] Performing analysis...  [*****]                                                                   (10.9s @ 0.88GB)
   2,848 (73.44%) of  3,878 classes reachable
   3,437 (51.18%) of  6,716 fields reachable
  12,572 (43.26%) of 29,061 methods reachable
      28 classes,     0 fields, and   317 methods registered for reflection
      58 classes,    58 fields, and    52 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                               (1.6s @ 0.44GB)
[4/7] Parsing methods...      [*]                                                                        (1.2s @ 1.15GB)
[5/7] Inlining methods...     [***]                                                                      (0.7s @ 0.59GB)
[6/7] Compiling methods...    [***]                                                                     (11.3s @ 0.62GB)
[7/7] Creating image...                                                                                  (2.0s @ 1.07GB)
   4.06MB (36.12%) for code area:     7,225 compilation units
   6.93MB (61.64%) for image heap:   97,186 objects and 5 resources
 258.41KB ( 2.24%) for other data
  11.24MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
 656.25KB java.util                                          908.25KB java.lang.String
 329.20KB java.lang                                          897.75KB byte[] for code metadata
 264.91KB java.text                                          843.42KB byte[] for general heap data
 216.35KB java.util.regex                                    625.03KB java.lang.Class
 192.82KB java.util.concurrent                               526.44KB byte[] for java.lang.String
 146.93KB java.math                                          430.88KB java.util.HashMap$Node
 116.98KB java.lang.invoke                                   222.50KB com.oracle.svm.core.hub.DynamicHubCompanion
 105.05KB com.oracle.svm.core.genscavenge                    206.36KB java.util.HashMap$Node[]
  94.10KB java.util.logging                                  158.86KB java.lang.String[]
  93.86KB java.util.stream                                   155.81KB java.util.concurrent.ConcurrentHashMap$Node
   1.84MB for 119 more packages                                1.55MB for 775 more object types
------------------------------------------------------------------------------------------------------------------------
                        0.8s (2.3% of total time) in 19 GCs | Peak RSS: 3.12GB | CPU load: 6.24
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /disk/graal/upstream-sources/graal/build_stats.json (json)
 /disk/graal/upstream-sources/graal/hello-debug (executable)
 /disk/graal/upstream-sources/graal/hello-debug.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'hello-debug' in 32.5s.

Also the debuginfo size comparing on-disk vs. what the build output produces:

on-disk:       10195464
build-output:  10037856

jerboaa avatar Aug 16 '22 14:08 jerboaa

I pushed your fix, see https://github.com/oracle/graal/commit/0e7ba4b15c0026009033a6ce5f9cf84cddcc4598.

fniephaus avatar Sep 23 '22 07:09 fniephaus

Great, thanks!

jerboaa avatar Sep 23 '22 08:09 jerboaa