Development Tools for Ruby

TruffleRuby gets tools automatically provided by GraalVM. Run --help:tools to see a full list of options.

The following program is used for illustration:

require 'chunky_png'

a = ChunkyPNG::Image.from_file('a.png')
b = ChunkyPNG::Image.from_file('b.png')
c = a.compose(b, 0, 0)
c.save('c.png')

Profiling #

CPU Tracer #

The CPU tracer records the number of times methods, blocks, or statements are run and prints a histogram. Enable it with --cputracer.

-----------------------------------------------------------------------------------------------------------------------------------
Tracing Histogram. Counted a total of 41663 element executions.
  Total Count: Number of times the element was executed and percentage of total executions.
  Interpreted Count: Number of times the element was interpreted and percentage of total executions of this element.
  Compiled Count: Number of times the compiled element was executed and percentage of total executions of this element.
-----------------------------------------------------------------------------------------------------------------------------------
 Name                                               |          Total Count |    Interpreted Count |       Compiled Count | Location
-----------------------------------------------------------------------------------------------------------------------------------
 ChunkyPNG::Color#a                                 |          6474  15.5% |          6474 100.0% |             0   0.0% | chunky_png/color.rb~296-298:12189-12237
 block in ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_paeth |          5248  12.6% |          5248 100.0% |             0   0.0% | chunky_png/canvas/png_decoding.rb~476:25098-25131
 ChunkyPNG::Color#int8_mult                         |          3619   8.7% |          3619 100.0% |             0   0.0% | chunky_png/color.rb~344-347:13821-13900
 ChunkyPNG::Canvas#width                            |          3205   7.7% |          3205 100.0% |             0   0.0% | chunky_png/canvas.rb~50:1853-1874
 block in ChunkyPNG::Canvas::PNGEncoding#encode_png_str_scanline_up |          3072   7.4% |          3072 100.0% |             0   0.0% | chunky_png/canvas/png_encoding.rb~404:20111-20145
 ChunkyPNG::Canvas#get_pixel                        |          2048   4.9% |          2048 100.0% |             0   0.0% | chunky_png/canvas.rb~184-186:7144-7203
 ChunkyPNG::Color#opaque?                           |          1872   4.5% |          1872 100.0% |             0   0.0% | chunky_png/color.rb~304-306:12410-12468
 ChunkyPNG::Color#fully_transparent?                |          1500   3.6% |          1500 100.0% |             0   0.0% | chunky_png/color.rb~327-329:13138-13207
 block in ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_up |          1408   3.4% |          1408 100.0% |             0   0.0% | chunky_png/canvas/png_decoding.rb~453:24009-24042
 block in ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_sub |          1280   3.1% |          1280 100.0% |             0   0.0% | chunky_png/canvas/png_decoding.rb~443:23517-23550
 ChunkyPNG::Color#b                                 |          1037   2.5% |          1037 100.0% |             0   0.0% | chunky_png/color.rb~288-290:11969-12024
 ChunkyPNG::Color#r                                 |          1036   2.5% |          1036 100.0% |             0   0.0% | chunky_png/color.rb~272-274:11534-11590
 ChunkyPNG::Color#g                                 |          1035   2.5% |          1035 100.0% |             0   0.0% | chunky_png/color.rb~280-282:11752-11808
 ChunkyPNG::Color#compose_quick                     |          1024   2.5% |          1024 100.0% |             0   0.0% | chunky_png/color.rb~358-368:14300-14740
 ChunkyPNG::Canvas#set_pixel                        |          1024   2.5% |          1024 100.0% |             0   0.0% | chunky_png/canvas.rb~149-151:5706-5780
 block (2 levels) in ChunkyPNG::Canvas::Operations#compose! |          1024   2.5% |          1024 100.0% |             0   0.0% | chunky_png/canvas/operations.rb~58:2331-2367
 block in ChunkyPNG::Palette#opaque?                |           848   2.0% |           848 100.0% |             0   0.0% | chunky_png/palette.rb~89:3319-3361
 SortedSet#add                                      |           848   2.0% |           848 100.0% |             0   0.0% | lib/mri/set.rb~741-745:1000-1169
 ChunkyPNG::Color#rgba                              |           518   1.2% |           518 100.0% |             0   0.0% | chunky_png/color.rb~100-102:3905-3973
 block in ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_average |           256   0.6% |           256 100.0% |             0   0.0% | chunky_png/canvas/png_decoding.rb~464:24513-24546
 rb_str_new                                         |           199   0.5% |           199 100.0% |             0   0.0% | src/main/c/cext/ruby.c~837:23634-23684
 is_managed_rstring_ptr                             |           193   0.5% |           193 100.0% |             0   0.0% | src/main/c/cext/ruby.c~832:23499-23538
 is_rstring_ptr                                     |           193   0.5% |           193 100.0% |             0   0.0% | src/main/c/cext/ruby.c~828:23430-23461
 strlen                                             |           190   0.5% |           190 100.0% |             0   0.0% | string.c~56:0
 rb_str_new_cstr                                    |           190   0.5% |           190 100.0% |             0   0.0% | src/main/c/cext/ruby.c~858:24268-24310
 RB_NIL_P                                           |           138   0.3% |           138 100.0% |             0   0.0% | src/main/c/cext/ruby.c~413:11068-11094

