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 +4 -4
- data/Gemfile +2 -0
- data/README.md +23 -21
- data/lib/rack/profiler.rb +63 -90
- data/lib/rack/profiler/version.rb +1 -1
- data/public/rack-profiler.html +2 -2
- data/spec/rack/profiler_spec.rb +48 -65
- data/spec/spec_helper.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d84a8f5ef46d358a0da6c75940fdb9c5faf28d2c
|
4
|
+
data.tar.gz: 80a1d8da3c99c037717710177999f98a90d2e6d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 875f0998cdf03527b766fa2b9d83fa946673683fdd4842e737fa69fe8c82fb5a60fcf8767fccd0b1249f5d9354206189c364b96015fde59f4a25dcc945f73da6
|
7
|
+
data.tar.gz: 3c71402f03f05b104d2d5a2294ff17e4533e24b9925a75e2bc436ac1704f4be750bc7bdb04b597a581de362ddc36c5044d1870f7d6ca56bddc22bdd0579334e9
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,22 +1,34 @@
|
|
1
1
|
# Rack::Profiler
|
2
2
|
|
3
|
+
[](https://travis-ci.org/dawanda/rack-profiler) [](https://codeclimate.com/github/dawanda/rack-profiler) [](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
|
6
|
-
and
|
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
|
-
|
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
|
+

|
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
|
81
|
+
use Rack::Profiler do |profiler|
|
71
82
|
# Subscribe to email delivery in a Rails app
|
72
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
16
|
-
|
17
|
-
end
|
9
|
+
attr_reader :events, :backtrace_filter, :subscriptions
|
10
|
+
attr_accessor :dashboard_path
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
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
|
-
|
48
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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 ==
|
40
|
+
if req.path == dashboard_path
|
68
41
|
render_dashboard
|
69
42
|
elsif req.params.has_key?('rack-profiler')
|
70
|
-
|
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
|
-
@
|
45
|
+
@app.call(env)
|
85
46
|
end
|
86
47
|
end
|
87
48
|
|
88
|
-
def subscribe(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
105
|
-
|
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',
|
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
|
117
|
-
|
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
|
105
|
+
if backtrace_filter.nil?
|
133
106
|
backtrace
|
134
107
|
else
|
135
|
-
backtrace.select(&
|
108
|
+
backtrace.select(&backtrace_filter)
|
136
109
|
end
|
137
110
|
end
|
138
111
|
|
data/public/rack-profiler.html
CHANGED
@@ -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:
|
244
|
+
$.ajax( path, { type: type, data: ["rack-profiler", data].join("&") } )
|
245
245
|
.done(function( data, status, xhr ) {
|
246
246
|
$(".stats").show()
|
247
247
|
|
data/spec/rack/profiler_spec.rb
CHANGED
@@ -16,14 +16,28 @@ describe Rack::Profiler do
|
|
16
16
|
|
17
17
|
describe :initialize do
|
18
18
|
it "subscribes to all subscriptions" do
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
"
|
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 "
|
224
|
-
|
225
|
-
|
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
|
-
|
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
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
|
+
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-
|
12
|
+
date: 2015-01-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|