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.
- checksums.yaml +4 -4
- data/{Ruby/CHANGELOG → CHANGELOG} +33 -0
- data/{Ruby/README.md → README.md} +51 -27
- data/{Ruby/lib → lib}/html/includes.css +0 -0
- data/{Ruby/lib → lib}/html/includes.js +90 -75
- data/{Ruby/lib → lib}/html/includes.less +0 -0
- data/{Ruby/lib → lib}/html/includes.tmpl +3 -1
- data/{Ruby/lib → lib}/html/jquery.1.7.1.js +0 -0
- data/{Ruby/lib → lib}/html/jquery.tmpl.js +0 -0
- data/{Ruby/lib → lib}/html/list.css +2 -2
- data/{Ruby/lib → lib}/html/list.js +1 -1
- data/{Ruby/lib → lib}/html/list.tmpl +2 -2
- data/lib/html/profile_handler.js +1 -0
- data/{Ruby/lib → lib}/html/share.html +2 -2
- data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +11 -11
- data/{Ruby/lib → lib}/mini_profiler/client_timer_struct.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/config.rb +18 -11
- data/{Ruby/lib → lib}/mini_profiler/context.rb +1 -1
- data/{Ruby/lib → lib}/mini_profiler/custom_timer_struct.rb +0 -0
- data/lib/mini_profiler/gc_profiler.rb +181 -0
- data/{Ruby/lib → lib}/mini_profiler/page_timer_struct.rb +4 -4
- data/{Ruby/lib → lib}/mini_profiler/profiler.rb +163 -141
- data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +31 -11
- data/{Ruby/lib → lib}/mini_profiler/request_timer_struct.rb +5 -5
- data/{Ruby/lib → lib}/mini_profiler/sql_timer_struct.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +4 -3
- data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/memory_store.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +3 -3
- data/{Ruby/lib → lib}/mini_profiler/timer_struct.rb +0 -0
- data/lib/mini_profiler/version.rb +5 -0
- data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +6 -2
- data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
- data/{Ruby/lib → lib}/patches/sql_patches.rb +6 -1
- data/{Ruby/lib → lib}/rack-mini-profiler.rb +0 -0
- data/rack-mini-profiler.gemspec +6 -5
- metadata +45 -46
- data/Ruby/lib/html/flamegraph.html +0 -351
- data/Ruby/lib/html/profile_handler.js +0 -1
- data/Ruby/lib/mini_profiler/flame_graph.rb +0 -54
- data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
- data/Ruby/lib/mini_profiler/version.rb +0 -5
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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
|
-
|
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;}
|
@@ -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
|
-
|
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
|
File without changes
|
@@ -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, :
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
}
|
File without changes
|
@@ -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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
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
|
336
|
+
if flamegraph
|
316
337
|
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
|
338
|
+
return self.flamegraph(flamegraph)
|
322
339
|
end
|
323
340
|
|
324
341
|
|
325
|
-
|
326
|
-
|
327
|
-
|
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
|
342
|
-
if headers
|
343
|
-
headers
|
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
|
347
|
-
|
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>.
|
409
|
+
# implicit </body> and </html>. Don't do anything.
|
380
410
|
|
381
|
-
return fragment
|
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
|
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=
|
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
|
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
|
459
|
-
|
460
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
-
|
526
|
-
regex = Regexp.new("\\{#{
|
527
|
-
script.gsub!(regex,
|
546
|
+
settings.each do |k,v|
|
547
|
+
regex = Regexp.new("\\{#{k.to_s}\\}")
|
548
|
+
script.gsub!(regex, v.to_s)
|
528
549
|
end
|
529
|
-
|
550
|
+
|
551
|
+
current.inject_js = false if current
|
530
552
|
script
|
531
553
|
end
|
532
554
|
|