Note how the C function strlen, and the C preprocessor macro RB_NIL_P show up, being called by the zlib C extension.

CPU Sampler #

The CPU tracer records the cumulative time spent executing methods, blocks, or statements, and prints a histogram. Enable it with --cpusampler.

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sampling Histogram. Recorded 3895 samples with period 1ms.
  Self Time: Time spent on the top of the stack.
  Total Time: Time spent somewhere on the stack.
  Opt %: Percent of time spent in compiled and therefore non-interpreted code.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Thread: Thread[main,5,main]
 Name                                                                                |      Total Time     |  Opt % ||       Self Time     |  Opt % | Location             
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 <main>                                                                              |       3635ms  93.3% |   0.0% ||       1086ms  27.9% |   0.0% | test.rb~1-6:0-140
 <top (required)>                                                                    |       1068ms  27.4% |   0.0% ||        708ms  18.2% |   0.0% | chunky_png.rb~1-179:0-5528
 block in ChunkyPNG::Canvas::PNGEncoding#encode_png_str_scanline_up                  |        643ms  16.5% |   0.0% ||        643ms  16.5% |   0.0% | chunky_png/canvas/png_encoding.rb~404:20111-20145
 __sulong_dispose_context                                                            |        260ms   6.7% |   0.0% ||        257ms   6.6% |   0.0% | sulong_dispose_context.c~33:0
 block in ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_paeth               |         58ms   1.5% |   0.0% ||         58ms   1.5% |   0.0% | chunky_png/canvas/png_decoding.rb~476:25098-25131
 do_checksum                                                                         |         77ms   2.0% |   0.0% ||         47ms   1.2% |   0.0% | src/main/c/zlib/zlib.c~389:12721-12796
 ChunkyPNG::Palette#initialize                                                       |         52ms   1.3% |   0.0% ||         41ms   1.1% |   0.0% | chunky_png/palette.rb~21-24:954-1074
 block in Gem::Specification.load                                                    |         39ms   1.0% |   0.0% ||         39ms   1.0% |   0.0% | bigdecimal-1.4.1.gemspec~5:126-154
 rb_define_method                                                                    |         50ms   1.3% |   0.0% ||         36ms   0.9% |   0.0% | src/main/c/cext/ruby.c~2185:61849-61941
 rb_tr_init                                                                          |         44ms   1.1% |   0.0% ||         34ms   0.9% |   0.0% | src/main/c/cext/ruby.c~93:2351-2384
 <top (required)>                                                                    |         42ms   1.1% |   0.0% ||         33ms   0.8% |   0.0% | chunky_png/canvas.rb~1-372:0-13593
 __sulong_byte_array_to_native                                                       |         30ms   0.8% |   0.0% ||         30ms   0.8% |   0.0% | crt0.c~59:0
 rb_str_new                                                                          |         33ms   0.8% |   0.0% ||         29ms   0.7% |   0.0% | src/main/c/cext/ruby.c~837:23634-23684
 __sulong_init_context                                                               |         68ms   1.7% |   0.0% ||         26ms   0.7% |   0.0% | crt0.c~80:0
 Init_zlib                                                                           |        145ms   3.7% |   0.0% ||         24ms   0.6% |   0.0% | src/main/c/zlib/zlib.c~4465:112360-112374
 rb_zlib_crc32                                                                       |        101ms   2.6% |   0.0% ||         24ms   0.6% |   0.0% | src/main/c/zlib/zlib.c~473:14760-14808
 rb_data_typed_object_wrap                                                           |         21ms   0.5% |   0.0% ||         21ms   0.5% |   0.0% | src/main/c/cext/ruby.c~2834:79544-79639
 rb_tr_load_library                                                                  |         22ms   0.6% |   0.0% ||         20ms   0.5% |   0.0% | src/main/c/cext/ruby.c~2906:81858-81897
 ChunkyPNG::Canvas::PNGEncoding#encode_png_str_scanline_up                           |        662ms  17.0% |   0.0% ||         19ms   0.5% |   0.0% | chunky_png/canvas/png_encoding.rb~403-409:20027-20347
 rb_define_class_id_under                                                            |         18ms   0.5% |   0.0% ||         18ms   0.5% |   0.0% | src/main/c/cext/ruby.c~2165:61106-61178
 ChunkyPNG::Chunk.read                                                               |        137ms   3.5% |   0.0% ||         17ms   0.4% |   0.0% | chunky_png/chunk.rb~18-26:783-1054
 Datastream                                                                          |         15ms   0.4% |   0.0% ||         15ms   0.4% |   0.0% | chunky_png/datastream.rb~11:364-381
 ChunkyPNG::Color#compose_quick                                                      |         37ms   0.9% |   0.0% ||         14ms   0.4% |   0.0% | chunky_png/color.rb~358-368:14300-14740
 rb_deflate_s_deflate                                                                |         27ms   0.7% |   0.0% ||         13ms   0.3% |   0.0% | src/main/c/zlib/zlib.c~1622:44042-44097
 ChunkyPNG::Canvas::PNGDecoding#decode_png_str_scanline_paeth                        |         71ms   1.8% |   0.0% ||         13ms   0.3% |   0.0% | chunky_png/canvas/png_decoding.rb~475-488:25010-25650
 ChunkyPNG::Palette.from_canvas                                                      |         64ms   1.6% |   0.0% ||         12ms   0.3% |   0.0% | chunky_png/palette.rb~63-68:2377-2629
 __sulong_byte_arrays_to_native                                                      |         42ms   1.1% |   0.0% ||         12ms   0.3% |   0.0% | crt0.c~69:0
 ChunkyPNG::Chunk::ImageData.combine_chunks                                          |        167ms   4.3% |   0.0% ||         12ms   0.3% |   0.0% | chunky_png/chunk.rb~247-253:9766-9980
 rb_inflate_initialize                                                               |         19ms   0.5% |   0.0% ||         12ms   0.3% |   0.0% | src/main/c/zlib/zlib.c~1887:51522-51576
 strlen                                                                              |         12ms   0.3% |   0.0% ||         12ms   0.3% |   0.0% | string.c~56:0
 zstream_run                                                                         |         76ms   2.0% |   0.0% ||         12ms   0.3% |   0.0% | src/main/c/zlib/zlib.c~1024:27867-27929

