rack-mini-profiler 0.1 → 0.1.5
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/CHANGELOG +26 -0
- data/README.md +21 -24
- data/lib/html/includes.js +12 -0
- data/lib/mini_profiler/client_timer_struct.rb +33 -0
- data/lib/mini_profiler/config.rb +51 -0
- data/lib/mini_profiler/context.rb +10 -0
- data/lib/mini_profiler/page_timer_struct.rb +6 -2
- data/lib/mini_profiler/profiler.rb +227 -131
- data/lib/mini_profiler/profiling_methods.rb +73 -0
- data/lib/mini_profiler/request_timer_struct.rb +40 -9
- data/lib/mini_profiler/sql_timer_struct.rb +16 -5
- data/lib/mini_profiler/storage/redis_store.rb +2 -1
- data/lib/mini_profiler_rails/railtie.rb +62 -10
- data/lib/patches/sql_patches.rb +125 -19
- data/rack-mini-profiler.gemspec +4 -3
- metadata +9 -4
data/CHANGELOG
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
28-June-2012 - Sam
|
2
|
+
|
3
|
+
* Started change log
|
4
|
+
* Corrected profiler so it properly captures POST requests (was supressing non 200s)
|
5
|
+
* Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
|
6
|
+
* Fixed bug where unviewed missing ids never got cleared
|
7
|
+
* Supress all '/assets/' in the rails tie (makes debugging easier)
|
8
|
+
* record_sql was mega buggy
|
9
|
+
|
10
|
+
9-July-2012 - Sam
|
11
|
+
|
12
|
+
* Cleaned up mechanism for profiling in production, all you need to do now
|
13
|
+
is call Rack::MiniProfiler.authorize_request to get profiling working in
|
14
|
+
production
|
15
|
+
* Added option to display full backtraces pp=full-backtrace
|
16
|
+
* Cleaned up railties, got rid of the post authorize callback
|
17
|
+
* Version 0.1.3
|
18
|
+
|
19
|
+
12-July-2012 - Sam
|
20
|
+
|
21
|
+
* Fixed incorrect profiling steps (was not indenting or measuring start time right
|
22
|
+
* Implemented native PG and MySql2 interceptors, this gives way more accurate times
|
23
|
+
* Refactored context so its a proper class and not a hash
|
24
|
+
* Added some more client probing built in to rails
|
25
|
+
* More tests
|
26
|
+
|
data/README.md
CHANGED
@@ -1,27 +1,31 @@
|
|
1
1
|
# rack-mini-profiler
|
2
2
|
|
3
|
-
Middleware that displays speed badge for every html page.
|
3
|
+
Middleware that displays speed badge for every html page. Designed to work both in production and in development.
|
4
4
|
|
5
|
-
##
|
6
|
-
|
7
|
-
MiniProfiler keeps you aware of your site's performance as you are developing it.
|
8
|
-
It does this by....
|
9
|
-
|
10
|
-
`env['profiler.mini']` is the profiler
|
11
|
-
|
12
|
-
## Using mini-profiler in your app
|
5
|
+
## Using rack-mini-profiler in your app
|
13
6
|
|
14
7
|
Install/add to Gemfile
|
15
8
|
|
16
9
|
```ruby
|
17
10
|
gem 'rack-mini-profiler'
|
18
11
|
```
|
12
|
+
Using Rails:
|
19
13
|
|
20
|
-
|
14
|
+
All you have to do is include the Gem and you're good to go in development.
|
21
15
|
|
22
|
-
|
16
|
+
rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
|
17
|
+
|
18
|
+
For example:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# A hook in your ApplicationController
|
22
|
+
def authorize
|
23
|
+
if current_user.is_admin?
|
24
|
+
Rack::MiniProfiler.authorize_request
|
25
|
+
end
|
26
|
+
end
|
27
|
+
````
|
23
28
|
|
24
|
-
All you have to do is include the Gem and you're good to go.
|
25
29
|
|
26
30
|
Using Builder:
|
27
31
|
|
@@ -58,25 +62,18 @@ You can set configuration options using the configuration accessor on Rack::Mini
|
|
58
62
|
|
59
63
|
```
|
60
64
|
# Have Mini Profiler show up on the right
|
61
|
-
Rack::MiniProfiler.
|
65
|
+
Rack::MiniProfiler.config.position = 'right'
|
62
66
|
```
|
63
67
|
|
64
68
|
In a Rails app, this can be done conveniently in an initializer such as config/initializers/mini_profiler.rb.
|
65
69
|
|
66
70
|
## Available Options
|
67
71
|
|
68
|
-
*
|
72
|
+
* pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
|
69
73
|
* position - Can either be 'right' or 'left'. Default is 'left'.
|
70
|
-
* skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'true'
|
71
|
-
|
74
|
+
* skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development.
|
72
75
|
|
73
|
-
##
|
76
|
+
## Special query strings
|
74
77
|
|
75
|
-
|
76
|
-
- Decide if we hook up SQL at the driver level (eg mysql gem) or library level (eg active record) - my personal perference is to do driver level hooks (Sam)
|
77
|
-
- Add automatic instrumentation for Rails (Controller times, Action times, Partial times, Layout times)
|
78
|
-
- Grab / display the parameters of SQL executed for parameterized SQL
|
79
|
-
- Beef up the documentation
|
80
|
-
- Auto-wire-up rails middleware
|
81
|
-
- Review our API and ensure it is trivial
|
78
|
+
If you include the query string `pp=help` at the end of your request you will see the various option you have. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
|
82
79
|
|
data/lib/html/includes.js
CHANGED
@@ -475,6 +475,18 @@ var MiniProfiler = (function ($) {
|
|
475
475
|
})();
|
476
476
|
}
|
477
477
|
|
478
|
+
// also fetch results after ExtJS requests, in case it is being used
|
479
|
+
if (typeof (Ext) != 'undefined' && typeof (Ext.Ajax) != 'undefined' && typeof (Ext.Ajax.on) != 'undefined') {
|
480
|
+
// Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
|
481
|
+
Ext.Ajax.on('requestcomplete', function(e, xhr, settings) {
|
482
|
+
var stringIds = xhr.getResponseHeader('X-MiniProfiler-Ids');
|
483
|
+
if (stringIds) {
|
484
|
+
var ids = typeof JSON != 'undefined' ? JSON.parse(stringIds) : eval(stringIds);
|
485
|
+
fetchResults(ids);
|
486
|
+
}
|
487
|
+
});
|
488
|
+
}
|
489
|
+
|
478
490
|
// some elements want to be hidden on certain doc events
|
479
491
|
bindDocumentEvents();
|
480
492
|
};
|
@@ -6,6 +6,19 @@ module Rack
|
|
6
6
|
# This class holds the client timings
|
7
7
|
class ClientTimerStruct < TimerStruct
|
8
8
|
|
9
|
+
def self.init_instrumentation
|
10
|
+
"<script type=\"text/javascript\">mPt=function(){var t=[];return{t:t,probe:function(n){t.push({d:new Date(),n:n})}}}()</script>"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.instrument(name,orig)
|
14
|
+
probe = "<script>mPt.probe('#{name}')</script>"
|
15
|
+
wrapped = probe
|
16
|
+
wrapped << orig
|
17
|
+
wrapped << probe
|
18
|
+
wrapped
|
19
|
+
end
|
20
|
+
|
21
|
+
|
9
22
|
def initialize(env={})
|
10
23
|
super
|
11
24
|
end
|
@@ -21,6 +34,26 @@ module Rack
|
|
21
34
|
baseTime = clientTimes['navigationStart'].to_i if clientTimes
|
22
35
|
return unless clientTimes && baseTime
|
23
36
|
|
37
|
+
probes = form['clientProbes']
|
38
|
+
translated = {}
|
39
|
+
if probes
|
40
|
+
probes.each do |id, val|
|
41
|
+
name = val["n"]
|
42
|
+
translated[name] ||= {}
|
43
|
+
if translated[name][:start]
|
44
|
+
translated[name][:finish] = val["d"]
|
45
|
+
else
|
46
|
+
translated[name][:start] = val["d"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
translated.each do |name, data|
|
52
|
+
h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
|
53
|
+
h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
|
54
|
+
timings.push(h)
|
55
|
+
end
|
56
|
+
|
24
57
|
clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
|
25
58
|
start = clientTimes[k].to_i - baseTime
|
26
59
|
finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
|
@@ -0,0 +1,51 @@
|
|
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 :auto_inject, :base_url_path, :pre_authorize_cb, :position,
|
16
|
+
:backtrace_remove, :backtrace_filter, :skip_schema_queries,
|
17
|
+
:storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode
|
18
|
+
|
19
|
+
def self.default
|
20
|
+
new.instance_eval {
|
21
|
+
@auto_inject = true # automatically inject on every html page
|
22
|
+
@base_url_path = "/mini-profiler-resources/"
|
23
|
+
|
24
|
+
# called prior to rack chain, to ensure we are allowed to profile
|
25
|
+
@pre_authorize_cb = lambda {|env| true}
|
26
|
+
|
27
|
+
# called after rack chain, to ensure we are REALLY allowed to profile
|
28
|
+
@position = 'left' # Where it is displayed
|
29
|
+
@skip_schema_queries = false
|
30
|
+
@storage = MiniProfiler::MemoryStore
|
31
|
+
@user_provider = Proc.new{|env| Rack::Request.new(env).ip}
|
32
|
+
@authorization_mode = :allow_all
|
33
|
+
self
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge!(config)
|
38
|
+
return unless config
|
39
|
+
if Hash === config
|
40
|
+
config.each{|k,v| instance_variable_set "@#{k}",v}
|
41
|
+
else
|
42
|
+
self.class.attributes.each{ |k|
|
43
|
+
v = config.send k
|
44
|
+
instance_variable_set "@#{k}", v if v
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -35,15 +35,19 @@ module Rack
|
|
35
35
|
def duration_ms
|
36
36
|
@attributes['Root']['DurationMilliseconds']
|
37
37
|
end
|
38
|
+
|
39
|
+
def root
|
40
|
+
@attributes['Root']
|
41
|
+
end
|
38
42
|
|
39
43
|
def to_json(*a)
|
40
44
|
attribs = @attributes.merge(
|
41
45
|
"Started" => '/Date(%d)/' % @attributes['Started'],
|
42
46
|
"DurationMilliseconds" => @attributes['Root']['DurationMilliseconds']
|
43
47
|
)
|
44
|
-
::JSON.generate(attribs,
|
48
|
+
::JSON.generate(attribs, :max_nesting => 100)
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
end
|
49
|
-
end
|
53
|
+
end
|
@@ -11,81 +11,117 @@ require 'mini_profiler/storage/abstract_store'
|
|
11
11
|
require 'mini_profiler/storage/memory_store'
|
12
12
|
require 'mini_profiler/storage/redis_store'
|
13
13
|
require 'mini_profiler/storage/file_store'
|
14
|
+
require 'mini_profiler/config'
|
15
|
+
require 'mini_profiler/profiling_methods'
|
16
|
+
require 'mini_profiler/context'
|
14
17
|
|
15
18
|
module Rack
|
16
19
|
|
17
20
|
class MiniProfiler
|
18
21
|
|
19
|
-
VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA='.freeze
|
20
|
-
@@instance = nil
|
22
|
+
VERSION = 'rZlycOOTnzxZvxTmFuOEV0dSmu4P5m5bLrCtwJHVXPA=A'.freeze
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
class << self
|
25
|
+
|
26
|
+
include Rack::MiniProfiler::ProfilingMethods
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
def generate_id
|
29
|
+
rand(36**20).to_s(36)
|
30
|
+
end
|
29
31
|
|
30
|
-
|
31
|
-
|
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
|
32
|
+
def reset_config
|
33
|
+
@config = Config.default
|
34
|
+
end
|
44
35
|
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
# So we can change the configuration if we want
|
37
|
+
def config
|
38
|
+
@config ||= Config.default
|
39
|
+
end
|
48
40
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
41
|
+
def share_template
|
42
|
+
return @share_template unless @share_template.nil?
|
43
|
+
@share_template = ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
44
|
+
end
|
45
|
+
|
46
|
+
def current
|
47
|
+
Thread.current[:mini_profiler_private]
|
48
|
+
end
|
49
|
+
|
50
|
+
def current=(c)
|
51
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
52
|
+
Thread.current[:mini_profiler_private]= c
|
53
|
+
end
|
54
|
+
|
55
|
+
# discard existing results, don't track this request
|
56
|
+
def discard_results
|
57
|
+
self.current.discard = true if current
|
58
|
+
end
|
59
|
+
|
60
|
+
# user has the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
61
|
+
def has_profiling_cookie?(env)
|
62
|
+
env['HTTP_COOKIE'] && env['HTTP_COOKIE'].include?("__profilin=stylin")
|
63
|
+
end
|
64
|
+
|
65
|
+
# remove the mini profiler cookie, only used when config.authorization_mode == :whitelist
|
66
|
+
def remove_profiling_cookie(headers)
|
67
|
+
Rack::Utils.delete_cookie_header!(headers, '__profilin')
|
68
|
+
end
|
53
69
|
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
def set_profiling_cookie(headers)
|
71
|
+
Rack::Utils.set_cookie_header!(headers, '__profilin', 'stylin')
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_current(env={}, options={})
|
75
|
+
# profiling the request
|
76
|
+
self.current = Context.new
|
77
|
+
self.current.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
78
|
+
self.current.page_struct = PageTimerStruct.new(env)
|
79
|
+
self.current.current_timer = current.page_struct['Root']
|
80
|
+
end
|
81
|
+
|
82
|
+
def authorize_request
|
83
|
+
Thread.current[:mp_authorized] = true
|
84
|
+
end
|
85
|
+
|
86
|
+
def deauthorize_request
|
87
|
+
Thread.current[:mp_authorized] = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
def request_authorized?
|
91
|
+
Thread.current[:mp_authorized]
|
92
|
+
end
|
57
93
|
end
|
58
94
|
|
59
95
|
#
|
60
96
|
# options:
|
61
97
|
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
62
|
-
def initialize(app,
|
63
|
-
|
64
|
-
MiniProfiler.
|
65
|
-
@options = MiniProfiler.configuration
|
98
|
+
def initialize(app, config = nil)
|
99
|
+
MiniProfiler.config.merge!(config)
|
100
|
+
@config = MiniProfiler.config
|
66
101
|
@app = app
|
67
|
-
@
|
68
|
-
unless @
|
69
|
-
@storage = @
|
102
|
+
@config.base_url_path << "/" unless @config.base_url_path.end_with? "/"
|
103
|
+
unless @config.storage_instance
|
104
|
+
@storage = @config.storage_instance = @config.storage.new(@config.storage_options)
|
70
105
|
end
|
71
106
|
end
|
72
107
|
|
73
108
|
def user(env)
|
74
|
-
|
109
|
+
@config.user_provider.call(env)
|
75
110
|
end
|
76
111
|
|
77
112
|
def serve_results(env)
|
78
113
|
request = Rack::Request.new(env)
|
79
|
-
|
114
|
+
id = request['id']
|
115
|
+
page_struct = @storage.load(id)
|
80
116
|
unless page_struct
|
81
|
-
@storage.set_viewed(user(env),
|
82
|
-
|
117
|
+
@storage.set_viewed(user(env), id)
|
118
|
+
return [404, {}, ["Request not found: #{request['id']} - user #{user(env)}"]]
|
83
119
|
end
|
84
120
|
unless page_struct['HasUserViewed']
|
85
121
|
page_struct['ClientTimings'].init_from_form_data(env, page_struct)
|
86
122
|
page_struct['HasUserViewed'] = true
|
87
123
|
@storage.save(page_struct)
|
88
|
-
@storage.set_viewed(user(env),
|
124
|
+
@storage.set_viewed(user(env), id)
|
89
125
|
end
|
90
126
|
|
91
127
|
result_json = page_struct.to_json
|
@@ -96,7 +132,7 @@ module Rack
|
|
96
132
|
|
97
133
|
# Otherwise give the HTML back
|
98
134
|
html = MiniProfiler.share_template.dup
|
99
|
-
html.gsub!(/\{path\}/, @
|
135
|
+
html.gsub!(/\{path\}/, @config.base_url_path)
|
100
136
|
html.gsub!(/\{version\}/, MiniProfiler::VERSION)
|
101
137
|
html.gsub!(/\{json\}/, result_json)
|
102
138
|
html.gsub!(/\{includes\}/, get_profile_script(env))
|
@@ -109,7 +145,7 @@ module Rack
|
|
109
145
|
end
|
110
146
|
|
111
147
|
def serve_html(env)
|
112
|
-
file_name = env['PATH_INFO'][(@
|
148
|
+
file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
|
113
149
|
return serve_results(env) if file_name.eql?('results')
|
114
150
|
full_path = ::File.expand_path("../html/#{file_name}", ::File.dirname(__FILE__))
|
115
151
|
return [404, {}, ["Not found"]] unless ::File.exists? full_path
|
@@ -119,15 +155,7 @@ module Rack
|
|
119
155
|
f.serving env
|
120
156
|
end
|
121
157
|
|
122
|
-
|
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
|
-
|
158
|
+
|
131
159
|
def current
|
132
160
|
MiniProfiler.current
|
133
161
|
end
|
@@ -136,54 +164,73 @@ module Rack
|
|
136
164
|
MiniProfiler.current=c
|
137
165
|
end
|
138
166
|
|
139
|
-
def options
|
140
|
-
@options
|
141
|
-
end
|
142
167
|
|
143
|
-
def
|
144
|
-
|
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']
|
168
|
+
def config
|
169
|
+
@config
|
149
170
|
end
|
150
171
|
|
172
|
+
|
151
173
|
def call(env)
|
152
|
-
|
174
|
+
status = headers = body = nil
|
175
|
+
path = env['PATH_INFO']
|
153
176
|
|
154
|
-
|
155
|
-
|
177
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
|
178
|
+
(@config.skip_paths && @config.skip_paths.any?{ |p| path[0,p.length] == p}) ||
|
179
|
+
env["QUERY_STRING"] =~ /pp=skip/
|
180
|
+
|
181
|
+
has_profiling_cookie = MiniProfiler.has_profiling_cookie?(env)
|
182
|
+
|
183
|
+
if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
|
184
|
+
status,headers,body = @app.call(env)
|
185
|
+
if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
|
186
|
+
MiniProfiler.set_profiling_cookie(headers)
|
187
|
+
end
|
188
|
+
return [status,headers,body]
|
189
|
+
end
|
156
190
|
|
157
|
-
|
158
|
-
return serve_html(env) if env['PATH_INFO'].start_with? @
|
191
|
+
# handle all /mini-profiler requests here
|
192
|
+
return serve_html(env) if env['PATH_INFO'].start_with? @config.base_url_path
|
159
193
|
|
160
|
-
MiniProfiler.create_current(env, @
|
161
|
-
if
|
162
|
-
|
194
|
+
MiniProfiler.create_current(env, @config)
|
195
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
196
|
+
if env["QUERY_STRING"] =~ /pp=no-backtrace/
|
197
|
+
current.skip_backtrace = true
|
198
|
+
elsif env["QUERY_STRING"] =~ /pp=full-backtrace/
|
199
|
+
current.full_backtrace = true
|
163
200
|
end
|
164
201
|
|
165
|
-
start = Time.now
|
166
|
-
|
167
202
|
done_sampling = false
|
168
203
|
quit_sampler = false
|
169
204
|
backtraces = nil
|
205
|
+
missing_stacktrace = false
|
170
206
|
if env["QUERY_STRING"] =~ /pp=sample/
|
171
207
|
backtraces = []
|
172
208
|
t = Thread.current
|
173
209
|
Thread.new {
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
210
|
+
begin
|
211
|
+
require 'stacktrace' rescue nil
|
212
|
+
if !t.respond_to? :stacktrace
|
213
|
+
missing_stacktrace = true
|
214
|
+
quit_sampler = true
|
215
|
+
return
|
216
|
+
end
|
217
|
+
i = 10000 # for sanity never grab more than 10k samples
|
218
|
+
while i > 0
|
219
|
+
break if done_sampling
|
220
|
+
i -= 1
|
221
|
+
backtraces << t.stacktrace
|
222
|
+
sleep 0.001
|
223
|
+
end
|
224
|
+
ensure
|
225
|
+
quit_sampler = true
|
179
226
|
end
|
180
|
-
quit_sampler = true
|
181
227
|
}
|
182
228
|
end
|
183
229
|
|
184
230
|
status, headers, body = nil
|
231
|
+
start = Time.now
|
185
232
|
begin
|
186
|
-
status,headers,
|
233
|
+
status,headers,body = @app.call(env)
|
187
234
|
ensure
|
188
235
|
if backtraces
|
189
236
|
done_sampling = true
|
@@ -191,13 +238,41 @@ module Rack
|
|
191
238
|
end
|
192
239
|
end
|
193
240
|
|
194
|
-
|
241
|
+
skip_it = current.discard
|
242
|
+
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
243
|
+
MiniProfiler.remove_profiling_cookie(headers)
|
244
|
+
skip_it = true
|
245
|
+
end
|
246
|
+
|
247
|
+
return [status,headers,body] if skip_it
|
248
|
+
|
249
|
+
# we must do this here, otherwise current[:discard] is not being properly treated
|
250
|
+
if env["QUERY_STRING"] =~ /pp=env/
|
251
|
+
body.close if body.respond_to? :close
|
252
|
+
return dump_env env
|
253
|
+
end
|
254
|
+
|
255
|
+
if env["QUERY_STRING"] =~ /pp=help/
|
256
|
+
body.close if body.respond_to? :close
|
257
|
+
return help
|
258
|
+
end
|
259
|
+
|
260
|
+
page_struct = current.page_struct
|
195
261
|
page_struct['Root'].record_time((Time.now - start) * 1000)
|
196
262
|
|
197
|
-
|
263
|
+
if backtraces
|
264
|
+
body.close if body.respond_to? :close
|
265
|
+
return help(:stacktrace) if missing_stacktrace
|
266
|
+
return analyze(backtraces, page_struct)
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
271
|
+
@storage.set_unviewed(user(env), page_struct['Id'])
|
272
|
+
@storage.save(page_struct)
|
273
|
+
|
274
|
+
# inject headers, script
|
198
275
|
if status == 200
|
199
|
-
@storage.save(page_struct)
|
200
|
-
@storage.set_unviewed(user(env), page_struct['Id'])
|
201
276
|
|
202
277
|
# inject header
|
203
278
|
if headers.is_a? Hash
|
@@ -205,7 +280,7 @@ module Rack
|
|
205
280
|
end
|
206
281
|
|
207
282
|
# inject script
|
208
|
-
if current
|
283
|
+
if current.inject_js \
|
209
284
|
&& headers.has_key?('Content-Type') \
|
210
285
|
&& !headers['Content-Type'].match(/text\/html/).nil? then
|
211
286
|
body = MiniProfiler::BodyAddProxy.new(body, self.get_profile_script(env))
|
@@ -223,8 +298,68 @@ module Rack
|
|
223
298
|
current = nil
|
224
299
|
end
|
225
300
|
|
301
|
+
def dump_env(env)
|
302
|
+
headers = {'Content-Type' => 'text/plain'}
|
303
|
+
body = ""
|
304
|
+
env.each do |k,v|
|
305
|
+
body << "#{k}: #{v}\n"
|
306
|
+
end
|
307
|
+
[200, headers, [body]]
|
308
|
+
end
|
309
|
+
|
310
|
+
def help(category = nil)
|
311
|
+
headers = {'Content-Type' => 'text/plain'}
|
312
|
+
body = "Append the following to your query string:
|
313
|
+
|
314
|
+
pp=help : display this screen
|
315
|
+
pp=env : display the rack environment
|
316
|
+
pp=skip : skip mini profiler for this request
|
317
|
+
pp=no-backtrace : don't collect stack traces from all the SQL executed
|
318
|
+
pp=full-backtrace : enable full backtrace for SQL executed
|
319
|
+
pp=sample : sample stack traces and return a report isolating heavy usage (requires the stacktrace gem)
|
320
|
+
"
|
321
|
+
if (category == :stacktrace)
|
322
|
+
body = "pp=stacktrace requires the stacktrace gem - add gem 'stacktrace' to your Gemfile"
|
323
|
+
end
|
324
|
+
|
325
|
+
[200, headers, [body]]
|
326
|
+
end
|
327
|
+
|
328
|
+
def analyze(traces, page_struct)
|
329
|
+
headers = {'Content-Type' => 'text/plain'}
|
330
|
+
body = "Collected: #{traces.count} stack traces. Duration(ms): #{page_struct.duration_ms}"
|
331
|
+
|
332
|
+
seen = {}
|
333
|
+
fulldump = ""
|
334
|
+
traces.each do |trace|
|
335
|
+
fulldump << "\n\n"
|
336
|
+
distinct = {}
|
337
|
+
trace.each do |frame|
|
338
|
+
name = "#{frame.klass} #{frame.method}"
|
339
|
+
unless distinct[name]
|
340
|
+
distinct[name] = true
|
341
|
+
seen[name] ||= 0
|
342
|
+
seen[name] += 1
|
343
|
+
end
|
344
|
+
fulldump << name << "\n"
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
body << "\n\nStack Trace Analysis\n"
|
349
|
+
seen.to_a.sort{|x,y| y[1] <=> x[1]}.each do |name, count|
|
350
|
+
if count > traces.count / 10
|
351
|
+
body << "#{name} x #{count}\n"
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
body << "\n\n\nRaw traces \n"
|
356
|
+
body << fulldump
|
357
|
+
|
358
|
+
[200, headers, [body]]
|
359
|
+
end
|
360
|
+
|
226
361
|
def ids_json(env)
|
227
|
-
ids = [current
|
362
|
+
ids = [current.page_struct["Id"]] + (@storage.get_unviewed_ids(user(env)) || [])
|
228
363
|
::JSON.generate(ids.uniq)
|
229
364
|
end
|
230
365
|
|
@@ -236,14 +371,14 @@ module Rack
|
|
236
371
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
237
372
|
def get_profile_script(env)
|
238
373
|
ids = ids_json(env)
|
239
|
-
path = @
|
374
|
+
path = @config.base_url_path
|
240
375
|
version = MiniProfiler::VERSION
|
241
|
-
position = @
|
376
|
+
position = @config.position
|
242
377
|
showTrivial = false
|
243
378
|
showChildren = false
|
244
379
|
maxTracesToShow = 10
|
245
380
|
showControls = false
|
246
|
-
currentId = current
|
381
|
+
currentId = current.page_struct["Id"]
|
247
382
|
authorized = true
|
248
383
|
useExistingjQuery = false
|
249
384
|
# TODO : cache this snippet
|
@@ -255,52 +390,13 @@ module Rack
|
|
255
390
|
end
|
256
391
|
# replace the '{{' and '}}''
|
257
392
|
script.gsub!(/\{\{/, '{').gsub!(/\}\}/, '}')
|
258
|
-
current
|
393
|
+
current.inject_js = false
|
259
394
|
script
|
260
395
|
end
|
261
396
|
|
262
397
|
# cancels automatic injection of profile script for the current page
|
263
398
|
def cancel_auto_inject(env)
|
264
|
-
current
|
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'])
|
399
|
+
current.inject_js = false
|
304
400
|
end
|
305
401
|
|
306
402
|
end
|