rack-profiler 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 10111d8a7faa32f82444b864868a3364a4c557b3
4
- data.tar.gz: b77adab1e71c584c5075da95b8eb60f737c84d79
3
+ metadata.gz: d84a8f5ef46d358a0da6c75940fdb9c5faf28d2c
4
+ data.tar.gz: 80a1d8da3c99c037717710177999f98a90d2e6d6
5
5
  SHA512:
6
- metadata.gz: 3306764da5aa9f681a1025ce5a0c43ea7cd08d210b8813e0a8e9ff7ab93ab7551ffdf11305479a28f7889e2641a0207170e32ed21e84b6c27117580529494514
7
- data.tar.gz: 0647b6ba8a86a09f3d45e2d71c3c71164d494637364cdfdb8af90865ab1a488affc1fe115c9d2530f39f18af3ae53e638bf9b15f3a3e950a594a9d4d8d290fde
6
+ metadata.gz: 875f0998cdf03527b766fa2b9d83fa946673683fdd4842e737fa69fe8c82fb5a60fcf8767fccd0b1249f5d9354206189c364b96015fde59f4a25dcc945f73da6
7
+ data.tar.gz: 3c71402f03f05b104d2d5a2294ff17e4533e24b9925a75e2bc436ac1704f4be750bc7bdb04b597a581de362ddc36c5044d1870f7d6ca56bddc22bdd0579334e9
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in rack-profiler.gemspec
4
4
  gemspec
5
+
6
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -1,22 +1,34 @@
1
1
  # Rack::Profiler
2
2
 