By default you see this histogram, but you can also see a call tree with --cpusampler.Output=calltree.

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sampling CallTree. Recorded 3102 samples with period 1ms.
  Self Time: Time spent on the top of the stack.
  Total Time: Time spent somewhere on the stack.
  Opt %: Percent of time spent in compiled and therefore non-interpreted code.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Thread: Thread[main,5,main]
 Name                                                                                        |      Total Time     |  Opt % ||       Self Time     |  Opt % | Location             
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  ChunkyPNG::Canvas::PNGEncoding#save                                                        |        582ms  18.8% |   0.0% ||          5ms   0.2% |   0.0% | chunky_png/canvas/png_encoding.rb~42-44:1754-1871
   block in ChunkyPNG::Canvas::PNGEncoding#save                                              |        577ms  18.6% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~43:1797-1861
    ChunkyPNG::Canvas::PNGEncoding#write                                                     |        577ms  18.6% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~34-36:1430-1521
     ChunkyPNG::Image#to_datastream                                                          |        571ms  18.4% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/image.rb~61-65:2314-2447
      ChunkyPNG::Canvas::PNGEncoding#to_datastream                                           |        569ms  18.3% |   0.0% ||          1ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~74-89:3715-4561
       ChunkyPNG::Canvas::PNGEncoding#encode_png_pixelstream                                 |        489ms  15.8% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~153-165:7702-8570
        ChunkyPNG::Canvas::PNGEncoding#encode_png_image_without_interlacing                  |        489ms  15.8% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~172-176:8925-9195
         ChunkyPNG::Canvas::PNGEncoding#encode_png_image_pass_to_stream                      |        489ms  15.8% |   0.0% ||          3ms   0.1% |   0.0% | chunky_png/canvas/png_encoding.rb~203-231:10502-11685
          block in ChunkyPNG::Canvas::PNGEncoding#encode_png_image_pass_to_stream            |        481ms  15.5% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~225:11418-11456
           ChunkyPNG::Canvas::PNGEncoding#encode_png_str_scanline_up                         |        481ms  15.5% |   0.0% ||         11ms   0.4% |   0.0% | chunky_png/canvas/png_encoding.rb~403-409:20027-20347
            block in ChunkyPNG::Canvas::PNGEncoding#encode_png_str_scanline_up               |        470ms  15.2% |   0.0% ||        470ms  15.2% |   0.0% | chunky_png/canvas/png_encoding.rb~404:20111-20145
          block in ChunkyPNG::Canvas::PNGEncoding#encode_png_image_pass_to_stream            |          5ms   0.2% |   0.0% ||          1ms   0.0% |   0.0% | chunky_png/canvas/png_encoding.rb~219:11252-11284
           ChunkyPNG::Canvas::PNGEncoding#encode_png_pixels_to_scanline_truecolor_8bit       |          4ms   0.1% |   0.0% ||          4ms   0.1% |   0.0% | chunky_png/canvas/png_encoding.rb~236-238:11896-12009
       ChunkyPNG::Canvas::PNGEncoding#determine_png_encoding                                 |         60ms   1.9% |   0.0% ||          2ms   0.1% |   0.0% | chunky_png/canvas/png_encoding.rb~102-145:5085-7303
        ChunkyPNG::Canvas#palette                                                            |         51ms   1.6% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/canvas.rb~268-270:10089-10154
         ChunkyPNG::Palette.from_canvas                                                      |         51ms   1.6% |   0.0% ||          9ms   0.3% |   0.0% | chunky_png/palette.rb~63-68:2377-2629
          ChunkyPNG::Palette#initialize                                                      |         42ms   1.4% |   0.0% ||         30ms   1.0% |   0.0% | chunky_png/palette.rb~21-24:954-1074
           SortedSet#initialize                                                              |         12ms   0.4% |   0.0% ||          0ms   0.0% |   0.0% | lib/mri/set.rb~726-729:725-819
            SortedSet#merge                                                                  |         12ms   0.4% |   0.0% ||          6ms   0.2% |   0.0% | lib/mri/set.rb~770-773:1759-1847
             SortedSet#add                                                                   |          6ms   0.2% |   0.0% ||          6ms   0.2% |   0.0% | lib/mri/set.rb~741-745:1000-1169
        ChunkyPNG::Palette#best_color_settings                                               |          7ms   0.2% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/palette.rb~195-211:7150-7613
         ChunkyPNG::Palette#opaque?                                                          |          3ms   0.1% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/palette.rb~88-90:3303-3369
          SortedSet#each                                                                     |          3ms   0.1% |   0.0% ||          2ms   0.1% |   0.0% | lib/mri/set.rb~775-779:1850-2004
           block in ChunkyPNG::Palette#opaque?                                               |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/palette.rb~89:3319-3361
            ChunkyPNG::Color#opaque?                                                         |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/color.rb~304-306:12410-12468
             ChunkyPNG::Color#a                                                              |          1ms   0.0% |   0.0% ||          1ms   0.0% |   0.0% | chunky_png/color.rb~296-298:12189-12237
         ChunkyPNG::Palette#black_and_white?                                                 |          3ms   0.1% |   0.0% ||          1ms   0.0% |   0.0% | chunky_png/palette.rb~104-106:3841-3940
          SortedSet#each                                                                     |          2ms   0.1% |   0.0% ||          1ms   0.0% |   0.0% | lib/mri/set.rb~775-779:1850-2004
           SortedSet#to_a                                                                    |          1ms   0.0% |   0.0% ||          1ms   0.0% |   0.0% | lib/mri/set.rb~781-784:2007-2116
         ChunkyPNG::Palette#grayscale?                                                       |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | chunky_png/palette.rb~96-98:3570-3642
          SortedSet#each                                                                     |          1ms   0.0% |   0.0% ||          1ms   0.0% |   0.0% | lib/mri/set.rb~775-779:1850-2004
       ChunkyPNG::Chunk::ImageData.split_in_chunks                                           |         19ms   0.6% |   0.0% ||          7ms   0.2% |   0.0% | chunky_png/chunk.rb~255-259:9983-10267
        rb_deflate_s_deflate                                                                 |         12ms   0.4% |   0.0% ||          4ms   0.1% |   0.0% | src/main/c/zlib/zlib.c~1622:44042-44097
         rb_ensure                                                                           |          5ms   0.2% |   0.0% ||          1ms   0.0% |   0.0% | src/main/c/cext/ruby.c~2107:59266-59360
          deflate_run                                                                        |          4ms   0.1% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/zlib/zlib.c~1589:43218-43240
           zstream_run                                                                       |          4ms   0.1% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/zlib/zlib.c~1024:27867-27929
            zstream_append_input                                                             |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/zlib/zlib.c~827:23738-23804
             rb_str_buf_cat                                                                  |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/cext/ruby.c~981:27520-27591
              rb_str_cat                                                                     |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/cext/ruby.c~882:24901-24968
               rb_enc_str_new                                                                |          1ms   0.0% |   0.0% ||          0ms   0.0% |   0.0% | src/main/c/cext/ruby.c~1301:37035-37101

