Red Artisan

Agile Ruby on Rails Specialists

Sprinkle Some Powder!

posted by crafterm, 27 May 2008

Provisioning a brand new server or VPS slice can be quite tricky, tedious and time consuming, particularly if done manually with changing software versions and configurations.

In the Rails world, most of us are using virtual private servers which are instantiated from base operating system images, it takes only a few minutes to create a slice, however installing the rest of your server's stack, be it Rails, Merb or another framework is where the work begins. Provisioning in this sense, is installing all software required post operating system install.

Sprinkle is a new prototype tool that you can use to provision your servers/slices. Its declarative policy/state based approach for specifying how a remote system should be provisioned with intelligent logic to support dependencies, multiple installer types and remote installation is really compelling.

Several free and commercially available tools already exist to help automate the installation of software however most fall into two styles of design:

1 - Task based, where the tool issues a list of commands to run on the remote system, either remotely via a network connection or smart client.
2 - Policy/state based, where the tool determines what needs to be run on the remote system by examining its current and final state.

Task based solutions are usually quite easy and fast to get up and running, but can be problematic as the user has to define all of the commands manually (not to mention get them right with testing). Policy/state based solutions have much more intelligence about how to modify and adapt the remote system, but often require specialized software to run remotely.

Sprinkle is a prototype tool I've been working on recently in this space that merges both concepts together, using a Ruby domain specific language to declaratively describe the state of the remote system. Using Sprinkle, provisioning your brand new remote server or slice can be automated using pre-defined and/or customized scripts from a single machine at your fingertips.

Sprinkle reads a script that defines a set of packages, a set of policies that define what packages should be installed on what roles of target machines, and a deployment section that defines the delivery mechanism for communicating with remote machines, and any default settings.

Packages can have relationships between each other to support dependencies. Virtual packages are also supported allowing you to define a role that a package (or multiple) fulfills, with the user or Sprinkle selecting which concrete package should be used at runtime.

Packages can also support arbitrary installer types, allowing you to install packages from source, gems, apt, or any other installer you'd like to employ. Installer types know what commands need to be issued to install packages, so all that needs to be specified in a script is the installer type and metadata about the package itself.

In essence, Sprinkle is about defining a domain specific meta-language for describing and processing the installation of software.

Example Sprinkle Script

Here's an example Sprinkle deployment script, annotated to explain each section:

# Annotated Example Sprinkle Rails deployment script
#
# This is an example Sprinkle script configured to install Rails from Gems, Apache, Ruby and
# Sphinx from source, and MySQL from APT on an Ubuntu system.
#
# Installation is configured to run via capistrano (and an accompanying deploy.rb recipe script).
# Source based packages are downloaded and built into /usr/local on the remote system.
#
# A sprinkle script is separated into 3 different sections. Packages, policies and deployment:


# Packages (separate files for brevity)
#
#  Defines the world of packages as we know it. Each package has a name and
#  set of metadata including its installer type (eg. apt, source, gem, etc). Packages can have
#  relationships to each other via dependencies.

require 'packages/essential'
require 'packages/rails'
require 'packages/database'
require 'packages/server'
require 'packages/search'


# Policies
#
#  Names a group of packages (optionally with versions) that apply to a particular set of roles:
#
#   Associates the rails policy to the application servers. Contains rails, and surrounding
#   packages. Note, appserver, database, webserver and search are all virtual packages defined above.
#   If there's only one implementation of a virtual package, it's selected automatically, otherwise
#   the user is requested to select which one to use.

policy :rails, :roles => :app do
  requires :rails, :version => '2.0.2'
  requires :appserver
  requires :database
  requires :webserver
  requires :search
end


# Deployment
#
#  Defines script wide settings such as a delivery mechanism for executing commands on the target
#  system (eg. capistrano), and installer defaults (eg. build locations, etc):
#
#   Configures sprinkle to use capistrano for delivery of commands to the remote machines (via
#   the named 'deploy' recipe). Also configures 'source' installer defaults to put package gear
#   in /usr/local

deployment do

  # mechanism for deployment
  delivery :capistrano do
    recipes 'deploy'
  end

  # source based package installer defaults
  source do
    prefix   '/usr/local'
    archives '/usr/local/sources'
    builds   '/usr/local/build'
  end

end

