devver-rack-contrib 0.9.4 → 0.9.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.
- data/lib/rack/contrib/perftools_profiler.rb +275 -64
- data/rack-contrib.gemspec +1 -1
- metadata +2 -2
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'perftools'
|
2
2
|
require 'rbconfig'
|
3
|
+
require 'pstore'
|
3
4
|
|
4
5
|
# You'll need graphviz to generate call graphs using dot:
|
5
6
|
#
|
@@ -9,8 +10,137 @@ require 'rbconfig'
|
|
9
10
|
# You'll need ps2pdf to generate PDFs (comes with ghostscript)
|
10
11
|
# sudo port install ghostscript
|
11
12
|
|
13
|
+
# There are two modes for the profiler
|
14
|
+
#
|
15
|
+
# First, you can run in 'simple' mode. Just visit the url you want to profile, but
|
16
|
+
# add the 'profile' and (optionally) the 'times' GET params
|
17
|
+
#
|
18
|
+
# example:
|
19
|
+
# curl http://localhost:8080/foobar?profile=true×=3
|
20
|
+
#
|
21
|
+
# Note that this will change the status, body, and headers of the response (you'll get
|
22
|
+
# back the profiling data, NOT the original response.
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# The other mode is start/stop mode.
|
26
|
+
#
|
27
|
+
# example:
|
28
|
+
# curl http://localhost:8080/__start__
|
29
|
+
# curl http://localhost:8080/foobar
|
30
|
+
# curl http://localhost:8080/foobaz
|
31
|
+
# curl http://localhost:8080/__stop__
|
32
|
+
# curl http://localhost:8080/__data__
|
33
|
+
#
|
34
|
+
# In this mode, all responses are normal. You must visit __stop__ to complete profiling and
|
35
|
+
# then you can view the profiling data by visiting __data__
|
36
|
+
|
37
|
+
|
12
38
|
module Rack
|
13
39
|
|
40
|
+
class Action
|
41
|
+
|
42
|
+
DEFAULT_PRINTER = :text
|
43
|
+
|
44
|
+
PRINTER_CONTENT_TYPE = {
|
45
|
+
:text => 'text/plain',
|
46
|
+
:gif => 'image/gif',
|
47
|
+
:pdf => 'application/pdf'
|
48
|
+
}
|
49
|
+
|
50
|
+
def initialize(request, profiler)
|
51
|
+
@request = request
|
52
|
+
@profiler = profiler
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.for_request(request, profiler)
|
56
|
+
return ProfileOnce.new(request, profiler) if ProfileOnce.has_special_param?(request)
|
57
|
+
case request.path
|
58
|
+
when '/__start__'
|
59
|
+
StartProfiling.new(request, profiler)
|
60
|
+
when '/__stop__'
|
61
|
+
StopProfiling.new(request, profiler)
|
62
|
+
when '/__data__'
|
63
|
+
ReturnData.new(request, profiler)
|
64
|
+
else
|
65
|
+
CallAppDirectly.new(request, profiler)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
class StartProfiling < Action
|
72
|
+
|
73
|
+
def act
|
74
|
+
@profiler.start
|
75
|
+
end
|
76
|
+
|
77
|
+
def response
|
78
|
+
Response.new("Profiling is enabled\n", 200, {'Content-Type' => 'text/plain'}).finish
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
class StopProfiling < Action
|
84
|
+
|
85
|
+
def act
|
86
|
+
@profiler.stop
|
87
|
+
end
|
88
|
+
|
89
|
+
def response
|
90
|
+
Response.new("Profiling is disabled\n", 200, {'Content-Type' => 'text/plain'}).finish
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class ProfileOnce < Action
|
96
|
+
|
97
|
+
def self.has_special_param?(request)
|
98
|
+
request.params['profile'] != nil
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(request, profiler)
|
102
|
+
super
|
103
|
+
@times = (request.params.fetch('times') {1}).to_i
|
104
|
+
@printer = parse_printer(profiler.printer)
|
105
|
+
@env = delete_custom_params(request.params)
|
106
|
+
end
|
107
|
+
|
108
|
+
def act
|
109
|
+
@profiler.profile_call(@env, @times)
|
110
|
+
end
|
111
|
+
|
112
|
+
def response
|
113
|
+
@profiler.return_profiling_data(@request.params, @printer)
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete_custom_params(env)
|
117
|
+
env.delete('profile')
|
118
|
+
env.delete('times')
|
119
|
+
env.delete('printer')
|
120
|
+
env
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_printer(printer)
|
124
|
+
if printer.nil?
|
125
|
+
DEFAULT_PRINTER
|
126
|
+
else
|
127
|
+
printer.to_sym
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
class CallAppDirectly < Action
|
134
|
+
|
135
|
+
def act
|
136
|
+
@result = @profiler.call_without_profile
|
137
|
+
end
|
138
|
+
|
139
|
+
def reponse
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
14
144
|
# Pass the :printer option to pick a different result format.
|
15
145
|
class PerftoolsProfiler
|
16
146
|
MODES = %w(
|
@@ -18,8 +148,8 @@ module Rack
|
|
18
148
|
#wall_time
|
19
149
|
)
|
20
150
|
|
151
|
+
PROFILING_DATA_FILE = '/tmp/rack_perftools_profiler.data'
|
21
152
|
DEFAULT_PRINTER = :text
|
22
|
-
#DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
23
153
|
|
24
154
|
PRINTER_CONTENT_TYPE = {
|
25
155
|
:text => 'text/plain',
|
@@ -27,93 +157,174 @@ module Rack
|
|
27
157
|
:pdf => 'application/pdf'
|
28
158
|
}
|
29
159
|
|
30
|
-
|
31
|
-
|
160
|
+
def self.clear_data
|
161
|
+
::File.delete(PROFILING_DATA_FILE) if ::File.exists?(PROFILING_DATA_FILE)
|
162
|
+
end
|
163
|
+
|
164
|
+
def self.with_profiling_off(app, options = {})
|
165
|
+
instance = self.new(app, options)
|
166
|
+
instance.send(:profiling=, false)
|
167
|
+
instance
|
168
|
+
end
|
169
|
+
|
32
170
|
def initialize(app, options = {})
|
33
171
|
@app = app
|
34
172
|
@printer = parse_printer(options[:printer])
|
35
173
|
@times = (options[:times] || 1).to_i
|
36
174
|
end
|
37
175
|
|
176
|
+
def printer
|
177
|
+
@printer
|
178
|
+
end
|
179
|
+
|
38
180
|
def call(env)
|
39
|
-
|
40
|
-
|
181
|
+
@original_request = Request.new(env)
|
182
|
+
if enable_profiling_request?
|
183
|
+
action = Action.for_request(@original_request, self)
|
184
|
+
action.act
|
185
|
+
action.response
|
186
|
+
elsif disable_profiling_request?
|
187
|
+
action = Action.for_request(@original_request, self)
|
188
|
+
action.act
|
189
|
+
action.response
|
190
|
+
elsif profiling_data_request?
|
191
|
+
if in_profiling_mode?
|
192
|
+
[400, {'Content-Type' => 'text/plain'}, 'No profiling data available.']
|
193
|
+
else
|
194
|
+
return_profiling_data(env, @printer)
|
195
|
+
end
|
41
196
|
else
|
42
|
-
|
197
|
+
if mode = profiling?(env.clone)
|
198
|
+
action = Action.for_request(@original_request, self)
|
199
|
+
action.act
|
200
|
+
action.response
|
201
|
+
else
|
202
|
+
@app.call(env)
|
203
|
+
end
|
43
204
|
end
|
44
205
|
end
|
45
206
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# false
|
58
|
-
#end
|
59
|
-
end
|
60
|
-
end
|
207
|
+
def start
|
208
|
+
start_profiling
|
209
|
+
end
|
210
|
+
|
211
|
+
def stop
|
212
|
+
stop_profiling
|
213
|
+
end
|
214
|
+
|
215
|
+
def profile_call(env, times)
|
216
|
+
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE) do
|
217
|
+
times.times { result = @app.call(env) }
|
61
218
|
end
|
219
|
+
end
|
62
220
|
|
63
|
-
|
64
|
-
|
221
|
+
def return_profiling_data(env, printer)
|
222
|
+
if ::File.exists?(PROFILING_DATA_FILE)
|
223
|
+
[200, headers(printer, env), print(printer)]
|
224
|
+
else
|
225
|
+
[404, {'Content-Type' => 'text/plain'}, 'No profiling data available.']
|
226
|
+
end
|
227
|
+
end
|
65
228
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
#end
|
73
|
-
#GC.disable_stats if GC.respond_to?(:disable_stats)
|
229
|
+
private
|
230
|
+
|
231
|
+
def start_profiling
|
232
|
+
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE)
|
233
|
+
self.profiling = true
|
234
|
+
end
|
74
235
|
|
75
|
-
|
236
|
+
def stop_profiling
|
237
|
+
PerfTools::CpuProfiler.stop
|
238
|
+
self.profiling = false
|
239
|
+
end
|
240
|
+
|
241
|
+
def profiling=(value)
|
242
|
+
pstore_transaction(false) do |store|
|
243
|
+
store[:profiling?] = value
|
76
244
|
end
|
245
|
+
end
|
77
246
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
#printer.new(result).print(body, :min_percent => 0.01)
|
83
|
-
#body.rewind
|
84
|
-
#body
|
247
|
+
def pstore_transaction(read_only)
|
248
|
+
pstore = PStore.new('/tmp/rack_perftools_profiler.config')
|
249
|
+
pstore.transaction(read_only) do
|
250
|
+
yield pstore if block_given?
|
85
251
|
end
|
252
|
+
end
|
86
253
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
filetype = printer
|
91
|
-
filename='profile'
|
92
|
-
headers['Content-Disposition'] = %(attachment; filename="#{filename}.#{mode}.#{filetype}")
|
93
|
-
end
|
94
|
-
headers
|
254
|
+
def in_profiling_mode?
|
255
|
+
pstore_transaction(true) do |store|
|
256
|
+
store[:profiling?]
|
95
257
|
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def enable_profiling_request?
|
261
|
+
@original_request.path == '/__start__'
|
262
|
+
end
|
96
263
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
264
|
+
def disable_profiling_request?
|
265
|
+
@original_request.path == '/__stop__'
|
266
|
+
end
|
267
|
+
|
268
|
+
def profiling_data_request?
|
269
|
+
@original_request.path == '/__data__'
|
270
|
+
end
|
271
|
+
|
272
|
+
def simple_profiling_mode?(env)
|
273
|
+
request = Rack::Request.new(env)
|
274
|
+
request.params.delete('profile') != nil
|
275
|
+
end
|
276
|
+
|
277
|
+
def profiling?(env)
|
278
|
+
unless PerfTools::CpuProfiler.running?
|
279
|
+
request = Rack::Request.new(env)
|
280
|
+
@profiling = request.params.delete('profile')
|
281
|
+
if @profiling #|| get_profiling
|
282
|
+
true
|
283
|
+
#if RubyProf.const_defined?(mode.upcase)
|
284
|
+
# mode
|
285
|
+
#else
|
286
|
+
#env['rack.errors'].write "Invalid RubyProf measure_mode: " +
|
287
|
+
# "#{mode}. Use one of #{MODES.to_a.join(', ')}"
|
288
|
+
# false
|
289
|
+
#end
|
102
290
|
end
|
103
|
-
#elsif printer.is_a?(Class)
|
104
|
-
# printer
|
105
|
-
#else
|
106
|
-
# name = "#{camel_case(printer)}Printer"
|
107
|
-
# if RubyProf.const_defined?(name)
|
108
|
-
# RubyProf.const_get(name)
|
109
|
-
# else
|
110
|
-
# DEFAULT_PRINTER
|
111
|
-
# end
|
112
|
-
#end
|
113
291
|
end
|
292
|
+
end
|
114
293
|
|
115
|
-
|
116
|
-
|
294
|
+
def profile(env, mode)
|
295
|
+
result = nil
|
296
|
+
PerfTools::CpuProfiler.start(PROFILING_DATA_FILE) do
|
297
|
+
@times.times { result = @app.call(env) }
|
117
298
|
end
|
299
|
+
if @profiling
|
300
|
+
return_profiling_data(env, @printer)
|
301
|
+
else
|
302
|
+
result
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def print(printer)
|
307
|
+
cmd = "pprof.rb --#{printer} #{PROFILING_DATA_FILE}"
|
308
|
+
`#{cmd}`
|
309
|
+
end
|
310
|
+
|
311
|
+
def headers(printer, env)
|
312
|
+
headers = { 'Content-Type' => PRINTER_CONTENT_TYPE[printer] }
|
313
|
+
if printer==:pdf
|
314
|
+
filetype = printer
|
315
|
+
filename='profile_data'
|
316
|
+
headers['Content-Disposition'] = %(attachment; filename="#{filename}.#{filetype}")
|
317
|
+
end
|
318
|
+
headers
|
319
|
+
end
|
320
|
+
|
321
|
+
def parse_printer(printer)
|
322
|
+
if printer.nil?
|
323
|
+
DEFAULT_PRINTER
|
324
|
+
else
|
325
|
+
printer.to_sym
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
118
329
|
end
|
119
330
|
end
|
data/rack-contrib.gemspec
CHANGED
@@ -3,7 +3,7 @@ Gem::Specification.new do |s|
|
|
3
3
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
4
4
|
|
5
5
|
s.name = 'devver-rack-contrib'
|
6
|
-
s.version = '0.9.
|
6
|
+
s.version = '0.9.5'
|
7
7
|
s.date = '2010-01-10'
|
8
8
|
|
9
9
|
s.description = "The Devver fork of rack-contrib"
|