rack-mini-profiler 0.1.21 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
|
|