rack-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.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- data/README.md +82 -0
- data/lib/html/MiniProfilerHandler.cs +419 -0
- data/lib/html/includes.css +1 -0
- data/lib/html/includes.js +802 -0
- data/lib/html/includes.less +468 -0
- data/lib/html/includes.tmpl +195 -0
- data/lib/html/jquery.1.7.1.js +4 -0
- data/lib/html/jquery.tmpl.js +486 -0
- data/lib/html/list.css +9 -0
- data/lib/html/list.js +37 -0
- data/lib/html/list.tmpl +34 -0
- data/lib/html/profile_handler.js +62 -0
- data/lib/html/share.html +11 -0
- data/lib/mini_profiler/body_add_proxy.rb +45 -0
- data/lib/mini_profiler/client_timer_struct.rb +43 -0
- data/lib/mini_profiler/page_timer_struct.rb +49 -0
- data/lib/mini_profiler/profiler.rb +309 -0
- data/lib/mini_profiler/request_timer_struct.rb +65 -0
- data/lib/mini_profiler/sql_timer_struct.rb +37 -0
- data/lib/mini_profiler/storage/abstract_store.rb +27 -0
- data/lib/mini_profiler/storage/file_store.rb +108 -0
- data/lib/mini_profiler/storage/memory_store.rb +68 -0
- data/lib/mini_profiler/storage/redis_store.rb +43 -0
- data/lib/mini_profiler/timer_struct.rb +31 -0
- data/lib/mini_profiler_rails/railtie.rb +30 -0
- data/lib/patches/sql_patches.rb +72 -0
- data/lib/rack-mini-profiler.rb +6 -0
- data/rack-mini-profiler.gemspec +24 -0
- metadata +146 -0
data/lib/html/list.css
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
tbody tr:nth-child(odd) { background-color:#eee; }
|
2
|
+
tbody tr:nth-child(even) { background-color:#fff; }
|
3
|
+
table { border: 0; border-spacing:0;}
|
4
|
+
tr {border: 0;}
|
5
|
+
.date {font-size: 11px; color: #666;}
|
6
|
+
td {padding: 8px;}
|
7
|
+
.time {text-align:center;}
|
8
|
+
thead tr {background-color: #bbb; color: #444; font-size: 12px;}
|
9
|
+
thead tr th { padding: 5px 15px;}
|
data/lib/html/list.js
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
var MiniProfiler = MiniProfiler || {};
|
2
|
+
MiniProfiler.list = {
|
3
|
+
init:
|
4
|
+
function (options) {
|
5
|
+
var opt = options || {};
|
6
|
+
|
7
|
+
var updateGrid = function (id) {
|
8
|
+
jQueryMP.ajax({
|
9
|
+
url: options.path + 'results-list',
|
10
|
+
data: { "last-id": id },
|
11
|
+
dataType: 'json',
|
12
|
+
type: 'GET',
|
13
|
+
success: function (data) {
|
14
|
+
jQueryMP('table tbody').append(jQueryMP("#rowTemplate").tmpl(data));
|
15
|
+
var oldId = id;
|
16
|
+
var oldData = data;
|
17
|
+
setTimeout(function () {
|
18
|
+
var newId = oldId;
|
19
|
+
if (oldData.length > 0) {
|
20
|
+
newId = oldData[oldData.length - 1].Id;
|
21
|
+
}
|
22
|
+
updateGrid(newId);
|
23
|
+
}, 4000);
|
24
|
+
}
|
25
|
+
});
|
26
|
+
}
|
27
|
+
|
28
|
+
MiniProfiler.path = options.path;
|
29
|
+
jQueryMP.get(options.path + 'list.tmpl?v=' + options.version, function (data) {
|
30
|
+
if (data) {
|
31
|
+
jQueryMP('body').append(data);
|
32
|
+
jQueryMP('body').append(jQueryMP('#tableTemplate').tmpl());
|
33
|
+
updateGrid();
|
34
|
+
}
|
35
|
+
});
|
36
|
+
}
|
37
|
+
};
|
data/lib/html/list.tmpl
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
<script id="tableTemplate" type="text/x-jquery-tmpl">
|
2
|
+
<table>
|
3
|
+
<thead>
|
4
|
+
<tr>
|
5
|
+
<th>Name</th>
|
6
|
+
<th>Started</th>
|
7
|
+
<th>Sql Duration</th>
|
8
|
+
<th>Total Duration</th>
|
9
|
+
<th>Request Start</th>
|
10
|
+
<th>Response Start</th>
|
11
|
+
<th>Dom Complete</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
|
16
|
+
</tbody>
|
17
|
+
</table>
|
18
|
+
</script>
|
19
|
+
<script id="rowTemplate" type="text/x-jquery-tmpl">
|
20
|
+
<tr>
|
21
|
+
<td>
|
22
|
+
<a href="${MiniProfiler.path}results?id=${Id}">${Name}</a></td>
|
23
|
+
<td class="date">${MiniProfiler.renderDate(Started)}</td>
|
24
|
+
<td class="time">${DurationMillisecondsInSql}</td>
|
25
|
+
<td class="time">${DurationMilliseconds}</td>
|
26
|
+
{{if ClientTimings}}
|
27
|
+
<td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Request").Start}</td>
|
28
|
+
<td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Response").Start}</td>
|
29
|
+
<td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Dom Complete").Start}</td>
|
30
|
+
{{else}}
|
31
|
+
<td colspan="3"></td>
|
32
|
+
{{/if}}
|
33
|
+
</tr>
|
34
|
+
</script>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<script type="text/javascript">
|
2
|
+
(function(){{
|
3
|
+
var init = function() {{
|
4
|
+
var load = function(s,f){{
|
5
|
+
var sc = document.createElement('script');
|
6
|
+
sc.async = 'async';
|
7
|
+
sc.type = 'text/javascript';
|
8
|
+
sc.src = s;
|
9
|
+
var l = false;
|
10
|
+
sc.onload = sc.onreadystatechange = function(_, abort) {{
|
11
|
+
if (!l && (!sc.readyState || /loaded|complete/.test(sc.readyState))) {{
|
12
|
+
if (!abort){{l=true; f();}}
|
13
|
+
}}
|
14
|
+
}};
|
15
|
+
|
16
|
+
document.getElementsByTagName('head')[0].appendChild(sc);
|
17
|
+
}};
|
18
|
+
|
19
|
+
var initMp = function(){{
|
20
|
+
load('{path}includes.js?v={version}',function(){{
|
21
|
+
MiniProfiler.init({{
|
22
|
+
ids: {ids},
|
23
|
+
path: '{path}',
|
24
|
+
version: '{version}',
|
25
|
+
renderPosition: '{position}',
|
26
|
+
showTrivial: {showTrivial},
|
27
|
+
showChildrenTime: {showChildren},
|
28
|
+
maxTracesToShow: {maxTracesToShow},
|
29
|
+
showControls: {showControls},
|
30
|
+
currentId: '{currentId}',
|
31
|
+
authorized: {authorized}
|
32
|
+
}});
|
33
|
+
}});
|
34
|
+
}};
|
35
|
+
if ({useExistingjQuery}) {{
|
36
|
+
jQueryMP = jQuery;
|
37
|
+
initMp();
|
38
|
+
}} else {{
|
39
|
+
load('{path}jquery.1.7.1.js?v={version}', initMp);
|
40
|
+
}}
|
41
|
+
|
42
|
+
}};
|
43
|
+
|
44
|
+
var w = 0;
|
45
|
+
var f = false;
|
46
|
+
var deferInit = function(){{
|
47
|
+
if (f) return;
|
48
|
+
if (window.performance && window.performance.timing && window.performance.timing.loadEventEnd == 0 && w < 10000){{
|
49
|
+
setTimeout(deferInit, 100);
|
50
|
+
w += 100;
|
51
|
+
}} else {{
|
52
|
+
f = true;
|
53
|
+
init();
|
54
|
+
}}
|
55
|
+
}};
|
56
|
+
if (document.addEventListener) {{
|
57
|
+
document.addEventListener('DOMContentLoaded',deferInit);
|
58
|
+
}}
|
59
|
+
var o = window.onload;
|
60
|
+
window.onload = function(){{if(o)o; deferInit()}};
|
61
|
+
}})();
|
62
|
+
</script>
|
data/lib/html/share.html
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<title>{name} ({duration} ms) - Profiling Results</title>
|
4
|
+
<script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
|
5
|
+
<script type='text/javascript'> var profiler = {json}; </script>
|
6
|
+
{includes}
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<div class='profiler-result-full'></div>
|
10
|
+
</body>
|
11
|
+
</html>
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
|
4
|
+
# This class acts as a proxy to the Body so that we can
|
5
|
+
# safely append to the end without knowing about the internals
|
6
|
+
# of the body class.
|
7
|
+
class BodyAddProxy
|
8
|
+
def initialize(body, additional_text)
|
9
|
+
@body = body
|
10
|
+
@additional_text = additional_text
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to?(*args)
|
14
|
+
super or @body.respond_to?(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(*args, &block)
|
18
|
+
@body.__send__(*args, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
# In the case of to_str we don't want to use method_missing as it might avoid
|
22
|
+
# a call to each (such as in Rack::Test)
|
23
|
+
def to_str
|
24
|
+
result = ""
|
25
|
+
each {|token| result << token}
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def each(&block)
|
30
|
+
|
31
|
+
# In ruby 1.9 we don't support String#each
|
32
|
+
if @body.is_a?(String)
|
33
|
+
yield @body
|
34
|
+
else
|
35
|
+
@body.each(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
yield @additional_text
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
# This class holds the client timings
|
7
|
+
class ClientTimerStruct < TimerStruct
|
8
|
+
|
9
|
+
def initialize(env={})
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def init_from_form_data(env, page_struct)
|
14
|
+
timings = []
|
15
|
+
clientTimes, clientPerf, baseTime = nil
|
16
|
+
form = env['rack.request.form_hash']
|
17
|
+
|
18
|
+
clientPerf = form['clientPerformance'] if form
|
19
|
+
clientTimes = clientPerf['timing'] if clientPerf
|
20
|
+
|
21
|
+
baseTime = clientTimes['navigationStart'].to_i if clientTimes
|
22
|
+
return unless clientTimes && baseTime
|
23
|
+
|
24
|
+
clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
|
25
|
+
start = clientTimes[k].to_i - baseTime
|
26
|
+
finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
|
27
|
+
duration = 0
|
28
|
+
duration = finish - start if finish > start
|
29
|
+
name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map{|s| s.capitalize}.join(' ')
|
30
|
+
timings.push({"Name" => name, "Start" => start, "Duration" => duration}) if start >= 0
|
31
|
+
end
|
32
|
+
|
33
|
+
clientTimes.keys.find_all{|k| !(k =~ /(End|Start)$/)}.each do |k|
|
34
|
+
timings.push("Name" => k, "Start" => clientTimes[k].to_i - baseTime, "Duration" => -1)
|
35
|
+
end
|
36
|
+
|
37
|
+
self['RedirectCount'] = env['rack.request.form_hash']['clientPerformance']['navigation']['redirectCount']
|
38
|
+
self['Timings'] = timings
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
class PageTimerStruct < TimerStruct
|
11
|
+
def initialize(env)
|
12
|
+
super("Id" => MiniProfiler.generate_id,
|
13
|
+
"Name" => env['PATH_INFO'],
|
14
|
+
"Started" => (Time.now.to_f * 1000).to_i,
|
15
|
+
"MachineName" => env['SERVER_NAME'],
|
16
|
+
"Level" => 0,
|
17
|
+
"User" => "unknown user",
|
18
|
+
"HasUserViewed" => false,
|
19
|
+
"ClientTimings" => ClientTimerStruct.new,
|
20
|
+
"DurationMilliseconds" => 0,
|
21
|
+
"HasTrivialTimings" => true,
|
22
|
+
"HasAllTrivialTimigs" => false,
|
23
|
+
"TrivialDurationThresholdMilliseconds" => 2,
|
24
|
+
"Head" => nil,
|
25
|
+
"DurationMillisecondsInSql" => 0,
|
26
|
+
"HasSqlTimings" => true,
|
27
|
+
"HasDuplicateSqlTimings" => false,
|
28
|
+
"ExecutedReaders" => 0,
|
29
|
+
"ExecutedScalars" => 0,
|
30
|
+
"ExecutedNonQueries" => 0)
|
31
|
+
name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
|
32
|
+
self['Root'] = RequestTimerStruct.createRoot(name, self)
|
33
|
+
end
|
34
|
+
|
35
|
+
def duration_ms
|
36
|
+
@attributes['Root']['DurationMilliseconds']
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json(*a)
|
40
|
+
attribs = @attributes.merge(
|
41
|
+
"Started" => '/Date(%d)/' % @attributes['Started'],
|
42
|
+
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
|
43
|
+
)
|
44
|
+
::JSON.generate(attribs, a[0])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'timeout'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
require 'mini_profiler/page_timer_struct'
|
6
|
+
require 'mini_profiler/sql_timer_struct'
|
7
|
+
require 'mini_profiler/client_timer_struct'
|
8
|
+
require 'mini_profiler/request_timer_struct'
|
9
|
+
require 'mini_profiler/body_add_proxy'
|
10
|
+
require 'mini_profiler/storage/abstract_store'
|
11
|
+
require 'mini_profiler/storage/memory_store'
|
12
|
+
require 'mini_profiler/storage/redis_store'
|
13
|
+
require 'mini_profiler/storage/file_store'
|
14
|
+
|
15
|
+
module Rack
|
16
|
+
|
17
|
+
class MiniProfiler
|
18
|
+
|
19
|
+
VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA='.freeze
|
20
|
+
@@instance = nil
|
21
|
+
|
22
|
+
def self.instance
|
23
|
+
@@instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.generate_id
|
27
|
+
rand(36**20).to_s(36)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Defaults for MiniProfiler's configuration
|
31
|
+
def self.configuration_defaults
|
32
|
+
{
|
33
|
+
:auto_inject => true, # automatically inject on every html page
|
34
|
+
:base_url_path => "/mini-profiler-resources/",
|
35
|
+
:authorize_cb => lambda {|env| true}, # callback returns true if this request is authorized to profile
|
36
|
+
:position => 'left', # Where it is displayed
|
37
|
+
:backtrace_remove => nil,
|
38
|
+
:backtrace_filter => nil,
|
39
|
+
:skip_schema_queries => true,
|
40
|
+
:storage => MiniProfiler::MemoryStore,
|
41
|
+
:user_provider => Proc.new{|env| "TODO" }
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.reset_configuration
|
46
|
+
@configuration = configuration_defaults
|
47
|
+
end
|
48
|
+
|
49
|
+
# So we can change the configuration if we want
|
50
|
+
def self.configuration
|
51
|
+
@configuration ||= configuration_defaults.dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.share_template
|
55
|
+
return @share_template unless @share_template.nil?
|
56
|
+
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# options:
|
61
|
+
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
62
|
+
def initialize(app, opts={})
|
63
|
+
@@instance = self
|
64
|
+
MiniProfiler.configuration.merge!(opts)
|
65
|
+
@options = MiniProfiler.configuration
|
66
|
+
@app = app
|
67
|
+
@options[:base_url_path] << "/" unless @options[:base_url_path].end_with? "/"
|
68
|
+
unless @options[:storage_instance]
|
69
|
+
@storage = @options[:storage_instance] = @options[:storage].new(@options[:storage_options])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def user(env)
|
74
|
+
options[:user_provider].call(env)
|
75
|
+
end
|
76
|
+
|
77
|
+
def serve_results(env)
|
78
|
+
request = Rack::Request.new(env)
|
79
|
+
page_struct = @storage.load(request['id'])
|
80
|
+
unless page_struct
|
81
|
+
@storage.set_viewed(user(env), request['Id'])
|
82
|
+
return [404, {}, ["No such result #{request['id']}"]]
|
83
|
+
end
|
84
|
+
unless page_struct['HasUserViewed']
|
85
|
+
page_struct['ClientTimings'].init_from_form_data(env, page_struct)
|
86
|
+
page_struct['HasUserViewed'] = true
|
87
|
+
@storage.save(page_struct)
|
88
|
+
@storage.set_viewed(user(env), page_struct['Id'])
|
89
|
+
end
|
90
|
+
|
91
|
+
result_json = page_struct.to_json
|
92
|
+
# If we're an XMLHttpRequest, serve up the contents as JSON
|
93
|
+
if request.xhr?
|
94
|
+
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
95
|
+
else
|
96
|
+
|
97
|
+
# Otherwise give the HTML back
|
98
|
+
html = MiniProfiler.share_template.dup
|
99
|
+
html.gsub!(/\{path\}/, @options[:base_url_path])
|
100
|
+
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
101
|
+
html.gsub!(/\{json\}/, result_json)
|
102
|
+
html.gsub!(/\{includes\}/, get_profile_script(env))
|
103
|
+
html.gsub!(/\{name\}/, page_struct['Name'])
|
104
|
+
html.gsub!(/\{duration\}/, page_struct.duration_ms.round(1).to_s)
|
105
|
+
|
106
|
+
[200, {'Content-Type' => 'text/html'}, [html]]
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def serve_html(env)
|
112
|
+
file_name = env['PATH_INFO'][(@options[:base_url_path].length)..1000]
|
113
|
+
return serve_results(env) if file_name.eql?('results')
|
114
|
+
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
115
|
+
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
116
|
+
f = Rack::File.new nil
|
117
|
+
f.path = full_path
|
118
|
+
f.cache_control = "max-age:86400"
|
119
|
+
f.serving env
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.current
|
123
|
+
Thread.current['profiler.mini.private']
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.current=(c)
|
127
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
128
|
+
Thread.current['profiler.mini.private'] = c
|
129
|
+
end
|
130
|
+
|
131
|
+
def current
|
132
|
+
MiniProfiler.current
|
133
|
+
end
|
134
|
+
|
135
|
+
def current=(c)
|
136
|
+
MiniProfiler.current=c
|
137
|
+
end
|
138
|
+
|
139
|
+
def options
|
140
|
+
@options
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.create_current(env={}, options={})
|
144
|
+
# profiling the request
|
145
|
+
self.current = {}
|
146
|
+
self.current['inject_js'] = options[:auto_inject] && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
147
|
+
self.current['page_struct'] = PageTimerStruct.new(env)
|
148
|
+
self.current['current_timer'] = current['page_struct']['Root']
|
149
|
+
end
|
150
|
+
|
151
|
+
def call(env)
|
152
|
+
status = headers = body = nil
|
153
|
+
|
154
|
+
# only profile if authorized
|
155
|
+
return @app.call(env) unless @options[:authorize_cb].call(env)
|
156
|
+
|
157
|
+
# handle all /mini-profiler requests here
|
158
|
+
return serve_html(env) if env['PATH_INFO'].start_with? @options[:base_url_path]
|
159
|
+
|
160
|
+
MiniProfiler.create_current(env, @options)
|
161
|
+
if env["QUERY_STRING"] =~ /pp=skip-backtrace/
|
162
|
+
current['skip-backtrace'] = true
|
163
|
+
end
|
164
|
+
|
165
|
+
start = Time.now
|
166
|
+
|
167
|
+
done_sampling = false
|
168
|
+
quit_sampler = false
|
169
|
+
backtraces = nil
|
170
|
+
if env["QUERY_STRING"] =~ /pp=sample/
|
171
|
+
backtraces = []
|
172
|
+
t = Thread.current
|
173
|
+
Thread.new {
|
174
|
+
i = 10000 # for sanity never grab more than 10k samples
|
175
|
+
unless done_sampling || i < 0
|
176
|
+
i -= 1
|
177
|
+
backtraces << t.backtrace
|
178
|
+
sleep 0.001
|
179
|
+
end
|
180
|
+
quit_sampler = true
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
status, headers, body = nil
|
185
|
+
begin
|
186
|
+
status,headers, body = @app.call(env)
|
187
|
+
ensure
|
188
|
+
if backtraces
|
189
|
+
done_sampling = true
|
190
|
+
sleep 0.001 until quit_sampler
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
page_struct = current['page_struct']
|
195
|
+
page_struct['Root'].record_time((Time.now - start) * 1000)
|
196
|
+
|
197
|
+
# inject headers, script
|
198
|
+
if status == 200
|
199
|
+
@storage.save(page_struct)
|
200
|
+
@storage.set_unviewed(user(env), page_struct['Id'])
|
201
|
+
|
202
|
+
# inject header
|
203
|
+
if headers.is_a? Hash
|
204
|
+
headers['X-MiniProfiler-Ids'] = ids_json(env)
|
205
|
+
end
|
206
|
+
|
207
|
+
# inject script
|
208
|
+
if current['inject_js'] \
|
209
|
+
&& headers.has_key?('Content-Type') \
|
210
|
+
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
211
|
+
body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
216
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
217
|
+
headers.delete('ETag')
|
218
|
+
headers.delete('Date')
|
219
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
220
|
+
[status, headers, body]
|
221
|
+
ensure
|
222
|
+
# Make sure this always happens
|
223
|
+
current = nil
|
224
|
+
end
|
225
|
+
|
226
|
+
def ids_json(env)
|
227
|
+
ids = [current['page_struct']["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
|
228
|
+
::JSON.generate(ids.uniq)
|
229
|
+
end
|
230
|
+
|
231
|
+
# get_profile_script returns script to be injected inside current html page
|
232
|
+
# By default, profile_script is appended to the end of all html requests automatically.
|
233
|
+
# Calling get_profile_script cancels automatic append for the current page
|
234
|
+
# Use it when:
|
235
|
+
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
236
|
+
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
237
|
+
def get_profile_script(env)
|
238
|
+
ids = ids_json(env)
|
239
|
+
path = @options[:base_url_path]
|
240
|
+
version = MiniProfiler::VERSION
|
241
|
+
position = @options[:position]
|
242
|
+
showTrivial = false
|
243
|
+
showChildren = false
|
244
|
+
maxTracesToShow = 10
|
245
|
+
showControls = false
|
246
|
+
currentId = current['page_struct']["Id"]
|
247
|
+
authorized = true
|
248
|
+
useExistingjQuery = false
|
249
|
+
# TODO : cache this snippet
|
250
|
+
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
251
|
+
# replace the variables
|
252
|
+
[:ids, :path, :version, :position, :showTrivial, :showChildren, :maxTracesToShow, :showControls, :currentId, :authorized, :useExistingjQuery].each do |v|
|
253
|
+
regex = Regexp.new("\\{#{v.to_s}\\}")
|
254
|
+
script.gsub!(regex, eval(v.to_s).to_s)
|
255
|
+
end
|
256
|
+
# replace the '{{' and '}}''
|
257
|
+
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
258
|
+
current['inject_js'] = false
|
259
|
+
script
|
260
|
+
end
|
261
|
+
|
262
|
+
# cancels automatic injection of profile script for the current page
|
263
|
+
def cancel_auto_inject(env)
|
264
|
+
current['inject_js'] = false
|
265
|
+
end
|
266
|
+
|
267
|
+
# perform a profiling step on given block
|
268
|
+
def self.step(name)
|
269
|
+
if current
|
270
|
+
old_timer = current['current_timer']
|
271
|
+
new_step = RequestTimerStruct.new(name, current['page_struct'])
|
272
|
+
current['current_timer'] = new_step
|
273
|
+
new_step['Name'] = name
|
274
|
+
start = Time.now
|
275
|
+
result = yield if block_given?
|
276
|
+
new_step.record_time((Time.now - start)*1000)
|
277
|
+
old_timer.add_child(new_step)
|
278
|
+
current['current_timer'] = old_timer
|
279
|
+
result
|
280
|
+
else
|
281
|
+
yield if block_given?
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def self.profile_method(klass, method, &blk)
|
286
|
+
default_name = klass.to_s + " " + method.to_s
|
287
|
+
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
288
|
+
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
289
|
+
|
290
|
+
klass.send :alias_method, without_profiling, method
|
291
|
+
klass.send :define_method, with_profiling do |*args, &orig|
|
292
|
+
name = default_name
|
293
|
+
name = blk.bind(self).call(*args) if blk
|
294
|
+
::Rack::MiniProfiler.step name do
|
295
|
+
self.send without_profiling, *args, &orig
|
296
|
+
end
|
297
|
+
end
|
298
|
+
klass.send :alias_method, method, with_profiling
|
299
|
+
end
|
300
|
+
|
301
|
+
def record_sql(query, elapsed_ms)
|
302
|
+
c = current
|
303
|
+
c['current_timer'].add_sql(query, elapsed_ms, c['page_struct'], c['skip-backtrace']) if (c && c['current_timer'])
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|