File: event_prof.md

package info (click to toggle)
ruby-test-prof 1.6.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,448 kB
  • sloc: ruby: 13,093; sh: 4; makefile: 4
file content (246 lines) | stat: -rw-r--r-- 7,967 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# EventProf

EventProf collects instrumentation (such as ActiveSupport::Notifications) metrics during your test suite run.

It works very similar to `rspec --profile` but can track arbitrary events.

Example output:

```sh
[TEST PROF INFO] EventProf results for sql.active_record

Total time: 00:00.256 of 00:00.512 (50.00%)
Total events: 1031

Top 5 slowest suites (by time):

AnswersController (./spec/controllers/answers_controller_spec.rb:3) – 00:00.119 (549 / 20) of 00:00.200 (59.50%)
QuestionsController (./spec/controllers/questions_controller_spec.rb:3) – 00:00.105 (360 / 18) of 00:00.125 (84.00%)
CommentsController (./spec/controllers/comments_controller_spec.rb:3) – 00:00.032 (122 / 4) of 00:00.064 (50.00%)

Top 5 slowest tests (by time):

destroys question (./spec/controllers/questions_controller_spec.rb:38) – 00:00.022 (29) of 00:00.064 (34.38%)
change comments count (./spec/controllers/comments_controller_spec.rb:7) – 00:00.011 (34) of 00:00.022 (50.00%)
change Votes count (./spec/shared_examples/controllers/voted_examples.rb:23) – 00:00.008 (25) of 00:00.022 (36.36%)
change Votes count (./spec/shared_examples/controllers/voted_examples.rb:23) – 00:00.008 (32) of 00:00.035 (22.86%)
fails (./spec/shared_examples/controllers/invalid_examples.rb:3) – 00:00.007 (34) of 00:00.014 (50.00%)

```

## Instructions

Currently, EventProf supports only ActiveSupport::Notifications

To activate EventProf with:

### RSpec

Use `EVENT_PROF` environment variable set to event name:

```sh
# Collect SQL queries stats for every suite and example
EVENT_PROF='sql.active_record' rspec ...
```

You can track multiple events simultaneously:

```sh
EVENT_PROF='sql.active_record,perform.active_job' rspec ...
```

### Minitest

In Minitest 6+, you must first activate TestProf plugin by adding `Minitest.load :test_prof` in your test helper.

Use `EVENT_PROF` environment variable set to event name:

```sh
# Collect SQL queries stats for every suite and example
EVENT_PROF='sql.active_record' rake test
```

or use CLI options as well:

```sh
# Run a specific file using CLI option
ruby test/my_super_test.rb --event-prof=sql.active_record

# Show the list of possible options:
ruby test/my_super_test.rb --help
```

### Using with Minitest::Reporters

If you're using `Minitest::Reporters` in your project you have to explicitly declare it
in your test helper file:

```sh
require 'minitest/reporters'
Minitest::Reporters.use! [YOUR_FAVORITE_REPORTERS]
```

#### NOTICE

When you have `minitest-reporters` installed as a gem but not declared in your `Gemfile`
make sure to always prepend your test run command with `bundle exec` (but we sure that you always do it).
Otherwise, you'll get an error caused by Minitest plugin system, which scans all the entries in the
`$LOAD_PATH` for any `minitest/*_plugin.rb`, thus initialization of `minitest-reporters` plugin which is
available in that case doesn't happens correctly.

