rack-mini-profiler 0.1.31 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -0
- data/README.md +285 -0
- data/{Ruby/lib → lib}/html/includes.css +15 -4
- data/{Ruby/lib → lib}/html/includes.js +95 -59
- data/{Ruby/lib → lib}/html/includes.less +21 -5
- data/{Ruby/lib → lib}/html/includes.tmpl +50 -50
- data/{Ruby/lib → lib}/html/jquery.1.7.1.js +0 -0
- data/{Ruby/lib → lib}/html/jquery.tmpl.js +0 -0
- data/{Ruby/lib → lib}/html/list.css +2 -2
- data/{Ruby/lib → lib}/html/list.js +1 -1
- data/lib/html/list.tmpl +34 -0
- data/lib/html/profile_handler.js +1 -0
- data/{Ruby/lib → lib}/html/share.html +2 -2
- data/lib/mini_profiler/asset_version.rb +5 -0
- data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +3 -3
- data/lib/mini_profiler/config.rb +65 -0
- data/{Ruby/lib → lib}/mini_profiler/context.rb +0 -0
- data/lib/mini_profiler/gc_profiler.rb +181 -0
- data/{Ruby/lib → lib}/mini_profiler/profiler.rb +120 -96
- data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +15 -17
- data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +30 -8
- data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +5 -7
- data/lib/mini_profiler/storage/memory_store.rb +115 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +19 -11
- data/lib/mini_profiler/timer_struct/base.rb +33 -0
- data/lib/mini_profiler/timer_struct/client.rb +89 -0
- data/lib/mini_profiler/timer_struct/custom.rb +22 -0
- data/lib/mini_profiler/timer_struct/page.rb +62 -0
- data/lib/mini_profiler/timer_struct/request.rb +126 -0
- data/lib/mini_profiler/timer_struct/sql.rb +59 -0
- data/lib/mini_profiler/version.rb +5 -0
- data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +23 -6
- data/lib/patches/db/activerecord.rb +42 -0
- data/lib/patches/db/moped.rb +12 -0
- data/lib/patches/db/mysql2.rb +30 -0
- data/lib/patches/db/pg.rb +104 -0
- data/lib/patches/db/plucky.rb +47 -0
- data/lib/patches/db/rsolr.rb +24 -0
- data/lib/patches/db/sequel.rb +10 -0
- data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
- data/lib/patches/sql_patches.rb +46 -0
- data/lib/rack-mini-profiler.rb +35 -0
- data/rack-mini-profiler.gemspec +28 -16
- metadata +171 -52
- data/Ruby/CHANGELOG +0 -161
- data/Ruby/README.md +0 -172
- data/Ruby/lib/html/list.tmpl +0 -34
- data/Ruby/lib/html/profile_handler.js +0 -1
- data/Ruby/lib/mini_profiler/client_timer_struct.rb +0 -78
- data/Ruby/lib/mini_profiler/config.rb +0 -58
- data/Ruby/lib/mini_profiler/custom_timer_struct.rb +0 -22
- data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
- data/Ruby/lib/mini_profiler/gc_profiler_ruby_head.rb +0 -40
- data/Ruby/lib/mini_profiler/page_timer_struct.rb +0 -58
- data/Ruby/lib/mini_profiler/request_timer_struct.rb +0 -115
- data/Ruby/lib/mini_profiler/sql_timer_struct.rb +0 -58
- data/Ruby/lib/mini_profiler/storage/memory_store.rb +0 -65
- data/Ruby/lib/mini_profiler/timer_struct.rb +0 -33
- data/Ruby/lib/mini_profiler/version.rb +0 -5
- data/Ruby/lib/patches/sql_patches.rb +0 -277
- data/Ruby/lib/rack-mini-profiler.rb +0 -7
File without changes
|
File without changes
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
tbody tr:nth-child(odd) { background-color:#eee; }
|
2
2
|
tbody tr:nth-child(even) { background-color:#fff; }
|
3
3
|
table { border: 0; border-spacing:0;}
|
4
4
|
tr {border: 0;}
|
@@ -6,4 +6,4 @@ tr {border: 0;}
|
|
6
6
|
td {padding: 8px;}
|
7
7
|
.time {text-align:center;}
|
8
8
|
thead tr {background-color: #bbb; color: #444; font-size: 12px;}
|
9
|
-
thead tr th { padding: 5px 15px;}
|
9
|
+
thead tr th { padding: 5px 15px;}
|
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">${duration_milliseconds_in_sql}</td>
|
25
|
+
<td class="time">${duration_milliseconds}</td>
|
26
|
+
{{if client_timings}}
|
27
|
+
<td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Request").start}</td>
|
28
|
+
<td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Response").start}</td>
|
29
|
+
<td class="time">${MiniProfiler.getClientTimingByName(client_timings,"Dom Complete").start}</td>
|
30
|
+
{{else}}
|
31
|
+
<td colspan="3"></td>
|
32
|
+
{{/if}}
|
33
|
+
</tr>
|
34
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
<script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
<html>
|
2
2
|
<head>
|
3
3
|
<title>{name} ({duration} ms) - Profiling Results</title>
|
4
4
|
<script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
|
@@ -8,4 +8,4 @@
|
|
8
8
|
<body>
|
9
9
|
<div class='profiler-result-full'></div>
|
10
10
|
</body>
|
11
|
-
</html>
|
11
|
+
</html>
|
@@ -5,8 +5,8 @@ module Rack
|
|
5
5
|
COOKIE_NAME = "__profilin"
|
6
6
|
|
7
7
|
BACKTRACE_DEFAULT = nil
|
8
|
-
BACKTRACE_FULL
|
9
|
-
BACKTRACE_NONE
|
8
|
+
BACKTRACE_FULL = 1
|
9
|
+
BACKTRACE_NONE = 2
|
10
10
|
|
11
11
|
attr_accessor :disable_profiling
|
12
12
|
attr_accessor :backtrace_level
|
@@ -30,7 +30,7 @@ module Rack
|
|
30
30
|
def write!(headers)
|
31
31
|
if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
|
32
32
|
settings = {"p" => "t" }
|
33
|
-
settings["dp"] = "t"
|
33
|
+
settings["dp"] = "t" if @disable_profiling
|
34
34
|
settings["bt"] = @backtrace_level if @backtrace_level
|
35
35
|
settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
|
36
36
|
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class Config
|
4
|
+
|
5
|
+
def self.attr_accessor(*vars)
|
6
|
+
@attributes ||= []
|
7
|
+
@attributes.concat vars
|
8
|
+
super(*vars)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.attributes
|
12
|
+
@attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores, :backtrace_includes, :backtrace_remove,
|
16
|
+
:backtrace_threshold_ms, :base_url_path, :disable_caching, :enabled, :flamegraph_sample_rate, :logger, :position,
|
17
|
+
:pre_authorize_cb, :skip_paths, :skip_schema_queries, :start_hidden, :storage, :storage_failure,
|
18
|
+
:storage_instance, :storage_options, :toggle_shortcut, :user_provider
|
19
|
+
|
20
|
+
# Deprecated options
|
21
|
+
attr_accessor :use_existing_jquery
|
22
|
+
|
23
|
+
def self.default
|
24
|
+
new.instance_eval {
|
25
|
+
@auto_inject = true # automatically inject on every html page
|
26
|
+
@base_url_path = "/mini-profiler-resources/"
|
27
|
+
@disable_caching = true
|
28
|
+
# called prior to rack chain, to ensure we are allowed to profile
|
29
|
+
@pre_authorize_cb = lambda {|env| true}
|
30
|
+
|
31
|
+
# called after rack chain, to ensure we are REALLY allowed to profile
|
32
|
+
@position = 'left' # Where it is displayed
|
33
|
+
@skip_schema_queries = false
|
34
|
+
@storage = MiniProfiler::MemoryStore
|
35
|
+
@user_provider = Proc.new{|env| Rack::Request.new(env).ip}
|
36
|
+
@authorization_mode = :allow_all
|
37
|
+
@toggle_shortcut = 'Alt+P'
|
38
|
+
@start_hidden = false
|
39
|
+
@backtrace_threshold_ms = 0
|
40
|
+
@flamegraph_sample_rate = 0.5
|
41
|
+
@storage_failure = Proc.new do |exception|
|
42
|
+
if @logger
|
43
|
+
@logger.warn("MiniProfiler storage failure: #{exception.message}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@enabled = true
|
47
|
+
self
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def merge!(config)
|
52
|
+
return unless config
|
53
|
+
if Hash === config
|
54
|
+
config.each{|k,v| instance_variable_set "@#{k}",v}
|
55
|
+
else
|
56
|
+
self.class.attributes.each{ |k|
|
57
|
+
v = config.send k
|
58
|
+
instance_variable_set "@#{k}", v if v
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
File without changes
|
@@ -0,0 +1,181 @@
|
|
1
|
+
class Rack::MiniProfiler::GCProfiler
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@ignore = []
|
5
|
+
@ignore << @ignore.__id__
|
6
|
+
end
|
7
|
+
|
8
|
+
def object_space_stats
|
9
|
+
stats = {}
|
10
|
+
ids = {}
|
11
|
+
|
12
|
+
@ignore << stats.__id__
|
13
|
+
@ignore << ids.__id__
|
14
|
+
|
15
|
+
i=0
|
16
|
+
ObjectSpace.each_object { |o|
|
17
|
+
begin
|
18
|
+
i = stats[o.class] || 0
|
19
|
+
i += 1
|
20
|
+
stats[o.class] = i
|
21
|
+
ids[o.__id__] = o if Integer === o.__id__
|
22
|
+
rescue NoMethodError
|
23
|
+
# protect against BasicObject
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
@ignore.each do |id|
|
28
|
+
if ids.delete(id)
|
29
|
+
klass = ObjectSpace._id2ref(id).class
|
30
|
+
stats[klass] -= 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
result = {:stats => stats, :ids => ids}
|
35
|
+
@ignore << result.__id__
|
36
|
+
|
37
|
+
result
|
38
|
+
end
|
39
|
+
|
40
|
+
def diff_object_stats(before, after)
|
41
|
+
diff = {}
|
42
|
+
after.each do |k,v|
|
43
|
+
diff[k] = v - (before[k] || 0)
|
44
|
+
end
|
45
|
+
before.each do |k,v|
|
46
|
+
diff[k] = 0 - v unless after[k]
|
47
|
+
end
|
48
|
+
|
49
|
+
diff
|
50
|
+
end
|
51
|
+
|
52
|
+
def analyze_strings(ids_before, ids_after)
|
53
|
+
result = {}
|
54
|
+
ids_after.each do |id,_|
|
55
|
+
obj = ObjectSpace._id2ref(id)
|
56
|
+
if String === obj && !ids_before.include?(obj.object_id)
|
57
|
+
result[obj] ||= 0
|
58
|
+
result[obj] += 1
|
59
|
+
end
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def analyze_growth(ids_before, ids_after)
|
65
|
+
new_objects = 0
|
66
|
+
memory_allocated = 0
|
67
|
+
|
68
|
+
ids_after.each do |id,_|
|
69
|
+
if !ids_before.include?(id) && obj=ObjectSpace._id2ref(id)
|
70
|
+
# this is going to be version specific (may change in 2.1)
|
71
|
+
size = ObjectSpace.memsize_of(obj)
|
72
|
+
memory_allocated += size
|
73
|
+
new_objects += 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
[new_objects, memory_allocated]
|
78
|
+
end
|
79
|
+
|
80
|
+
def analyze_initial_state(ids_before)
|
81
|
+
memory_allocated = 0
|
82
|
+
objects = 0
|
83
|
+
|
84
|
+
ids_before.each do |id,_|
|
85
|
+
if obj=ObjectSpace._id2ref(id)
|
86
|
+
# this is going to be version specific (may change in 2.1)
|
87
|
+
memory_allocated += ObjectSpace.memsize_of(obj)
|
88
|
+
objects += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
[objects,memory_allocated]
|
93
|
+
end
|
94
|
+
|
95
|
+
def profile_gc_time(app, env)
|
96
|
+
body = []
|
97
|
+
|
98
|
+
begin
|
99
|
+
GC::Profiler.clear
|
100
|
+
prev_profiler_state = GC::Profiler.enabled?
|
101
|
+
prev_gc_state = GC.enable
|
102
|
+
GC::Profiler.enable
|
103
|
+
b = app.call(env)[2]
|
104
|
+
b.close if b.respond_to? :close
|
105
|
+
body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
|
106
|
+
body << GC::Profiler.result
|
107
|
+
ensure
|
108
|
+
prev_gc_state ? GC.disable : GC.enable
|
109
|
+
GC::Profiler.disable unless prev_profiler_state
|
110
|
+
end
|
111
|
+
|
112
|
+
return [200, {'Content-Type' => 'text/plain'}, body]
|
113
|
+
end
|
114
|
+
|
115
|
+
def profile_gc(app, env)
|
116
|
+
|
117
|
+
# for memsize_of
|
118
|
+
require 'objspace'
|
119
|
+
|
120
|
+
body = [];
|
121
|
+
|
122
|
+
stat_before,stat_after,diff,string_analysis,
|
123
|
+
new_objects, memory_allocated, stat, memory_before, objects_before = nil
|
124
|
+
|
125
|
+
# clean up before
|
126
|
+
GC.start
|
127
|
+
stat = GC.stat
|
128
|
+
prev_gc_state = GC.disable
|
129
|
+
stat_before = object_space_stats
|
130
|
+
b = app.call(env)[2]
|
131
|
+
b.close if b.respond_to? :close
|
132
|
+
stat_after = object_space_stats
|
133
|
+
# so we don't blow out on memory
|
134
|
+
prev_gc_state ? GC.disable : GC.enable
|
135
|
+
|
136
|
+
diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
|
137
|
+
string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
|
138
|
+
new_objects, memory_allocated = analyze_growth(stat_before[:ids], stat_after[:ids])
|
139
|
+
objects_before, memory_before = analyze_initial_state(stat_before[:ids])
|
140
|
+
|
141
|
+
|
142
|
+
body << "
|
143
|
+
Overview
|
144
|
+
------------------------------------
|
145
|
+
Initial state: object count - #{objects_before} , memory allocated outside heap (bytes) #{memory_before}
|
146
|
+
|
147
|
+
GC Stats: #{stat.map{|k,v| "#{k} : #{v}" }.join(", ")}
|
148
|
+
|
149
|
+
New bytes allocated outside of Ruby heaps: #{memory_allocated}
|
150
|
+
New objects: #{new_objects}
|
151
|
+
"
|
152
|
+
|
153
|
+
body << "
|
154
|
+
ObjectSpace delta caused by request:
|
155
|
+
--------------------------------------------\n"
|
156
|
+
diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
|
157
|
+
body << "#{k} : #{v}\n" if v != 0
|
158
|
+
end
|
159
|
+
|
160
|
+
body << "\n
|
161
|
+
ObjectSpace stats:
|
162
|
+
-----------------\n"
|
163
|
+
|
164
|
+
stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
|
165
|
+
body << "#{k} : #{v}\n"
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
body << "\n
|
170
|
+
String stats:
|
171
|
+
------------\n"
|
172
|
+
|
173
|
+
string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
|
174
|
+
body << "#{count} : #{string}\n"
|
175
|
+
end
|
176
|
+
|
177
|
+
return [200, {'Content-Type' => 'text/plain'}, body]
|
178
|
+
ensure
|
179
|
+
prev_gc_state ? GC.disable : GC.enable
|
180
|
+
end
|
181
|
+
end
|
@@ -1,30 +1,5 @@
|
|
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
|
-
# TODO
|
22
|
-
# require 'mini_profiler/gc_profiler_ruby_head' if Gem::Version.new('2.1.0') <= Gem::Version.new(RUBY_VERSION)
|
23
|
-
|
24
1
|
module Rack
|
25
|
-
|
26
2
|
class MiniProfiler
|
27
|
-
|
28
3
|
class << self
|
29
4
|
|
30
5
|
include Rack::MiniProfiler::ProfilingMethods
|
@@ -43,8 +18,7 @@ module Rack
|
|
43
18
|
end
|
44
19
|
|
45
20
|
def share_template
|
46
|
-
|
47
|
-
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
21
|
+
@share_template ||= ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
48
22
|
end
|
49
23
|
|
50
24
|
def current
|
@@ -53,7 +27,7 @@ module Rack
|
|
53
27
|
|
54
28
|
def current=(c)
|
55
29
|
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
56
|
-
Thread.current[:mini_profiler_private]= c
|
30
|
+
Thread.current[:mini_profiler_private] = c
|
57
31
|
end
|
58
32
|
|
59
33
|
# discard existing results, don't track this request
|
@@ -63,10 +37,10 @@ module Rack
|
|
63
37
|
|
64
38
|
def create_current(env={}, options={})
|
65
39
|
# profiling the request
|
66
|
-
self.current
|
67
|
-
self.current.inject_js
|
68
|
-
self.current.page_struct
|
69
|
-
self.current.current_timer = current.page_struct[
|
40
|
+
self.current = Context.new
|
41
|
+
self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
42
|
+
self.current.page_struct = TimerStruct::Page.new(env)
|
43
|
+
self.current.current_timer = current.page_struct[:root]
|
70
44
|
end
|
71
45
|
|
72
46
|
def authorize_request
|
@@ -89,7 +63,7 @@ module Rack
|
|
89
63
|
def initialize(app, config = nil)
|
90
64
|
MiniProfiler.config.merge!(config)
|
91
65
|
@config = MiniProfiler.config
|
92
|
-
@app
|
66
|
+
@app = app
|
93
67
|
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
94
68
|
unless @config.storage_instance
|
95
69
|
@config.storage_instance = @config.storage.new(@config.storage_options)
|
@@ -102,16 +76,18 @@ module Rack
|
|
102
76
|
end
|
103
77
|
|
104
78
|
def serve_results(env)
|
105
|
-
request
|
106
|
-
id
|
79
|
+
request = Rack::Request.new(env)
|
80
|
+
id = request[:id]
|
107
81
|
page_struct = @storage.load(id)
|
108
82
|
unless page_struct
|
109
83
|
@storage.set_viewed(user(env), id)
|
110
|
-
|
84
|
+
id = ERB::Util.html_escape(request['id'])
|
85
|
+
user_info = ERB::Util.html_escape(user(env))
|
86
|
+
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
111
87
|
end
|
112
|
-
unless page_struct[
|
113
|
-
page_struct[
|
114
|
-
page_struct[
|
88
|
+
unless page_struct[:has_user_viewed]
|
89
|
+
page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
|
90
|
+
page_struct[:has_user_viewed] = true
|
115
91
|
@storage.save(page_struct)
|
116
92
|
@storage.set_viewed(user(env), id)
|
117
93
|
end
|
@@ -125,10 +101,10 @@ module Rack
|
|
125
101
|
# Otherwise give the HTML back
|
126
102
|
html = MiniProfiler.share_template.dup
|
127
103
|
html.gsub!(/\{path\}/, "#{env['SCRIPT_NAME']}#{@config.base_url_path}")
|
128
|
-
html.gsub!(/\{version\}/, MiniProfiler::
|
104
|
+
html.gsub!(/\{version\}/, MiniProfiler::ASSET_VERSION)
|
129
105
|
html.gsub!(/\{json\}/, result_json)
|
130
106
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
131
|
-
html.gsub!(/\{name\}/, page_struct[
|
107
|
+
html.gsub!(/\{name\}/, page_struct[:name])
|
132
108
|
html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
|
133
109
|
|
134
110
|
[200, {'Content-Type' => 'text/html'}, [html]]
|
@@ -138,10 +114,12 @@ module Rack
|
|
138
114
|
|
139
115
|
def serve_html(env)
|
140
116
|
file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
|
117
|
+
|
141
118
|
return serve_results(env) if file_name.eql?('results')
|
119
|
+
|
142
120
|
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
143
121
|
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
144
|
-
f
|
122
|
+
f = Rack::File.new nil
|
145
123
|
f.path = full_path
|
146
124
|
|
147
125
|
begin
|
@@ -162,7 +140,7 @@ module Rack
|
|
162
140
|
end
|
163
141
|
|
164
142
|
def current=(c)
|
165
|
-
MiniProfiler.current=c
|
143
|
+
MiniProfiler.current = c
|
166
144
|
end
|
167
145
|
|
168
146
|
|
@@ -177,7 +155,7 @@ module Rack
|
|
177
155
|
|
178
156
|
status = headers = body = nil
|
179
157
|
query_string = env['QUERY_STRING']
|
180
|
-
path
|
158
|
+
path = env['PATH_INFO']
|
181
159
|
|
182
160
|
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
183
161
|
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
@@ -202,11 +180,12 @@ module Rack
|
|
202
180
|
skip_it = true
|
203
181
|
end
|
204
182
|
|
205
|
-
if query_string =~ /pp=enable/
|
183
|
+
if query_string =~ /pp=enable/ && (@config.authorization_mode != :whitelist || MiniProfiler.request_authorized?)
|
206
184
|
skip_it = false
|
185
|
+
config.enabled = true
|
207
186
|
end
|
208
187
|
|
209
|
-
if skip_it
|
188
|
+
if skip_it || !config.enabled
|
210
189
|
status,headers,body = @app.call(env)
|
211
190
|
client_settings.disable_profiling = true
|
212
191
|
client_settings.write!(headers)
|
@@ -216,8 +195,18 @@ module Rack
|
|
216
195
|
end
|
217
196
|
|
218
197
|
if query_string =~ /pp=profile-gc/
|
198
|
+
current.measure = false if current
|
199
|
+
|
219
200
|
if query_string =~ /pp=profile-gc-time/
|
220
201
|
return Rack::MiniProfiler::GCProfiler.new.profile_gc_time(@app, env)
|
202
|
+
elsif query_string =~ /pp=profile-gc-ruby-head/
|
203
|
+
result = StringIO.new
|
204
|
+
report = MemoryProfiler.report do
|
205
|
+
_,_,body = @app.call(env)
|
206
|
+
body.close if body.respond_to? :close
|
207
|
+
end
|
208
|
+
report.pretty_print(result)
|
209
|
+
return text_result(result.string)
|
221
210
|
else
|
222
211
|
return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
|
223
212
|
end
|
@@ -247,7 +236,7 @@ module Rack
|
|
247
236
|
|
248
237
|
if trace_exceptions
|
249
238
|
exceptions = []
|
250
|
-
trace
|
239
|
+
trace = TracePoint.new(:raise) do |tp|
|
251
240
|
exceptions << tp.raised_exception
|
252
241
|
end
|
253
242
|
trace.enable
|
@@ -257,8 +246,10 @@ module Rack
|
|
257
246
|
|
258
247
|
# Strip all the caching headers so we don't get 304s back
|
259
248
|
# This solves a very annoying bug where rack mini profiler never shows up
|
260
|
-
|
261
|
-
|
249
|
+
if config.disable_caching
|
250
|
+
env['HTTP_IF_MODIFIED_SINCE'] = ''
|
251
|
+
env['HTTP_IF_NONE_MATCH'] = ''
|
252
|
+
end
|
262
253
|
|
263
254
|
if query_string =~ /pp=flamegraph/
|
264
255
|
unless defined?(Flamegraph) && Flamegraph.respond_to?(:generate)
|
@@ -268,9 +259,16 @@ module Rack
|
|
268
259
|
else
|
269
260
|
# do not sully our profile with mini profiler timings
|
270
261
|
current.measure = false
|
271
|
-
|
272
|
-
|
273
|
-
|
262
|
+
match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
|
263
|
+
|
264
|
+
mode = query_string =~ /mode=c/ ? :c : :ruby
|
265
|
+
|
266
|
+
if match_data && !match_data[1].to_f.zero?
|
267
|
+
sample_rate = match_data[1].to_f
|
268
|
+
else
|
269
|
+
sample_rate = config.flamegraph_sample_rate
|
270
|
+
end
|
271
|
+
flamegraph = Flamegraph.generate(nil, :fidelity => sample_rate, :embed_resources => query_string =~ /embed/, :mode => mode) do
|
274
272
|
status,headers,body = @app.call(env)
|
275
273
|
end
|
276
274
|
end
|
@@ -312,8 +310,8 @@ module Rack
|
|
312
310
|
end
|
313
311
|
|
314
312
|
page_struct = current.page_struct
|
315
|
-
page_struct[
|
316
|
-
page_struct[
|
313
|
+
page_struct[:user] = user(env)
|
314
|
+
page_struct[:root].record_time((Time.now - start) * 1000)
|
317
315
|
|
318
316
|
if flamegraph
|
319
317
|
body.close if body.respond_to? :close
|
@@ -321,16 +319,21 @@ module Rack
|
|
321
319
|
end
|
322
320
|
|
323
321
|
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
# inject headers, script
|
329
|
-
if headers['Content-Type'] && status == 200
|
330
|
-
client_settings.write!(headers)
|
322
|
+
begin
|
323
|
+
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
324
|
+
@storage.set_unviewed(page_struct[:user], page_struct[:id])
|
325
|
+
@storage.save(page_struct)
|
331
326
|
|
332
|
-
|
333
|
-
|
327
|
+
# inject headers, script
|
328
|
+
if headers['Content-Type'] && status == 200
|
329
|
+
client_settings.write!(headers)
|
330
|
+
result = inject_profiler(env,status,headers,body)
|
331
|
+
return result if result
|
332
|
+
end
|
333
|
+
rescue Exception => e
|
334
|
+
if @config.storage_failure != nil
|
335
|
+
@config.storage_failure.call(e)
|
336
|
+
end
|
334
337
|
end
|
335
338
|
|
336
339
|
client_settings.write!(headers)
|
@@ -338,7 +341,7 @@ module Rack
|
|
338
341
|
|
339
342
|
ensure
|
340
343
|
# Make sure this always happens
|
341
|
-
current = nil
|
344
|
+
self.current = nil
|
342
345
|
end
|
343
346
|
|
344
347
|
def inject_profiler(env,status,headers,body)
|
@@ -346,9 +349,12 @@ module Rack
|
|
346
349
|
# Rack::ETag has already inserted some nonesense in the chain
|
347
350
|
content_type = headers['Content-Type']
|
348
351
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
+
if config.disable_caching
|
353
|
+
headers.delete('ETag')
|
354
|
+
headers.delete('Date')
|
355
|
+
end
|
356
|
+
|
357
|
+
headers['Cache-Control'] = "#{"no-store, " if config.disable_caching}must-revalidate, private, max-age=0"
|
352
358
|
|
353
359
|
# inject header
|
354
360
|
if headers.is_a? Hash
|
@@ -357,7 +363,7 @@ module Rack
|
|
357
363
|
|
358
364
|
if current.inject_js && content_type =~ /text\/html/
|
359
365
|
response = Rack::Response.new([], status, headers)
|
360
|
-
script
|
366
|
+
script = self.get_profile_script(env)
|
361
367
|
|
362
368
|
if String === body
|
363
369
|
response.write inject(body,script)
|
@@ -383,9 +389,9 @@ module Rack
|
|
383
389
|
regex = /<\/html>/i
|
384
390
|
close_tag = '</html>'
|
385
391
|
else
|
386
|
-
# implicit </body> and </html>.
|
392
|
+
# implicit </body> and </html>. Don't do anything.
|
387
393
|
|
388
|
-
return fragment
|
394
|
+
return fragment
|
389
395
|
end
|
390
396
|
|
391
397
|
matches = fragment.scan(regex).length
|
@@ -410,7 +416,7 @@ module Rack
|
|
410
416
|
|
411
417
|
def dump_exceptions(exceptions)
|
412
418
|
headers = {'Content-Type' => 'text/plain'}
|
413
|
-
body
|
419
|
+
body = "Exceptions (#{exceptions.length} raised during request)\n\n"
|
414
420
|
exceptions.each do |e|
|
415
421
|
body << "#{e.class} #{e.message}\n#{e.backtrace.join("\n")}\n\n\n\n"
|
416
422
|
end
|
@@ -419,7 +425,6 @@ module Rack
|
|
419
425
|
end
|
420
426
|
|
421
427
|
def dump_env(env)
|
422
|
-
headers = {'Content-Type' => 'text/plain'}
|
423
428
|
body = "Rack Environment\n---------------\n"
|
424
429
|
env.each do |k,v|
|
425
430
|
body << "#{k}: #{v}\n"
|
@@ -438,6 +443,11 @@ module Rack
|
|
438
443
|
body << "User #{user(env)}\n"
|
439
444
|
body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
|
440
445
|
|
446
|
+
text_result(body)
|
447
|
+
end
|
448
|
+
|
449
|
+
def text_result(body)
|
450
|
+
headers = {'Content-Type' => 'text/plain'}
|
441
451
|
[200, headers, [body]]
|
442
452
|
end
|
443
453
|
|
@@ -455,7 +465,10 @@ module Rack
|
|
455
465
|
pp=enable : enable profiling for this session (if previously disabled)
|
456
466
|
pp=profile-gc: perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
457
467
|
pp=profile-gc-time: perform built-in gc profiling on this request (ruby 1.9.3 only)
|
468
|
+
pp=profile-gc-ruby-head: requires the memory_profiler gem, new location based report
|
458
469
|
pp=flamegraph: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
|
470
|
+
pp=flamegraph&flamegraph_sample_rate=1: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
471
|
+
pp=flamegraph_embed: works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem), embedded resources for use on an intranet.
|
459
472
|
pp=trace-exceptions: requires Ruby 2.0, will return all the spots where your application raises execptions
|
460
473
|
"
|
461
474
|
|
@@ -468,16 +481,17 @@ module Rack
|
|
468
481
|
[200, headers, [graph]]
|
469
482
|
end
|
470
483
|
|
471
|
-
def
|
484
|
+
def ids(env)
|
472
485
|
# cap at 10 ids, otherwise there is a chance you can blow the header
|
473
|
-
|
474
|
-
|
486
|
+
([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]).uniq
|
487
|
+
end
|
488
|
+
|
489
|
+
def ids_json(env)
|
490
|
+
::JSON.generate(ids(env))
|
475
491
|
end
|
476
492
|
|
477
493
|
def ids_comma_separated(env)
|
478
|
-
|
479
|
-
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]
|
480
|
-
ids.join(",")
|
494
|
+
ids(env).join(",")
|
481
495
|
end
|
482
496
|
|
483
497
|
# get_profile_script returns script to be injected inside current html page
|
@@ -487,26 +501,38 @@ module Rack
|
|
487
501
|
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
488
502
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
489
503
|
def get_profile_script(env)
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
504
|
+
path = "#{env['SCRIPT_NAME']}#{@config.base_url_path}"
|
505
|
+
|
506
|
+
settings = {
|
507
|
+
:path => path,
|
508
|
+
:version => MiniProfiler::ASSET_VERSION,
|
509
|
+
:position => @config.position,
|
510
|
+
:showTrivial => false,
|
511
|
+
:showChildren => false,
|
512
|
+
:maxTracesToShow => 10,
|
513
|
+
:showControls => false,
|
514
|
+
:authorized => true,
|
515
|
+
:toggleShortcut => @config.toggle_shortcut,
|
516
|
+
:startHidden => @config.start_hidden
|
517
|
+
}
|
518
|
+
|
519
|
+
if current && current.page_struct
|
520
|
+
settings[:ids] = ids_comma_separated(env)
|
521
|
+
settings[:currentId] = current.page_struct[:id]
|
522
|
+
else
|
523
|
+
settings[:ids] = []
|
524
|
+
settings[:currentId] = ""
|
525
|
+
end
|
526
|
+
|
502
527
|
# TODO : cache this snippet
|
503
528
|
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
504
529
|
# replace the variables
|
505
|
-
|
506
|
-
regex = Regexp.new("\\{#{
|
507
|
-
script.gsub!(regex,
|
530
|
+
settings.each do |k,v|
|
531
|
+
regex = Regexp.new("\\{#{k.to_s}\\}")
|
532
|
+
script.gsub!(regex, v.to_s)
|
508
533
|
end
|
509
|
-
|
534
|
+
|
535
|
+
current.inject_js = false if current
|
510
536
|
script
|
511
537
|
end
|
512
538
|
|
@@ -516,6 +542,4 @@ module Rack
|
|
516
542
|
end
|
517
543
|
|
518
544
|
end
|
519
|
-
|
520
545
|
end
|
521
|
-
|