Coverage #

The coverage tool reports coverage by statement, line, and root. Root means the root of a function, so how many methods and blocks were covered. Enable it with --coverage.

-------------------------------------------------------------------------------------------------------------------------------------
Code coverage histogram.
  Shows what percent of each element was covered during execution
-------------------------------------------------------------------------------------------------------------------------------------
 Path                                                                                            |  Statements |    Lines |    Roots
-------------------------------------------------------------------------------------------------------------------------------------
 chunky_png-1.3.11/lib/chunky_png.rb                                                             |     100.00% |  100.00% |  100.00%
 chunky_png-1.3.11/lib/chunky_png/canvas.rb                                                      |      58.40% |   69.41% |   35.90%
 chunky_png-1.3.11/lib/chunky_png/canvas/adam7_interlacing.rb                                    |      28.57% |   50.00% |   28.57%
 chunky_png-1.3.11/lib/chunky_png/canvas/data_url_exporting.rb                                   |      80.00% |   83.33% |   80.00%
 chunky_png-1.3.11/lib/chunky_png/canvas/data_url_importing.rb                                   |      57.14% |   70.00% |   80.00%
 chunky_png-1.3.11/lib/chunky_png/canvas/drawing.rb                                              |       8.28% |   44.02% |   13.33%
 chunky_png-1.3.11/lib/chunky_png/canvas/masking.rb                                              |      28.57% |   51.72% |   44.44%
 chunky_png-1.3.11/lib/chunky_png/canvas/operations.rb                                           |      42.42% |   65.07% |   21.95%
 chunky_png-1.3.11/lib/chunky_png/canvas/png_decoding.rb                                         |      52.84% |   68.27% |   26.98%
 chunky_png-1.3.11/lib/chunky_png/canvas/png_encoding.rb                                         |      44.44% |   62.07% |   40.43%
 chunky_png-1.3.11/lib/chunky_png/canvas/resampling.rb                                           |      17.46% |   48.51% |   25.00%
 chunky_png-1.3.11/lib/chunky_png/canvas/stream_exporting.rb                                     |      61.54% |   72.22% |   44.44%
 chunky_png-1.3.11/lib/chunky_png/canvas/stream_importing.rb                                     |      31.82% |   45.83% |   40.00%
 chunky_png-1.3.11/lib/chunky_png/chunk.rb                                                       |      82.84% |   86.32% |   68.42%
 chunky_png-1.3.11/lib/chunky_png/color.rb                                                       |      41.82% |   59.00% |   33.93%
 chunky_png-1.3.11/lib/chunky_png/compatibility.rb                                               |      75.00% |   66.67% |   75.00%
 chunky_png-1.3.11/lib/chunky_png/datastream.rb                                                  |      83.56% |   87.50% |   80.00%
 chunky_png-1.3.11/lib/chunky_png/dimension.rb                                                   |      42.11% |   62.07% |   23.08%
 chunky_png-1.3.11/lib/chunky_png/image.rb                                                       |      85.00% |   90.32% |   90.00%
 chunky_png-1.3.11/lib/chunky_png/palette.rb                                                     |      41.18% |   63.46% |   42.31%
 chunky_png-1.3.11/lib/chunky_png/point.rb                                                       |      42.86% |   62.96% |   25.00%
 chunky_png-1.3.11/lib/chunky_png/vector.rb                                                      |      40.98% |   63.10% |   10.34%
 chunky_png-1.3.11/lib/chunky_png/version.rb                                                     |     100.00% |  100.00% |  100.00%
-------------------------------------------------------------------------------------------------------------------------------------

Debugging #

Chrome Inspector #

GraalVM lets you debug Ruby programs, and any other language supported by GraalVM, using the Chrome DevTools Protocol to attach to debuggers such as Chrome Developer Tools.

Run with --inspect, open the given URL in Chrome, drag your file system into the sources list, and then set a breakpoint and resume execution. Having the debugger attached and simple breakpoints set should not reduce performance.

Chrome Developer Tools debugger session

NetBeans #

You can also debug GraalVM languages using NetBeans. Run with --jvm --vm.agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y and attach to the process as if you were debugging a Java program. Then click Toggle pause in GraalVM script (the pause icon with a G superimposed) and break and you will break into the Ruby code, and see Ruby stack frames and local variables rather than Java stack frames and local variables.

NetBeans debugger session