Ruby VM measurements

Insight into the behavior of the Ruby virtual machine can help you understand and improve the performance of your application as a whole. New Relic gathers some key metrics that can help you get a better picture of what your Ruby VM is doing. This can also help you to evaluate the impact of adjustments to VM configuration to improve performance.

Minimum requirements

Ruby VM metric collections are available in versions 3.8.0 or higher of the Ruby agent (although earlier versions did support collection of some basic garbage collection measurements).

In addition, in order to use this feature, you'll need to be on a Ruby version compatible with MRI (Matz's Ruby Interpreter, the most commonly used Ruby implementation) 1.9.2 or higher. The sections below on each individual measurement explains what Ruby versions support collection of each measurement.

Lastly, in order to capture GC timings, you'll need to enable GC::Profiler in your application.

Viewing Ruby VM data

To view data about the performance of your Ruby VMs: Go to rpm.newrelic.com/apm > (select an app) > Monitoring > Ruby VMs. This page appears only if your application uses Ruby agent version 3.8.0 or higher.

Measurement details

Most of these metrics help understand the behavior of Ruby's garbage collector.

Not all metrics can be collected across all Ruby versions. In general, you'll get the most complete data if you're on the most recent version of MRI. The list below explains exactly what's available where:

Object allocations

Available on: MRI 2.0+

The number of Ruby objects allocated in your processes. In the UI, this measurement is normalized by the number of requests, so that you can get a sense of how many objects are allocated per request.

This is an important number to watch, since the frequency of object allocations is one of the biggest drivers for how often Ruby's garbage collector must run.

The amount of time spent in garbage collection (both mark and sweep phases) in your Ruby processes.

The Ruby agent actually measures this value in two different ways: garbage collection time that is incurred in the middle of request processing, and total garbage collection time. Garbage collection time that happens in the middle of request processing is presented as a band on the main overview graph, and in the breakdown charts for individual transactions. Total GC time as a percentage of wall clock time is shown on the Ruby VMs tab.

This measurement is derived from GC::Profiler.total_time.

Note: You must enable GC::Profiler in order to get this measurement. For more information, see GC instrumentation.

JRuby note: Due to what appears to be a bug in either JRuby or certain versions of the JVM, GC timings may be off by a factor of 1000 on JRuby. For this reason, we don't currently recommend enabling GC::Profiler on JRuby.

Frequency of garbage collector runs

Available on: MRI 1.9.2+, JRuby 1.7+, Rubinius 2.x

How often does the garbage collector need to stop your Ruby process in order to run? On MRI 2.1+, this will be segmented into major and minor GC runs. This number is presented in the UI as the number of requests processed per GC run.

Depending on the version of Ruby in use, this value may be derived from GC.count, or from GC.stat[:minor_gc_count] and GC.stat[:major_gc_count].

Size of the Ruby heap

Available on: MRI 1.9.2+

The Ruby interpreter stores objects on a heap, with each object occupying a single heap slot. As slots within the heap fill up with objects, the heap will be expanded by the Ruby VM as necessary.

The Ruby agent measures both the number of live objects in the heap, and the number of unoccupied heap slots on a periodic basis.

Generally speaking, the more objects there are on the heap, the longer each GC run will take (since potentially every object on the heap must be examined). More objects on the heap also correlates to higher memory usage for your application.

This value is derived from GC.stat[:heap_live_slot] or GC.stat[:heap_live_num], and GC.stat[:heap_free_slot] or GC.stat[:heap_free_num].

Memory usage

Available on: Any Ruby version

This measurement shows the memory usage (resident set size) of your Ruby processes, presented as the average size per instance (process). Keeping memory usage in check is an important consideration when tuning your Ruby VM settings. If it grows too large, you can cause the host machine to start paging to disk, or bump up against software-enforced memory limits.

On Linux hosts, this is derived from the VmRSS field of /proc/PID/status.

Method cache invalidation rate

Available on: MRI 2.1+

Every time a method is invoked on an object, Ruby must locate the implementation of that method by searching up the ancestor chain of the object. Because these searches are expensive, the Ruby VM will cache the results of these searches.

In older versions of MRI (prior to 2.0), there was a single, global method cache, and there were a variety of operations that could cause the entire cache to be invalidated.

In MRI 2.1 or higher, the global cache has been broken up into a set of smaller, per-class caches, so that cache-invalidating changes to one class won't throw out cache entries for other unrelated classes.

There are, however, still some operations that will cause all method caches to be invalidated. The Ruby agent will measure how frequently these invalidations happen, and report this measurement normalized against the number of requests, to give you an idea of how many times per request all of the method caches are invalidated.

Ruby caches the locations of constants in a way that's similar to the method caching behavior described above. The Ruby agent can also measure the number of times the global constant cache is invalidated, and report this as an average number of invalidations per request.

This value is derived from RubyVM.stat[:global_constant_state].

Thread count

Available on: MRI 1.9.2+, JRuby 1.7+, Rubinius 2.x

The Ruby agent can keep track of the number of threads in your Ruby processes. If you're using a multi-threaded web server, this can be used to confirm that your thread pool is actually the size you have configured it to be. It can also highlight if you have a thread leak (where you're spawning threads that never get torn down).

Background processes

By default, data from all processes reporting into a given application name in New Relic will be combined on the Ruby VM page in the user interface. This means that if you have both web and background processes (such as Resque, Sidekiq, DelayedJob, etc.) reporting into the same New Relic application, the data may be confusing.

There are two ways to work around this issue:

Pull your web and background processes into separate applications in New Relic, by setting the app_name configuration setting, or the NEW_RELIC_APP_NAME environment variable.