Tracking down unused templates
… and check why 5600+ Rails engineers read also this
Tracking down unused templates
Few days ago, my colleague raised #sci-fi idea. Maybe we could somehow track templates rendered in application to track down which ones aren’t used? Maybe we could have metrics how often they’re used? Metrics? That sounds like gathering data using Chillout.io. We have already installed Chillout gem in other project I work at.
Track down used templates
To know which templates aren’t used we firstly would like to know which ones are used.
We would like somehow to hook into rails internals and increment specific counter (named like counter_app/views/posts/new.html.erb
) when rails renders a template.
Well, that sounds hacky. However it’s good to work with people more experienced in rails - they know about parts of rails, you haven’t idea about. There exists a module called Active Support Instrumentation. Let’s read what’s its purpose:
“Active Support is a part of core Rails that provides Ruby language extensions, utilities and other things. One of the things it includes is an instrumentation API that can be used inside an application to measure certain actions that occur within Ruby code, such as that inside a Rails application or the framework itself.”
These are the methods we are looking for! After quick look on table of contents, we can see two hooks which would suit us: render_partial.action_view
and render_template.action_view
. Both of them return identifier of the template which is full path to the template. Great, now we have to learn how to subscribe to these hooks.
Example from the same rails guide:
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*args|
event = ActiveSupport::Notifications::Event.new *args
event.name # => "process_action.action_controller"
event.duration # => 10 (in milliseconds)
event.payload # => {:extra=>information}
Rails.logger.info "#{event} Received!"
end
Now let’s write the code which will track using of our partials. We put it into config/initializers/template_monitoring.rb
because we want it to execute only once.
require 'active_support/notifications'
%w(render_template.action_view render_partial.action_view).map do |event_name|
ActiveSupport::Notifications.subscribe(event_name) do |*data|
event = ActiveSupport::Notifications::Event.new(*data)
template_name = event.payload[:identifier]
Chillout::Metric.track(template_name)
end
end
As you can probably guess, Chillout::Metric.track(name)
is incrementing a counter named template_name
.
Thus now every time rails renders a template it notifies Chillout which handles the rest.
Full paths are not what we want
However, again from previously referenced rails guide, event.payload[:identifier]
is an absolute path to the template. That’s not good - what will happen when we deploy with capistrano new version of our application? In absolute path we have number of release which changes on each deployment. Let’s change that.
def metric_name(path)
template_name = path.sub(/\A#{Rails.root}/, '')
"template_#{partial_name}"
end
Obviously now in our previous code we’ve to change
template_name = event.payload[:identifier]
to
template_name = metric_name(event.payload[:identifier])
Great, now we are tracking usage of used templates! We got chillout report and we can read how many each one partial was rendered.
And it’s total opposite of what we wanted to achieve because partials which weren’t rendered at least once are not present on the list.
Track down not used templates
That’s going to be pretty chillout-specific. Firstly we need to create container which keeps templates’ counters.
template_name = metric_name(event.payload[:identifier])
Thread.current[:creations] ||= Chillout::CreationsContainer.new
container = Thread.current[:creations]
We’re assigning it to Thread.current[:creations]
because that’s place where chillout seeks for container (or creates it, if it’s uninitialized).
Then we need to initialize counters for all templates to 0. We can do that by asking chillout “What is counter of template_name
now?”. We do that by fetching container[template_name]
. From that moment Chillout will be aware that there exists such counter named template_name
. Thus it will show it in reports.
template_name = metric_name(event.payload[:identifier])
Dir.glob("#{Rails.root}/app/views/**/_**").each do |raw_path|
template_name = metric_name(raw_path)
value = container[template_name]
Rails.logger.info "[Chillout] #{template_name}: #{value}"
end
In the end whole config/initializers/template_monitoring.rb
looks like this:
template_name = metric_name(event.payload[:identifier])
require 'active_support/notifications'
def metric_name(path)
template_name = path.sub(/\A#{Rails.root}/, '')
"template_#{template_name}"
end
%w(render_template.action_view render_partial.action_view).map do |event_name|
ActiveSupport::Notifications.subscribe(event_name) do |*data|
event = ActiveSupport::Notifications::Event.new(*data)
template_name = metric_name(event.payload[:identifier])
Chillout::Metric.track(template_name)
end
end
Thread.current[:creations] ||= Chillout::CreationsContainer.new
container = Thread.current[:creations]
Dir.glob("#{Rails.root}/app/views/**/_**").each do |raw_path|
template_name = metric_name(raw_path)
value = container[template_name]
Rails.logger.info "[Chillout] #{template_name}: #{value}"
end
Conclusions
That’s how we are tracking unused templates in our app. Obviously we can’t be 100% sure that templates which have counter equal to 0 aren’t used anywhere. Maybe this template is just very rarely used? But it’s also very useful information. Now we can discuss that with client. Maybe maintenance of the feature using that template is not worth it? Maybe we could drop it?
Note that you could make this not only by using chillout. One of my colleagues did this using plain redis hash. Take a look on Active Support Instrumentation and use it creativly.