See [Rails guides](http://guides.rubyonrails.org/active_support_instrumentation.html)
for the list of available events if you're using Rails.

If you're using [rom-rb](http://rom-rb.org) you might be interested in profiling `'sql.rom'` event.

## Configuration

By default, EventProf collects information only about top-level groups (aka suites),
but you can also profile individual examples. Just set the configuration option:

```ruby
TestProf::EventProf.configure do |config|
  config.per_example = true
end
```

Or provide the `EVENT_PROF_EXAMPLES=1` env variable.

Another useful configuration parameter – `rank_by`. It's responsible for sorting stats –
either by the time spent in the event or by the number of occurrences:

```sh
EVENT_PROF_RANK=count EVENT_PROF='instantiation.active_record' be rspec
```

See [event_prof.rb](https://github.com/test-prof/test-prof/tree/master/lib/test_prof/event_prof.rb) for all available configuration options and their usage.

## Using with RSpecStamp

EventProf can be used with [RSpec Stamp](../recipes/rspec_stamp.md) to automatically mark _slow_ examples with custom tags. For example:

```sh
EVENT_PROF="sql.active_record" EVENT_PROF_STAMP="slow:sql" rspec ...
```

After running the command above the slowest example groups (and examples if configured) would be marked with the `slow: :sql` tag.

## Custom Instrumentation

To use EventProf with your instrumentation engine just complete the two following steps:

- Add a wrapper for your instrumentation:

```ruby
# Wrapper over your instrumentation
module MyEventsWrapper
  # Should contain the only one method
  def self.subscribe(event)
    raise ArgumentError, "Block is required!" unless block_given?

    ::MyEvents.subscribe(event) do |start, finish, *|
      yield (finish - start)
    end
  end
end
```

- Set instrumenter in the config:

```ruby
TestProf::EventProf.configure do |config|
  config.instrumenter = MyEventsWrapper
end
```

## Custom Events

### `"factory.create"`

FactoryBot provides its own instrumentation ('factory_bot.run_factory'); but there is a caveat – it fires an event every time a factory is used, even when we use factory for nested associations. Thus it's not possible to calculate the total time spent in factories due to the double calculation.

EventProf comes with a little patch for FactoryBot which provides instrumentation only for top-level `FactoryBot.create` calls. It is loaded automatically if you use `"factory.create"` event:

```sh
EVENT_PROF=factory.create bundle exec rspec
```

Also supports Fabrication (tracks implicit and explicit `Fabricate.create` calls).

### `"sidekiq.jobs"`

Collects statistics about Sidekiq jobs that have been run inline:

```sh
EVENT_PROF=sidekiq.jobs bundle exec rspec
```

**NOTE**: automatically sets `rank_by` to `count` ('cause it doesn't make sense to collect the information about time spent – see below).

### `"sidekiq.inline"`

Collects statistics about Sidekiq jobs that have been run inline (excluding nested jobs):

```sh
EVENT_PROF=sidekiq.inline bundle exec rspec
```

Use this event to profile the time spent running Sidekiq jobs.

## Profile arbitrary methods

You can also add your custom events to profile specific methods (for example, after figuring out some hot calls with [RubyProf](./ruby_prof.md) or [StackProf](./stack_prof.md)).

For example, having a class doing some heavy work:

```ruby
class Work
  def do_smth(*args)
    # do something
  end
end
```

You can profile it by adding a _monitor_:

```ruby
# provide a class, event name and methods to monitor
TestProf::EventProf.monitor(Work, "my.work", :do_smth)
```

And then run EventProf as usual:

```sh
EVENT_PROF=my.work bundle exec rake test
```

You can also provide additional options:

- `top_level: true | false` (defaults to `false`): defines whether you want to take into account only top-level invocations and ignore nested triggers of this event (that's how "factory.create" is [implemented](https://github.com/test-prof/test-prof/blob/master/lib/test_prof/event_prof/custom_events/factory_create.rb))
- `guard: Proc` (defaults to `nil`): provide a Proc which could prevent from triggering an event: the method is instrumented only if `guard` returns `true`; `guard` is executed using `instance_exec` and the method arguments are passed to it.

For example:

```ruby
TestProf::EventProf.monitor(
  Sidekiq::Client,
  "sidekiq.inline",
  :raw_push,
  top_level: true,
  guard: ->(*) { Sidekiq::Testing.inline? }
)
```

You can add monitors _on demand_ (i.e. only when you want to track the specified event) by wrapping
the code in `TestProf::EventProf::CustomEvents.register` method:

```ruby
TestProf::EventProf::CustomEvents.register("my.work") do
  TestProf::EventProf.monitor(Work, "my.work", :do_smth)
end

# Then call `activate_all` with the provided event
TestProf::EventProf::CustomEvents.activate_all(TestProf::EventProf.config.event)
```

The block is evaluated only if the specified event is enabled with EventProf.