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.
@@ -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