devver-rack-contrib 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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&times=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
- # Accepts a :printer => [:call_tree|:graph_html|:graph|:flat] option
31
- # defaulting to :call_tree.
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
- if mode = profiling?(env)
40
- profile(env, mode)
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
- @app.call(env)
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
- private
47
- def profiling?(env)
48
- unless PerfTools::CpuProfiler.running?
49
- request = Rack::Request.new(env)
50
- if mode = request.params.delete('profile')
51
- true
52
- #if RubyProf.const_defined?(mode.upcase)
53
- # mode
54
- #else
55
- #env['rack.errors'].write "Invalid RubyProf measure_mode: " +
56
- # "#{mode}. Use one of #{MODES.to_a.join(', ')}"
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
- def profile(env, mode)
64
- #RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
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
- #GC.enable_stats if GC.respond_to?(:enable_stats)
67
- #result = RubyProf.profile do
68
- temp_file = "/tmp/perftools_file"
69
- PerfTools::CpuProfiler.start(temp_file) do
70
- @times.times { @app.call(env) }
71
- end
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
- [200, headers(@printer, env, mode), print(@printer, temp_file)]
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
- def print(printer, temp_file)
79
- cmd = "pprof.rb --#{printer} #{temp_file}"
80
- `#{cmd}`
81
- #body = StringIO.new
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
- def headers(printer, env, mode)
88
- headers = { 'Content-Type' => PRINTER_CONTENT_TYPE[printer] || DEFAULT_CONTENT_TYPE }
89
- if printer==:pdf
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
- def parse_printer(printer)
98
- if printer.nil?
99
- DEFAULT_PRINTER
100
- else
101
- printer.to_sym
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
- def camel_case(word)
116
- word.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
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.4'
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"
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 9
8
- - 4
9
- version: 0.9.4
8
+ - 5
9
+ version: 0.9.5
10
10
  platform: ruby
11
11
  authors:
12
12
  - rack-devel