rack-mini-profiler 0.1.27 → 0.9.0.pre

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.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/{Ruby/CHANGELOG → CHANGELOG} +33 -0
  3. data/{Ruby/README.md → README.md} +51 -27
  4. data/{Ruby/lib → lib}/html/includes.css +0 -0
  5. data/{Ruby/lib → lib}/html/includes.js +90 -75
  6. data/{Ruby/lib → lib}/html/includes.less +0 -0
  7. data/{Ruby/lib → lib}/html/includes.tmpl +3 -1
  8. data/{Ruby/lib → lib}/html/jquery.1.7.1.js +0 -0
  9. data/{Ruby/lib → lib}/html/jquery.tmpl.js +0 -0
  10. data/{Ruby/lib → lib}/html/list.css +2 -2
  11. data/{Ruby/lib → lib}/html/list.js +1 -1
  12. data/{Ruby/lib → lib}/html/list.tmpl +2 -2
  13. data/lib/html/profile_handler.js +1 -0
  14. data/{Ruby/lib → lib}/html/share.html +2 -2
  15. data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +11 -11
  16. data/{Ruby/lib → lib}/mini_profiler/client_timer_struct.rb +0 -0
  17. data/{Ruby/lib → lib}/mini_profiler/config.rb +18 -11
  18. data/{Ruby/lib → lib}/mini_profiler/context.rb +1 -1
  19. data/{Ruby/lib → lib}/mini_profiler/custom_timer_struct.rb +0 -0
  20. data/lib/mini_profiler/gc_profiler.rb +181 -0
  21. data/{Ruby/lib → lib}/mini_profiler/page_timer_struct.rb +4 -4
  22. data/{Ruby/lib → lib}/mini_profiler/profiler.rb +163 -141
  23. data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +31 -11
  24. data/{Ruby/lib → lib}/mini_profiler/request_timer_struct.rb +5 -5
  25. data/{Ruby/lib → lib}/mini_profiler/sql_timer_struct.rb +0 -0
  26. data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +4 -3
  27. data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +0 -0
  28. data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +0 -0
  29. data/{Ruby/lib → lib}/mini_profiler/storage/memory_store.rb +0 -0
  30. data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +3 -3
  31. data/{Ruby/lib → lib}/mini_profiler/timer_struct.rb +0 -0
  32. data/lib/mini_profiler/version.rb +5 -0
  33. data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +6 -2
  34. data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
  35. data/{Ruby/lib → lib}/patches/sql_patches.rb +6 -1
  36. data/{Ruby/lib → lib}/rack-mini-profiler.rb +0 -0
  37. data/rack-mini-profiler.gemspec +6 -5
  38. metadata +45 -46
  39. data/Ruby/lib/html/flamegraph.html +0 -351
  40. data/Ruby/lib/html/profile_handler.js +0 -1
  41. data/Ruby/lib/mini_profiler/flame_graph.rb +0 -54
  42. data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
  43. data/Ruby/lib/mini_profiler/version.rb +0 -5
File without changes
@@ -1,4 +1,4 @@
1
- <script id="profilerTemplate" type="text/x-jquery-tmpl">
1
+ <script id="profilerTemplate" type="text/x-jquery-tmpl">
2
2
 
3
3
  <div class="profiler-result">
4
4
 
@@ -159,6 +159,8 @@
159
159
  <td class="profiler-duration" title="aggregate duration of all queries in this step (excludes children)">
160
160
  ${MiniProfiler.formatDuration(timing.SqlTimingsDurationMilliseconds)}
161
161
  </td>
162
+ {{else}}
163
+ <td colspan="2"></td>
162
164
  {{/if}}
163
165
 
164
166
  {{each page.CustomTimingNames}}
