Rails profiling story, or how I caught Faker trying to teach my app Australian Slang
Tags: ruby, rails, profiling
Published: 2019-02-20 18:30:55

---
title: Rails profiling story, or how I caught Faker trying to teach my app Australian Slang
published: true
canonical_url: https://evilmartians.com/chronicles/rails-profiling-story-or-how-i-caught-faker-trying-to-teach-my-app-australian-slang
description: Profile application boot time using test-prof, stackprof and memory_profiler to measure the impact of loading tons of locales for Faker gem
tags: ruby, rails, profiling
cover_image: https://thepracticaldev.s3.amazonaws.com/i/7m0izg2gzfo7ec0a84kg.png
---

Once upon a time (actually, a few days ago) I've been working on a small refactoring task: integrating [`factory_bot`](https://github.com/thoughtbot/factory_bot) into mailer previews.

Seemed like a pretty simple task: load `factory_bot_rails` and include syntax methods:

```ruby
# spec/mailers/previews/application_preview.rb

require "factory_bot_rails"

class ApplicationPreview < ActionMailer::Preview
  include FactoryBot::Syntax::Methods

  private

  # user record for previews
  def user
    @user ||= build_stubbed :user
  end
end
```

After doing that I've checked that everything is OK locally and found something strange:

![mailer preview](https://thepracticaldev.s3.amazonaws.com/i/1ocu4gtknx72rm2xypl1.png)

Wow! We have plenty of locales! In our English-only app! That's awesome!
_No, it's not_.

I quickly found where this came from: the [`faker`](https://github.com/stympy/faker) gem we use in our factories.

I've checked [the repo](https://github.com/stympy/faker/blob/e2081e7989344c7f11990d974266ea728531f42c/lib/faker.rb#L9-L15) and was a little bit surprised:

```ruby
require 'i18n'

#...

I18n.load_path += Dir[File.join(mydir, 'locales', '**/*.yml')]
I18n.reload! if I18n.backend.initialized?
```

Faker loads all its locale files and doesn't allow you to choose the only locale you want. That was an unexpected behavior to me.

**NOTE:** you can explicitly specify which locales you want to use in your application by setting [`config.i18n.available_locales`](https://guides.rubyonrails.org/i18n.html#configure-the-i18n-module); if it's not set all the loaded locales would be available.

I decided to check how the loading of a bunch of unnecessary YML files affects the application boot time.

Since `i18n` loads locales lazily, I’ve added the following line to my `rspec_helper.rb` to do that forcefully:

```ruby
# add to rails_helper.rb
I18n.backend.load_translations
```

NOTE: since we use `faker` in factories, we need locales almost in every test. 

I've already had [`test-prof`](https://test-prof.evilmartians.io/#/) in my bundle, so profiling the boot time was as simple as running this one command:

```sh
$ SAMPLE=1 TEST_STACK_PROF=boot TEST_STACK_PROF_FORMAT=json bundle exec rspec
```

Let me explain what happened here.

We “told” Test Prof to profile our test run using [Stack Prof](https://github.com/tmm1/stackprof) and [_boot_ mode](https://test-prof.evilmartians.io/#/stack_prof?id=profiling-application-boot).

In that mode, `stackprof` starts collecting samples right after loading and stops right before the first test runs (more precisely, in the RSpec `before(:suite)` hook).

We also pass `SAMPLE=1` to execute only one random test (this feature is also [provided by Test Prof](https://test-prof.evilmartians.io/#/tests_sampling)): we don’t care about a particular example since we’re profiling the boot time.

Finally, `TEST_STACK_PROF_FORMAT` is used to generate a profiling report in JSON format (the feature [has been added](https://github.com/tmm1/stackprof/pull/103) to `stackprof` itself, but hasn’t been released yet).

What to do with this JSON report? Let’s load it into [Speedscope](https://www.speedscope.app)!

[Speedscope](https://www.speedscope.app) is a flame graph viewer, which visualizes JSON reports generated by popular profilers (including `stackprof`).

NOTE: What are flame graphs and how to read them? Check out the [“official docs”](http://www.brendangregg.com/flamegraphs.html) and also [this talk by Miha Rekar](https://www.youtube.com/watch?v=6uKZXIwd6M0).

That’s what I found:

![flame graph with default faker](https://thepracticaldev.s3.amazonaws.com/i/7m0izg2gzfo7ec0a84kg.png)
<figcaption>Default Faker behaviour (loading all translations)</figcaption>

We spent ~1.1s loading locales.

What if we only load English files (using [this patch](https://gist.github.com/palkan/79fc88207dc856532fb777b8204f7131))?

![flame graph with slim faker](https://thepracticaldev.s3.amazonaws.com/i/9i3hzx4r32f4xjewx9rl.png)
<figcaption>Patched Faker (loading only English translations)</figcaption>

It only took ~0.4s to load locales.

Looks like it’s not a big deal 😕

And if we add [`bootsnap`](https://github.com/Shopify/bootsnap) we could achieve the same result without "patching" `faker`:

![flame graph with bootsnap](https://thepracticaldev.s3.amazonaws.com/i/dr450wbfa6z6h8sh6una.png)
<figcaption>Default Faker with Bootsnap</figcaption>

Maybe, we waste a lot of memory?

Let’s try to measure the memory footprint using [`memory_profiler`](https://github.com/SamSaffron/memory_profiler) gem:

```ruby
require "memory_profiler"

MemoryProfiler.report do
  I18n.backend.load_translations
end.yield_self(&:pretty_print)
```

When loading all available locales:

```
retained objects by gem
-----------------------------------
    147586  psych
      5462  i18n-1.5.3
         2  activesupport-6.0.0.beta1

retained objects by class
-----------------------------------
    148628  String
      2550  Array
      1022  Symbol
       848  Hash
         2  Proc
```

And English only version:

```
retained objects by gem
-----------------------------------
     44301  psych
      3418  i18n-1.5.3
         2  activesupport-6.0.0.beta1

retained objects by class
-----------------------------------
     45240  String
      1202  Array
       910  Symbol
       367  Hash
         2  Proc
```

So, 2.6MB vs. 7.8MB–the difference is negligible. Again.

We must admit, that in this particular case our profiling didn’t reveal any significant issues.

And that could also be considered a happy end 🙂)

P.S. Check out also these posts on profiling with Test Prof: ["A good doctor for slow Ruby tests"](https://evilmartians.com/chronicles/testprof-a-good-doctor-for-slow-ruby-tests) and ["Factory therapy for your Ruby tests"](https://evilmartians.com/chronicles/testprof-2-factory-therapy-for-your-ruby-tests-rspec-minitest).

P.P.S. Yes, Faker really supports [Australian Slang](https://github.com/stympy/faker/blob/e2081e7989344c7f11990d974266ea728531f42c/lib/locales/en-au-ocker.yml).

---

Read more dev articles on [https://evilmartians.com/chronicles](https://evilmartians.com/chronicles)!