# End of script, given the above information, Spinkle will apply the defined policy on all roles using the
# deployment settings specified.

Given such a script, Sprinkle will apply the defined policy rails on the target machines identified by the role app, where the policy rails is composed of packages for the rails gem itself, an application server, webserver, search daemon, and the ruby runtime.

Currently, Sprinkle uses Capistrano internally for communicating with remote systems, however this is pluggable as well, allowing for just about any concievable delivery mechanism in the future. The deployment section above identifies Capistrano as the delivery mechanism, specifying a local deploy.rb script that defines what roles are available, and what machines are defined within those roles.

This particular script breaks the package section up into multiple files, here are some of the actual package definitions (complete example available here):

package :ruby do
  description 'Ruby Virtual Machine'
  version '1.8.6'
  source "ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-#{version}-p111.tar.gz" # implicit :style => :gnu
  requires :ruby_dependencies
end

package :rubygems do
  description 'Ruby Gems Package Management System'
  version '1.0.1'
  source "http://rubyforge.org/frs/download.php/29548/rubygems-#{version}.tgz" do
    custom_install 'ruby setup.rb'
  end
  requires :ruby
end

package :rails do
  description 'Ruby on Rails'
  gem 'rails'
  version '2.0.2'
end

package :sphinx, :provides => :search do
  description 'MySQL full text search engine'
  version '0.9.8-rc2'
  source "http://www.sphinxsearch.com/downloads/sphinx-#{version}.tar.gz"
  requires :mysql_dev
end

package :apache, :provides => :webserver do
  description 'Apache 2 HTTP Server'
  version '2.2.6'
  source "http://apache.wildit.net.au/httpd/httpd-#{version}.tar.bz2" do
    enable %w( mods-shared=all proxy proxy-balancer proxy-http rewrite cache headers ssl deflate so )
    prefix "/opt/local/apache2-#{version}"
    post :install, 'install -m 755 support/apachectl /etc/init.d/apache2', 'update-rc.d -f apache2 defaults'
  end
  requires :apache_dependencies
end

Each package includes a description, optional version, optional list of dependencies and an installer type (also optional allowing for meta-packages).

Source installers are particularly intelligent and will download, configure and install source archives from a remote location directly on the target machine. They assume GNU style source archives by default (ie. tar.gz/tar.bz2 compressed archives, configure script and make, make install style semantics), however are completely customziable to support any arbitrary build style (rubygems for example does this above), with pre and post commands.

The Apache installer for example, specifies a few extra source installer options such as a set of --enable options, an alternate installation prefix and a series of post installation commands to be executed.

With this example configuration, lets take a look at actually using Sprinkle to provision a remote server.

Usage

Sprinkle supports several command line options:

Usage
=====

$> sprinkle [options]

Options are:

  -s, --script=PATH                Path to a sprinkle script to run
  -t, --test                       Process but don't perform any actions
  -v, --verbose                    Verbose output
  -c, --cloud                      Show powder cloud, ie. package hierarchy and installation order
  -h, --help                       Show this help message.

where you can name the script to be procesed, enable testing mode or verbose output, and/or examine the cloud of packages and operations that will be performed.

Viewing the powder cloud!

Sprinkle calculates all operations to be performed on remote servers upfront which is nice, as it allows you to inspect what modifications will be made to the system before any are actually performed. Lets inspect the powder (ie. package) cloud for the above script:

$> sprinkle -c -t -s rails.rb
--> Cloud hierarchy for policy rails

Policy rails requires package rails
        Package rails requires rubygems
                Package rubygems requires build_essential
                Package rubygems requires ruby
                        Package ruby requires build_essential
                        Package ruby requires ruby_dependencies

Policy rails requires package appserver
Selecting mongrel_cluster for virtual package appserver
        Package mongrel_cluster requires rubygems
                Package rubygems requires build_essential
                Package rubygems requires ruby
                        Package ruby requires build_essential
                        Package ruby requires ruby_dependencies
        Package mongrel_cluster requires mongrel
                Package mongrel requires rubygems
                        Package rubygems requires build_essential
                        Package rubygems requires ruby
                                Package ruby requires build_essential
                                Package ruby requires ruby_dependencies

Policy rails requires package database
Selecting mysql for virtual package database

Policy rails requires package webserver
Selecting apache for virtual package webserver
        Package apache requires build_essential
        Package apache requires apache_dependencies

