Production Code Analysis
Hello. I am Dan Mayer from LivingSocial
I am here to talk about production code analysis, Tales of
Deleting 200K+ LOC.
As the talk title indicates I am focusing on finding unused
code using analysis.
Spaghetti Code
spaghetti code!
We have all seen spaghetti code.
It is hard to understand
Hard to follow all the code paths.
It is difficult to change and maintain.
As projects grow and age it is very difficult to entirely
avoid.
Untangling it is hard but runtime analysis can help.
First I want to briefly explain
how a project can reach a point where there is over 200KLOC that
can be removed.
Then I can explain some processes to help find
and remove unnecessary complexity in your apps.
-
Pre Existing
Yes, we have a MonoRail app. A monolithic multiple years old
Rails app with legacy code...
Actually we have more than one, how did we get here. A bit of
history.
The application began as a fork of an existing project to
quickly validate an idea.
-
Consumer
That grew into a consumer business, which is what most
people know as LivingSocial today.
That app grew a lot and had many teams working to extend its
functionality.
Eventually it was forked into multiple other applications.
-
This means all these apps share some of the same
code, but after each split they have less responsibility.
Which code is needed for the smaller slice of responsibilities, what
can be removed from the parent or child app?
No Cleanup...
If you keep going like this forking apps and growing them for
years, without ever cleaning up the code.
You will eventually crash.
Looking at what the actual code is doing can help can help
tell you what is necessary.
While the forking of one MonoRail into two might be uniq to
our situation, dead code in large apps is not.
(images are from one of my favroite episodes of the Simpsons)
How Dead Code Sticks Around
-
Large teams aren't good at communicating what is no
longer needed (both Biz & Dev)
- A/B tests, never removing a loser
- One offs that are irrelevant over time
- Deprecation of old endpoints takes time
- Refactoring leaves dead code paths behind
- Making a 'safe' change & creating new methods
opposed to altering existing methods with multiple callers
- There are tests, but it is never used in production
- etc...
Beyond Monorails, dead code can slip into a system in many
ways.
I am not really going into all the details, but suffice to
say. Every system I have worked on grows and at some point has
unnecessary code tucked away into dark corners.
Features that don't quite justify their UI complexity,
pivoting away from some direction,
either for business or development reasons,
AB tests, the list goes on and on.
I am sure many of you have seen this in your own apps.
All code is bad
Also, the bridge was designed as a suspension bridge, but nobody actually knew how to build a suspension bridge, so they got halfway through it and then just added extra support columns to keep the thing standing
Peter Welch (Programming Still Sucks)
There is a pretty hilarious article titled "programming still
sucks" by Peter Welch, recently making the rounds on the net.
In it he makes the analogy of a team
building a bridge, not knowing what they are doing.
resulting in them building half a suspension and half a support bridge.
Several things in this article jumped out at me.
such as the statement, "all code is bad".
Which obviously not entirely true, must mean...
Less Code Is Better
- is easier to reason about
- is easier to upgrade (Rails, Ruby, and Gem versions)
- is easier to refactor
- is easier to adapt to new requirements
- means there are fewer and faster tests
...Less code is better.
Obviously if code is doing work for your business it has some value,
but I don't think I have to spend much time time explaining why having less code
& simpler systems is better.
Less Code for many reasons makes it easier you and your team to focus on making
important changes.
Solution: Clean Up Dead Code
- If "All Code is Bad"
- Less Code is Better
- Keep Only the Code Needed, No More
So if the problem is all code is bad and less code is better, the
solution is..
clean up unused code.
Keeping only the code needed.
We can make our projects better by only working on what is
providing value.
Code that is actively in use by our business
and users.
Sounds good, let's delete all the code...
Or some of it, which code...
All code is bad
They left the suspension cables because they're still sort
of holding up parts of the bridge. Nobody knows which parts, but
everybody's pretty sure they're important parts.
Peter Welch (Programming Still Sucks)
back to the bridge ananlogy in the article
something else struck me...
"They left the suspension cables because they're still sort
of holding up parts of the bridge."
and now for the line that really stood out
How to know: Production Code Analysis
Nobody knows which parts, but everybody's pretty sure they're important parts.
Peter Welch (Programming Still Sucks)
"Nobody knows which parts, but everybody's pretty sure they're important parts."
Hmmm Really?
Nobody knows which parts?
We don't have to settle for guessing, we can solve this
problem...
We can use data.
We can use code and analytics.
Analyzing Production Code
Hopefully that explains a bit about how a team can end up in a
place where they need data to help them know what is going on
with their systems.
I am going to go through some examples of processes we have
used to find unused code. From very simple methods, to more
complicated processes.
We will talk about 3rd party tools
Cumstom Stats
Using Logs
and Prodcution Code Coverage
Also, note if you run `rake stats` on a
Rail app and have less than 3-5K lines of code.
You can probably just reason about the code in your head.
and these tools might seem like overkill.
We had apps pushing 80K lines of app code
excluding the view layer, javascript, and CSS.
Street Cred
(grown up marketing speak: "street cred" => "social
proof")
These aren't just unproven ideas, these are processes we
have been able to put into practice.
Here is my history on one
of our Monorails apps.
I am pretty proud to have removed
nearly 2 lines of code for every one added, while delivery
additional functionality and faster performance.
Team Effort
My diff over last 2 years
+20973, -47034
Team diff over last 2 years
+197326, -215514
Also, wanted to point out getting code bases into shape. Is
really a team effort.
You can see my contributions over the last 2 years are
dwarfed by the teams efforts
As a team we celebrate our code improvements.
Pointing out and cheering on particularly good commits, faster tests,
code deletion.
Linking to little victories in campfire all the way.
Git Spelunking
[~/projects/deals]git log --numstat --pretty="%H" --author="dan.mayer" --since="2 years ago" app | awk 'NF==3 {plus+=$1; minus+=$2} END {printf("+%d, -%d\n", plus, minus)}'
[~/projects/deals] git log --numstat --pretty="%H" --since="2 years ago" app | awk 'NF==3 {plus+=$1; minus+=$2} END {printf("+%d, -%d\n", plus, minus)}'
If you want to grab some of your own stats, I wanted to
share a fun git trick to pull up stats for an author and the team.
Problem: Unused Actions
To find large sections of code no longer in use.
Let's start by finding actions and routes that are out of use.
This is one of the easiest ways to find unnecessary code.
It also can be the most rewarding by removing the top level
action
then following through to remove associated helpers, views,
and occasionally models.
Using 3rd Party tool (NewRelic)
Easiest way, look transactions over last 7 days: (note won't
help with specific formats or never hit endpoints)
One of the easiest ways to gain insight into what code is
being used in production is 3rd party
performance monitoring tools.
There are a number of popular Ruby performance monitoring
services. Each allow you to hook into their service sending some
data to help you monitor and find performance issues.
In this case NewRelic but Skylight.io & Traceview can get you the same data.
In the NewRelic transaction view you can sort transactions broken down by controller
route by usage.
Anything with 1 view in the last seven days likely isn't pulling its
weight. It might be worth discussing the cost of those features
with the team.
Some of them you may be able to cut.
A problem with this view, is that it doesn't show which actions received 0 requests.
Using 3rd Party tool (NewRelic)
LS made a gem to help,
newrelic_route_check
compare NR reports to Rails routes.
download the `controller_summary.csv`
run `bundle exec rake newrelic:compare_with_routes`
found 335 uniq new relic controller action hits
found 562 uniq Rails routes controller action pairs
exists in new relic, but not in routes: 0
never accessed in new relic stats: ***
Pipeline::DealsController#show
Pipeline::EmailTemplatesController#show
SubscribeButtonController#dropdown_for_deal
...
AuthorizationRulesController#graph
AuthorizationRulesController#change
I created a gem to make this easy to check. You can download
the CSV of NR transactions
and it can compare it to your Rails routes.
Finding any routes you have which are never hit.
The gem works by loading the routes into memory and comparing
thme with the downloaded data.
Running this on old production apps always seems to find some long
forgotten routes.
Stats Instrumentation
- Actions
- Background Events
- Mailers
- Views
- Translations
- One Off Trackers
- Two Methods
Moving away from 3rd party tools
Let's look at Custom Stats
Custom Stats instrumentation is very flexible. We are going to
look at a number of techniques to gain insight into what your
application is actually doing in production.
We will be looking at (read the list).
Stats Instrumentation: Tools
#shared code for examples
STATSD= Statsd.new('stat.my.us', PORT).tap{|sd| sd.namespace = 'app_name'}
REDIS = Redis::Namespace.new(:app_name, :redis => Redis.new)
To gather those stats we often rely on the same small set of
tools.
Which I wanted to quickly mention.
We use graphite via StatsD
I wanted to give a shout out to Etsy here, as we rely heavily
on their tools
They have shared in great detail how they also use metrics to gain insights.
StatsD is also extremely performant and while it can be lossy,
we haven't ever run into performance issues while adding many metrics.
Below you see some shared constants that will appear in
several of the examples.
Internally we have wrappers around our Redis and StatsD usage to deal with things like app name-spacing, common error handling, configurations, etc.
In the examples I am just using Redis and StatsD directly.
Problem: Unused Actions
We already discussed how finding unused actions be one of the
most valuable things to clean up.
Earlier we solved this using 3rd party performance tools, but
it is easy to solve with some custom stats.
No NewRelic, No Problem
thanks Jeff
Whitmire
class ApplicationController < ActionController::Base
before_filter :track_controller_traffic
around_filter :track_controller_timing
def endpoint_name
"#{params['controller'].gsub('/','.')}.#{params['action']}"
end
def track_controller_traffic
STATSD.increment "traffic.total"
STATSD.increment "traffic.#{endpoint_name}"
end
def track_controller_timing
STATSD.time("timing.#{endpoint_name}") do
yield
end
end
If you don't pay for any of the performance tools this can get
you some useful basic information.
Obviously 3rd party performance monitors give you a
lot more details than this, but this level of data can still be very useful
We have a couple of ways we have added tracking on endpoints
and timing in Rails. I think the simplest and cleanest example
was done by
Jeff
Whitmire.
The code hear just hooks into ApplicationController
Using a simple before filter to increment each endpoint
and using an around filter to record basic timing information
for each endpoint.
Comparing your routes to traffic can find no longer used actions.
Problem: Background Events
All events being triggered?
If you run a sizable Rails app you likely run background
jobs.
Some jobs will come and go over time. It is pretty easy to
remove code that queues a job, while leaving around the job code and
related methods.
Background Events
# Example for Resque background jobs
def before_perform(*args)
STATSD.increment "resque.processed.#{get_event_name}"
end
def after_perform(*args)
STATSD.increment "resque.completed.#{get_event_name}"
end
It is easy to instrument how often a event is processed.
With a bit more work you can record execution time around jobs or
success/failures on completion.
This is a simple example assuming hooking into Resque
but is pretty similar for all queueing frameworks.
Problem: Mailers
Are you still sending all your mailers?
Mailers like background jobs can come and go.
Stating every mailer as it is sent lets you know when a mailer is no longer needed.
Alternatively it lets you know your most popular mailers which
might be worth spending more time improving.
Mailers
# Example for ActionMailer
class BaseMailer < ActionMailer::Base
def initialize(method_name=nil, *parameters)
STATSD.increment "mailers.base_mailer.#{method_name}" if method_name
#...
super(method_name, *parameters)
end
end
This example hooks into ActionMailer::Base to record stats on
every mailer sent.
Like all the other stats you can check graphite to see if a
mailer is out of use before working on the code.
We were able to remove a number of mailers like this opposed to
spending the time fixing them when upgrading Rails.
Problem: Views
which views are ever rendered: partials, templates, layouts,
for each format?
The view layer more than anywhere else can quickly get messy
and hard to follow.
Templates inside layouts, with partials inside partials... Oh My.
Often a new view gets AB tested and the old one isn't removed.
Devs aren't sure if the partial is used anywhere else. So they
copy it and make a new one opposed to changing the shared partial.
Views also bloat out your helpers over time leaving many view
specific helpers spread through out the code.
Views Rendered
subscriptions =
render_partial.action_view|render_template.action_view
ActiveSupport::Notifications.subscribe /subscriptions/ do |name, start, finish, id, payload|
RenderTracker.track_template(name, start, finish, id, payload) unless name.include?('!')
end
class RenderTracker
def self.track_template(name, start, finish, id, payload)
if file = payload[:identifier]
STATSD.increment "views.#{file}"
end
if layout = payload[:layout]
Rails.logger.info "[RenderTracker] layout: #{layout}"
end
end
end
ActiveSupport notifications makes it is easy to stats every view file as it is
rendered.
In this example we subscribe to
render_partial.action_view and
render_template.action_view
This shows how you can either stat or log via the tracking
method.
When working on a view file you can now check what kind traffic
volume the change will effect.
Or if the view file you were about to refactor, upgrade, and
fix is out of use.
Views Rendered (with Flatfoot)
We made a gem for Rails 3 and up to help: Flatfoot
FLATFOOT = Flatfoot::Tracker.new(REDIS)
ActiveSupport::Notifications.subscribe /render_partial.action_view|render_template.action_view/ do |name, start, finish, id, payload|
FLATFOOT.track_views(name, start, finish, id, payload) unless name.include?('!')
end
FLATFOOT.used_views
=> ["app/views/home/index.html.erb",...
FLATFOOT.unused_views
=> ["app/views/something/_old_partial.html.erb",...
The view tracking pattern is simple and I wanted to reuse it
a few places.
So we made a gem called Flatfoot to quickly hook it
up into Rails apps.
You can see flatfoot still uses notifications subscriptions
and stores view data in a redis set.
It provides some helpers to output unused
views by comparing the view renderings with the files on disk.
Making it super simple to dig into view
layer cleanup.
Often the biggest wins when removing view files are associated
helpers, Javascript, and CSS.
ActiveSupport::Notifications Oddity
###
# Annoyingly while you get full path for templates
# templates with file extensions
# layouts without
# http://edgeguides.rubyonrails.org/active_support_instrumentation.html#render_partial-action_view
###
if layout_file = payload[:layout]
unless logged_views.include?(layout_file)
logged_views << layout_file
store.sadd(tracker_key, layout_file)
end
end
#note cut this slide if short on time
I did want to briefly mention a little ActiveSupport oddity I
found.
ActiveSupport violated the principle of
least surprise I felt as I was working on this.
Template files in the notifications include the full pathname and file
extensions.
For layout files it does not.
It tripped me up a bit so I figured
I would mention it. Perhaps someone here can later tell me why.
Flatfoot has some methods to help track layouts without extensions.
Problem: Translations Usage
How many translation keys are you
loading in memory & never using?
Another piece of an application that can easily become a out
of sync is translation keys.
Over time, it is hard to know which
translations are still in use.
Often when changing features and removing old code
translations are left behind.
Translation keys
end up causing a sizable memory bloat for production rails
processes.
As apps load all translations and keep them available in memory.
Translations Usage
We made a gem for that @the_chrismo (Chris Morris) built: Humperdink
class KeyTracker
def initialize(REDIS, key)
redis_set = Humperdink::RedisDirtySet.new(:redis => redis, :key => key, :max_dirty_items => 9)
@tracker = Humperdink::Tracker.new(redis_set, :enabled => true)
end
def on_translate(locale, key, options = {})
begin
if @tracker.tracker_enabled
requested_key = normalize_requested_key(key, options)
@tracker.track(requested_key)
end
rescue => e
@tracker.shutdown(e)
end
end
...
Chris Morris built Humperdink which has a
number of uses, but is particularly well suited to help track down
no longer used translation keys.
The code to set this up is too large to fully display here
but it makes it easy to find unused keys and is optimized for
performance.
Check out the gem for more details on how
to track translations, but this slide shows a bit of the basics.
Problem: Complex Code Paths
Reading the code, unsure where / if it is called?
One problem with tracking only controllers actions, is that
the often serve multiple formats and request paths.
Also, frequently in scary dark corners of models, helpers, and
libs folders
it is hard to be sure if something is entirely out
of use.
For that,
one off trackers can be useful.
Tracking format specific
endpoints, conditional paths, the ever growing case statement
Really to quickly see the frequency of use of any piece of code.
One Off Trackers
# Example Tracking a code path
class HomeController < ApplicationController
def show
if request.xhr?
#some weird logic
STATSD.increment "deprecated.home_controller.show.xhr"
end
respond_to do |format|
STATSD.increment "deprecated.home.show.#{format}"
format.html { #... }
format.json { #... }
format.mobile { #... }
end
end
end
We use the deprecated namespace in
our apps to track helper methods, models, views. It
also provides a quick way to jump into code cleanup. Grep the
code for deprecated stats, check graphite and safely remove
code no longer in use.
Problem: Two Methods
Which is best, which to keep?
shout out to @ubermajestix (Tyler
Montgomery) for showing me the next trick
This isn't actually related to finding unused code, but I like
this trick so much I wanted to share it.
Shout out to Tyler Montgomery, who checked this great idea
into our git one day.
One off trackers, aren't only useful for finding unused
code. It can be a great way to launch performance enhancements.
If you currently have two existing solutions and are decided
while refactoring which to use, you can take the guess work out
by measure real world usage.
Prod Performance Checks
def example_html_stripping_method
strip_method = rand(2)&1 == 0 ? 'nokogiri' : 'strip_tags'
desc = STATSD.time("application_helper.example_html_stripping_method.#{strip_method}") do
if strip_method == 'strip_tags'
strip_tags(desc_raw).gsub(/^\s+/,'').gsub(/\s+$/,'')
else
Nokogiri::HTML.parse(desc_raw).text.strip
end
end
#...
end
To gather data on the performance of two implementations with
production data sets. Simply split
the code between old and new while sending the timing info to
StatsD. Gather performance information against actual production
usage and data, to determine the best algorithm and have a good idea
of how much better it actually is.
Logs
-
icanhas.cheezburger.com
That covers various custom stats so let's talk a little bit
about logs.
Your application logs are a gold mine of information just waiting
to help you out.
When you have multiple applications and you are
trying to refactor or remove old endpoints knowing, which
clients and versions are using the application can be critical.
Again if you don't have NR or 3rd party
performance tools. You can get great rollups of performance for
controllers and actions with Kibana, Splunk, or other log
querying tools.
Logs
- Logs need to be searchable, real time is best.
(ElasticSearch/Kibana, Splunk, Hadoop)
- If you have multiple apps that communicate they should
be in the same system.
- All your logs should be in one place
(cron, Nginx access/error, background jobs, rails logs)
- Try to standardize log format: important keys / variables
across systems
To get the most from your logs it takes a bit of work to get
them into shape.
You need a near real time log query tool.
Try to get all your apps logging to the same query tool.
Apps with multiple with logs like nginx, cron, app, and
background jobs like wise need to all be sent to the same tool.
You can implement render_tracking, translation_key tracking,
and most of the other mentioned systems just by logging data in
query-able formats to your logs and crafting the correct queries.
Logs can go deep when you are digging into specific problems like
debugging exceptions or finding the last few callers while
deprecating endpoints.
Log Queries
# most common format (HTML, JSON) for controller/action
source="*app/*" status=200 |
top format by controller action
# endpoints with more 406 Not acceptable than 200s
source="*app/*" |
stats count(eval(status=406)) as UNAUTH,
count(eval(status=200)) as SUCC
by controller action | where UNAUTH > SUCC
# endpoints with more redirects than 200s
source="*app/*" |
stats count(eval(status=301 OR status=302)) as REDIRECT,
count(eval(status=200)) as SUCC
by controller action |
where REDIRECT > SUCC
# find all requests by a given user
source="*app/*" user_id=XXX
Here are some of the examples of the kinds of queries you can
craft once you have well formatted logs.
Most common format per action controller pair
Endpoints with more 406 Not acceptable than status 200
success, this often means old clients which are no longer
valid are never successfully getting to an endpoint. Perhaps
it can be removed
Which endpoint redirects more than returns 200
View all requests by a given user
I just wanted to share a few examples of questions you can
answer with your logs.
Logs: Deprecation with caller trace
clean_trace = Rails.backtrace_cleaner.clean(backtrace).join(',')
Rails.logger.info "deprecated=true trace: #{clean_trace}"
If you are trying to discover how the code reaches a path you never
expect or intend to be executed anymore.
Tracking that down can be complicated. Detailed logs can help.
You can log the caller inside a
method to figure out the call path that reached there.
These deprecation log lines will let you also discover the initial entry
point and parameters into your application, if you log
request_id, which I will mention shortly.
this idea was introduced into our codebase by
Sam Livingston-Gray
def deprecation_trace(backtrace = caller)
log_backtrace(backtrace)
stat_name = stat_name_line(backtrace.first)
STATSD.increment stat_name
end
def log_backtrace(backtrace)
#...
end
TRACE_SUBS = [
[ File.expand_path(Rails.root.to_s) , ''],
[ /:in `(.*)'/, '#\1' ],
[ '.rb' , '' ],
[ /:\d+/ , '' ],
]
def stat_name_line(trace_line)
t_line = TRACE_SUBS.inject(trace_line){ |ln, gsubs|
ln.gsub(*gsubs) }
components = t_line.split(/\W+/).reject(&:blank?).map(&:underscore)
components = %w[ deprecated ] + components
components.join('.')
end
Combine the deprecation logging with a good idea from
Sam Livingston-Gray,
which automatically builds the deprecation stat based on the
caller.
You end up with a really useful deprecation_trace helper method
you can use while investigating code to remove.
This code is a bit to complicated to fit on the slide but it
basically extracts the class and method name to build a well
formatted deprecation namespace.
Making it easy to find code from the stat name.
Logs: Better with Imprint
Make your logs better with
Imprint
- Request tracing in logs:
All Rails.logger calls during a request tagged with a trace_id
- Exceptions include trace_id so you can fetch all logs
related to a request that caused an exception
- Background Jobs failures include the trace_id to find
the request that queued the job
- Cross app tracing, have client gems pass a header, and
back end APIs will include the same trace_id as the initial
front end request
One place I turn to logs is to know what is happening on a
the system between applications or background queues.
Logs can be particularly useful for this level of
information, Imprint helps make your logs easy to trace across
apps, jobs, and exceptions.
Imprint builds on ideas from Twitter. Twitter created ZipKin,
but most of the tooling is in the Scala world. REcent versions
of Rails also
provided a request_id which IMPRINT can use.
Imprint makes it
easy to capture a request trace and help propagate it through
multiple systems. The gem helps simplify the process to integrate it into
your tools.
We use imprint in our base internal api-client. This means
all inter-app service calls will pass the current requests
trace_id as a header to the server. The receiving api service
then uses imprint to set the existing trace_id for its logging.
By logging request_id's, and placing that ID in the header,
you get a powerful debugging tool.
all your exceptions should now be able to link you back
to the original logs related to the request. Similarly failed
background jobs, logs can be found, and even traced to the
initial request that queued the background job.
Production Code Coverage
Most of the mention stat monitors focus on a particular piece of the
puzzle.
Stats takes effort to setup ahead of time and then time to collect and analyze the data.
It seemed like a pattern could be
abstracted, which would be more generally useful.
Really I wanted to know how often a given line of code was being run in production.
Ruby VM tooling isn't quite there to let us do this, but it keeps getting
better.
while Ruby's StdLib Coverage make this easy to do for tests, it has
some bugs that prevent it being used for production.
Ruby 2.1 introduced a sampling profiler, which I am
investigating for real time code coverage without larger
performance impacts.
Although being on Ruby 2.1 would limit the usefulness for
large old monoRail apps.
To get support in Ruby 1.9 I built a solution with set_trace_func
Production Code Coverage: Coverband
- Based on `set_trace_func`
- It would be better to be based on `Coverage` but there is a bug in Ruby
- (Looking for C help, so I can try to patch Ruby, anyone
got those skills?)
- Performance hit reduced by sampling
-
github.com/danmayer/coverband
While useful being based on set_trace_func,
can be slow.
To deal with that I allow for request sampling.
We are using it in production on large Rails apps with a low
sample rate and collecting some pretty good data.
Unfortunately, the performance hit is pretty large for big
applications.
I am running this on Monorail size apps with about
1% of requests being sampled. While I can run 60-90% of requests
small Sinatra apps without noticing much of a performance
impact.
Unfortunately the Std Lib Coverage bug mentioned was causing segfaults
in Ruby 1.9 and 2.0. While tht was fix and it doesn't segfault in 2.1 it still
doesn't work correctly when sampling, only reporting data for a
file once, ignoring usage after that.
Based on Terance's talk earloer at the conference I am preparing
a good and reproducable bug report to submit to
ruby-core.
Thanks for inspiring me to do more than complain on twitter, Terence.
Also, I am currently in progress with a native c extension which
should significantly improve performance. This would allow
faster data collection still going back to Ruby 1.9
br/>
If anyone has strong C skills and is
interested in helping out, I am definitely in need of some help
in this area.
Production Code Coverage: Coverband
baseline = Coverband.parse_baseline
Coverband.configure do |config|
config.root = Dir.pwd
config.redis = REDIS
config.coverage_baseline = baseline
config.root_paths = ['/app/']
config.ignore = ['vendor']
config.startup_delay = Rails.env.production? ? 15 : 1
config.percentage = Rails.env.production? ? 10.0 : 95.0
config.stats = STATSD
config.verbose = Rails.env.production? ? false : true
config.logger = Rails.logger
end
Coverband works as rack middleware. It decides whether to
sample a request as it comes in. When enabled for a request stores the
coverage of code run during the entire request and sync it up to Redis
at the end of the request.
To configure coverband you can either put this config block
where necessary of place it in `config/coverband.rb` which it
gets picked up when you call Coverband.configure.
`Coverband.parse_baseline` tells Coverband where to find a
baseline recording of code coverage just from loading the
application. A lot of Ruby code is run just loading the Rails
app, and that isn't capture during the request cycle. So we can
just record that once and merge it in with live coverage data.
Setting it up, you can see I provide different settings for
development and production. It makes it easier to test and
verify in development with a large sample rate.
A couple settings to note, `config.startup_delay` Rails
defines a lot of methods dynamically on the first few requests
so they are ignored because they can run much slower with Coverband.
percentage is the number of requests that we will sample and
record with coverband.
ignore lets you not record section of code you wish to
skip. Skipping heavy non app code like vendor or lib can also
help to reduce the performance impact.
Production Code Coverage: Coverband
#configure rake
require 'coverband'
Coverband.configure
require 'coverband/tasks'
#setup middleware in `config.ru`
require File.dirname(__FILE__) + '/config/environment'
require 'coverband'
Coverband.configure
use Coverband::Middleware
run ActionController::Dispatcher.new
After installing the gem and creating a config file. You want
to include the rake tasks into your `Rakefile` and you want to
setup the middleware.
In this example I am setting up the middleware as early as
possible in the `config.ru`. You can also, just set it up in the
standard Rails middleware stack.
Production Code Coverage: Coverband
config.verbose = 'debug'
coverband file usage:
["./app/models/deal_modules.rb", 9],
...
["./app/models/deal.rb", 20606],
["./app/helpers/application_helper.rb", 43150]]
file:
./app/helpers/application_helper.rb =>
[[448, 1], [202, 1],
...
[516, 38577]]
Coverband can give you interesting information and help you to
find code hotspots in development mode as well.
If you enable
verbose mode in the config, at the end of each request it
outputs how often a file was accessed.
If you set verbose mode to 'debug' it will further break down
the number of times an individual line on a file was called.
In the example output of the slide you can see application
helper was hit
43K times during this request. While like 516 of that file was
hit 38K times.
That doesn't always mean there is a problem, but it might be
worth investigating what is going on there.
Gems
Here is a list of the gems, I mentioned during the talk.
These are all open source released by various LivingSocial engineers.
Other Useful Gems
While I focused on code analysis to understand what a system
is doing and finding unused code paths. Obviously there are a
lot of other amazing production code analysis tools. Ruby
historically hasn't been very good it has been improving.
We have members of the community that are
working on making better.
So I wanted to point out some other gems and tools that can
either help you understand what is going on with your system
or with the performance of the code as it runs. Some of these
tools can be used in production while others are a probably
best in development only until the tooling gets better.
Anything by by Aman Gupta is generally awesome. Not only has
he released amazing gems and tools. He is now on the frontline
improving the Ruby VM to help it provide valuable runtime data
back to clients.
Sam Rawlins has been doing amazing work related to GC tools to
help you understand what is going on on systems. I recommend his
MtnWestRuby talk
change.org has related a interesting MethodProfiler tool.
Simeon Willbanks has a interesting tool that will collect
method cache invalidations for Ruby 2.1
A lot of great work is going on to help give us better
insights into what is happening with our code on production.
Thanks
I wanted to say thanks to LivingSocial who lets me work some
of these tools and sends me around to some conferences. Also, if
you think any of this is interesting we are hiring.
Thanks for listening, hope you found some of this useful for
your applications.
←
/
→
speaker notes