rack-mini-profiler 0.1.21 → 0.1.26
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 +7 -0
- data/Ruby/CHANGELOG +135 -0
- data/{README.md → Ruby/README.md} +40 -9
- data/Ruby/lib/html/flamegraph.html +325 -0
- data/Ruby/lib/html/includes.css +451 -0
- data/{lib → Ruby/lib}/html/includes.js +135 -24
- data/{lib → Ruby/lib}/html/includes.less +38 -35
- data/{lib → Ruby/lib}/html/includes.tmpl +40 -15
- data/{lib → Ruby/lib}/html/jquery.1.7.1.js +1 -1
- data/{lib → Ruby/lib}/html/jquery.tmpl.js +1 -1
- data/{lib → Ruby/lib}/html/list.css +0 -0
- data/{lib → Ruby/lib}/html/list.js +7 -6
- data/{lib → Ruby/lib}/html/list.tmpl +0 -0
- data/Ruby/lib/html/profile_handler.js +1 -0
- data/{lib → Ruby/lib}/html/share.html +0 -0
- data/{lib → Ruby/lib}/mini_profiler/client_settings.rb +0 -0
- data/{lib → Ruby/lib}/mini_profiler/client_timer_struct.rb +1 -1
- data/{lib → Ruby/lib}/mini_profiler/config.rb +57 -52
- data/{lib → Ruby/lib}/mini_profiler/context.rb +11 -10
- data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
- data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
- data/{lib → Ruby/lib}/mini_profiler/gc_profiler.rb +35 -12
- data/{lib → Ruby/lib}/mini_profiler/page_timer_struct.rb +7 -2
- data/{lib → Ruby/lib}/mini_profiler/profiler.rb +209 -144
- data/{lib → Ruby/lib}/mini_profiler/profiling_methods.rb +131 -108
- data/{lib → Ruby/lib}/mini_profiler/request_timer_struct.rb +20 -1
- data/{lib → Ruby/lib}/mini_profiler/sql_timer_struct.rb +0 -0
- data/{lib → Ruby/lib}/mini_profiler/storage/abstract_store.rb +31 -27
- data/{lib → Ruby/lib}/mini_profiler/storage/file_store.rb +111 -109
- data/{lib → Ruby/lib}/mini_profiler/storage/memcache_store.rb +11 -9
- data/{lib → Ruby/lib}/mini_profiler/storage/memory_store.rb +65 -63
- data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
- data/{lib → Ruby/lib}/mini_profiler/timer_struct.rb +0 -0
- data/Ruby/lib/mini_profiler/version.rb +5 -0
- data/Ruby/lib/mini_profiler_rails/railtie.rb +89 -0
- data/Ruby/lib/patches/net_patches.rb +14 -0
- data/{lib → Ruby/lib}/patches/sql_patches.rb +89 -48
- data/{lib → Ruby/lib}/rack-mini-profiler.rb +1 -0
- data/rack-mini-profiler.gemspec +6 -4
- metadata +53 -64
- data/CHANGELOG +0 -99
- data/lib/html/includes.css +0 -75
- data/lib/html/profile_handler.js +0 -62
- data/lib/mini_profiler/storage/redis_store.rb +0 -44
- data/lib/mini_profiler_rails/railtie.rb +0 -85
@@ -3,10 +3,16 @@ class Rack::MiniProfiler::GCProfiler
|
|
3
3
|
def object_space_stats
|
4
4
|
stats = {}
|
5
5
|
ids = Set.new
|
6
|
+
i=0
|
6
7
|
ObjectSpace.each_object { |o|
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
10
16
|
}
|
11
17
|
{:stats => stats, :ids => ids}
|
12
18
|
end
|
@@ -35,24 +41,42 @@ class Rack::MiniProfiler::GCProfiler
|
|
35
41
|
result
|
36
42
|
end
|
37
43
|
|
38
|
-
def
|
39
|
-
|
40
|
-
body = [];
|
44
|
+
def profile_gc_time(app,env)
|
45
|
+
body = []
|
41
46
|
|
42
|
-
stat_after = nil
|
43
|
-
stat_before = object_space_stats
|
44
47
|
begin
|
45
48
|
GC::Profiler.clear
|
46
49
|
GC::Profiler.enable
|
47
50
|
b = app.call(env)[2]
|
48
51
|
b.close if b.respond_to? :close
|
49
|
-
|
52
|
+
body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
|
50
53
|
body << GC::Profiler.result
|
51
54
|
ensure
|
55
|
+
GC.enable
|
52
56
|
GC::Profiler.disable
|
53
57
|
end
|
54
58
|
|
55
|
-
|
59
|
+
return [200, {'Content-Type' => 'text/plain'}, body]
|
60
|
+
end
|
61
|
+
|
62
|
+
def profile_gc(app,env)
|
63
|
+
|
64
|
+
body = [];
|
65
|
+
|
66
|
+
stat_before,stat_after,diff,string_analysis = nil
|
67
|
+
begin
|
68
|
+
GC.disable
|
69
|
+
stat_before = object_space_stats
|
70
|
+
b = app.call(env)[2]
|
71
|
+
b.close if b.respond_to? :close
|
72
|
+
stat_after = object_space_stats
|
73
|
+
|
74
|
+
diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
|
75
|
+
string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
|
76
|
+
ensure
|
77
|
+
GC.enable
|
78
|
+
end
|
79
|
+
|
56
80
|
|
57
81
|
body << "
|
58
82
|
ObjectSpace delta caused by request:
|
@@ -69,13 +93,12 @@ ObjectSpace stats:
|
|
69
93
|
body << "#{k} : #{v}\n"
|
70
94
|
end
|
71
95
|
|
72
|
-
r = analyze_strings(stat_before[:ids], stat_after[:ids])
|
73
96
|
|
74
97
|
body << "\n
|
75
98
|
String stats:
|
76
99
|
------------\n"
|
77
100
|
|
78
|
-
|
101
|
+
string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
|
79
102
|
body << "#{count} : #{string}\n"
|
80
103
|
end
|
81
104
|
|
@@ -7,6 +7,7 @@ module Rack
|
|
7
7
|
# Root: RequestTimer
|
8
8
|
# :has_many RequestTimer children
|
9
9
|
# :has_many SqlTimer children
|
10
|
+
# :has_many CustomTimer children
|
10
11
|
class PageTimerStruct < TimerStruct
|
11
12
|
def initialize(env)
|
12
13
|
super("Id" => MiniProfiler.generate_id,
|
@@ -27,7 +28,10 @@ module Rack
|
|
27
28
|
"HasDuplicateSqlTimings" => false,
|
28
29
|
"ExecutedReaders" => 0,
|
29
30
|
"ExecutedScalars" => 0,
|
30
|
-
"ExecutedNonQueries" => 0
|
31
|
+
"ExecutedNonQueries" => 0,
|
32
|
+
"CustomTimingNames" => [],
|
33
|
+
"CustomTimingStats" => {}
|
34
|
+
)
|
31
35
|
name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
32
36
|
self['Root'] = RequestTimerStruct.createRoot(name, self)
|
33
37
|
end
|
@@ -43,7 +47,8 @@ module Rack
|
|
43
47
|
def to_json(*a)
|
44
48
|
attribs = @attributes.merge(
|
45
49
|
"Started" => '/Date(%d)/' % @attributes['Started'],
|
46
|
-
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
|
50
|
+
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
|
51
|
+
"CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
|
47
52
|
)
|
48
53
|
::JSON.generate(attribs, :max_nesting => 100)
|
49
54
|
end
|
@@ -2,8 +2,10 @@ require 'json'
|
|
2
2
|
require 'timeout'
|
3
3
|
require 'thread'
|
4
4
|
|
5
|
+
require 'mini_profiler/version'
|
5
6
|
require 'mini_profiler/page_timer_struct'
|
6
7
|
require 'mini_profiler/sql_timer_struct'
|
8
|
+
require 'mini_profiler/custom_timer_struct'
|
7
9
|
require 'mini_profiler/client_timer_struct'
|
8
10
|
require 'mini_profiler/request_timer_struct'
|
9
11
|
require 'mini_profiler/storage/abstract_store'
|
@@ -16,15 +18,14 @@ require 'mini_profiler/profiling_methods'
|
|
16
18
|
require 'mini_profiler/context'
|
17
19
|
require 'mini_profiler/client_settings'
|
18
20
|
require 'mini_profiler/gc_profiler'
|
21
|
+
require 'mini_profiler/flame_graph'
|
19
22
|
|
20
23
|
module Rack
|
21
24
|
|
22
|
-
|
25
|
+
class MiniProfiler
|
23
26
|
|
24
|
-
|
27
|
+
class << self
|
25
28
|
|
26
|
-
class << self
|
27
|
-
|
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
|
@@ -78,82 +79,83 @@ module Rack
|
|
78
79
|
def request_authorized?
|
79
80
|
Thread.current[:mp_authorized]
|
80
81
|
end
|
82
|
+
|
81
83
|
end
|
82
84
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
85
|
+
#
|
86
|
+
# options:
|
87
|
+
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
88
|
+
def initialize(app, config = nil)
|
87
89
|
MiniProfiler.config.merge!(config)
|
88
|
-
@config = MiniProfiler.config
|
89
|
-
|
90
|
-
|
90
|
+
@config = MiniProfiler.config
|
91
|
+
@app = app
|
92
|
+
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
91
93
|
unless @config.storage_instance
|
92
94
|
@config.storage_instance = @config.storage.new(@config.storage_options)
|
93
95
|
end
|
94
|
-
@storage = @config.storage_instance
|
95
|
-
|
96
|
-
|
96
|
+
@storage = @config.storage_instance
|
97
|
+
end
|
98
|
+
|
97
99
|
def user(env)
|
98
100
|
@config.user_provider.call(env)
|
99
101
|
end
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
+
def serve_results(env)
|
104
|
+
request = Rack::Request.new(env)
|
103
105
|
id = request['id']
|
104
|
-
|
106
|
+
page_struct = @storage.load(id)
|
105
107
|
unless page_struct
|
106
|
-
@storage.set_viewed(user(env), id)
|
107
|
-
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)}"]]
|
108
110
|
end
|
109
|
-
|
111
|
+
unless page_struct['HasUserViewed']
|
110
112
|
page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
|
111
|
-
|
112
|
-
@storage.save(page_struct)
|
113
|
-
@storage.set_viewed(user(env), id)
|
114
|
-
|
113
|
+
page_struct['HasUserViewed'] = true
|
114
|
+
@storage.save(page_struct)
|
115
|
+
@storage.set_viewed(user(env), id)
|
116
|
+
end
|
115
117
|
|
116
118
|
result_json = page_struct.to_json
|
117
119
|
# If we're an XMLHttpRequest, serve up the contents as JSON
|
118
120
|
if request.xhr?
|
119
|
-
|
121
|
+
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
120
122
|
else
|
121
123
|
|
122
124
|
# Otherwise give the HTML back
|
123
|
-
html = MiniProfiler.share_template.dup
|
124
|
-
html.gsub!(/\{path\}/, @config.base_url_path)
|
125
|
-
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
125
|
+
html = MiniProfiler.share_template.dup
|
126
|
+
html.gsub!(/\{path\}/, @config.base_url_path)
|
127
|
+
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
126
128
|
html.gsub!(/\{json\}/, result_json)
|
127
129
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
128
130
|
html.gsub!(/\{name\}/, page_struct['Name'])
|
129
|
-
html.gsub!(/\{duration\}/, page_struct.duration_ms
|
130
|
-
|
131
|
+
html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
|
132
|
+
|
131
133
|
[200, {'Content-Type' => 'text/html'}, [html]]
|
132
134
|
end
|
133
135
|
|
134
|
-
|
136
|
+
end
|
135
137
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
143
145
|
|
144
|
-
begin
|
146
|
+
begin
|
145
147
|
f.cache_control = "max-age:86400"
|
146
148
|
f.serving env
|
147
149
|
rescue
|
148
|
-
# old versions of rack have a different api
|
150
|
+
# old versions of rack have a different api
|
149
151
|
status, headers, body = f.serving
|
150
152
|
headers.merge! 'Cache-Control' => "max-age:86400"
|
151
153
|
[status, headers, body]
|
152
154
|
end
|
153
155
|
|
154
|
-
|
156
|
+
end
|
157
|
+
|
155
158
|
|
156
|
-
|
157
159
|
def current
|
158
160
|
MiniProfiler.current
|
159
161
|
end
|
@@ -168,7 +170,7 @@ module Rack
|
|
168
170
|
end
|
169
171
|
|
170
172
|
|
171
|
-
|
173
|
+
def call(env)
|
172
174
|
|
173
175
|
client_settings = ClientSettings.new(env)
|
174
176
|
|
@@ -178,20 +180,20 @@ module Rack
|
|
178
180
|
|
179
181
|
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
180
182
|
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
181
|
-
query_string =~ /pp=skip/
|
182
|
-
|
183
|
+
query_string =~ /pp=skip/
|
184
|
+
|
183
185
|
has_profiling_cookie = client_settings.has_cookie?
|
184
|
-
|
186
|
+
|
185
187
|
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
186
188
|
status,headers,body = @app.call(env)
|
187
|
-
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
188
|
-
client_settings.write!(headers)
|
189
|
+
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
190
|
+
client_settings.write!(headers)
|
189
191
|
end
|
190
192
|
return [status,headers,body]
|
191
193
|
end
|
192
194
|
|
193
195
|
# handle all /mini-profiler requests here
|
194
|
-
|
196
|
+
return serve_html(env) if path.start_with? @config.base_url_path
|
195
197
|
|
196
198
|
has_disable_cookie = client_settings.disable_profiling?
|
197
199
|
# manual session disable / enable
|
@@ -208,10 +210,16 @@ module Rack
|
|
208
210
|
client_settings.disable_profiling = true
|
209
211
|
client_settings.write!(headers)
|
210
212
|
return [status,headers,body]
|
213
|
+
else
|
214
|
+
client_settings.disable_profiling = false
|
211
215
|
end
|
212
216
|
|
213
217
|
if query_string =~ /pp=profile-gc/
|
214
|
-
|
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
|
215
223
|
end
|
216
224
|
|
217
225
|
MiniProfiler.create_current(env, @config)
|
@@ -231,31 +239,26 @@ module Rack
|
|
231
239
|
done_sampling = false
|
232
240
|
quit_sampler = false
|
233
241
|
backtraces = nil
|
234
|
-
|
235
|
-
if query_string =~ /pp=sample/
|
242
|
+
|
243
|
+
if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
|
244
|
+
current.measure = false
|
236
245
|
skip_frames = 0
|
237
246
|
backtraces = []
|
238
247
|
t = Thread.current
|
239
|
-
|
240
|
-
begin
|
241
|
-
require 'stacktrace'
|
242
|
-
skip_frames = stacktrace.length
|
243
|
-
rescue LoadError
|
244
|
-
stacktrace_installed = false
|
245
|
-
end
|
246
248
|
|
247
249
|
Thread.new {
|
250
|
+
# new in Ruby 2.0
|
251
|
+
has_backtrace_locations = t.respond_to?(:backtrace_locations)
|
248
252
|
begin
|
249
|
-
i = 10000 # for sanity never grab more than 10k samples
|
253
|
+
i = 10000 # for sanity never grab more than 10k samples
|
250
254
|
while i > 0
|
251
255
|
break if done_sampling
|
252
256
|
i -= 1
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
sleep 0.001
|
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
|
259
262
|
end
|
260
263
|
ensure
|
261
264
|
quit_sampler = true
|
@@ -263,11 +266,11 @@ module Rack
|
|
263
266
|
}
|
264
267
|
end
|
265
268
|
|
266
|
-
|
267
|
-
start = Time.now
|
268
|
-
begin
|
269
|
+
status, headers, body = nil
|
270
|
+
start = Time.now
|
271
|
+
begin
|
269
272
|
|
270
|
-
# Strip all the caching headers so we don't get 304s back
|
273
|
+
# Strip all the caching headers so we don't get 304s back
|
271
274
|
# This solves a very annoying bug where rack mini profiler never shows up
|
272
275
|
env['HTTP_IF_MODIFIED_SINCE'] = nil
|
273
276
|
env['HTTP_IF_NONE_MATCH'] = nil
|
@@ -275,17 +278,18 @@ module Rack
|
|
275
278
|
status,headers,body = @app.call(env)
|
276
279
|
client_settings.write!(headers)
|
277
280
|
ensure
|
278
|
-
if backtraces
|
281
|
+
if backtraces
|
279
282
|
done_sampling = true
|
280
283
|
sleep 0.001 until quit_sampler
|
281
284
|
end
|
282
285
|
end
|
283
286
|
|
284
287
|
skip_it = current.discard
|
288
|
+
|
285
289
|
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
286
290
|
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
287
291
|
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
288
|
-
if status == 200 && ((Time.now - start) > 0.1)
|
292
|
+
if status == 200 && ((Time.now - start) > 0.1)
|
289
293
|
client_settings.discard_cookie!(headers)
|
290
294
|
end
|
291
295
|
skip_it = true
|
@@ -303,43 +307,49 @@ module Rack
|
|
303
307
|
body.close if body.respond_to? :close
|
304
308
|
return help(client_settings)
|
305
309
|
end
|
306
|
-
|
310
|
+
|
307
311
|
page_struct = current.page_struct
|
308
|
-
|
312
|
+
page_struct['User'] = user(env)
|
313
|
+
page_struct['Root'].record_time((Time.now - start) * 1000)
|
309
314
|
|
310
315
|
if backtraces
|
311
316
|
body.close if body.respond_to? :close
|
312
|
-
|
317
|
+
if query_string =~ /pp=sample/
|
318
|
+
return analyze(backtraces, page_struct)
|
319
|
+
else
|
320
|
+
return flame_graph(backtraces, page_struct)
|
321
|
+
end
|
313
322
|
end
|
314
|
-
|
323
|
+
|
315
324
|
|
316
325
|
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
317
|
-
@storage.set_unviewed(
|
318
|
-
|
319
|
-
|
326
|
+
@storage.set_unviewed(page_struct['User'], page_struct['Id'])
|
327
|
+
@storage.save(page_struct)
|
328
|
+
|
320
329
|
# inject headers, script
|
321
|
-
|
330
|
+
if status == 200
|
322
331
|
|
323
332
|
client_settings.write!(headers)
|
324
|
-
|
333
|
+
|
325
334
|
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
326
335
|
# Rack::ETag has already inserted some nonesense in the chain
|
327
336
|
headers.delete('ETag')
|
328
337
|
headers.delete('Date')
|
329
338
|
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
330
339
|
|
331
|
-
|
340
|
+
# inject header
|
332
341
|
if headers.is_a? Hash
|
333
342
|
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
334
343
|
end
|
335
344
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
345
|
+
# inject script
|
346
|
+
if current.inject_js \
|
347
|
+
&& headers.has_key?('Content-Type') \
|
348
|
+
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
349
|
+
|
341
350
|
response = Rack::Response.new([], status, headers)
|
342
351
|
script = self.get_profile_script(env)
|
352
|
+
|
343
353
|
if String === body
|
344
354
|
response.write inject(body,script)
|
345
355
|
else
|
@@ -347,34 +357,70 @@ module Rack
|
|
347
357
|
end
|
348
358
|
body.close if body.respond_to? :close
|
349
359
|
return response.finish
|
350
|
-
|
351
|
-
|
360
|
+
end
|
361
|
+
end
|
352
362
|
|
353
363
|
client_settings.write!(headers)
|
354
|
-
|
364
|
+
[status, headers, body]
|
355
365
|
ensure
|
356
366
|
# Make sure this always happens
|
357
367
|
current = nil
|
358
|
-
|
368
|
+
end
|
359
369
|
|
360
370
|
def inject(fragment, script)
|
361
|
-
fragment.
|
362
|
-
#
|
363
|
-
|
364
|
-
|
365
|
-
|
371
|
+
if fragment.match(/<\/body>/i)
|
372
|
+
# explicit </body>
|
373
|
+
|
374
|
+
regex = /<\/body>/i
|
375
|
+
close_tag = '</body>'
|
376
|
+
elsif fragment.match(/<\/html>/i)
|
377
|
+
# implicit </body>
|
378
|
+
|
379
|
+
regex = /<\/html>/i
|
380
|
+
close_tag = '</html>'
|
381
|
+
else
|
382
|
+
# implicit </body> and </html>. Just append the script.
|
383
|
+
|
384
|
+
return fragment + script
|
385
|
+
end
|
386
|
+
|
387
|
+
matches = fragment.scan(regex).length
|
388
|
+
index = 1
|
389
|
+
fragment.gsub(regex) do
|
390
|
+
# though malformed there is an edge case where /body exists earlier in the html, work around
|
391
|
+
if index < matches
|
392
|
+
index += 1
|
393
|
+
close_tag
|
366
394
|
else
|
367
|
-
|
395
|
+
|
396
|
+
# if for whatever crazy reason we dont get a utf string,
|
397
|
+
# just force the encoding, no utf in the mp scripts anyway
|
398
|
+
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
399
|
+
(script + close_tag).force_encoding(fragment.encoding)
|
400
|
+
else
|
401
|
+
script + close_tag
|
402
|
+
end
|
368
403
|
end
|
369
404
|
end
|
370
405
|
end
|
371
406
|
|
372
407
|
def dump_env(env)
|
373
408
|
headers = {'Content-Type' => 'text/plain'}
|
374
|
-
body = ""
|
409
|
+
body = "Rack Environment\n---------------\n"
|
375
410
|
env.each do |k,v|
|
376
411
|
body << "#{k}: #{v}\n"
|
377
412
|
end
|
413
|
+
|
414
|
+
body << "\n\nEnvironment\n---------------\n"
|
415
|
+
ENV.each do |k,v|
|
416
|
+
body << "#{k}: #{v}\n"
|
417
|
+
end
|
418
|
+
|
419
|
+
body << "\n\nInternals\n---------------\n"
|
420
|
+
body << "Storage Provider #{config.storage_instance}\n"
|
421
|
+
body << "User #{user(env)}\n"
|
422
|
+
body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
|
423
|
+
|
378
424
|
[200, headers, [body]]
|
379
425
|
end
|
380
426
|
|
@@ -387,28 +433,42 @@ module Rack
|
|
387
433
|
pp=skip : skip mini profiler for this request
|
388
434
|
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)
|
389
435
|
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
|
390
|
-
pp=full-backtrace #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
|
391
|
-
pp=sample : sample stack traces and return a report isolating heavy usage (
|
392
|
-
pp=disable : disable profiling for this session
|
436
|
+
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
|
+
pp=disable : disable profiling for this session
|
393
439
|
pp=enable : enable profiling for this session (if previously disabled)
|
394
|
-
pp=profile-gc: perform gc profiling on this request (ruby 1.9.3 only)
|
440
|
+
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
441
|
+
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.
|
395
443
|
"
|
396
|
-
|
444
|
+
|
397
445
|
client_settings.write!(headers)
|
398
446
|
[200, headers, [body]]
|
399
447
|
end
|
400
448
|
|
449
|
+
def flame_graph(traces, page_struct)
|
450
|
+
graph = FlameGraph.new(traces)
|
451
|
+
data = graph.graph_data
|
452
|
+
|
453
|
+
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
|
+
|
401
461
|
def analyze(traces, page_struct)
|
402
462
|
headers = {'Content-Type' => 'text/plain'}
|
403
463
|
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
404
464
|
|
405
465
|
seen = {}
|
406
466
|
fulldump = ""
|
407
|
-
traces.each do |trace|
|
467
|
+
traces.each do |trace|
|
408
468
|
fulldump << "\n\n"
|
409
469
|
distinct = {}
|
410
470
|
trace.each do |frame|
|
411
|
-
frame =
|
471
|
+
frame = frame.to_s unless String === frame
|
412
472
|
unless distinct[frame]
|
413
473
|
distinct[frame] = true
|
414
474
|
seen[frame] ||= 0
|
@@ -424,7 +484,7 @@ module Rack
|
|
424
484
|
body << "#{name} x #{count}\n"
|
425
485
|
end
|
426
486
|
end
|
427
|
-
|
487
|
+
|
428
488
|
body << "\n\n\nRaw traces \n"
|
429
489
|
body << fulldump
|
430
490
|
|
@@ -437,43 +497,48 @@ module Rack
|
|
437
497
|
::JSON.generate(ids.uniq)
|
438
498
|
end
|
439
499
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
500
|
+
def ids_comma_separated(env)
|
501
|
+
# cap at 10 ids, otherwise there is a chance you can blow the header
|
502
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
|
503
|
+
ids.join(",")
|
504
|
+
end
|
505
|
+
|
506
|
+
# get_profile_script returns script to be injected inside current html page
|
507
|
+
# By default, profile_script is appended to the end of all html requests automatically.
|
508
|
+
# Calling get_profile_script cancels automatic append for the current page
|
509
|
+
# Use it when:
|
510
|
+
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
511
|
+
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
512
|
+
def get_profile_script(env)
|
513
|
+
ids = ids_comma_separated(env)
|
514
|
+
path = @config.base_url_path
|
515
|
+
version = MiniProfiler::VERSION
|
516
|
+
position = @config.position
|
517
|
+
showTrivial = false
|
518
|
+
showChildren = false
|
519
|
+
maxTracesToShow = 10
|
520
|
+
showControls = false
|
521
|
+
currentId = current.page_struct["Id"]
|
522
|
+
authorized = true
|
523
|
+
toggleShortcut = @config.toggle_shortcut
|
524
|
+
startHidden = @config.start_hidden
|
525
|
+
# TODO : cache this snippet
|
526
|
+
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
527
|
+
# replace the variables
|
528
|
+
[:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :toggleShortcut, :startHidden].each do |v|
|
529
|
+
regex = Regexp.new("\\{#{v.to_s}\\}")
|
530
|
+
script.gsub!(regex, eval(v.to_s).to_s)
|
531
|
+
end
|
532
|
+
current.inject_js = false
|
533
|
+
script
|
534
|
+
end
|
535
|
+
|
536
|
+
# cancels automatic injection of profile script for the current page
|
537
|
+
def cancel_auto_inject(env)
|
538
|
+
current.inject_js = false
|
539
|
+
end
|
540
|
+
|
541
|
+
end
|
477
542
|
|
478
543
|
end
|
479
544
|
|