Policy rails requires package search
Selecting sphinx for virtual package search
        Package sphinx requires build_essential
        Package sphinx requires mysql_dev

--> Normalized installation order for all packages: build_essential, ruby_dependencies, ruby, rubygems, rails, mongrel, mongrel_cluster, mysql, apache_dependencies, apache, mysql_dev, sphinx

-c indicates that Sprinkle should print the powder cloud (ie. the output above)
-t indicates that we're operating in test mode, so we won't actually perform any remote commands
-s identifies the Sprinkle script that should be processed

Above we can see that the policy rails required packages rails, appserver, database, webserver and search.

Note that all of these packages bar rails are actually virtual packages, so Sprinkle has selected an appropriate implementation of each virtual package automatically based on the supplied package definitions. If more than one package provided an implementation of a virtual package, then the user would be given the opportunity to select which one they prefer.

Under each package is a textual representation of that package's dependency tree, including all sub-dependencies, etc. Dependencies are packages that need to be installed first before a higher level package can be installed.

You'll notice that several packages have the same dependencies, eg. both rails and mongrel require ruby, which has its own dependencies as well. Sprinkle will install all packages in reverse dependency order so that lower level dependencies are installed before higher level packages, and it will also normalize the final package list to remove duplicates so that packages aren't installed multiple times unnecessarily. This is the final line in the output above which lists the actual packages to be installed and order of installation.

Provisioning a remote system

To actually provision a remote server we simply remove the testing (and if desired cloud) flags from the command issued above and Sprinkle will process the configuration and provision the remote system. Note for the moment, you'll need to ensure that your SSH keys are appropriately installed on the remote server under a user that has enough privileges to install software (generally the root user):

$> sprinkle -s rails.rb
--> Installing build_essential for roles: app
--> Installing ruby_dependencies for roles: app
--> Installing ruby for roles: app
--> Installing rubygems for roles: app
--> Installing rails for roles: app
--> Installing mongrel for roles: app
--> Installing mongrel_cluster for roles: app
--> Installing mysql for roles: app
--> Installing apache_dependencies for roles: app
--> Installing apache for roles: app
--> Installing mysql_dev for roles: app
--> Installing sphinx for roles: app

