rack-mini-profiler 0.1.23 → 0.1.28

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

@@ -123,6 +123,9 @@
123
123
 
124
124
  <script id="linksTemplate" type="text/x-jquery-tmpl">
125
125
  <a href="${MiniProfiler.shareUrl(Id)}" class="profiler-share-profiler-results" target="_blank">share</a>
126
+ {{if CustomLink}}
127
+ <a href="${CustomLink}" class="profiler-custom-link" target="_blank">${CustomLinkName}</a>
128
+ {{/if}}
126
129
  {{if HasTrivialTimings}}
127
130
  <a class="profiler-toggle-trivial" data-show-on-load="${HasAllTrivialTimings}" title="toggles any rows with &lt; ${TrivialDurationThresholdMilliseconds} ms">
128
131
  show trivial
@@ -156,12 +159,10 @@
156
159
  <td class="profiler-duration" title="aggregate duration of all queries in this step (excludes children)">
157
160
  ${MiniProfiler.formatDuration(timing.SqlTimingsDurationMilliseconds)}
158
161
  </td>
159
- {{else}}
160
- <td colspan='2'></td>
161
162
  {{/if}}
162
163
 
163
164
  {{each page.CustomTimingNames}}
164
- {{if timing.CustomTimings[$value]}}
165
+ {{if timing.CustomTimings && timing.CustomTimings[$value]}}
165
166
  <td class="profiler-duration" title="aggregate number of all ${$value.toLowerCase()} invocations in this step (excludes children)">
166
167
  ${timing.CustomTimings[$value].length} ${$value.toLowerCase()}
167
168
  </td>
@@ -1 +1 @@
1
- <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}"></script>
1
+ <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
@@ -7,14 +7,15 @@ module Rack
7
7
  @attributes.concat vars
8
8
  super(*vars)
9
9
  end
10
-
10
+
11
11
  def self.attributes
12
12
  @attributes
13
13
  end
14
14
 
15
15
  attr_accessor :auto_inject, :base_url_path, :pre_authorize_cb, :position,
16
- :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
17
- :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode
16
+ :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
17
+ :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode,
18
+ :toggle_shortcut, :start_hidden, :backtrace_threshold_ms
18
19
 
19
20
  # Deprecated options
20
21
  attr_accessor :use_existing_jquery
@@ -23,26 +24,29 @@ module Rack
23
24
  new.instance_eval {
24
25
  @auto_inject = true # automatically inject on every html page
25
26
  @base_url_path = "/mini-profiler-resources/"
26
-
27
+
27
28
  # called prior to rack chain, to ensure we are allowed to profile
28
- @pre_authorize_cb = lambda {|env| true}
29
-
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
30
31
  # called after rack chain, to ensure we are REALLY allowed to profile
31
32
  @position = 'left' # Where it is displayed
32
33
  @skip_schema_queries = false
33
34
  @storage = MiniProfiler::MemoryStore
34
35
  @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
35
36
  @authorization_mode = :allow_all
37
+ @toggle_shortcut = 'Alt+P'
38
+ @start_hidden = false
39
+ @backtrace_threshold_ms = 0
36
40
  self
37
41
  }
38
42
  end
39
43
 
40
44
  def merge!(config)
41
45
  return unless config
42
- if Hash === config
46
+ if Hash === config
43
47
  config.each{|k,v| instance_variable_set "@#{k}",v}
