What’s Happening in My Ruby Application?

If you’re wondering what’s going on with your Ruby application…

There are no fancy GUI tools

But we have the ObjectSpace module!

ObjectSpace gives you information about the current state of your application.

Let’s discover how it works.

Counting Objects

Using ObjectSpace you can know what objects are currently ‘alive’ in your program.

What does it mean for an object to be alive?

An object is alive as long as it has any references pointing to it. A reference is just a way to access the object, like a variable or a constant.

If an object can’t be reached then it means that it’s safe to be removed from memory.

Example:

# The variable 'name' holds a reference to the string 'Dave'.
name = 'Dave'

# The 'name' variable now points to 'John'.
# 'Dave' no longer has a reference pointing to it.
name = 'John'

Now let’s see an example of ObjectSpace in action:

require 'objspace'

# This is valid Ruby syntax, but doesn't work on irb/pry
ObjectSpace
  .each_object
  .inject(Hash.new 0) { |h,o| h[o.class] += 1; h }
  .sort_by { |k,v| -v }
  .take(10)
  .each { |klass, count| puts "#{count.to_s.ljust(10)} #{klass}" }


# Copy & paste version (use this for irb/pry)
ObjectSpace.each_object.inject(Hash.new 0) { |h,o| h[o.class] += 1; h }.sort_by { |k,v| -v }.take(10).each { |klass, count| puts "#{count.to_s.ljust(10)} #{klass}" }

This will print a table with the object count for your top-10 classes.

Count      Class
-------------------------
5436       String
315        Class
251        Array
101        Encoding
69         Regexp
45         Hash
26         Module
25         Gem::Version
22         Gem::StubSpecification::StubLine
22         Gem::StubSpecification

If you suspect a memory leak you could log this data every hour & find out if there is some object count that keeps increasing all the time but never goes down.

Fun with Objects

When using ObjectSpace you get access to the actual objects, not just information about them, so you can do some fun things like printing the value of all the strings or printing the path of all your File objects.

Example:

ObjectSpace
  .each_object(String)
  .sort_by { |s| s.size }
  .each { |s| p s }

This will print all the in-memory strings, sorted by size. You will notice that there are many strings that you didn’t create yourself, they are created by the Ruby interpreter.

Practical uses?

Well, this is mostly for debugging & gathering stats about your app πŸ™‚

Object Memory Size

Another thing you can do is to use ObjectSpace.memsize_of to find the memory size of a particular object.

Example:

o = "a" * 100
ObjectSpace.memsize_of(o)

One thing to keep in mind is this warning from the documentation:

“Note that the return size is incomplete. You need to deal with this information as only a HINT.”

If you try this method with different types of objects you will find some interesting things, like Fixnums always returning 0.

ObjectSpace.memsize_of(42)
# 0

The reason for this is that Ruby doesn’t internally create Fixnum objects, you can learn more about this in the post I wrote about numbers in Ruby.

Another interesting one are strings:

ObjectSpace.memsize_of("A" * 22)
# 40

ObjectSpace.memsize_of("A" * 23)
# 40

ObjectSpace.memsize_of("A" * 24)
# 65

I use "A" * size as a way to create a longer string without having to type it out πŸ™‚

Wait! What did just happen?

Well, it turns out that Ruby has a built-in optimization for strings smaller than 24 characters, that’s why there is a jump in memory use after that. You can see this in more detail in this post from Pat Shaughnessy.

How to Find Aliased Methods

Wouldn’t it be nice if there was a ‘master’ list of all the aliased methods in Ruby?

Wish granted!

Have a look at this:

class Module
  def aliased_methods
    instance_methods(false)
      .group_by { |m| instance_method(m) }
      .map(&:last)
      .keep_if { |symbols| symbols.length > 1 }
  end
end

I got this code from a Stackoverflow answer. It defines an aliased_methods method on the Module class, which uses the instance_methods method to get a list of all the instance methods defined on a class.

I know that may sound a bit confusing, but that’s metaprogramming for you!

Here is the rest of the code, which builds an array of all the class names that have at least one ‘alive’ object, then it calls aliased_methods on every class & prints the output.

objects = ObjectSpace.each_object.map(&:class).uniq

objects.each do |klass|
   methods = "n#{klass}n#{'-'*20}n"

   klass.send(:aliased_methods).each do |m1, m2|
     methods << "#{m1.to_s.ljust(15)} #{m2}n"
   end

   puts methods
end

This is what the output looks like:

Array
--------------------
inspect         to_s
[]              slice
length          size
find_index      index
collect         map
collect!        map!

Conclusion

I hope you enjoyed learning about the cool things you can do with ObjectSpace, now go try it out and let me know if you find anything interesting!

Don’t forget to share this post with all your programmer friends, it will help them learn something new & it will help me get more readers πŸ™‚

7 thoughts on “What’s Happening in My Ruby Application?”

Comments are closed.