File without changes
File without changes
@@ -1,4 +1,4 @@
1
- tbody tr:nth-child(odd) { background-color:#eee; }
1
+ tbody tr:nth-child(odd) { background-color:#eee; }
2
2
  tbody tr:nth-child(even) { background-color:#fff; }
3
3
  table { border: 0; border-spacing:0;}
4
4
  tr {border: 0;}
@@ -6,4 +6,4 @@ tr {border: 0;}
6
6
  td {padding: 8px;}
7
7
  .time {text-align:center;}
8
8
  thead tr {background-color: #bbb; color: #444; font-size: 12px;}
9
- thead tr th { padding: 5px 15px;}
9
+ thead tr th { padding: 5px 15px;}
@@ -1,4 +1,4 @@
1
- var MiniProfiler = MiniProfiler || {};
1
+ var MiniProfiler = MiniProfiler || {};
2
2
  MiniProfiler.list = {
3
3
  init:
4
4
  function (options) {
@@ -1,4 +1,4 @@
1
- <script id="tableTemplate" type="text/x-jquery-tmpl">
1
+ <script id="tableTemplate" type="text/x-jquery-tmpl">
2
2
  <table>
3
3
  <thead>
4
4
  <tr>
@@ -31,4 +31,4 @@
31
31
  <td colspan="3"></td>
32
32
  {{/if}}
33
33
  </tr>
34
- </script>
34
+ </script>
@@ -0,0 +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}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
@@ -1,4 +1,4 @@
1
- <html>
1
+ <html>
2
2
  <head>
3
3
  <title>{name} ({duration} ms) - Profiling Results</title>
4
4
  <script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
@@ -8,4 +8,4 @@
8
8
  <body>
9
9
  <div class='profiler-result-full'></div>
10
10
  </body>
11
- </html>
11
+ </html>
@@ -1,20 +1,20 @@
1
1
  module Rack
2
2
  class MiniProfiler
3
3
  class ClientSettings
4
-
4
+
5
5
  COOKIE_NAME = "__profilin"
6
6
 
7
7
  BACKTRACE_DEFAULT = nil
8
- BACKTRACE_FULL = 1
8
+ BACKTRACE_FULL = 1
9
9
  BACKTRACE_NONE = 2
10
10
 
11
11
  attr_accessor :disable_profiling
12
12
  attr_accessor :backtrace_level
13
13
 
14
-
14
+
15
15
  def initialize(env)
16
16
  request = ::Rack::Request.new(env)
17
- @cookie = request.cookies[COOKIE_NAME]
17
+ @cookie = request.cookies[COOKIE_NAME]
18
18
  if @cookie
19
19
  @cookie.split(",").map{|pair| pair.split("=")}.each do |k,v|
20
20
  @orig_disable_profiling = @disable_profiling = (v=='t') if k == "dp"
@@ -22,7 +22,7 @@ module Rack
22
22
  end
23
23
  end
24
24
 
25
- @backtrace_level = nil if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
25
+ @backtrace_level = nil if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
26
26
  @orig_backtrace_level = @backtrace_level
27
27
 
28
28
  end
@@ -30,7 +30,7 @@ module Rack
30
30
  def write!(headers)
31
31
  if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
32
32
  settings = {"p" => "t" }
33
- settings["dp"] = "t" if @disable_profiling
33
+ settings["dp"] = "t" if @disable_profiling
34
34
  settings["bt"] = @backtrace_level if @backtrace_level
35
35
  settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
36
36
  Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
@@ -45,19 +45,19 @@ module Rack
45
45
  !@cookie.nil?
46
46
  end
47
47
 
48
- def disable_profiling?
48
+ def disable_profiling?
49
49
  @disable_profiling
50
50
  end
51
51
 
52
- def backtrace_full?
52
+ def backtrace_full?
53
53
  @backtrace_level == BACKTRACE_FULL
54
54
  end
55
55
 
56
- def backtrace_default?
56
+ def backtrace_default?
57
57
  @backtrace_level == BACKTRACE_DEFAULT
58
58
  end
59
-
60
- def backtrace_none?
59
+
60
+ def backtrace_none?
61
61
  @backtrace_level == BACKTRACE_NONE
62
62
  end
63
63
  end
@@ -7,15 +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
- 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,
18
- :toggle_shortcut, :start_hidden, :backtrace_threshold_ms
15
+ attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores, :backtrace_includes, :backtrace_remove,
16
+ :backtrace_threshold_ms, :base_url_path, :enabled, :flamegraph_sample_rate, :logger, :position,
17
+ :pre_authorize_cb, :skip_paths, :skip_schema_queries, :start_hidden, :storage, :storage_failure,
18
+ :storage_instance, :storage_options, :toggle_shortcut, :user_provider
19
19
 
20
20
  # Deprecated options
21
21
  attr_accessor :use_existing_jquery
@@ -24,10 +24,10 @@ module Rack
24
24
  new.instance_eval {
25
25
  @auto_inject = true # automatically inject on every html page
26
26
  @base_url_path = "/mini-profiler-resources/"
27
-
27
+
28
28
  # called prior to rack chain, to ensure we are allowed to profile
29
- @pre_authorize_cb = lambda {|env| true}
30
-
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
31
31
  # called after rack chain, to ensure we are REALLY allowed to profile
32
32
  @position = 'left' # Where it is displayed
33
33
  @skip_schema_queries = false
@@ -37,16 +37,23 @@ module Rack
37
37
  @toggle_shortcut = 'Alt+P'
38
38
  @start_hidden = false
39
39
  @backtrace_threshold_ms = 0
40
+ @flamegraph_sample_rate = 0.5
41
+ @storage_failure = Proc.new do |exception|
42
+ if @logger
43
+ @logger.warn("MiniProfiler storage failure: #{exception.message}")
44
+ end
45
+ end
46
+ @enabled = true
40
47
  self
41
48
  }
42
49
  end
43
50
 
44
51
  def merge!(config)
45
52
  return unless config
46
- if Hash === config
53
+ if Hash === config
47
54
  config.each{|k,v| instance_variable_set "@#{k}",v}
48
- else
49
- self.class.attributes.each{ |k|
55
+ else
56
+ self.class.attributes.each{ |k|
50
57
  v = config.send k
51
58
  instance_variable_set "@#{k}", v if v
52
59
  }
@@ -1,6 +1,6 @@
1
1
  class Rack::MiniProfiler::Context
2
2
  attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
-
3
+
4
4
  def initialize(opts = {})
5
5
  opts["measure"] = true unless opts.key? "measure"
6
6
  opts.each do |k,v|
@@ -0,0 +1,181 @@
1
+ class Rack::MiniProfiler::GCProfiler
2
+
3
+ def initialize
4
+ @ignore = []
5
+ @ignore << @ignore.__id__
6
+ end
7
+
8
+ def object_space_stats
9
+ stats = {}
10
+ ids = {}
11
+
12
+ @ignore << stats.__id__
13
+ @ignore << ids.__id__
14
+
15
+ i=0
16
+ ObjectSpace.each_object { |o|
17
+ begin
18
+ i = stats[o.class] || 0
19
+ i += 1
20
+ stats[o.class] = i
21
+ ids[o.__id__] = o if Integer === o.__id__
22
+ rescue NoMethodError
23
+ # protect against BasicObject
24
+ end
25
+ }
26
+
27
+ @ignore.each do |id|
28
+ if ids.delete(id)
29
+ klass = ObjectSpace._id2ref(id).class
30
+ stats[klass] -= 1
31
+ end
32
+ end
33
+
34
+ result = {:stats => stats, :ids => ids}
35
+ @ignore << result.__id__
36
+
37
+ result
38
+ end
39
+
40
+ def diff_object_stats(before,after)
41
+ diff = {}
42
+ after.each do |k,v|
43
+ diff[k] = v - (before[k] || 0)
44
+ end
45
+ before.each do |k,v|
46
+ diff[k] = 0 - v unless after[k]
47
+ end
48
+
49
+ diff
50
+ end
51
+
52
+ def analyze_strings(ids_before,ids_after)
53
+ result = {}
54
+ ids_after.each do |id,_|
55
+ obj = ObjectSpace._id2ref(id)
56
+ if String === obj && !ids_before.include?(obj.object_id)
57
+ result[obj] ||= 0
58
+ result[obj] += 1
59
+ end
60
+ end
61
+ result
62
+ end
63
+
64
+ def analyze_growth(ids_before, ids_after)
65
+ new_objects = 0
66
+ memory_allocated = 0
67
+
68
+ ids_after.each do |id,_|
69
+ if !ids_before.include?(id) && obj=ObjectSpace._id2ref(id)
70
+ # this is going to be version specific (may change in 2.1)
71
+ size = ObjectSpace.memsize_of(obj)
72
+ memory_allocated += size
73
+ new_objects += 1
74
+ end
75
+ end
76
+
77
+ [new_objects, memory_allocated]
78
+ end
79
+
80
+ def analyze_initial_state(ids_before)
81
+ memory_allocated = 0
82
+ objects = 0
83
+
84
+ ids_before.each do |id,_|
85
+ if obj=ObjectSpace._id2ref(id)
86
+ # this is going to be version specific (may change in 2.1)
87
+ memory_allocated += ObjectSpace.memsize_of(obj)
88
+ objects += 1
89
+ end
90
+ end
91
+
92
+ [objects,memory_allocated]
93
+ end
94
+
95
+ def profile_gc_time(app,env)
96
+ body = []
97
+
98
+ begin
99
+ GC::Profiler.clear
100
+ prev_profiler_state = GC::Profiler.enabled?
101
+ prev_gc_state = GC.enable
102
+ GC::Profiler.enable
103
+ b = app.call(env)[2]
104
+ b.close if b.respond_to? :close
105
+ body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
106
+ body << GC::Profiler.result
107
+ ensure
108
+ prev_gc_state ? GC.disable : GC.enable
109
+ GC::Profiler.disable unless prev_profiler_state
110
+ end
111
+
112
+ return [200, {'Content-Type' => 'text/plain'}, body]
113
+ end
114
+
115
+ def profile_gc(app,env)
116
+
117
+ # for memsize_of
118
+ require 'objspace'
119
+
120
+ body = [];
121
+
122
+ stat_before,stat_after,diff,string_analysis,
123
+ new_objects, memory_allocated, stat, memory_before, objects_before = nil
124
+
125
+ # clean up before
126
+ GC.start
127
+ stat = GC.stat
128
+ prev_gc_state = GC.disable
129
+ stat_before = object_space_stats
130
+ b = app.call(env)[2]
131
+ b.close if b.respond_to? :close
132
+ stat_after = object_space_stats
133
+ # so we don't blow out on memory
134
+ prev_gc_state ? GC.disable : GC.enable
135
+
136
+ diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
137
+ string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
138
+ new_objects, memory_allocated = analyze_growth(stat_before[:ids], stat_after[:ids])
139
+ objects_before, memory_before = analyze_initial_state(stat_before[:ids])
140
+
141
+
142
+ body << "
143
+ Overview
144
+ ------------------------------------
145
+ Initial state: object count - #{objects_before} , memory allocated outside heap (bytes) #{memory_before}
146
+
147
+ GC Stats: #{stat.map{|k,v| "#{k} : #{v}" }.join(", ")}
148
+
149
+ New bytes allocated outside of Ruby heaps: #{memory_allocated}
150
+ New objects: #{new_objects}
151
+ "
152
+
153
+ body << "
154
+ ObjectSpace delta caused by request:
155
+ --------------------------------------------\n"
156
+ diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
157
+ body << "#{k} : #{v}\n" if v != 0
158
+ end
159
+
160
+ body << "\n
161
+ ObjectSpace stats:
162
+ -----------------\n"
163
+
164
+ stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
165
+ body << "#{k} : #{v}\n"
166
+ end
167
+
168
+
169
+ body << "\n
170
+ String stats:
171
+ ------------\n"
172
+
173
+ string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
174
+ body << "#{count} : #{string}\n"
175
+ end
176
+
177
+ return [200, {'Content-Type' => 'text/plain'}, body]
178
+ ensure
179
+ prev_gc_state ? GC.disable : GC.enable
180
+ end
181
+ end
@@ -43,16 +43,16 @@ module Rack
43
43
  def root
44
44
  @attributes['Root']
45
45
  end
46
-
46
+
47
47
  def to_json(*a)
48
48
  attribs = @attributes.merge(
49
- "Started" => '/Date(%d)/' % @attributes['Started'],
49
+ "Started" => '/Date(%d)/' % @attributes['Started'],
50
50
  "DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
51
51
  "CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
52
- )
52
+ )
53
53
  ::JSON.generate(attribs, :max_nesting => 100)
54
54
  end
55
55
  end
56
-
56
+
57
57
  end
58
58
  end
@@ -18,7 +18,8 @@ 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
+ # TODO
22
+ # require 'mini_profiler/gc_profiler_ruby_head' if Gem::Version.new('2.1.0') <= Gem::Version.new(RUBY_VERSION)
22
23
 
23
24
  module Rack
24
25
 
@@ -137,7 +138,9 @@ module Rack
137
138
 
138
139
  def serve_html(env)
139
140
  file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
141
+
140
142
  return serve_results(env) if file_name.eql?('results')
143
+
141
144
  full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
142
145
  return [404, {}, ["Not found"]] unless ::File.exists? full_path
143
146
  f = Rack::File.new nil
@@ -203,9 +206,10 @@ module Rack
203
206
 
204
207
  if query_string =~ /pp=enable/
205
208
  skip_it = false
209
+ config.enabled = true
206
210
  end
207
211
 
208
- if skip_it
212
+ if skip_it || !config.enabled
209
213
  status,headers,body = @app.call(env)
210
214
  client_settings.disable_profiling = true
211
215
  client_settings.write!(headers)
@@ -215,8 +219,18 @@ module Rack
215
219
  end
216
220
 
217
221
  if query_string =~ /pp=profile-gc/
222
+ current.measure = false if current
223
+
218
224
  if query_string =~ /pp=profile-gc-time/
219
225
  return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
226
+ elsif query_string =~ /pp=profile-gc-ruby-head/
227
+ result = StringIO.new
228
+ report = MemoryProfiler.report do
229
+ _,_,body = @app.call(env)
230
+ body.close if body.respond_to? :close
231
+ end
232
+ report.pretty_print(result)
233
+ return text_result(result.string)
220
234
  else
221
235
  return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
222
236
  end
@@ -224,6 +238,7 @@ module Rack
224
238
 
225
239
  MiniProfiler.create_current(env, @config)
226
240
  MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
241
+
227
242
  if query_string =~ /pp=normal-backtrace/
228
243
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
229
244
  elsif query_string =~ /pp=no-backtrace/
@@ -236,38 +251,21 @@ module Rack
236
251
  current.skip_backtrace = true
237
252
  end
238
253
 
239
- done_sampling = false
240
- quit_sampler = false
241
- backtraces = nil
242
-
243
- if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
244
- current.measure = false
245
- skip_frames = 0
246
- backtraces = []
247
- t = Thread.current
248
-
249
- Thread.new {
250
- # new in Ruby 2.0
251
- has_backtrace_locations = t.respond_to?(:backtrace_locations)
252
- begin
253
- i = 10000 # for sanity never grab more than 10k samples
254
- while i > 0
255
- break if done_sampling
256
- i -= 1
257
- backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
258
-
259
- # On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
260
- # with this fidelity analysis becomes very powerful
261
- sleep 0.0005
262
- end
263
- ensure
264
- quit_sampler = true
265
- end
266
- }
267
- end
254
+ flamegraph = nil
255
+
256
+ trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
257
+ status, headers, body, exceptions,trace = nil
268
258
 
269
- status, headers, body = nil
270
259
  start = Time.now
260
+
261
+ if trace_exceptions
262
+ exceptions = []
263
+ trace = TracePoint.new(:raise) do |tp|
264
+ exceptions << tp.raised_exception
265
+ end
266
+ trace.enable
267
+ end
268
+
271
269
  begin
272
270
 
273
271
  # Strip all the caching headers so we don't get 304s back
@@ -275,13 +273,31 @@ module Rack
275
273
  env['HTTP_IF_MODIFIED_SINCE'] = ''
276
274
  env['HTTP_IF_NONE_MATCH'] = ''
277
275
 
278
- status,headers,body = @app.call(env)
276
+ if query_string =~ /pp=flamegraph/
277
+ unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
278
+
279
+ flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
280
+ status,headers,body = @app.call(env)
281
+ else
282
+ # do not sully our profile with mini profiler timings
283
+ current.measure = false
284
+ match_data = query_string.match(/flamegraph_sample_rate=(?<rate>[\d\.]+)/)
285
+
286
+ if match_data && !match_data[:rate].to_f.zero?
287
+ sample_rate = match_data[:rate].to_f
288
+ else
289
+ sample_rate = config.flamegraph_sample_rate
290
+ end
291
+ flamegraph = Flamegraph.generate(nil, fidelity: sample_rate, embed_resources: query_string =~ /embed/) do
292
+ status,headers,body = @app.call(env)
293
+ end
294
+ end
295
+ else
296
+ status,headers,body = @app.call(env)
297
+ end
279
298
  client_settings.write!(headers)
280
299
  ensure
281
- if backtraces
282
- done_sampling = true
283
- sleep 0.001 until quit_sampler
284
- end
300
+ trace.disable if trace
285
301
  end
286
302
 
287
303
  skip_it = current.discard
@@ -298,6 +314,11 @@ module Rack
298
314
  return [status,headers,body] if skip_it
299
315
 
300
316
  # we must do this here, otherwise current[:discard] is not being properly treated
317
+ if trace_exceptions
318
+ body.close if body.respond_to? :close
319
+ return dump_exceptions exceptions
320
+ end
321
+
301
322
  if query_string =~ /pp=env/
302
323
  body.close if body.respond_to? :close
303
324
  return dump_env env
@@ -312,56 +333,65 @@ module Rack
312
333
  page_struct['User'] = user(env)
313
334
  page_struct['Root'].record_time((Time.now - start) * 1000)
314
335
 
315
- if backtraces
336
+ if flamegraph
316
337
  body.close if body.respond_to? :close
317
- if query_string =~ /pp=sample/
318
- return analyze(backtraces, page_struct)
319
- else
320
- return flame_graph(backtraces, page_struct)
321
- end
338
+ return self.flamegraph(flamegraph)
322
339
  end
323
340
 
324
341
 
325
- # no matter what it is, it should be unviewed, otherwise we will miss POST
326
- @storage.set_unviewed(page_struct['User'], page_struct['Id'])
327
- @storage.save(page_struct)
328
-
329
- content_type = headers['Content-Type']
330
- # inject headers, script
331
- if content_type && status == 200
332
-
333
- client_settings.write!(headers)
334
-
335
- # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
336
- # Rack::ETag has already inserted some nonesense in the chain
337
- headers.delete('ETag')
338
- headers.delete('Date')
339
- headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
342
+ begin
343
+ # no matter what it is, it should be unviewed, otherwise we will miss POST
344
+ @storage.set_unviewed(page_struct['User'], page_struct['Id'])
345
+ @storage.save(page_struct)
340
346
 
341
- # inject header
342
- if headers.is_a? Hash
343
- headers['X-MiniProfiler-Ids'] = ids_json(env)
347
+ # inject headers, script
348
+ if headers['Content-Type'] && status == 200
349
+ client_settings.write!(headers)
350
+ result = inject_profiler(env,status,headers,body)
351
+ return result if result
344
352
  end
345
-
346
- if current.inject_js && content_type =~ /text\/html/
347
- response = Rack::Response.new([], status, headers)
348
- script = self.get_profile_script(env)
349
-
350
- if String === body
351
- response.write inject(body,script)
352
- else
353
- body.each { |fragment| response.write inject(fragment, script) }
354
- end
355
- body.close if body.respond_to? :close
356
- return response.finish
353
+ rescue Exception => e
354
+ if @config.storage_failure != nil
355
+ @config.storage_failure.call(e)
357
356
  end
358
357
  end
359
358
 
360
359
  client_settings.write!(headers)
361
360
  [status, headers, body]
361
+
362
362
  ensure
363
363
  # Make sure this always happens
364
- current = nil
364
+ self.current = nil
365
+ end
366
+
367
+ def inject_profiler(env,status,headers,body)
368
+ # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
369
+ # Rack::ETag has already inserted some nonesense in the chain
370
+ content_type = headers['Content-Type']
371
+
372
+ headers.delete('ETag')
373
+ headers.delete('Date')
374
+ headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
375
+
376
+ # inject header
377
+ if headers.is_a? Hash
378
+ headers['X-MiniProfiler-Ids'] = ids_json(env)
379
+ end
380
+
381
+ if current.inject_js && content_type =~ /text\/html/
382
+ response = Rack::Response.new([], status, headers)
383
+ script = self.get_profile_script(env)
384
+
385
+ if String === body
386
+ response.write inject(body,script)
387
+ else
388
+ body.each { |fragment| response.write inject(fragment, script) }
389
+ end
390
+ body.close if body.respond_to? :close
391
+ response.finish
392
+ else
393
+ nil
394
+ end
365
395
  end
366
396
 
367
397
  def inject(fragment, script)
@@ -376,9 +406,9 @@ module Rack
376
406
  regex = /<\/html>/i
377
407
  close_tag = '</html>'
378
408
  else
379
- # implicit </body> and </html>. Just append the script.
409
+ # implicit </body> and </html>. Don't do anything.
380
410
 
381
- return fragment + script
411
+ return fragment
382
412
  end
383
413
 
384
414
  matches = fragment.scan(regex).length
@@ -401,8 +431,17 @@ module Rack
401
431
  end
402
432
  end
403
433
 
404
- def dump_env(env)
434
+ def dump_exceptions(exceptions)
405
435
  headers = {'Content-Type' => 'text/plain'}
436
+ body = "Exceptions (#{exceptions.length} raised during request)\n\n"
437
+ exceptions.each do |e|
438
+ body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
439
+ end
440
+
441
+ [200, headers, [body]]
442
+ end
443
+
444
+ def dump_env(env)
406
445
  body = "Rack Environment\n---------------\n"
407
446
  env.each do |k,v|
408
447
  body << "#{k}: #{v}\n"
@@ -413,11 +452,19 @@ module Rack
413
452
  body << "#{k}: #{v}\n"
414
453
  end
415
454
 
455
+ body << "\n\nRuby Version\n---------------\n"
456
+ body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n"
457
+
416
458
  body << "\n\nInternals\n---------------\n"
417
459
  body << "Storage Provider #{config.storage_instance}\n"
418
460
  body << "User #{user(env)}\n"
419
461
  body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
420
462
 
463
+ text_result(body)
464
+ end
465
+
466
+ def text_result(body)
467
+ headers = {'Content-Type' => 'text/plain'}
421
468
  [200, headers, [body]]
422
469
  end
423
470
 
@@ -431,73 +478,37 @@ module Rack
431
478
  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)
432
479
  pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
433
480
  pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
434
- pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
435
481
  pp=disable : disable profiling for this session
436
482
  pp=enable : enable profiling for this session (if previously disabled)
437
483
  pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
438
484
  pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
439
- pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
485
+ pp=profile-gc-ruby-head: requires the memory_profiler gem, new location based report
486
+ pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
487
+ pp=flamegraph&flamegraph_sample_rate=1: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
488
+ pp=flamegraph_embed: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem), embedded resources for use on an intranet.
489
+ pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
440
490
  "
441
491
 
442
492
  client_settings.write!(headers)
443
493
  [200, headers, [body]]
444
494
  end
445
495
 
446
- def flame_graph(traces, page_struct)
447
- graph = FlameGraph.new(traces)
448
- data = graph.graph_data
449
-
496
+ def flamegraph(graph)
450
497
  headers = {'Content-Type' => 'text/html'}
451
-
452
- body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
453
- body.gsub!("/*DATA*/", ::JSON.generate(data));
454
-
455
- [200, headers, [body]]
498
+ [200, headers, [graph]]
456
499
  end
457
500
 
458
- def analyze(traces, page_struct)
459
- headers = {'Content-Type' => 'text/plain'}
460
- body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
461
-
462
- seen = {}
463
- fulldump = ""
464
- traces.each do |trace|
465
- fulldump << "\n\n"
466
- distinct = {}
467
- trace.each do |frame|
468
- frame = frame.to_s unless String === frame
469
- unless distinct[frame]
470
- distinct[frame] = true
471
- seen[frame] ||= 0
472
- seen[frame] += 1
473
- end
474
- fulldump << frame << "\n"
475
- end
476
- end
477
-
478
- body << "\n\nStack Trace Analysis\n"
479
- seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
480
- if count > traces.count / 10
481
- body << "#{name} x #{count}\n"
482
- end
483
- end
484
-
485
- body << "\n\n\nRaw traces \n"
486
- body << fulldump
487
-
488
- [200, headers, [body]]
501
+ def ids(env)
502
+ # cap at 10 ids, otherwise there is a chance you can blow the header
503
+ ([current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]).uniq
489
504
  end
490
505
 
491
506
  def ids_json(env)
492
- # cap at 10 ids, otherwise there is a chance you can blow the header
493
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
494
- ::JSON.generate(ids.uniq)
507
+ ::JSON.generate(ids(env))
495
508
  end
496
509
 
497
510
  def ids_comma_separated(env)
498
- # cap at 10 ids, otherwise there is a chance you can blow the header
499
- ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
500
- ids.join(",")
511
+ ids(env).join(",")
501
512
  end
502
513
 
503
514
  # get_profile_script returns script to be injected inside current html page
@@ -507,26 +518,37 @@ module Rack
507
518
  # * you have disabled auto append behaviour throught :auto_inject => false flag
508
519
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
509
520
  def get_profile_script(env)
510
- ids = ids_comma_separated(env)
511
- path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
512
- version = MiniProfiler::VERSION
513
- position = @config.position
514
- showTrivial = false
515
- showChildren = false
516
- maxTracesToShow = 10
517
- showControls = false
518
- currentId = current.page_struct["Id"]
519
- authorized = true
520
- toggleShortcut = @config.toggle_shortcut
521
- startHidden = @config.start_hidden
521
+
522
+ settings = {
523
+ :path => "#{env['SCRIPT_NAME']}#{@config.base_url_path}",
524
+ :version => MiniProfiler::VERSION,
525
+ :position => @config.position,
526
+ :showTrivial => false,
527
+ :showChildren => false,
528
+ :maxTracesToShow => 10,
529
+ :showControls => false,
530
+ :authorized => true,
531
+ :toggleShortcut => @config.toggle_shortcut,
532
+ :startHidden => @config.start_hidden
533
+ }
534
+
535
+ if current && current.page_struct
536
+ settings[:ids] = ids_comma_separated(env)
537
+ settings[:currentId] = current.page_struct["Id"]
538
+ else
539
+ settings[:ids] = []
540
+ settings[:currentId] = ""
541
+ end
542
+
522
543
  # TODO : cache this snippet
523
544
  script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
524
545
  # replace the variables
525
- [:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
526
- regex = Regexp.new("\\{#{v.to_s}\\}")
527
- script.gsub!(regex, eval(v.to_s).to_s)
546
+ settings.each do |k,v|
547
+ regex = Regexp.new("\\{#{k.to_s}\\}")
548
+ script.gsub!(regex, v.to_s)
528
549
  end
529
- current.inject_js = false
550
+
551
+ current.inject_js = false if current
530
552
  script
531
553
  end
532
554