3
+ [![Build Status](https://travis-ci.org/dawanda/rack-profiler.svg)](https://travis-ci.org/dawanda/rack-profiler) [![Code Climate](https://codeclimate.com/github/dawanda/rack-profiler/badges/gpa.svg)](https://codeclimate.com/github/dawanda/rack-profiler) [![Test Coverage](https://codeclimate.com/github/dawanda/rack-profiler/badges/coverage.svg)](https://codeclimate.com/github/dawanda/rack-profiler)
4
+
3
5
  Simple profiler for Rack applications (Sinatra and Ruby on Rails for example).
6
+ It helps providing an answer to common questions like:
7
+
8
+ - Where is time spent in requests to my app?
9
+ - Which SQL queries are executed by `ActiveRecord`?
10
+ - Which are the parts of my app's request flow that need optimization?
11
+
12
+ And more.
4
13
 
5
- `Rack::Profiler` uses the [Active Support Instrumentation API](http://guides.rubyonrails.org/active_support_instrumentation.html)
6
- and subscribes to the following hooks:
14
+ `Rack::Profiler` uses the [Active Support Instrumentation
15
+ API](http://guides.rubyonrails.org/active_support_instrumentation.html) and
16
+ subscribes by default to the following hooks:
7
17
 
8
18
  * [sql.active_record](http://guides.rubyonrails.org/active_support_instrumentation.html#sql-active-record)
9
19
  * [render_template.action_view](http://guides.rubyonrails.org/active_support_instrumentation.html#render_template.action_view)
10
20
  * [render_partial.action_view](http://guides.rubyonrails.org/active_support_instrumentation.html#render_partial.action_view)
11
21
  * [process_action.action_controller](http://guides.rubyonrails.org/active_support_instrumentation.html#process_action.action_controller)
12
22
 
13
- You can also define your own events, by wrapping your code with the
14
- [`Rack::Profiler.step`](#custom-steps).
23
+ On top of this, you can also define your own events, by wrapping your code with
24
+ the [`Rack::Profiler.step`](#custom-steps).
15
25
 
16
26
  `Rack::Profiler` is easy to integrate in any Rack application and it produces a
17
27
  JSON response with the results. It also exposes a simple web dashboard to directly
18
28
  issue HTTP requests to your application and see the results of the profiling.
19
29
 
30
+ ![Rack::Profiler Web Dashboard](http://i.imgur.com/tcUSYle.png?1)
31
+
20
32
  ## Installation
21
33
 
22
34
  Add this line to your application's Gemfile:
@@ -25,8 +37,6 @@ Add this line to your application's Gemfile:
25
37
  gem 'rack-profiler'
26
38
  ```
27
39
 
28
- *You might want to put the gem in the development group if you are using Rails.*
29
-
30
40
  And then execute:
31
41
 
32
42
  $ bundle
@@ -56,6 +66,7 @@ module YourApp
56
66
  class Application < Rails::Application
57
67
 
58
68
  # ...
69
+
59
70
  config.middleware.insert_before Rack::Runtime, Rack::Profiler
60
71
 
61
72
  end
@@ -64,28 +75,19 @@ end
64
75
 
65
76
  ## Configuration
66
77
 
67
- You can configure `Rack::Profiler` to subscribe to more notifications:
78
+ You can configure `Rack::Profiler` passing a block to `use`. In the block you can subscribe to more notifications and change some defaults:
68
79
 
69
80
  ```ruby
70
- Rack::Profiler.configure do |config|
81
+ use Rack::Profiler do |profiler|
71
82
  # Subscribe to email delivery in a Rails app
72
- config.subscribe('deliver.action_mailer')
73
- end
74
- ```
75
-
76
- You can also specify a backtrace filter to exclude lines that are not
77
- interesting:
83
+ profiler.subscribe('deliver.action_mailer')
78
84
 
79
- ```ruby
80
- Rack::Profiler.configure do |config|
81
- # Exclude gems from the backtrace
82
- config.filter_backtrace { |line| !line.include? '/gems/' }
85
+ # You can also exclude lines that are not interesting from the backtrace
86
+ # For example, exclude gems from the backtrace:
87
+ profiler.filter_backtrace { |line| !line.include? '/gems/' }
83
88
  end
84
89
  ```
85
90
 
86
- You can put these configurations in your `config.ru` for a Rack/Sinatra application
87
- or in an initializer `config/rack_profiler.rb` for Rails apps.
88
-
89
91
  ## Usage
90
92
 
91
93
  ### Custom steps
data/lib/rack/profiler.rb CHANGED
@@ -5,134 +5,107 @@ require "active_support/notifications"
5
5
 
6
6
  module Rack
7
7
  class Profiler
8
- class DummyError < StandardError; end
9
-
10
- module ClassMethods
11
- def configure(&block)
12
- block.call(config)
13
- end
14
8
 
15
- def config
16
- @config ||= Configuration.new
17
- end
9
+ attr_reader :events, :backtrace_filter, :subscriptions
10
+ attr_accessor :dashboard_path
18
11
 
19
- def step(name, payload = {})
20
- ActiveSupport::Notifications.instrument('rack-profiler.step', payload.merge(step_name: name)) do
21
- yield
22
- end
23
- end
24
- end
12
+ DEFAULT_SUBSCRIPTIONS = ['sql.active_record',
13
+ 'render_template.action_view',
14
+ 'render_partial.action_view',
15
+ 'process_action.action_controller',
16
+ 'rack-profiler.total_time',
17
+ 'rack-profiler.step']
25
18
 
26
- class Configuration
27
- attr_reader :subscriptions, :backtrace_filter
28
- attr_accessor :dashboard_path
29
-
30
- DEFAULT_SUBSCRIPTIONS = ['sql.active_record',
31
- 'render_template.action_view',
32
- 'render_partial.action_view',
33
- 'process_action.action_controller',
34
- 'rack-profiler.total_time',
35
- 'rack-profiler.step']
36
-
37
- def initialize
38
- @subscriptions = DEFAULT_SUBSCRIPTIONS.clone
39
- @dashboard_path = '/rack-profiler'
40
- end
41
-
42
- def subscribe(*names)
43
- names.each { |name| @subscriptions << name }
44
- @subscriptions.uniq!
45
- end
19
+ class DummyError < StandardError; end
46
20
 
47
- def filter_backtrace(&block)
48
- @backtrace_filter = block
21
+ def self.step(name, payload = {})
22
+ ActiveSupport::Notifications.instrument('rack-profiler.step', payload.merge(step_name: name)) do
23
+ yield
49
24
  end
50
25
  end
51
26
 
52
- extend ClassMethods
53
-
54
- attr_reader :events
55
-
56
- def initialize(app)
57
- subscribe_to_all
58
- @events = []
59
- @app = app
27
+ def initialize(app, &block)
28
+ @events = []
29
+ @subscriptions = []
30
+ @dashboard_path = '/rack-profiler'
31
+ @app = app
32
+ subscribe_to_default
33
+ block.call(self) if block_given?
60
34
  end
61
35
 
62
36
  def call(env)
63
37
  @events = []
64
- status, headers, body = [nil, nil, nil]
65
38
  req = Rack::Request.new(env)
66
39
 
67
- if req.path == config.dashboard_path
40
+ if req.path == dashboard_path
68
41
  render_dashboard
69
42
  elsif req.params.has_key?('rack-profiler')
70
- ActiveSupport::Notifications.instrument('rack-profiler.total_time') do
71
- status, headers, body = @app.call(env)
72
- end
73
- [ 200,
74
- { 'Content-Type' => 'application/json' },
75
- [ { events: events.sort_by { |event| event[:start] },
76
- response: {
77
- status: status,
78
- headers: headers,
79
- body: stringify_body(body)
80
- }
81
- }.to_json ]
82
- ]
43
+ render_profiler_results(env)
83
44
  else
84
- @status, @headers, @body = @app.call(env)
45
+ @app.call(env)
85
46
  end
86
47
  end
87
48
 
88
- def subscribe(event_name)
89
- ActiveSupport::Notifications.subscribe(event_name) do |name, start, finish, id, payload|
90
- backtrace = filtered_backtrace(tap_backtrace)
91
- evt = {
92
- id: id,
93
- name: name,
94
- start: start.to_f,
95
- finish: finish.to_f,
96
- duration: (finish - start) * 1000,
97
- payload: payload,
98
- backtrace: backtrace
99
- }
100
- (@events ||= []) << evt
49
+ def subscribe(*events)
50
+ events.each do |event_name|
51
+ next if @subscriptions.include?(event_name)
52
+ ActiveSupport::Notifications.subscribe(event_name) do |name, start, finish, id, payload|
53
+ evt = {
54
+ id: id,
55
+ name: name,
56
+ start: start.to_f,
57
+ finish: finish.to_f,
58
+ duration: (finish - start) * 1000,
59
+ payload: payload,
60
+ backtrace: filtered_backtrace(caller(1))
61
+ }
62
+ (@events ||= []) << evt
63
+ end
64
+ @subscriptions << event_name
101
65
  end
102
66
  end
103
67
 
104
- def config
105
- self.class.config
68
+ def filter_backtrace(&block)
69
+ @backtrace_filter = block
106
70
  end
107
71
 
108
72
  private
109
73
 
74
+ def render_profiler_results(env)
75
+ status, headers, body = [nil, nil, nil]
76
+ ActiveSupport::Notifications.instrument('rack-profiler.total_time') do
77
+ status, headers, body = @app.call(env)
78
+ end
79
+ [ 200,
80
+ { 'Content-Type' => 'application/json' },
81
+ [ { events: events.sort_by { |event| event[:start] },
82
+ response: {
83
+ status: status,
84
+ headers: headers,
85
+ body: stringify_body(body)
86
+ }
87
+ }.to_json ]
88
+ ]
89
+ end
90
+
110
91
  def render_dashboard
111
- dashboard = ::File.expand_path( '../../public/rack-profiler.html', ::File.dirname( __FILE__ ) )
92
+ dashboard = ::File.expand_path( '../../public/rack-profiler.html',
93
+ ::File.dirname( __FILE__ ) )
112
94
  body = ::File.open(dashboard, ::File::RDONLY)
113
95
  [200, { 'Content-Type' => 'text/html' }, body]
114
96
  end
115
97
 
116
- def subscribe_to_all
117
- # Subscribe to interesting events
118
- config.subscriptions.each do |event|
98
+ def subscribe_to_default
99
+ DEFAULT_SUBSCRIPTIONS.each do |event|
119
100
  subscribe(event)
120
101
  end
121
102
  end
122
103
 
123
- def tap_backtrace
124
- begin
125
- raise DummyError.new
126
- rescue DummyError => e
127
- e.backtrace
128
- end
129
- end
130
-
131
104
  def filtered_backtrace(backtrace)
132
- if config.backtrace_filter.nil?
105
+ if backtrace_filter.nil?
133
106
  backtrace
134
107
  else
135
- backtrace.select(&config.backtrace_filter)
108
+ backtrace.select(&backtrace_filter)
136
109
  end
137
110
  end
138
111
 
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Profiler
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -96,7 +96,7 @@
96
96
  <input type="text" id="req-path" class="form-control" placeholder="Path">
97
97
  </div>
98
98
  <div class="col-md-6">
99
- <input type="text" id="req-data" class="form-control" placeholder="Data">
99
+ <input type="text" id="req-data" class="form-control" placeholder="Data (as query string/form data)">
100
100
  </div>
101
101
  <div class="col-md-1">
102
102
  <button type="submit" class="btn btn-success">Profile</button>
@@ -241,7 +241,7 @@
241
241
  $(".pr-queries tbody").html('')
242
242
  $(".pr-steps tbody").html('')
243
243
 
244
- $.ajax( path, { type: type, data: $.extend(data, { "rack-profiler": 1 }) } )
244
+ $.ajax( path, { type: type, data: ["rack-profiler", data].join("&") } )
245
245
  .done(function( data, status, xhr ) {
246
246
  $(".stats").show()
247
247
 
@@ -16,14 +16,28 @@ describe Rack::Profiler do
16
16
 
17
17
  describe :initialize do
18
18
  it "subscribes to all subscriptions" do
19
- config = Rack::Profiler::Configuration.new
20
- config.subscribe("bar")
21
- allow(Rack::Profiler).to receive(:config).and_return(config)
22
-
23
- profiler = Rack::Profiler.new(nil)
19
+ profiler = Rack::Profiler.new(nil) do |config|
20
+ config.subscribe("bar")
21
+ end
24
22
  trigger_dummy_event("bar")
25
23
  expect(profiler.events.last[:name]).to eq("bar")
26
24
  end
25
+
26
+ it "executes the given block passing self" do
27
+ block_arg = nil
28
+ profiler = Rack::Profiler.new(nil) do |p|
29
+ block_arg = p
30
+ end
31
+ expect(block_arg).to be(profiler)
32
+ end
33
+
34
+ it "sets the correct defaults" do
35
+ expect(profiler.dashboard_path).to eq('/rack-profiler')
36
+ expect(profiler.backtrace_filter).to be_nil
37
+ expect(profiler.subscriptions).to include(
38
+ *Rack::Profiler::DEFAULT_SUBSCRIPTIONS
39
+ )
40
+ end
27
41
  end
28
42
 
29
43
  describe :subscribe do
@@ -52,7 +66,7 @@ describe Rack::Profiler do
52
66
  end
53
67
 
54
68
  it "filters the backtrace if a filter was provided" do
55
- Rack::Profiler.config.filter_backtrace do |line|
69
+ profiler.filter_backtrace do |line|
56
70
  !line.include?("trigger_dummy_event")
57
71
  end
58
72
  trigger_dummy_event("foo")
@@ -70,6 +84,23 @@ describe Rack::Profiler do
70
84
  event = profiler.events.last
71
85
  expect(event[:payload]).to eq(bar: "baz")
72
86
  end
87
+
88
+ it "accepts more than one subscription" do
89
+ profiler.subscribe('baz', 'qux')
90
+ trigger_dummy_event('baz')
91
+ event = profiler.events.last
92
+ expect(event[:name]).to eq('baz')
93
+ trigger_dummy_event('qux')
94
+ event = profiler.events.last
95
+ expect(event[:name]).to eq('qux')
96
+ end
97
+
98
+ it "prevents duplicate subscriptions" do
99
+ profiler.subscribe('baz')
100
+ profiler.subscribe('baz')
101
+ trigger_dummy_event('baz')
102
+ expect(profiler.events.size).to eq(1)
103
+ end
73
104
  end
74
105
 
75
106
  describe :call do
@@ -117,11 +148,14 @@ describe Rack::Profiler do
117
148
 
118
149
  context "when the path is config.dashboard_path" do
119
150
  it "renders dashboard" do
120
- expect(profiler).to receive(:render_dashboard)
121
- profiler.call env.merge(
122
- "PATH_INFO" => profiler.config.dashboard_path,
123
- "REQUEST_PATH" => profiler.config.dashboard_path
151
+ path = ::File.expand_path('../../public/rack-profiler.html',
152
+ ::File.dirname( __FILE__ ) )
153
+ status, header, body = profiler.call env.merge(
154
+ "PATH_INFO" => profiler.dashboard_path,
155
+ "REQUEST_PATH" => profiler.dashboard_path
124
156
  )
157
+ expect(body).to be_a(File)
158
+ expect(body.path).to eq(path)
125
159
  end
126
160
  end
127
161
 
@@ -178,63 +212,12 @@ describe Rack::Profiler do
178
212
  "do stuff"
179
213
  end
180
214
  end
181
- end
182
-
183
- describe ".configure" do
184
- it "executes the given block passing the configuration object" do
185
- config_inside_block = nil
186
- Rack::Profiler.configure do |c|
187
- config_inside_block = c
188
- end
189
- expect(config_inside_block).to be(Rack::Profiler.config)
190
- end
191
- end
192
-
193
- describe ".config" do
194
- it "instantiates a Configuration object if there is none" do
195
- Rack::Profiler.send(:instance_variable_set, :@config, nil)
196
- expect(Rack::Profiler.config).to be_a(Rack::Profiler::Configuration)
197
- end
198
- end
199
- end
200
-
201
- describe Rack::Profiler::Configuration do
202
- let(:config) { Rack::Profiler::Configuration.new }
203
-
204
- it "has the correct defaults" do
205
- expect(config.dashboard_path).to eq('/rack-profiler')
206
- expect(config.backtrace_filter).to be_nil
207
- expect(config.subscriptions).to include(
208
- *Rack::Profiler::Configuration::DEFAULT_SUBSCRIPTIONS
209
- )
210
- end
211
-
212
- describe :subscribe do
213
- it "adds entries to subscriptions" do
214
- config.subscribe('bar')
215
- expect(config.subscriptions).to include('bar')
216
- end
217
-
218
- it "accepts more than one subscription" do
219
- config.subscribe('bar', 'baz')
220
- expect(config.subscriptions).to include('bar', 'baz')
221
- end
222
215
 
223
- it "does not add duplicates" do
224
- config.subscribe('bar', 'bar')
225
- expect(
226
- config.subscriptions.count { |s| s == 'bar' }
227
- ).to eq(1)
228
- end
229
- end
230
-
231
- describe :filter_backtrace do
232
- it "sets the backtrace_filter" do
233
- config.filter_backtrace do |line|
234
- line.include? 'foo'
216
+ it "returns whatever is returned by the block" do
217
+ returned = Rack::Profiler.step("xxx") do
218
+ "do stuff"
235
219
  end
236
- filtered = ['foo', 'foobar', 'baz'].select(&config.backtrace_filter)
237
- expect(filtered).to eq(['foo', 'foobar'])
220
+ expect(returned).to eq("do stuff")
238
221
  end
239
222
  end
240
223
  end
data/spec/spec_helper.rb CHANGED
@@ -1,2 +1,5 @@
1
+ require 'codeclimate-test-reporter'
2
+ CodeClimate::TestReporter.start
3
+
1
4
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
5
  require 'rack/profiler'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Ongaro
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-27 00:00:00.000000000 Z
12
+ date: 2015-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack