rack-mini-profiler 0.1.26 → 0.1.31
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.
- checksums.yaml +4 -4
- data/Ruby/CHANGELOG +28 -2
- data/Ruby/README.md +11 -0
- data/Ruby/lib/html/includes.js +82 -67
- data/Ruby/lib/html/includes.tmpl +2 -0
- data/Ruby/lib/mini_profiler/client_settings.rb +11 -11
- data/Ruby/lib/mini_profiler/config.rb +10 -9
- data/Ruby/lib/mini_profiler/context.rb +1 -1
- data/Ruby/lib/mini_profiler/gc_profiler_ruby_head.rb +40 -0
- data/Ruby/lib/mini_profiler/page_timer_struct.rb +4 -4
- data/Ruby/lib/mini_profiler/profiler.rb +98 -121
- data/Ruby/lib/mini_profiler/profiling_methods.rb +31 -11
- data/Ruby/lib/mini_profiler/request_timer_struct.rb +5 -5
- data/Ruby/lib/mini_profiler/sql_timer_struct.rb +1 -1
- data/Ruby/lib/mini_profiler/storage/abstract_store.rb +4 -3
- data/Ruby/lib/mini_profiler/storage/redis_store.rb +3 -3
- data/Ruby/lib/mini_profiler/version.rb +1 -1
- data/Ruby/lib/patches/sql_patches.rb +6 -1
- data/rack-mini-profiler.gemspec +2 -1
- metadata +6 -6
- data/Ruby/lib/html/flamegraph.html +0 -325
- data/Ruby/lib/mini_profiler/flame_graph.rb +0 -54
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'objspace'
|
2
|
+
|
3
|
+
class Rack::MiniProfiler::GCProfilerRubyHead
|
4
|
+
def profile_rack(app,env)
|
5
|
+
end
|
6
|
+
|
7
|
+
def profile(&block)
|
8
|
+
GC.start
|
9
|
+
GC.disable
|
10
|
+
|
11
|
+
items = []
|
12
|
+
objs = []
|
13
|
+
|
14
|
+
ObjectSpace.trace_object_allocations do
|
15
|
+
block.call
|
16
|
+
|
17
|
+
ObjectSpace.each_object do |o|
|
18
|
+
objs << o
|
19
|
+
end
|
20
|
+
|
21
|
+
objs.each do |o|
|
22
|
+
g = ObjectSpace.allocation_generation(o)
|
23
|
+
if g
|
24
|
+
l = ObjectSpace.allocation_sourceline(o)
|
25
|
+
f = ObjectSpace.allocation_sourcefile(o)
|
26
|
+
c = ObjectSpace.allocation_class_path(o)
|
27
|
+
m = ObjectSpace.allocation_method_id(o)
|
28
|
+
items << "Allocated #{c} in #{m} #{f}:#{l}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
items.group_by{|x| x}.sort{|a,b| b[1].length <=> a[1].length}.each do |row, group|
|
34
|
+
puts "#{row} x #{group.length}"
|
35
|
+
end
|
36
|
+
|
37
|
+
GC.enable
|
38
|
+
profile_allocations(name, &block)
|
39
|
+
end
|
40
|
+
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
|
-
|
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
|
|
@@ -123,7 +124,7 @@ module Rack
|
|
123
124
|
|
124
125
|
# Otherwise give the HTML back
|
125
126
|
html = MiniProfiler.share_template.dup
|
126
|
-
html.gsub!(/\{path\}/, @config.base_url_path)
|
127
|
+
html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
|
127
128
|
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
128
129
|
html.gsub!(/\{json\}/, result_json)
|
129
130
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
@@ -224,6 +225,7 @@ module Rack
|
|
224
225
|
|
225
226
|
MiniProfiler.create_current(env, @config)
|
226
227
|
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
228
|
+
|
227
229
|
if query_string =~ /pp=normal-backtrace/
|
228
230
|
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
229
231
|
elsif query_string =~ /pp=no-backtrace/
|
@@ -236,52 +238,48 @@ module Rack
|
|
236
238
|
current.skip_backtrace = true
|
237
239
|
end
|
238
240
|
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
241
|
+
flamegraph = nil
|
242
|
+
|
243
|
+
trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
|
244
|
+
status, headers, body, exceptions,trace = nil
|
268
245
|
|
269
|
-
status, headers, body = nil
|
270
246
|
start = Time.now
|
247
|
+
|
248
|
+
if trace_exceptions
|
249
|
+
exceptions = []
|
250
|
+
trace = TracePoint.new(:raise) do |tp|
|
251
|
+
exceptions << tp.raised_exception
|
252
|
+
end
|
253
|
+
trace.enable
|
254
|
+
end
|
255
|
+
|
271
256
|
begin
|
272
257
|
|
273
258
|
# Strip all the caching headers so we don't get 304s back
|
274
259
|
# This solves a very annoying bug where rack mini profiler never shows up
|
275
|
-
env['HTTP_IF_MODIFIED_SINCE'] =
|
276
|
-
env['HTTP_IF_NONE_MATCH'] =
|
260
|
+
env['HTTP_IF_MODIFIED_SINCE'] = ''
|
261
|
+
env['HTTP_IF_NONE_MATCH'] = ''
|
277
262
|
|
278
|
-
|
263
|
+
if query_string =~ /pp=flamegraph/
|
264
|
+
unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
|
265
|
+
|
266
|
+
flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
|
267
|
+
status,headers,body = @app.call(env)
|
268
|
+
else
|
269
|
+
# do not sully our profile with mini profiler timings
|
270
|
+
current.measure = false
|
271
|
+
# first param is the path
|
272
|
+
# 0.5 means attempt to collect a sample each 0.5 secs
|
273
|
+
flamegraph = Flamegraph.generate(nil, fidelity: 0.5) do
|
274
|
+
status,headers,body = @app.call(env)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
else
|
278
|
+
status,headers,body = @app.call(env)
|
279
|
+
end
|
279
280
|
client_settings.write!(headers)
|
280
281
|
ensure
|
281
|
-
if
|
282
|
-
done_sampling = true
|
283
|
-
sleep 0.001 until quit_sampler
|
284
|
-
end
|
282
|
+
trace.disable if trace
|
285
283
|
end
|
286
284
|
|
287
285
|
skip_it = current.discard
|
@@ -298,6 +296,11 @@ module Rack
|
|
298
296
|
return [status,headers,body] if skip_it
|
299
297
|
|
300
298
|
# we must do this here, otherwise current[:discard] is not being properly treated
|
299
|
+
if trace_exceptions
|
300
|
+
body.close if body.respond_to? :close
|
301
|
+
return dump_exceptions exceptions
|
302
|
+
end
|
303
|
+
|
301
304
|
if query_string =~ /pp=env/
|
302
305
|
body.close if body.respond_to? :close
|
303
306
|
return dump_env env
|
@@ -312,13 +315,9 @@ module Rack
|
|
312
315
|
page_struct['User'] = user(env)
|
313
316
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
314
317
|
|
315
|
-
if
|
318
|
+
if flamegraph
|
316
319
|
body.close if body.respond_to? :close
|
317
|
-
|
318
|
-
return analyze(backtraces, page_struct)
|
319
|
-
else
|
320
|
-
return flame_graph(backtraces, page_struct)
|
321
|
-
end
|
320
|
+
return self.flamegraph(flamegraph)
|
322
321
|
end
|
323
322
|
|
324
323
|
|
@@ -327,46 +326,51 @@ module Rack
|
|
327
326
|
@storage.save(page_struct)
|
328
327
|
|
329
328
|
# inject headers, script
|
330
|
-
if status == 200
|
331
|
-
|
329
|
+
if headers['Content-Type'] && status == 200
|
332
330
|
client_settings.write!(headers)
|
333
331
|
|
334
|
-
|
335
|
-
|
336
|
-
headers.delete('ETag')
|
337
|
-
headers.delete('Date')
|
338
|
-
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
339
|
-
|
340
|
-
# inject header
|
341
|
-
if headers.is_a? Hash
|
342
|
-
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
343
|
-
end
|
344
|
-
|
345
|
-
# inject script
|
346
|
-
if current.inject_js \
|
347
|
-
&& headers.has_key?('Content-Type') \
|
348
|
-
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
349
|
-
|
350
|
-
response = Rack::Response.new([], status, headers)
|
351
|
-
script = self.get_profile_script(env)
|
352
|
-
|
353
|
-
if String === body
|
354
|
-
response.write inject(body,script)
|
355
|
-
else
|
356
|
-
body.each { |fragment| response.write inject(fragment, script) }
|
357
|
-
end
|
358
|
-
body.close if body.respond_to? :close
|
359
|
-
return response.finish
|
360
|
-
end
|
332
|
+
result = inject_profiler(env,status,headers,body)
|
333
|
+
return result if result
|
361
334
|
end
|
362
335
|
|
363
336
|
client_settings.write!(headers)
|
364
337
|
[status, headers, body]
|
338
|
+
|
365
339
|
ensure
|
366
340
|
# Make sure this always happens
|
367
341
|
current = nil
|
368
342
|
end
|
369
343
|
|
344
|
+
def inject_profiler(env,status,headers,body)
|
345
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
346
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
347
|
+
content_type = headers['Content-Type']
|
348
|
+
|
349
|
+
headers.delete('ETag')
|
350
|
+
headers.delete('Date')
|
351
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
352
|
+
|
353
|
+
# inject header
|
354
|
+
if headers.is_a? Hash
|
355
|
+
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
356
|
+
end
|
357
|
+
|
358
|
+
if current.inject_js && content_type =~ /text\/html/
|
359
|
+
response = Rack::Response.new([], status, headers)
|
360
|
+
script = self.get_profile_script(env)
|
361
|
+
|
362
|
+
if String === body
|
363
|
+
response.write inject(body,script)
|
364
|
+
else
|
365
|
+
body.each { |fragment| response.write inject(fragment, script) }
|
366
|
+
end
|
367
|
+
body.close if body.respond_to? :close
|
368
|
+
response.finish
|
369
|
+
else
|
370
|
+
nil
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
370
374
|
def inject(fragment, script)
|
371
375
|
if fragment.match(/<\/body>/i)
|
372
376
|
# explicit </body>
|
@@ -388,9 +392,9 @@ module Rack
|
|
388
392
|
index = 1
|
389
393
|
fragment.gsub(regex) do
|
390
394
|
# though malformed there is an edge case where /body exists earlier in the html, work around
|
391
|
-
if index < matches
|
395
|
+
if index < matches
|
392
396
|
index += 1
|
393
|
-
close_tag
|
397
|
+
close_tag
|
394
398
|
else
|
395
399
|
|
396
400
|
# if for whatever crazy reason we dont get a utf string,
|
@@ -404,6 +408,16 @@ module Rack
|
|
404
408
|
end
|
405
409
|
end
|
406
410
|
|
411
|
+
def dump_exceptions(exceptions)
|
412
|
+
headers = {'Content-Type' => 'text/plain'}
|
413
|
+
body = "Exceptions (#{exceptions.length} raised during request)\n\n"
|
414
|
+
exceptions.each do |e|
|
415
|
+
body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
|
416
|
+
end
|
417
|
+
|
418
|
+
[200, headers, [body]]
|
419
|
+
end
|
420
|
+
|
407
421
|
def dump_env(env)
|
408
422
|
headers = {'Content-Type' => 'text/plain'}
|
409
423
|
body = "Rack Environment\n---------------\n"
|
@@ -416,6 +430,9 @@ module Rack
|
|
416
430
|
body << "#{k}: #{v}\n"
|
417
431
|
end
|
418
432
|
|
433
|
+
body << "\n\nRuby Version\n---------------\n"
|
434
|
+
body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n"
|
435
|
+
|
419
436
|
body << "\n\nInternals\n---------------\n"
|
420
437
|
body << "Storage Provider #{config.storage_instance}\n"
|
421
438
|
body << "User #{user(env)}\n"
|
@@ -434,61 +451,21 @@ module Rack
|
|
434
451
|
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)
|
435
452
|
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
|
436
453
|
pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
|
437
|
-
pp=sample : sample stack traces and return a report isolating heavy usage (works best on Ruby 2.0)
|
438
454
|
pp=disable : disable profiling for this session
|
439
455
|
pp=enable : enable profiling for this session (if previously disabled)
|
440
456
|
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
441
457
|
pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
|
442
|
-
pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity.
|
458
|
+
pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
|
459
|
+
pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
|
443
460
|
"
|
444
461
|
|
445
462
|
client_settings.write!(headers)
|
446
463
|
[200, headers, [body]]
|
447
464
|
end
|
448
465
|
|
449
|
-
def
|
450
|
-
graph = FlameGraph.new(traces)
|
451
|
-
data = graph.graph_data
|
452
|
-
|
466
|
+
def flamegraph(graph)
|
453
467
|
headers = {'Content-Type' => 'text/html'}
|
454
|
-
|
455
|
-
body = IO.read(::File.expand_path('../html/flamegraph.html', ::File.dirname(__FILE__)))
|
456
|
-
body.gsub!("/*DATA*/", ::JSON.generate(data));
|
457
|
-
|
458
|
-
[200, headers, [body]]
|
459
|
-
end
|
460
|
-
|
461
|
-
def analyze(traces, page_struct)
|
462
|
-
headers = {'Content-Type' => 'text/plain'}
|
463
|
-
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
464
|
-
|
465
|
-
seen = {}
|
466
|
-
fulldump = ""
|
467
|
-
traces.each do |trace|
|
468
|
-
fulldump << "\n\n"
|
469
|
-
distinct = {}
|
470
|
-
trace.each do |frame|
|
471
|
-
frame = frame.to_s unless String === frame
|
472
|
-
unless distinct[frame]
|
473
|
-
distinct[frame] = true
|
474
|
-
seen[frame] ||= 0
|
475
|
-
seen[frame] += 1
|
476
|
-
end
|
477
|
-
fulldump << frame << "\n"
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
body << "\n\nStack Trace Analysis\n"
|
482
|
-
seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
|
483
|
-
if count > traces.count / 10
|
484
|
-
body << "#{name} x #{count}\n"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
body << "\n\n\nRaw traces \n"
|
489
|
-
body << fulldump
|
490
|
-
|
491
|
-
[200, headers, [body]]
|
468
|
+
[200, headers, [graph]]
|
492
469
|
end
|
493
470
|
|
494
471
|
def ids_json(env)
|
@@ -511,7 +488,7 @@ module Rack
|
|
511
488
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
512
489
|
def get_profile_script(env)
|
513
490
|
ids = ids_comma_separated(env)
|
514
|
-
path = @config.base_url_path
|
491
|
+
path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
|
515
492
|
version = MiniProfiler::VERSION
|
516
493
|
position = @config.position
|
517
494
|
showTrivial = false
|
@@ -55,8 +55,16 @@ module Rack
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def
|
59
|
-
|
58
|
+
def counter_method(klass, method, &blk)
|
59
|
+
self.profile_method(klass, method, :counter, &blk)
|
60
|
+
end
|
61
|
+
|
62
|
+
def uncounter_method(klass, method)
|
63
|
+
self.unprofile_method(klass, method)
|
64
|
+
end
|
65
|
+
|
66
|
+
def profile_method(klass, method, type = :profile, &blk)
|
67
|
+
default_name = type==:counter ? method.to_s : klass.to_s + " " + method.to_s
|
60
68
|
clean = clean_method_name(method)
|
61
69
|
|
62
70
|
with_profiling = ("#{clean}_with_mini_profiler").intern
|
@@ -81,22 +89,34 @@ module Rack
|
|
81
89
|
end
|
82
90
|
end
|
83
91
|
|
84
|
-
parent_timer = Rack::MiniProfiler.current.current_timer
|
85
|
-
page_struct = Rack::MiniProfiler.current.page_struct
|
86
92
|
result = nil
|
93
|
+
parent_timer = Rack::MiniProfiler.current.current_timer
|
87
94
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
95
|
+
if type == :counter
|
96
|
+
start = Time.now
|
97
|
+
begin
|
98
|
+
result = self.send without_profiling, *args, &orig
|
99
|
+
ensure
|
100
|
+
duration_ms = (Time.now - start).to_f * 1000
|
101
|
+
parent_timer.add_custom(name, duration_ms, Rack::MiniProfiler.current.page_struct )
|
102
|
+
end
|
103
|
+
else
|
104
|
+
page_struct = Rack::MiniProfiler.current.page_struct
|
105
|
+
|
106
|
+
Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
|
107
|
+
begin
|
108
|
+
result = self.send without_profiling, *args, &orig
|
109
|
+
ensure
|
110
|
+
current_timer.record_time
|
111
|
+
Rack::MiniProfiler.current.current_timer = parent_timer
|
112
|
+
end
|
94
113
|
end
|
114
|
+
|
95
115
|
result
|
96
116
|
end
|
97
117
|
klass.send :alias_method, method, with_profiling
|
98
118
|
end
|
99
|
-
|
119
|
+
|
100
120
|
# Add a custom timing. These are displayed similar to SQL/query time in
|
101
121
|
# columns expanding to the right.
|
102
122
|
#
|