(its also possible to put the #!/usr/bin/env sprinkle -c line at the top of a sprinkle script and make it executable).

After the command is finished, all of the requested software will have been applied on your target system.

If you'd like to see more action printed as commands are run, specify the --verbose (-v) flag. Internally, Capistrano tasks are dynamically defined and executed at runtime for each package's installation, using a Capistrano configuration file to identify the actual roles and hostnames associated with those roles to communicate with. The verbose option will display Capistrano activity in addition to the usual Sprinkle output.

An extra benefit of leveraging Capistrano is that you can actually provision multiple servers/slices simultaneously and in parallel if desired.

I want!

If you're interested in downloading and experimenting with Sprinkle, you can clone and/or watch the project at GitHub, or download it from GitHub's gem server using:

$> sudo gem install crafterm-sprinkle --source http://gems.github.com/

The official Rubyforge gem server will also be updated over the coming days as well. If you download the source, you can create a gem package for installation by:

$> rake package
$> sudo gem install -l pkg/sprinkle-0.1.0

There are also specs with a decent amount of coverage over the code base that you can run as well:

$> rake spec

Prerequsites

Installing the Sprinkle gem will also install all pre-requsite gems such as activesupport, highline and capistrano. The only other pre-requisite is that you have SSH connectivity to the remote system you wish to provision, preferably with SSH keys in place to prevent passwords being asked for.

Finally

Sprinkle is a young project and while operational still in development, with several limitations. Currently, only Ubuntu/Debian has been tested as a target deployment platform, operating system abstraction and other platforms will be tested and supported in the future, along with several new features that are in the pipeline.

I'm most certainly open to ideas, suggestions and thoughts about how Sprinkle can be improved and generally made better for the community, and I really welcome any bug reports, patches and suggestions. Please feel free to contact me with any comments at all.

Special Thanks!

Several people have been really helpful during the development of Sprinkle. In particular I'd like to thank Ben Schwarz and Pete Yandell for their initial feedback and help after my first demos. I'd also really like to thank Matthew and Jared from Slicehost for their help and support as well.

Twilight Theme for Emacs!

posted by crafterm, 27 May 2008

Recently I’ve been returning to my Unix heritage and using Emacs as my day to day editor. Its been quite an enlightening journey returning to such a powerful editor with its limitless customization and features (probably worthy of a whole separate post), although one things I do miss from TextMate are the colour themes used for syntax highlighting.

Emacs has a package called color-theme which can be used to customize font faces/colours however in comparison to the styled themes TextMate offers, most of default ones available don’t quite compare.

In TextMate I used the Twilight theme quite often, so, after examining the color-theme package format, I’ve ported across the Twilight theme to Emacs, and have released it as a small project on GitHub.

Its definitely a work in progress, most of the colours within Ruby mode are working as expected, but please feel free to improve anything that needs some fine tuning (eg. Rails keywords, and particularly non-Ruby modes such as dired, ERB, SCM, etc). Patches are most certainly welcome.

Ruby && DTrace!

posted by crafterm, 18 May 2008

Performance, memory and runtime analysis of software has always been a tricky subject, often requiring special debug versions of code or application specific parameters to determine what's going on. Additionally, developer debugging information can often clutter source code making it harder to see the intent and design of source.

D-Trace offers an interesting solution to this problem, by dynamically instrumenting your application at runtime to enable probes to report various pieces of information as to how your application is running. DTrace is a part of Solaris, but is now also available under Mac OS X Leopard. A typical installation of DTrace can offer 20,000 different types of probes (or more depending on what applications are running), from kernel level information, all the way to application specific data.

Applications, such as the Ruby interpreter can also define their own domain specific probes, last year Joyent added support for D-Trace probes to MRI (Matz-Ruby), allowing developers to analyze runtime behaviour of their Ruby based applications. Starting with this particular commit, compatible D-Trace probes for Rubinius have been developed as well.

What can you do with DTrace?

The list of uses for DTrace are endless, as it provides the means to gain answers to many questions about how your application is behaving. Some practical questions DTrace can answer include:

  1. Tracing execution flow through your application as it steps through each method/class

  2. Determining runtime performance analysis, working out what methods are the most expensive (excellent for 80/20 performance analysis)

  3. Heap analysis, determining what objects are consuming the most memory

  4. Garbage collection impact, determining how often the garbage collector is running an impacting your applications performance

  5. and much more...

Getting started with DTrace and Ruby

To get started with Ruby and DTrace, you'll need a compatible operating system, eg. Mac OS X Leopard, and you'll need to get the Ruby source to build MRI with Joyent's DTrace patches. Luckily, if you're using Mac OS X Leopard, Apple has already appropriately patched their bundled version of Ruby to include DTrace patches for you, and no extra compilation is necessary. DTrace itself is also included by default under all Mac OS X Leopard operating system installs.

DTrace primer

There is a wealth of information available on the internet that I'd certainly recommend taking a look at to learn using DTrace in depth (in particular Sun's DTrace admin guides). Essentially to interact with DTrace, you write a script in a language called 'D', which defines what probes you're interested in, and what to do with the data when the probe fires. This script is then read and bytecode compiled by DTrace's command line and user land libraries, and then passed to the DTrace virtual machine running inside of the kernel to be interpreted. Probes are enabled, and appropriately fired, with data being collected according to your scripts for analysis.

Anatomy of a DTrace script

provider : module : function : name
/ predicate /
{
  action
}

Above is generic breakdown of a DTrace script (somewhat bearing similarity to an awk script). Most parts of the script are optional as you'll see further, such as the module or function name, or even the action.

Probes are grouped by providers, of which there are many (io, pid, objc, profile, to name a few). module and function's meaning are somewhat dependant on the provider being used. The name parameter identifies the actual name of the probe that is to be fired.

The predicate identifies a clause that must evaluate to true for the probe to fire and allows for conditional firing of probes.

The action contains arbitrary instructions to be performed when the probe fires.

To probe your application you also need root privileges on the machine you'll be running DTrace on, this is due to kernel level interaction of DTrace.

Usually you'll run DTrace and pass it the name of a D script (or include the script on the command line if it's brief), either with a command to run, or a process ID of an application that's already running that should be attached to. For example:

$> sudo dtrace -s profile.d -c 'ruby script.rb'

or:

