mini-mini-profiler 0.1
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.
- data/Ruby/CHANGELOG +135 -0
- data/Ruby/README.md +161 -0
- data/Ruby/lib/html/flamegraph.html +325 -0
- data/Ruby/lib/html/includes.css +451 -0
- data/Ruby/lib/html/includes.js +945 -0
- data/Ruby/lib/html/includes.less +471 -0
- data/Ruby/lib/html/includes.tmpl +108 -0
- data/Ruby/lib/html/jquery.1.7.1.js +4 -0
- data/Ruby/lib/html/jquery.tmpl.js +486 -0
- data/Ruby/lib/html/list.css +9 -0
- data/Ruby/lib/html/list.js +38 -0
- data/Ruby/lib/html/list.tmpl +34 -0
- data/Ruby/lib/html/profile_handler.js +1 -0
- data/Ruby/lib/html/share.html +11 -0
- data/Ruby/lib/mini_profiler/client_settings.rb +65 -0
- data/Ruby/lib/mini_profiler/client_timer_struct.rb +78 -0
- data/Ruby/lib/mini_profiler/config.rb +57 -0
- data/Ruby/lib/mini_profiler/context.rb +11 -0
- data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
- data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
- data/Ruby/lib/mini_profiler/gc_profiler.rb +107 -0
- data/Ruby/lib/mini_profiler/page_timer_struct.rb +58 -0
- data/Ruby/lib/mini_profiler/profiler.rb +544 -0
- data/Ruby/lib/mini_profiler/profiling_methods.rb +133 -0
- data/Ruby/lib/mini_profiler/request_timer_struct.rb +115 -0
- data/Ruby/lib/mini_profiler/sql_timer_struct.rb +58 -0
- data/Ruby/lib/mini_profiler/storage/abstract_store.rb +31 -0
- data/Ruby/lib/mini_profiler/storage/file_store.rb +111 -0
- data/Ruby/lib/mini_profiler/storage/memcache_store.rb +53 -0
- data/Ruby/lib/mini_profiler/storage/memory_store.rb +65 -0
- data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
- data/Ruby/lib/mini_profiler/timer_struct.rb +33 -0
- data/Ruby/lib/mini_profiler/version.rb +5 -0
- data/Ruby/lib/mini_profiler_rails/railtie.rb +107 -0
- data/Ruby/lib/patches/net_patches.rb +14 -0
- data/Ruby/lib/patches/sql_patches.rb +272 -0
- data/Ruby/lib/rack-mini-profiler.rb +7 -0
- data/mini-mini-profiler.gemspec +26 -0
- metadata +154 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
# PageTimerStruct
|
7
|
+
# Root: RequestTimer
|
8
|
+
# :has_many RequestTimer children
|
9
|
+
# :has_many SqlTimer children
|
10
|
+
# :has_many CustomTimer children
|
11
|
+
class PageTimerStruct < TimerStruct
|
12
|
+
def initialize(env)
|
13
|
+
super("Id" => MiniProfiler.generate_id,
|
14
|
+
"Name" => env['PATH_INFO'],
|
15
|
+
"Started" => (Time.now.to_f * 1000).to_i,
|
16
|
+
"MachineName" => env['SERVER_NAME'],
|
17
|
+
"Level" => 0,
|
18
|
+
"User" => "unknown user",
|
19
|
+
"HasUserViewed" => false,
|
20
|
+
"ClientTimings" => nil,
|
21
|
+
"DurationMilliseconds" => 0,
|
22
|
+
"HasTrivialTimings" => true,
|
23
|
+
"HasAllTrivialTimigs" => false,
|
24
|
+
"TrivialDurationThresholdMilliseconds" => 2,
|
25
|
+
"Head" => nil,
|
26
|
+
"DurationMillisecondsInSql" => 0,
|
27
|
+
"HasSqlTimings" => true,
|
28
|
+
"HasDuplicateSqlTimings" => false,
|
29
|
+
"ExecutedReaders" => 0,
|
30
|
+
"ExecutedScalars" => 0,
|
31
|
+
"ExecutedNonQueries" => 0,
|
32
|
+
"CustomTimingNames" => [],
|
33
|
+
"CustomTimingStats" => {}
|
34
|
+
)
|
35
|
+
name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
36
|
+
self['Root'] = RequestTimerStruct.createRoot(name, self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def duration_ms
|
40
|
+
@attributes['Root']['DurationMilliseconds']
|
41
|
+
end
|
42
|
+
|
43
|
+
def root
|
44
|
+
@attributes['Root']
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_json(*a)
|
48
|
+
attribs = @attributes.merge(
|
49
|
+
"Started" => '/Date(%d)/' % @attributes['Started'],
|
50
|
+
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds'],
|
51
|
+
"CustomTimingNames" => @attributes['CustomTimingStats'].keys.sort
|
52
|
+
)
|
53
|
+
::JSON.generate(attribs, :max_nesting => 100)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,544 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require 'mini_profiler/version'
|
6
|
+
require 'mini_profiler/page_timer_struct'
|
7
|
+
require 'mini_profiler/sql_timer_struct'
|
8
|
+
require 'mini_profiler/custom_timer_struct'
|
9
|
+
require 'mini_profiler/client_timer_struct'
|
10
|
+
require 'mini_profiler/request_timer_struct'
|
11
|
+
require 'mini_profiler/storage/abstract_store'
|
12
|
+
require 'mini_profiler/storage/memcache_store'
|
13
|
+
require 'mini_profiler/storage/memory_store'
|
14
|
+
require 'mini_profiler/storage/redis_store'
|
15
|
+
require 'mini_profiler/storage/file_store'
|
16
|
+
require 'mini_profiler/config'
|
17
|
+
require 'mini_profiler/profiling_methods'
|
18
|
+
require 'mini_profiler/context'
|
19
|
+
require 'mini_profiler/client_settings'
|
20
|
+
require 'mini_profiler/gc_profiler'
|
21
|
+
require 'mini_profiler/flame_graph'
|
22
|
+
|
23
|
+
module Rack
|
24
|
+
|
25
|
+
class MiniProfiler
|
26
|
+
|
27
|
+
class << self
|
28
|
+
|
29
|
+
include Rack::MiniProfiler::ProfilingMethods
|
30
|
+
|
31
|
+
def generate_id
|
32
|
+
rand(36**20).to_s(36)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset_config
|
36
|
+
@config = Config.default
|
37
|
+
end
|
38
|
+
|
39
|
+
# So we can change the configuration if we want
|
40
|
+
def config
|
41
|
+
@config ||= Config.default
|
42
|
+
end
|
43
|
+
|
44
|
+
def share_template
|
45
|
+
return @share_template unless @share_template.nil?
|
46
|
+
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
47
|
+
end
|
48
|
+
|
49
|
+
def current
|
50
|
+
Thread.current[:mini_profiler_private]
|
51
|
+
end
|
52
|
+
|
53
|
+
def current=(c)
|
54
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
55
|
+
Thread.current[:mini_profiler_private]= c
|
56
|
+
end
|
57
|
+
|
58
|
+
# discard existing results, don't track this request
|
59
|
+
def discard_results
|
60
|
+
self.current.discard = true if current
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_current(env={}, options={})
|
64
|
+
# profiling the request
|
65
|
+
self.current = Context.new
|
66
|
+
self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
67
|
+
self.current.page_struct = PageTimerStruct.new(env)
|
68
|
+
self.current.current_timer = current.page_struct['Root']
|
69
|
+
end
|
70
|
+
|
71
|
+
def authorize_request
|
72
|
+
Thread.current[:mp_authorized] = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def deauthorize_request
|
76
|
+
Thread.current[:mp_authorized] = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def request_authorized?
|
80
|
+
Thread.current[:mp_authorized]
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# options:
|
87
|
+
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
88
|
+
def initialize(app, config = nil)
|
89
|
+
MiniProfiler.config.merge!(config)
|
90
|
+
@config = MiniProfiler.config
|
91
|
+
@app = app
|
92
|
+
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
93
|
+
unless @config.storage_instance
|
94
|
+
@config.storage_instance = @config.storage.new(@config.storage_options)
|
95
|
+
end
|
96
|
+
@storage = @config.storage_instance
|
97
|
+
end
|
98
|
+
|
99
|
+
def user(env)
|
100
|
+
@config.user_provider.call(env)
|
101
|
+
end
|
102
|
+
|
103
|
+
def serve_results(env)
|
104
|
+
request = Rack::Request.new(env)
|
105
|
+
id = request['id']
|
106
|
+
page_struct = @storage.load(id)
|
107
|
+
unless page_struct
|
108
|
+
@storage.set_viewed(user(env), id)
|
109
|
+
return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
|
110
|
+
end
|
111
|
+
unless page_struct['HasUserViewed']
|
112
|
+
page_struct['ClientTimings'] = ClientTimerStruct.init_from_form_data(env, page_struct)
|
113
|
+
page_struct['HasUserViewed'] = true
|
114
|
+
@storage.save(page_struct)
|
115
|
+
@storage.set_viewed(user(env), id)
|
116
|
+
end
|
117
|
+
|
118
|
+
result_json = page_struct.to_json
|
119
|
+
# If we're an XMLHttpRequest, serve up the contents as JSON
|
120
|
+
if request.xhr?
|
121
|
+
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
122
|
+
else
|
123
|
+
|
124
|
+
# Otherwise give the HTML back
|
125
|
+
html = MiniProfiler.share_template.dup
|
126
|
+
html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
|
127
|
+
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
128
|
+
html.gsub!(/\{json\}/, result_json)
|
129
|
+
html.gsub!(/\{includes\}/, get_profile_script(env))
|
130
|
+
html.gsub!(/\{name\}/, page_struct['Name'])
|
131
|
+
html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
|
132
|
+
|
133
|
+
[200, {'Content-Type' => 'text/html'}, [html]]
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
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
|
145
|
+
|
146
|
+
begin
|
147
|
+
f.cache_control = "max-age:86400"
|
148
|
+
f.serving env
|
149
|
+
rescue
|
150
|
+
# old versions of rack have a different api
|
151
|
+
status, headers, body = f.serving
|
152
|
+
headers.merge! 'Cache-Control' => "max-age:86400"
|
153
|
+
[status, headers, body]
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
def current
|
160
|
+
MiniProfiler.current
|
161
|
+
end
|
162
|
+
|
163
|
+
def current=(c)
|
164
|
+
MiniProfiler.current=c
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def config
|
169
|
+
@config
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def call(env)
|
174
|
+
|
175
|
+
client_settings = ClientSettings.new(env)
|
176
|
+
|
177
|
+
status = headers = body = nil
|
178
|
+
query_string = env['QUERY_STRING']
|
179
|
+
path = env['PATH_INFO']
|
180
|
+
|
181
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
182
|
+
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
183
|
+
query_string =~ /pp=skip/
|
184
|
+
|
185
|
+
has_profiling_cookie = client_settings.has_cookie?
|
186
|
+
|
187
|
+
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
188
|
+
status,headers,body = @app.call(env)
|
189
|
+
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
190
|
+
client_settings.write!(headers)
|
191
|
+
end
|
192
|
+
return [status,headers,body]
|
193
|
+
end
|
194
|
+
|
195
|
+
# handle all /mini-profiler requests here
|
196
|
+
return serve_html(env) if path.start_with? @config.base_url_path
|
197
|
+
|
198
|
+
has_disable_cookie = client_settings.disable_profiling?
|
199
|
+
# manual session disable / enable
|
200
|
+
if query_string =~ /pp=disable/ || has_disable_cookie
|
201
|
+
skip_it = true
|
202
|
+
end
|
203
|
+
|
204
|
+
if query_string =~ /pp=enable/
|
205
|
+
skip_it = false
|
206
|
+
end
|
207
|
+
|
208
|
+
if skip_it
|
209
|
+
status,headers,body = @app.call(env)
|
210
|
+
client_settings.disable_profiling = true
|
211
|
+
client_settings.write!(headers)
|
212
|
+
return [status,headers,body]
|
213
|
+
else
|
214
|
+
client_settings.disable_profiling = false
|
215
|
+
end
|
216
|
+
|
217
|
+
if query_string =~ /pp=profile-gc/
|
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
|
223
|
+
end
|
224
|
+
|
225
|
+
MiniProfiler.create_current(env, @config)
|
226
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
227
|
+
if query_string =~ /pp=normal-backtrace/
|
228
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
229
|
+
elsif query_string =~ /pp=no-backtrace/
|
230
|
+
current.skip_backtrace = true
|
231
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
|
232
|
+
elsif query_string =~ /pp=full-backtrace/ || client_settings.backtrace_full?
|
233
|
+
current.full_backtrace = true
|
234
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL
|
235
|
+
elsif client_settings.backtrace_none?
|
236
|
+
current.skip_backtrace = true
|
237
|
+
end
|
238
|
+
|
239
|
+
done_sampling = false
|
240
|
+
quit_sampler = false
|
241
|
+
backtraces = nil
|
242
|
+
|
243
|
+
if query_string =~ /pp=sample/ || query_string =~ /pp=flamegraph/
|
244
|
+
current.measure = false
|
245
|
+
skip_frames = 0
|
246
|
+
backtraces = []
|
247
|
+
t = Thread.current
|
248
|
+
|
249
|
+
Thread.new {
|
250
|
+
# new in Ruby 2.0
|
251
|
+
has_backtrace_locations = t.respond_to?(:backtrace_locations)
|
252
|
+
begin
|
253
|
+
i = 10000 # for sanity never grab more than 10k samples
|
254
|
+
while i > 0
|
255
|
+
break if done_sampling
|
256
|
+
i -= 1
|
257
|
+
backtraces << (has_backtrace_locations ? t.backtrace_locations : t.backtrace)
|
258
|
+
|
259
|
+
# On my machine using Ruby 2.0 this give me excellent fidelity of stack trace per 1.2ms
|
260
|
+
# with this fidelity analysis becomes very powerful
|
261
|
+
sleep 0.0005
|
262
|
+
end
|
263
|
+
ensure
|
264
|
+
quit_sampler = true
|
265
|
+
end
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
status, headers, body = nil
|
270
|
+
start = Time.now
|
271
|
+
begin
|
272
|
+
|
273
|
+
# Strip all the caching headers so we don't get 304s back
|
274
|
+
# This solves a very annoying bug where rack mini profiler never shows up
|
275
|
+
env['HTTP_IF_MODIFIED_SINCE'] = nil
|
276
|
+
env['HTTP_IF_NONE_MATCH'] = nil
|
277
|
+
|
278
|
+
status,headers,body = @app.call(env)
|
279
|
+
client_settings.write!(headers)
|
280
|
+
ensure
|
281
|
+
if backtraces
|
282
|
+
done_sampling = true
|
283
|
+
sleep 0.001 until quit_sampler
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
skip_it = current.discard
|
288
|
+
|
289
|
+
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
290
|
+
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
291
|
+
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
292
|
+
if status == 200 && ((Time.now - start) > 0.1)
|
293
|
+
client_settings.discard_cookie!(headers)
|
294
|
+
end
|
295
|
+
skip_it = true
|
296
|
+
end
|
297
|
+
|
298
|
+
return [status,headers,body] if skip_it
|
299
|
+
|
300
|
+
# we must do this here, otherwise current[:discard] is not being properly treated
|
301
|
+
if query_string =~ /pp=env/
|
302
|
+
body.close if body.respond_to? :close
|
303
|
+
return dump_env env
|
304
|
+
end
|
305
|
+
|
306
|
+
if query_string =~ /pp=help/
|
307
|
+
body.close if body.respond_to? :close
|
308
|
+
return help(client_settings)
|
309
|
+
end
|
310
|
+
|
311
|
+
page_struct = current.page_struct
|
312
|
+
page_struct['User'] = user(env)
|
313
|
+
page_struct['Root'].record_time((Time.now - start) * 1000)
|
314
|
+
|
315
|
+
if backtraces
|
316
|
+
body.close if body.respond_to? :close
|
317
|
+
if query_string =~ /pp=sample/
|
318
|
+
return analyze(backtraces, page_struct)
|
319
|
+
else
|
320
|
+
return flame_graph(backtraces, page_struct)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
326
|
+
@storage.set_unviewed(page_struct['User'], page_struct['Id'])
|
327
|
+
@storage.save(page_struct)
|
328
|
+
|
329
|
+
# inject headers, script
|
330
|
+
if status == 200
|
331
|
+
|
332
|
+
client_settings.write!(headers)
|
333
|
+
|
334
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
335
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
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
|
361
|
+
end
|
362
|
+
|
363
|
+
client_settings.write!(headers)
|
364
|
+
[status, headers, body]
|
365
|
+
ensure
|
366
|
+
# Make sure this always happens
|
367
|
+
current = nil
|
368
|
+
end
|
369
|
+
|
370
|
+
def inject(fragment, script)
|
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
|
394
|
+
else
|
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
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def dump_env(env)
|
408
|
+
headers = {'Content-Type' => 'text/plain'}
|
409
|
+
body = "Rack Environment\n---------------\n"
|
410
|
+
env.each do |k,v|
|
411
|
+
body << "#{k}: #{v}\n"
|
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
|
+
|
424
|
+
[200, headers, [body]]
|
425
|
+
end
|
426
|
+
|
427
|
+
def help(client_settings)
|
428
|
+
headers = {'Content-Type' => 'text/plain'}
|
429
|
+
body = "Append the following to your query string:
|
430
|
+
|
431
|
+
pp=help : display this screen
|
432
|
+
pp=env : display the rack environment
|
433
|
+
pp=skip : skip mini profiler for this request
|
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)
|
435
|
+
pp=normal-backtrace #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
|
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
|
439
|
+
pp=enable : enable profiling for this session (if previously disabled)
|
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.
|
443
|
+
"
|
444
|
+
|
445
|
+
client_settings.write!(headers)
|
446
|
+
[200, headers, [body]]
|
447
|
+
end
|
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
|
+
|
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]]
|
492
|
+
end
|
493
|
+
|
494
|
+
def ids_json(env)
|
495
|
+
# cap at 10 ids, otherwise there is a chance you can blow the header
|
496
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
|
497
|
+
::JSON.generate(ids.uniq)
|
498
|
+
end
|
499
|
+
|
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 = "#{env['SCRIPT_NAME']}#{@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
|
542
|
+
|
543
|
+
end
|
544
|
+
|