44
- else
45
- self.class.attributes.each{ |k|
48
+ else
49
+ self.class.attributes.each{ |k|
46
50
  v = config.send k
47
51
  instance_variable_set "@#{k}", v if v
48
52
  }
@@ -1,10 +1,11 @@
1
- class Rack::MiniProfiler::Context
2
- attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init
3
-
4
- def initialize(opts = {})
5
- opts.each do |k,v|
6
- self.instance_variable_set('@' + k, v)
7
- end
8
- end
9
-
10
- end
1
+ class Rack::MiniProfiler::Context
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
+
4
+ def initialize(opts = {})
5
+ opts["measure"] = true unless opts.key? "measure"
6
+ opts.each do |k,v|
7
+ self.instance_variable_set('@' + k, v)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,54 @@
1
+ # inspired by https://github.com/brendangregg/FlameGraph
2
+
3
+ class Rack::MiniProfiler::FlameGraph
4
+ def initialize(stacks)
5
+ @stacks = stacks
6
+ end
7
+
8
+ def graph_data
9
+ height = 0
10
+
11
+ table = []
12
+ prev = []
13
+
14
+ # a 2d array makes collapsing easy
15
+ @stacks.each_with_index do |stack, pos|
16
+ col = []
17
+
18
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
19
+
20
+ if !prev[i].nil?
21
+ last_col = prev[i]
22
+ if last_col[0] == frame
23
+ last_col[1] += 1
24
+ col << nil
25
+ next
26
+ end
27
+ end
28
+
29
+ prev[i] = [frame, 1]
30
+ col << prev[i]
31
+ end
32
+ prev = prev[0..col.length-1].to_a
33
+ table << col
34
+ end
35
+
36
+ data = []
37
+
38
+ # a 1d array makes rendering easy
39
+ table.each_with_index do |col, col_num|
40
+ col.each_with_index do |row, row_num|
41
+ next unless row && row.length == 2
42
+ data << {
43
+ :x => col_num + 1,
44
+ :y => row_num + 1,
45
+ :width => row[1],
46
+ :frame => row[0]
47
+ }
48
+ end
49
+ end
50
+
51
+ data
52
+ end
53
+
54
+ end
@@ -5,10 +5,14 @@ class Rack::MiniProfiler::GCProfiler
5
5
  ids = Set.new
6
6
  i=0
7
7
  ObjectSpace.each_object { |o|
8
- i = stats[o.class] || 0
9
- i += 1
10
- stats[o.class] = i
11
- ids << o.object_id if Integer === o.object_id
8
+ begin
9
+ i = stats[o.class] || 0
10
+ i += 1
11
+ stats[o.class] = i
12
+ ids << o.object_id if Integer === o.object_id
13
+ rescue NoMethodError
14
+ # Redis::Future undefines .class and .object_id super weird
15
+ end
12
16
  }
13
17
  {:stats => stats, :ids => ids}
14
18
  end
@@ -18,13 +18,14 @@ require 'mini_profiler/profiling_methods'
18
18
  require 'mini_profiler/context'
19
19
  require 'mini_profiler/client_settings'
20
20
  require 'mini_profiler/gc_profiler'
21
+ require 'mini_profiler/flame_graph'
21
22
 
22
23
  module Rack
23
24
 
24
25
  class MiniProfiler
25
-
26
- class << self
27
-
26
+
27
+ class << self
28
+
28
29
  include Rack::MiniProfiler::ProfilingMethods
29
30
 
30
31
  def generate_id
@@ -44,7 +45,7 @@ module Rack
44
45
  return @share_template unless @share_template.nil?
45
46
  @share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
46
47
  end
47
-
48
+
48
49
  def current
49
50
  Thread.current[:mini_profiler_private]
50
51
  end
@@ -79,104 +80,82 @@ module Rack
79
80
  Thread.current[:mp_authorized]
80
81
  end
81
82
 
82
- # Add a custom timing. These are displayed similar to SQL/query time in
83
- # columns expanding to the right.
84
- #
85
- # type - String counter type. Each distinct type gets its own column.
86
- # duration_ms - Duration of the call in ms. Either this or a block must be
87
- # given but not both.
88
- #
89
- # When a block is given, calculate the duration by yielding to the block
90
- # and keeping a record of its run time.
91
- #
92
- # Returns the result of the block, or nil when no block is given.
93
- def counter(type, duration_ms=nil)
94
- result = nil
95
- if block_given?
96
- start = Time.now
97
- result = yield
98
- duration_ms = (Time.now - start).to_f * 1000
99
- end
100
- return result if current.nil? || !request_authorized?
101
- current.current_timer.add_custom(type, duration_ms, current.page_struct)
102
- result
103
- end
104
83
  end
105
84
 
106
- #
107
- # options:
108
- # :auto_inject - should script be automatically injected on every html page (not xhr)
109
- def initialize(app, config = nil)
85
+ #
86
+ # options:
87
+ # :auto_inject - should script be automatically injected on every html page (not xhr)
88
+ def initialize(app, config = nil)
110
89
  MiniProfiler.config.merge!(config)
111
- @config = MiniProfiler.config
112
- @app = app
113
- @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
90
+ @config = MiniProfiler.config
91
+ @app = app
92
+ @config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
114
93
  unless @config.storage_instance
115
94
  @config.storage_instance = @config.storage.new(@config.storage_options)
116
95
  end
117
- @storage = @config.storage_instance
118
- end
119
-
96
+ @storage = @config.storage_instance
97
+ end
98
+
120
99
  def user(env)
121
100
  @config.user_provider.call(env)
122
101
  end
123
102
 
124
- def serve_results(env)
125
- request = Rack::Request.new(env)
103
+ def serve_results(env)
104
+ request = Rack::Request.new(env)
126
105
  id = request['id']
127
- page_struct = @storage.load(id)
106
+ page_struct = @storage.load(id)
128
107
  unless page_struct
129
- @storage.set_viewed(user(env), id)
130
- return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
108
+ @storage.set_viewed(user(env), id)
109
+ return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
131
110
  end
132
- unless page_struct['HasUserViewed']
111
+ unless page_struct['HasUserViewed']
133
112
  page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
134
- page_struct['HasUserViewed'] = true
135
- @storage.save(page_struct)
136
- @storage.set_viewed(user(env), id)
137
- end
113
+ page_struct['HasUserViewed'] = true
114
+ @storage.save(page_struct)
115
+ @storage.set_viewed(user(env), id)
116
+ end
138
117
 
139
118
  result_json = page_struct.to_json
140
119
  # If we're an XMLHttpRequest, serve up the contents as JSON
141
120
  if request.xhr?
142
- [200, { 'Content-Type' => 'application/json'}, [result_json]]
121
+ [200, { 'Content-Type' => 'application/json'}, [result_json]]
143
122
  else
144
123
 
145
124
  # Otherwise give the HTML back
146
- html = MiniProfiler.share_template.dup
147
- html.gsub!(/\{path\}/, @config.base_url_path)
148
- html.gsub!(/\{version\}/, MiniProfiler::VERSION)
125
+ html = MiniProfiler.share_template.dup
126
+ html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
127
+ html.gsub!(/\{version\}/, MiniProfiler::VERSION)
149
128
  html.gsub!(/\{json\}/, result_json)
150
129
  html.gsub!(/\{includes\}/, get_profile_script(env))
151
130
  html.gsub!(/\{name\}/, page_struct['Name'])
152
131
  html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
153
-
132
+
154
133
  [200, {'Content-Type' => 'text/html'}, [html]]
155
134
  end
156
135
 
157
- end
136
+ end
158
137
 
159
- def serve_html(env)
160
- file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
161
- return serve_results(env) if file_name.eql?('results')
162
- full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
163
- return [404, {}, ["Not found"]] unless ::File.exists? full_path
164
- f = Rack::File.new nil
165
- f.path = full_path
138
+ def serve_html(env)
139
+ file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
140
+ return serve_results(env) if file_name.eql?('results')
141
+ full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
142
+ return [404, {}, ["Not found"]] unless ::File.exists? full_path
143
+ f = Rack::File.new nil
144
+ f.path = full_path
166
145
 
167
- begin
146
+ begin
168
147
  f.cache_control = "max-age:86400"
169
148
  f.serving env
170
149
  rescue
171
- # old versions of rack have a different api
150
+ # old versions of rack have a different api
172
151
  status, headers, body = f.serving
173
152
  headers.merge! 'Cache-Control' => "max-age:86400"
174
153
  [status, headers, body]
175
154
  end
176
155
 
177
- end
156
+ end
157
+
178
158
 
179
-
180
159
  def current
181
160
  MiniProfiler.current
182
161
  end
@@ -191,7 +170,7 @@ module Rack
191
170
  end
192
171
 
193
172
 
194
- def call(env)
173
+ def call(env)
195
174
 
196
175
  client_settings = ClientSettings.new(env)
197
176
 
@@ -201,20 +180,20 @@ module Rack
201
180
 
202
181
  skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
203
182
  (@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
204
- query_string =~ /pp=skip/
205
-
183
+ query_string =~ /pp=skip/
184
+
206
185
  has_profiling_cookie = client_settings.has_cookie?
207
-
186
+
208
187
  if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
209
188
  status,headers,body = @app.call(env)
210
- if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
211
- client_settings.write!(headers)
189
+ if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
190
+ client_settings.write!(headers)
212
191
  end
213
192
  return [status,headers,body]
214
193
  end
215
194
 
216
195
  # handle all /mini-profiler requests here
217
- return serve_html(env) if path.start_with? @config.base_url_path
196
+ return serve_html(env) if path.start_with? @config.base_url_path
218
197
 
219
198
  has_disable_cookie = client_settings.disable_profiling?
220
199
  # manual session disable / enable
@@ -236,21 +215,16 @@ module Rack
236
215
  end
237
216
 
238
217
  if query_string =~ /pp=profile-gc/
239
- # begin
240
- if query_string =~ /pp=profile-gc-time/
241
- return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
242
- else
243
- return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
244
- end
245
- # rescue => e
246
- # p e
247
- # e.backtrace.each do |s|
248
- # puts s
249
- # end
250
- # end
218
+ if query_string =~ /pp=profile-gc-time/
219
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
220
+ else
221
+ return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
222
+ end
251
223
  end
224
+
252
225
  MiniProfiler.create_current(env, @config)
253
226
  MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
227
+
254
228
  if query_string =~ /pp=normal-backtrace/
255
229
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
256
230
  elsif query_string =~ /pp=no-backtrace/
@@ -266,31 +240,26 @@ module Rack
266
240
  done_sampling = false
267
241
  quit_sampler = false
268
242
  backtraces = nil
269
- stacktrace_installed = true
270
- if query_string =~ /pp=sample/
243
+
244
+ if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
245
+ current.measure = false
271
246
  skip_frames = 0
272
247
  backtraces = []
273
248
  t = Thread.current
274
-
275
- begin
276
- require 'stacktrace'
277
- skip_frames = stacktrace.length
278
- rescue LoadError
279
- stacktrace_installed = false
280
- end
281
249
 
282
250
  Thread.new {
251
+ # new in Ruby 2.0
252
+ has_backtrace_locations = t.respond_to?(:backtrace_locations)
283
253
  begin
284
- i = 10000 # for sanity never grab more than 10k samples
254
+ i = 10000 # for sanity never grab more than 10k samples
285
255
  while i > 0
286
256
  break if done_sampling
287
257
  i -= 1
288
- if stacktrace_installed
289
- backtraces << t.stacktrace(0,-(1+skip_frames), StackFrame::Flags::METHOD | StackFrame::Flags::KLASS)
290
- else
291
- backtraces << t.backtrace
292
- end
293
- sleep 0.001
258
+ backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
259
+
260
+ # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
261
+ # with this fidelity analysis becomes very powerful
262
+ sleep 0.0005
294
263
  end
295
264
  ensure
296
265
  quit_sampler = true
@@ -298,29 +267,30 @@ module Rack
298
267
  }
299
268
  end
300
269
 
301
- status, headers, body = nil
302
- start = Time.now
303
- begin
270
+ status, headers, body = nil
271
+ start = Time.now
272
+ begin
304
273
 
305
- # Strip all the caching headers so we don't get 304s back
274
+ # Strip all the caching headers so we don't get 304s back
306
275
  # This solves a very annoying bug where rack mini profiler never shows up
307
- env['HTTP_IF_MODIFIED_SINCE'] = nil
308
- env['HTTP_IF_NONE_MATCH'] = nil
276
+ env['HTTP_IF_MODIFIED_SINCE'] = ''
277
+ env['HTTP_IF_NONE_MATCH'] = ''
309
278
 
310
279
  status,headers,body = @app.call(env)
311
280
  client_settings.write!(headers)
312
281
  ensure
313
- if backtraces
282
+ if backtraces
314
283
  done_sampling = true
315
284
  sleep 0.001 until quit_sampler
316
285
  end
317
286
  end
318
287
 
319
288
  skip_it = current.discard
289
+
320
290
  if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
321
291
  # this is non-obvious, don't kill the profiling cookie on errors or short requests
322
292
  # this ensures that stuff that never reaches the rails stack does not kill profiling
323
- if status == 200 && ((Time.now - start) > 0.1)
293
+ if status == 200 && ((Time.now - start) > 0.1)
324
294
  client_settings.discard_cookie!(headers)
325
295
  end
326
296
  skip_it = true
@@ -338,44 +308,46 @@ module Rack
338
308
  body.close if body.respond_to? :close
339
309
  return help(client_settings)
340
310
  end
341
-
311
+
342
312
  page_struct = current.page_struct
343
313
  page_struct['User'] = user(env)
344
- page_struct['Root'].record_time((Time.now - start) * 1000)
314
+ page_struct['Root'].record_time((Time.now - start) * 1000)
345
315
 
346
316
  if backtraces
347
317
  body.close if body.respond_to? :close
348
- return analyze(backtraces, page_struct)
318
+ if query_string =~ /pp=sample/
319
+ return analyze(backtraces, page_struct)
320
+ else
321
+ return flame_graph(backtraces, page_struct)
322
+ end
349
323
  end
350
-
324
+
351
325
 
352
326
  # no matter what it is, it should be unviewed, otherwise we will miss POST
353
327
  @storage.set_unviewed(page_struct['User'], page_struct['Id'])
354
- @storage.save(page_struct)
355
-
328
+ @storage.save(page_struct)
329
+
330
+ content_type = headers['Content-Type']
356
331
  # inject headers, script
357
- if status == 200
332
+ if content_type && status == 200
358
333
 
359
334
  client_settings.write!(headers)
360
-
335
+
361
336
  # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
362
337
  # Rack::ETag has already inserted some nonesense in the chain
363
338
  headers.delete('ETag')
364
339
  headers.delete('Date')
365
340
  headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
366
341
 
367
- # inject header
342
+ # inject header
368
343
  if headers.is_a? Hash
369
344
  headers['X-MiniProfiler-Ids'] = ids_json(env)
370
345
  end
371
346
 
372
- # inject script
373
- if current.inject_js \
374
- && headers.has_key?('Content-Type') \
375
- && !headers['Content-Type'].match(/text\/html/).nil? then
376
-
347
+ if current.inject_js && content_type =~ /text\/html/
377
348
  response = Rack::Response.new([], status, headers)
378
349
  script = self.get_profile_script(env)
350
+
379
351
  if String === body
380
352
  response.write inject(body,script)
381
353
  else
@@ -383,15 +355,15 @@ module Rack
383
355
  end
384
356
  body.close if body.respond_to? :close
385
357
  return response.finish
386
- end
387
- end
358
+ end
359
+ end
388
360
 
389
361
  client_settings.write!(headers)
390
- [status, headers, body]
362
+ [status, headers, body]
391
363
  ensure
392
364
  # Make sure this always happens
393
365
  current = nil
394
- end
366
+ end
395
367
 
396
368
  def inject(fragment, script)
397
369
  if fragment.match(/<\/body>/i)
@@ -410,23 +382,43 @@ module Rack
410
382
  return fragment + script
411
383
  end
412
384
 
413
- fragment.sub(regex) do
414
- # if for whatever crazy reason we dont get a utf string,
415
- # just force the encoding, no utf in the mp scripts anyway
416
- if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
417
- (script + close_tag).force_encoding(fragment.encoding)
385
+ matches = fragment.scan(regex).length
386
+ index = 1
387
+ fragment.gsub(regex) do
388
+ # though malformed there is an edge case where /body exists earlier in the html, work around
389
+ if index < matches
390
+ index += 1
391
+ close_tag
418
392
  else
419
- script + close_tag
393
+
394
+ # if for whatever crazy reason we dont get a utf string,
395
+ # just force the encoding, no utf in the mp scripts anyway
396
+ if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
397
+ (script + close_tag).force_encoding(fragment.encoding)
398
+ else
399
+ script + close_tag
400
+ end
420
401
  end
421
402
  end
422
403
  end
423
404
 
424
405
  def dump_env(env)
425
406
  headers = {'Content-Type' => 'text/plain'}
426
- body = ""
407
+ body = "Rack Environment\n---------------\n"
427
408
  env.each do |k,v|
428
409
  body << "#{k}: #{v}\n"
429
410
  end
411
+
412
+ body << "\n\nEnvironment\n---------------\n"
413
+ ENV.each do |k,v|
414
+ body << "#{k}: #{v}\n"
415
+ end
416
+
417
+ body << "\n\nInternals\n---------------\n"
418
+ body << "Storage Provider #{config.storage_instance}\n"
419
+ body << "User #{user(env)}\n"
420
+ body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
421
+
430
422
  [200, headers, [body]]
431
423
  end
432
424
 
@@ -439,29 +431,42 @@ module Rack
439
431
  pp=skip : skip mini profiler for this request
440
432
  pp=no-backtrace #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use pp=normal-backtrace to enable)
441
433
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
442
- pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
443
- pp=sample : sample stack traces and return a report isolating heavy usage (experimental works best with the stacktrace gem)
444
- pp=disable : disable profiling for this session
434
+ pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
435
+ pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
436
+ pp=disable : disable profiling for this session
445
437
  pp=enable : enable profiling for this session (if previously disabled)
446
438
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
447
439
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
440
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
448
441
  "
449
-
442
+
450
443
  client_settings.write!(headers)
451
444
  [200, headers, [body]]
452
445
  end
453
446
 
447
+ def flame_graph(traces, page_struct)
448
+ graph = FlameGraph.new(traces)
449
+ data = graph.graph_data
450
+
451
+ headers = {'Content-Type' => 'text/html'}
452
+
453
+ body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
454
+ body.gsub!("/*DATA*/", ::JSON.generate(data));
455
+
456
+ [200, headers, [body]]
457
+ end
458
+
454
459
  def analyze(traces, page_struct)
455
460
  headers = {'Content-Type' => 'text/plain'}
456
461
  body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
457
462
 
458
463
  seen = {}
459
464
  fulldump = ""
460
- traces.each do |trace|
465
+ traces.each do |trace|
461
466
  fulldump << "\n\n"
462
467
  distinct = {}
463
468
  trace.each do |frame|
464
- frame = "#{frame.klass}::#{frame.method}" unless String === frame
469
+ frame = frame.to_s unless String === frame
465
470
  unless distinct[frame]
466
471
  distinct[frame] = true
467
472
  seen[frame] ||= 0
@@ -477,7 +482,7 @@ module Rack
477
482
  body << "#{name} x #{count}\n"
478
483
  end
479
484
  end
480
-
485
+
481
486
  body << "\n\n\nRaw traces \n"
482
487
  body << fulldump
483
488
 
@@ -496,40 +501,42 @@ module Rack
496
501
  ids.join(",")
497
502
  end
498
503
 
499
- # get_profile_script returns script to be injected inside current html page
500
- # By default, profile_script is appended to the end of all html requests automatically.
501
- # Calling get_profile_script cancels automatic append for the current page
502
- # Use it when:
503
- # * you have disabled auto append behaviour throught :auto_inject => false flag
504
- # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
505
- def get_profile_script(env)
506
- ids = ids_comma_separated(env)
507
- path = @config.base_url_path
508
- version = MiniProfiler::VERSION
509
- position = @config.position
510
- showTrivial = false
511
- showChildren = false
512
- maxTracesToShow = 10
513
- showControls = false
514
- currentId = current.page_struct["Id"]
515
- authorized = true
516
- # TODO : cache this snippet
517
- script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
518
- # replace the variables
519
- [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized].each do |v|
520
- regex = Regexp.new("\\{#{v.to_s}\\}")
521
- script.gsub!(regex, eval(v.to_s).to_s)
522
- end
523
- current.inject_js = false
524
- script
525
- end
526
-
527
- # cancels automatic injection of profile script for the current page
528
- def cancel_auto_inject(env)
529
- current.inject_js = false
530
- end
531
-
532
- end
504
+ # get_profile_script returns script to be injected inside current html page
505
+ # By default, profile_script is appended to the end of all html requests automatically.
506
+ # Calling get_profile_script cancels automatic append for the current page
507
+ # Use it when:
508
+ # * you have disabled auto append behaviour throught :auto_inject => false flag
509
+ # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
510
+ def get_profile_script(env)
511
+ ids = ids_comma_separated(env)
512
+ path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
513
+ version = MiniProfiler::VERSION
514
+ position = @config.position
515
+ showTrivial = false
516
+ showChildren = false
517
+ maxTracesToShow = 10
518
+ showControls = false
519
+ currentId = current.page_struct["Id"]
520
+ authorized = true
521
+ toggleShortcut = @config.toggle_shortcut
522
+ startHidden = @config.start_hidden
523
+ # TODO : cache this snippet
524
+ script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
525
+ # replace the variables
526
+ [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
527
+ regex = Regexp.new("\\{#{v.to_s}\\}")
528
+ script.gsub!(regex, eval(v.to_s).to_s)
529
+ end
530
+ current.inject_js = false
531
+ script
532
+ end
533
+
534
+ # cancels automatic injection of profile script for the current page
535
+ def cancel_auto_inject(env)
536
+ current.inject_js = false
537
+ end
538
+
539
+ end
533
540
 
534
541
  end
535
542