$> ps aux|grep ruby
crafterm 29877   0.0  0.0   590472    188 s001  R+    5:04pm   0:00.00 grep ruby
crafterm 29875   0.0  1.2   622564  25016 s001  S     5:04pm   0:00.85 ruby
$> sudo dtrace -s profile.d 29875

Ruby Provider Probes

To see how many probes are available, and those that are Ruby related, we can ask DTrace to print their specifics to the console, eg:

$> sudo dtrace -l | wc -l
31569

indicating I currently have 31569 probes available to query, this number will change depending on what applications are running at the time of running the dtrace command.

Ruby specific probes can be found by:

$> sudo dtrace -l | grep ruby
21427  ruby53816   libruby.1.dylib                          rb_call0 function-entry
21428  ruby53816   libruby.1.dylib                          rb_call0 function-return
21429  ruby53816   libruby.1.dylib                   garbage_collect gc-begin
21430  ruby53816   libruby.1.dylib                   garbage_collect gc-end
21431  ruby53816   libruby.1.dylib                           rb_eval line
21432  ruby53816   libruby.1.dylib                      rb_obj_alloc object-create-done
21433  ruby53816   libruby.1.dylib                      rb_obj_alloc object-create-start
21434  ruby53816   libruby.1.dylib                   garbage_collect object-free
21435  ruby53816   libruby.1.dylib                        rb_longjmp raise
21436  ruby53816   libruby.1.dylib                           rb_eval rescue
21437  ruby53816   libruby.1.dylib                 ruby_dtrace_probe ruby-probe

As you can see from the list in the last column of the output, probes are available between method invocations, runs of the garbage collector, creation and destruction of objects and exceptions. The last probe actually allows the application writer to fire an arbitrary ruby probe containing application specific data from ruby code.

A full list of probes and arguments supplied to them is available at Joyent's Ruby provider wiki page.

Example 1: Tracing execution flow through your application

Lets start by tracing the execution flow through a small Ruby program.

Simple Ruby Program

class World
  def say(message)
    puts message
  end
end

world = World.new
world.say('hello')

This small Ruby program creates an instance of the World class, sends it the say message with hello as a String parameter which is printed to the console.

execution-flow.d

ruby$target:::function-entry
{
    printf("%s:%s\n", copyinstr(arg0), copyinstr(arg1));
}

ruby$target:::function-return
{
   printf("%s:%s\n", copyinstr(arg0), copyinstr(arg1));
}

This particular script enables the function-entry probe on the Ruby provider, and prints the first and second arguments passed to the probe in a C-style printf command.

The first and second arguments passed are provider specific, but in the Ruby provider's case for these probes they are always the class/module and method names being executed.

The $target variable enables the probe on the PID of the command specified via the -c parameter to DTrace itself.

Results

$> sudo dtrace -q -F -s execution-flow.d -c "ruby hello.rb"
CPU FUNCTION
1  -> rb_call0                              Class:inherited
1  <- rb_call0                              Class:inherited
1  -> rb_call0                              Module:method_added
1  <- rb_call0                              Module:method_added
1  -> rb_call0                              Class:new
1    -> rb_call0                            Object:initialize
1    <- rb_call0                            Object:initialize
1  <- rb_call0                              Class:new
1  -> rb_call0                              World:say
1    -> rb_call0                            Object:puts
1      -> rb_call0                          IO:write
1      <- rb_call0                          IO:write
1      -> rb_call0                          IO:write
1      <- rb_call0                          IO:write
1    <- rb_call0                            Object:puts
1  <- rb_call0                              World:say

Taking a look at the results we can almost visually 'see' how the script was parsed and executed. First Class was 'inherited', this is part of the creation of our 'World' class, then we defined World#say (invoking the Module:method_added method) to contain some operations. We then created a new instance of our class (Class:new and Object:initialize to create and construct our object), and then invoked World#say which we can see calls Object:puts and IO:write.

(In this particular case, the program is small and the instructions simple, however just add "require 'rubygems'" to the top of the source and re-run the DTrace script again and you'll quickly be overwhelmed with too much information - writing effective DTrace scripts is an art, but it's well worth learning to ensure you get the answers you're looking for)

Example 2: Individual Method Performance

Lets use DTrace to take another look at our application from a different perspective and see what methods are most expensive. To do this we'll use the function entry and return probes to capture a time stamp interval for each method call.

We'll also use an aggregate DTrace variable to store a running average of how long each method takes so that multiple method calls are recorded together and averaged across the count of method invocations, and we'll print the results according to most expensive method execution time.

Method Performance DTrace script

timestamps.d

ruby$target:::function-entry
{
  self->start = timestamp;
}

ruby$target:::function-return
/self->start/
{
  @[copyinstr(arg0), copyinstr(arg1)] = avg(timestamp - self->start);
  self->start = 0;
}

This script introduces a few more DTrace constructs, associative arrays and aggregate functions.

We enable two probes, function entry and function return on our Ruby program. When any method is entered, we capture a timestamp and store it in the 'start' variable. When any method is exited, we gather another timestamp, subtract it from the entry, and pass it to the avg DTrace aggregate function to be averaged.

The average execution time is then stored in an associative array, indexed by the module/class and method name (arg0 and arg1 respectively). Finally, we reset 'start' to zero once its no longer required.

A predicate is also set on the function return probe to fire only if we have a 'start' timestamp value, which prevents us from seeing any errors or miscalculations if we attach our DTrace script to an application that is already running (since after attaching, a return probe could fire for which we have no start timestamp).

Results

$> sudo dtrace -s timestamps.d -c "ruby hello.rb"
Object                   initialize                     9185
Module                   method_added                   10021
Class                    inherited                      25323
IO                       write                          98956

DTrace automatically prints data collected unless you supply a custom output format (eg. by using the C-style printf DTrace function as in the method flow example above) which in this case is the associative array, indexed by class/module and method name.

From these results we can see that IO#write was the slowest of the methods called, taking an average of 98956 nanaseconds to run, most likely due to its interation with the rest of the system and IO nature.

Example 3: Quantized Method Performance

Average values can often be affected by a few large values during program startup, so lets take a closer look and see what values consititue the calculation the averages above. To do this we'll use the DTrace quantize aggregate function, which will provide us with a distribution breakdown of each individual component within the average. We'll also specifically target the IO#write method, and update our Ruby program to print 'hello' 10 times to collect some more data over a period of invocations.

timestamps-q.d

ruby$target:::function-entry
/copyinstr(arg0) == "IO" && copyinstr(arg1) == "write"/
{
  self->start = timestamp;
}

ruby$target:::function-return
/copyinstr(arg0) == "IO" && copyinstr(arg1) == "write" && self->start/
{
  @[copyinstr(arg0), copyinstr(arg1)] = quantize(timestamp - self->start);
  self->start = 0;
}

Results

$> sudo dtrace -s timestamps-q.d -c "ruby hello.rb"

  IO                                                  write
           value  ------------- Distribution ------------- count
            4096 |                                         0
            8192 |@@@@@@@@@@@@@@@@@@@@                     10
           16384 |@@@@@@@@@@@@@@@@                         8
           32768 |@@                                       1
           65536 |                                         0
          131072 |@@                                       1
          262144 |                                         0

Here we see the distribution of how long each invocation of IO#write took. There was one invocation that particuarly long, over 131k nanoseconds, with most landing between 8k and 16k nanoseconds.

From here we could step further into the runtime and determine which particular IO#write call was slower than the others by inspecting the user stack inside the virtual machine, and/or by enabling probes in lower level providers.

Example 4: Memory allocation

To profile memory allocation we need to enable the object-create-start, object-create-done, and object-free probes. Creation of objects is separated into two probes to allow you to determine exactly how long it takes to construct an object.

First, lets create a simple balance script to check that objects are being allocated and deallocated correctly within the Ruby runtime. The script we'll use will create an associative array and index a counter by object type. Each time an object is created we'll increment the counter, conversely each time an object is freed we'll decrement the counter.

object-balance.d

ruby$target:::object-create-done { @[copyinstr(arg0)] = sum(1); }
ruby$target:::object-free        { @[copyinstr(arg0)] = sum(-1); }

Results

$> sudo dtrace -s object-balance.d -c "ruby hello.rb"

  NoMemoryError                                                     1
  SystemStackError                                                  1
  ThreadGroup                                                       1
  World                                                             1
  fatal                                                             1
  Object                                                            3

Glancing over the results we can see that Ruby is creating an instance of several error classes and a thread group during the run of our application (perhaps during startup), we can also see a single instance of our World class, and three other Objects that have also been allocated.

Our particular hello.rb script is quite small, and probably finishes executing before the garbage collector has had a change to reclaim any unused objects. If you run this script over a large application though, you'll see a line for each Object type in the application, and essentially a reference count of how many have been created and free'd.

In an ideal application, after garbarge collector has finished, all object types (except those required to keep the application running) will be listed with the value '0' alongside it indicating a corresponding deallocation for each allocation.

Example 5: Inspecting memory allocation points

The object-create-start/done probes also provide the source file and line number of where the allocation was made in Ruby script which we can use. For example if our World class came from another developer's library and we wanted to find out where it was allocated we could use the following script:

object-world-location.d

ruby$target:::object-create-start
/copyinstr(arg0) == "World"/
{
  printf("%s was allocated in file %s, line number %d\n", copyinstr(arg0), copyinstr(arg1), arg2);
}

Results

$> sudo dtrace -s object-world-location.d -c "ruby hello.rb"

  World was allocated in file hello.rb, line number 7

which matches our source file.

Example 6: Inspecting stack traces

We also saw above that several other 'Object's are created within the C portion of the Ruby interpreter upon startup. We can also inspect where these objects were created by saving the user C stack inside the virtual machine at the point of allocation.

object-user-stack.d

ruby$target:::object-create-start
/copyinstr(arg0) == "Object"/
{
  @[ustack(4)] = count();
}

This particular script uses the DTrace ustack function to access the actual runtime stack of the Ruby interpreter in userland at the point in time when the probe fired, to a depth of 4 method calls.

We then use the stack as an index into an associative array and store the number of times an Object type was created at the same point in the interpreter. This example really shows how flexible associative arrays can be in DTrace, by using a full stack trace as an index.

Results

$> sudo dtrace -s object-user-stack.d -c "ruby hello.rb"

          libruby.1.dylib`rb_obj_alloc+0x90
          libruby.1.dylib`Init_Object+0x130b
          libruby.1.dylib`rb_call_inits+0x15
          libruby.1.dylib`ruby_init+0x14f
            1

          libruby.1.dylib`rb_obj_alloc+0x90
          libruby.1.dylib`Init_IO+0x1059
          libruby.1.dylib`rb_call_inits+0x6a
          libruby.1.dylib`ruby_init+0x14f
            1

          libruby.1.dylib`rb_obj_alloc+0x90
          libruby.1.dylib`Init_Hash+0x903
          libruby.1.dylib`rb_call_inits+0x51
          libruby.1.dylib`ruby_init+0x14f
            1

Here we can see three separate stack traces indicating a Hash, IO and Object being created as part of the ruby_init method inside the Ruby interpreter.

Summary

DTrace is a very powerful framework, allowing you to really hypothesise and ask arbitrary questions about the behaviour of your system and applications. Often, the answer to one question will lead to another, and this is very much in the sprit of DTrace. The ability to script questions and format results allows you to slice behavioural data from any perspective and depth from your application, all the way to the operating system kernel.

DTrace scripts are the key to reducing complexity and understanding the true behaviour of your application at runtime, and I certainly recommend learning as much about the D script format and language as you can. Fine tuning your script to return the exact data you're after can be an art, but its well worth learning so that you can specify exactly what data you are after, and not be cluttered with too much information obscuring the information you are searching for.

Collections of commonly used DTrace scripts are available as part of the DTraceToolkit, in particular several very useful and high quality Ruby DTrace scripts. I'd recommend taking a look at them to see how probes can be used in combination with each other, also in multi-threaded environments.

In future articles I'll step further into using DTrace via Instruments, and also look at instrumenting your Rails or Merb application to collect runtime data about the performance of your web applications.

DTrace at Cocoaheads

posted by crafterm, 09 May 2008

Last night we had our monthly Melbourne Cocoaheads meetup which was great. There were several talks given including one about the AUC, and I also gave a talk about DTrace, the popular dynamic tracing framework for Mac OS X Leopard and OpenSolaris.

I’ve uploaded the slides from my talk to slideshare for those interested - since most of the talk was a demo there are only a few slides included, however the links towards the end of the presentation provide some good references to more information about DTrace, the D language and using DTrace on your system. Happy DTracing! :)