speed_gun 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rubocop.yml +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +161 -0
- data/Rakefile +1 -0
- data/app/assets/javascripts/browser.js +65 -0
- data/app/assets/javascripts/profiler.js +45 -0
- data/app/views/speed_gun/_meter.html.slim +9 -0
- data/lib/speed_gun/app/public/browser.js +65 -0
- data/lib/speed_gun/app/public/jquery-1.10.2.min.js +6 -0
- data/lib/speed_gun/app/public/profile.js +5 -0
- data/lib/speed_gun/app/public/profiler.js +45 -0
- data/lib/speed_gun/app/public/style.css +170 -0
- data/lib/speed_gun/app/views/meter.html.slim +9 -0
- data/lib/speed_gun/app/views/profile.slim +97 -0
- data/lib/speed_gun/app.rb +58 -0
- data/lib/speed_gun/browser/navigation.rb +23 -0
- data/lib/speed_gun/browser/timing.rb +92 -0
- data/lib/speed_gun/browser.rb +22 -0
- data/lib/speed_gun/config.rb +59 -0
- data/lib/speed_gun/hook.rb +25 -0
- data/lib/speed_gun/middleware.rb +91 -0
- data/lib/speed_gun/profiler/action_controller.rb +12 -0
- data/lib/speed_gun/profiler/action_view.rb +12 -0
- data/lib/speed_gun/profiler/active_record.rb +16 -0
- data/lib/speed_gun/profiler/base.rb +139 -0
- data/lib/speed_gun/profiler/js.rb +17 -0
- data/lib/speed_gun/profiler/manual.rb +14 -0
- data/lib/speed_gun/profiler/rack.rb +7 -0
- data/lib/speed_gun/profiler.rb +124 -0
- data/lib/speed_gun/railtie.rb +33 -0
- data/lib/speed_gun/store/base.rb +9 -0
- data/lib/speed_gun/store/file.rb +62 -0
- data/lib/speed_gun/store/memcache.rb +27 -0
- data/lib/speed_gun/store/memory.rb +22 -0
- data/lib/speed_gun/store/redis.rb +28 -0
- data/lib/speed_gun/store.rb +6 -0
- data/lib/speed_gun/template.rb +15 -0
- data/lib/speed_gun/version.rb +3 -0
- data/lib/speed_gun.rb +52 -0
- data/speed_gun.gemspec +29 -0
- metadata +184 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'speed_gun/browser'
|
2
|
+
|
3
|
+
class SpeedGun::Browser::Navigation < Hash
|
4
|
+
NAVIGATION_TYPES = [
|
5
|
+
'Navigate',
|
6
|
+
'Reload',
|
7
|
+
'Back/Forward'
|
8
|
+
]
|
9
|
+
|
10
|
+
def initialize(hash)
|
11
|
+
hash.each_pair do |key, val|
|
12
|
+
self[key.to_s.to_sym] = val
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
NAVIGATION_TYPES[self[:type].to_i] || 'Unknown'
|
18
|
+
end
|
19
|
+
|
20
|
+
def redirect_count
|
21
|
+
self[:redirect_count].to_i
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'speed_gun/browser'
|
2
|
+
|
3
|
+
class SpeedGun::Browser::Timing < Hash
|
4
|
+
# rubocop:disable SymbolName
|
5
|
+
ATTRIBUTES = [
|
6
|
+
:navigationStart,
|
7
|
+
:unloadEventStart,
|
8
|
+
:unloadEventEnd,
|
9
|
+
:redirectStart,
|
10
|
+
:redirectEnd,
|
11
|
+
:fetchStart,
|
12
|
+
:domainLookupStart,
|
13
|
+
:domainLookupEnd,
|
14
|
+
:connectStart,
|
15
|
+
:connectEnd,
|
16
|
+
:secureConnectionStart,
|
17
|
+
:requestStart,
|
18
|
+
:responseStart,
|
19
|
+
:responseEnd,
|
20
|
+
:domLoading,
|
21
|
+
:domInteractive,
|
22
|
+
:domContentLoadedEventStart,
|
23
|
+
:domContentLoadedEventEnd,
|
24
|
+
:domComplete,
|
25
|
+
:loadEventStart,
|
26
|
+
:loadEventEnd,
|
27
|
+
]
|
28
|
+
# rubocop:enable all
|
29
|
+
|
30
|
+
class Timing
|
31
|
+
def initialize(name, base, started_at, ended_at)
|
32
|
+
@name = name
|
33
|
+
@started_at = started_at - base
|
34
|
+
@elapsed_time = ended_at - started_at
|
35
|
+
end
|
36
|
+
attr_reader :name, :started_at, :elapsed_time
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(hash)
|
40
|
+
hash.each_pair do |key, val|
|
41
|
+
self[key.to_s.to_sym] = val.to_i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ATTRIBUTES.each do |key|
|
46
|
+
define_method(key) { self[key] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_time
|
50
|
+
loadEventEnd - navigationStart
|
51
|
+
end
|
52
|
+
|
53
|
+
# rubocop:disable MethodLength
|
54
|
+
def timings
|
55
|
+
@timings ||= [
|
56
|
+
(
|
57
|
+
if redirectStart > 0
|
58
|
+
Timing.new('Redirect', navigationStart, redirectStart, redirectEnd)
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
),
|
63
|
+
Timing.new('Fetch all', navigationStart, fetchStart, responseEnd),
|
64
|
+
Timing.new(
|
65
|
+
'DNS Lookup', navigationStart, domainLookupStart, domainLookupEnd
|
66
|
+
),
|
67
|
+
Timing.new(
|
68
|
+
'TCP Connecting', navigationStart, connectStart, connectEnd
|
69
|
+
),
|
70
|
+
(
|
71
|
+
if secureConnectionStart > 0
|
72
|
+
Timing.new('SSL', navigationStart, secureConnectionStart, connectEnd)
|
73
|
+
else
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
),
|
77
|
+
Timing.new('Request', navigationStart, requestStart, responseStart),
|
78
|
+
Timing.new('Response', navigationStart, responseStart, responseEnd),
|
79
|
+
Timing.new('Unload', navigationStart, unloadEventStart, unloadEventEnd),
|
80
|
+
Timing.new('DOM Load', navigationStart, domLoading, domInteractive),
|
81
|
+
Timing.new(
|
82
|
+
'DOMContentLoaded Event',
|
83
|
+
navigationStart, domContentLoadedEventStart, domContentLoadedEventEnd
|
84
|
+
),
|
85
|
+
Timing.new(
|
86
|
+
'Sub resources loading', navigationStart, domInteractive, domComplete
|
87
|
+
),
|
88
|
+
Timing.new('Load Event', navigationStart, loadEventStart, loadEventEnd),
|
89
|
+
].reject { |timing| timing.nil? }
|
90
|
+
end
|
91
|
+
# rubocop:enable
|
92
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
require 'useragent'
|
3
|
+
|
4
|
+
class SpeedGun::Browser
|
5
|
+
def initialize(hash)
|
6
|
+
@user_agent = UserAgent.parse(hash['user_agent'] || '')
|
7
|
+
@navigation = Navigation.new(hash['navigation'] || {})
|
8
|
+
@timing = Timing.new(hash['timing'] || {})
|
9
|
+
end
|
10
|
+
attr_reader :user_agent, :navigation, :timing
|
11
|
+
|
12
|
+
def as_msgpack(*args)
|
13
|
+
{
|
14
|
+
user_agent: @user_agent.to_s,
|
15
|
+
navigation: @navigation,
|
16
|
+
timing: @timing,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'speed_gun/browser/navigation'
|
22
|
+
require 'speed_gun/browser/timing'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
|
3
|
+
class SpeedGun::Config < Hash
|
4
|
+
def enable?
|
5
|
+
enable && enable_if.call
|
6
|
+
end
|
7
|
+
|
8
|
+
def enable
|
9
|
+
fetch(:enable, true)
|
10
|
+
end
|
11
|
+
|
12
|
+
def enable_if
|
13
|
+
self[:enable_if] ||= -> { true }
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefix
|
17
|
+
self[:prefix] ||= '/speed_gun'
|
18
|
+
end
|
19
|
+
|
20
|
+
def prefix_regexp
|
21
|
+
self[:prefix_regexp] ||= /^#{Regexp.escape(prefix)}/x
|
22
|
+
end
|
23
|
+
|
24
|
+
def store
|
25
|
+
self[:store] ||= SpeedGun::Store::Memory.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def auto_inject?
|
29
|
+
fetch(:auto_inject, true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def backtrace_remove
|
33
|
+
self[:backtrace_remove] ||= ''
|
34
|
+
end
|
35
|
+
|
36
|
+
def backtrace_includes
|
37
|
+
self[:backtrace_includes] ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def show_button?
|
41
|
+
fetch(:show_button, true)
|
42
|
+
end
|
43
|
+
|
44
|
+
def no_include_jquery?
|
45
|
+
fetch(:no_include_jquery, false)
|
46
|
+
end
|
47
|
+
|
48
|
+
def skip_paths
|
49
|
+
self[:skip_paths] ||= [/favicon/]
|
50
|
+
end
|
51
|
+
|
52
|
+
def force_profile?
|
53
|
+
fetch(:force_profile, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def authorize_proc
|
57
|
+
self[:authorize_proc] ||= ->(request) { true }
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
|
3
|
+
class SpeedGun::Hook
|
4
|
+
HOOKS = []
|
5
|
+
|
6
|
+
def self.inherited(klass)
|
7
|
+
HOOKS.push(klass) unless HOOKS.include?(klass)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.invoke_all(profiler)
|
11
|
+
HOOKS.each { |hook| hook.invoke(profiler) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.invoke(profiler)
|
15
|
+
new(profiler).invoke
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(profiler)
|
19
|
+
@profiler = profiler
|
20
|
+
end
|
21
|
+
attr_reader :profiler
|
22
|
+
|
23
|
+
def invoke
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
require 'speed_gun/app'
|
3
|
+
require 'speed_gun/profiler'
|
4
|
+
require 'speed_gun/profiler/rack'
|
5
|
+
require 'speed_gun/template'
|
6
|
+
|
7
|
+
class SpeedGun::Middleware
|
8
|
+
BODY_END_REGEXP = /<\/(?:body|html)>/
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
return SpeedGun::App.call(env) if under_speed_gun?(env)
|
16
|
+
|
17
|
+
SpeedGun.current = SpeedGun::Profiler.new(env) if SpeedGun.enable?
|
18
|
+
|
19
|
+
call_with_speed_gun(env)
|
20
|
+
ensure
|
21
|
+
SpeedGun.current = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def call_with_speed_gun(env)
|
27
|
+
remove_conditional_get_headers(env)
|
28
|
+
|
29
|
+
status, headers, body = *SpeedGun.current.profile(:rack) { @app.call(env) }
|
30
|
+
|
31
|
+
if SpeedGun.active?
|
32
|
+
inject_header(headers)
|
33
|
+
SpeedGun.current.dump
|
34
|
+
end
|
35
|
+
|
36
|
+
if SpeedGun.config.auto_inject? && SpeedGun.active?
|
37
|
+
inject_body(status, headers, body)
|
38
|
+
else
|
39
|
+
return [status, headers, body]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_conditional_get_headers(env)
|
44
|
+
return if !SpeedGun.config.force_profile? && SpeedGun.active?
|
45
|
+
|
46
|
+
env['HTTP_IF_MODIFIED_SINCE'] = ''
|
47
|
+
env['HTTP_IF_NONE_MATCH'] = ''
|
48
|
+
end
|
49
|
+
|
50
|
+
def inject_header(headers)
|
51
|
+
if SpeedGun.config.force_profile?
|
52
|
+
headers.delete('ETag')
|
53
|
+
headers.delete('Date')
|
54
|
+
headers['Cache-Control'] = 'must-revalidate, private, max-age=0'
|
55
|
+
end
|
56
|
+
|
57
|
+
headers['X-SPEEDGUN-ID'] = SpeedGun.current.id
|
58
|
+
end
|
59
|
+
|
60
|
+
def inject_body(status, headers, body)
|
61
|
+
unless headers['Content-Type'] =~ /text\/html/
|
62
|
+
return [status, headers, body]
|
63
|
+
end
|
64
|
+
|
65
|
+
response = Rack::Response.new([], status, headers)
|
66
|
+
|
67
|
+
body = [body] if body.kind_of?(String)
|
68
|
+
body.each { |fragment| response.write(inject_fragment(fragment)) }
|
69
|
+
body.close if body.respond_to?(:close)
|
70
|
+
|
71
|
+
response.finish
|
72
|
+
end
|
73
|
+
|
74
|
+
def inject_fragment(body)
|
75
|
+
return body unless body.match(BODY_END_REGEXP)
|
76
|
+
|
77
|
+
body.sub(BODY_END_REGEXP) do |matched|
|
78
|
+
SpeedGun::Template.render + matched
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def under_speed_gun?(env)
|
83
|
+
if SpeedGun.config.prefix_regexp.match(env['PATH_INFO'])
|
84
|
+
env['PATH_INFO'] =
|
85
|
+
env['PATH_INFO'].sub(SpeedGun.config.prefix_regexp, '')
|
86
|
+
true
|
87
|
+
else
|
88
|
+
false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'speed_gun/profiler/base'
|
2
|
+
|
3
|
+
class SpeedGun::Profiler::ActionController < SpeedGun::Profiler::Base
|
4
|
+
hook_method ::ActionController::Base, :process
|
5
|
+
|
6
|
+
attr_reader :action_name
|
7
|
+
alias_method :title, :action_name
|
8
|
+
|
9
|
+
def before_profile(controller, action)
|
10
|
+
@action_name = "#{controller.class.name}##{action}"
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'speed_gun/profiler/base'
|
2
|
+
|
3
|
+
class SpeedGun::Profiler::ActionView < SpeedGun::Profiler::Base
|
4
|
+
hook_method ::ActionView::Template, :render
|
5
|
+
|
6
|
+
attr_reader :template_path
|
7
|
+
alias_method :title, :template_path
|
8
|
+
|
9
|
+
def before_profile(action_view, *args)
|
10
|
+
@template_path = action_view.instance_variable_get(:@virtual_path)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'speed_gun/profiler/base'
|
2
|
+
|
3
|
+
class SpeedGun::Profiler::ActiveRecord < SpeedGun::Profiler::Base
|
4
|
+
def title
|
5
|
+
"#{@name}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def html
|
9
|
+
%Q{<pre class="sql">#{@sql}</pre>}
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_profile(adapter, sql, name = nil)
|
13
|
+
@sql = sql
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'speed_gun/profiler'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
class SpeedGun::Profiler::Base
|
5
|
+
def self.label
|
6
|
+
name.sub(/.*::/, '')
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.profiler_type
|
10
|
+
name\
|
11
|
+
.sub(/.*::/, '')\
|
12
|
+
.gsub(/(.)([A-Z])/) { |m| "#{$1}_#{$2.downcase}" }\
|
13
|
+
.downcase\
|
14
|
+
.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.inherited(klass)
|
18
|
+
SpeedGun::Profiler::PROFILERS[klass.profiler_type] = klass
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.hook_method(klass, method_name)
|
22
|
+
without_profiling = "#{method_name}_withtout_profile".intern
|
23
|
+
with_profiling = "#{method_name}_with_profile".intern
|
24
|
+
return unless klass.send(:method_defined?, method_name)
|
25
|
+
return if klass.send(:method_defined?, with_profiling)
|
26
|
+
|
27
|
+
profiler = profiler_type
|
28
|
+
klass.send(:alias_method, without_profiling, method_name)
|
29
|
+
klass.send(:define_method, with_profiling) do |*args, &block|
|
30
|
+
return send(without_profiling, *args, &block) unless SpeedGun.current
|
31
|
+
|
32
|
+
profile_args = [self] + args
|
33
|
+
SpeedGun.current.profile(profiler, *profile_args) do
|
34
|
+
send(without_profiling, *args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
klass.send(:alias_method, method_name, with_profiling)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load(data)
|
41
|
+
data.delete('label')
|
42
|
+
type = data.delete('type')
|
43
|
+
profiler = SpeedGun::Profiler::PROFILERS[type.to_sym]
|
44
|
+
profile = profiler.new
|
45
|
+
|
46
|
+
data.each_pair do |key, val|
|
47
|
+
profile.send(:instance_variable_set, :"@#{key}", val)
|
48
|
+
end
|
49
|
+
|
50
|
+
profile
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.profile(profiler, *args, &block)
|
54
|
+
profile = new
|
55
|
+
profiler.profiles << profile
|
56
|
+
profile.profile(*args, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@id = SecureRandom.uuid
|
61
|
+
@parent_profile_id = nil
|
62
|
+
@elapsed_time = 0
|
63
|
+
@backtrace = []
|
64
|
+
end
|
65
|
+
attr_reader :id, :elapsed_time, :parent_profile_id, :backtrace
|
66
|
+
|
67
|
+
def title
|
68
|
+
warn 'Override this method'
|
69
|
+
end
|
70
|
+
|
71
|
+
def html
|
72
|
+
''
|
73
|
+
end
|
74
|
+
|
75
|
+
def label
|
76
|
+
self.class.label
|
77
|
+
end
|
78
|
+
|
79
|
+
def type
|
80
|
+
self.class.profiler_type
|
81
|
+
end
|
82
|
+
|
83
|
+
def profile(*args, &block)
|
84
|
+
@backtrace = cleanup_caller(caller(3))
|
85
|
+
parent_profile = SpeedGun.current.now_profile
|
86
|
+
SpeedGun.current.now_profile = self
|
87
|
+
call_without_defined(:before_profile, *args, &block)
|
88
|
+
result = measure(&block)
|
89
|
+
call_without_defined(:after_profile, *args, &block)
|
90
|
+
return result
|
91
|
+
ensure
|
92
|
+
SpeedGun.current.now_profile = parent_profile
|
93
|
+
@parent_profile_id = parent_profile.id if parent_profile
|
94
|
+
end
|
95
|
+
|
96
|
+
def measure(&block)
|
97
|
+
now = Time.now
|
98
|
+
result = yield
|
99
|
+
@elapsed_time = Time.now - now
|
100
|
+
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
def as_msgpack(*args)
|
105
|
+
hash = {}
|
106
|
+
instance_variables.each do |key|
|
107
|
+
unless key.to_s =~ /^\@_/
|
108
|
+
hash[key.to_s.sub(/^\@/, '')] = instance_variable_get(key)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
hash['type'] = type.to_s
|
112
|
+
hash['label'] = label
|
113
|
+
hash['title'] = title
|
114
|
+
hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_msgpack(*args)
|
118
|
+
as_msgpack(*args).to_msgpack(*args)
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def call_without_defined(method, *args)
|
124
|
+
send(method, *args) if respond_to?(method)
|
125
|
+
end
|
126
|
+
|
127
|
+
def cleanup_caller(caller)
|
128
|
+
backtraces = caller.map do |backtrace|
|
129
|
+
backtrace.sub(SpeedGun.config.backtrace_remove, '')
|
130
|
+
end
|
131
|
+
backtraces.reject! do |backtrace|
|
132
|
+
!SpeedGun.config.backtrace_includes.any? do |regexp|
|
133
|
+
backtrace =~ regexp
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
backtraces
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'speed_gun/profiler/base'
|
2
|
+
|
3
|
+
class SpeedGun::Profiler::Js < SpeedGun::Profiler::Base
|
4
|
+
def self.profile(profiler, title, elapsed_time, backtrace)
|
5
|
+
profile = new
|
6
|
+
profiler.profiles << profile
|
7
|
+
profile.save(title, elapsed_time, backtrace)
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :title
|
11
|
+
|
12
|
+
def save(title, elapsed_time, backtrace)
|
13
|
+
@title = title.to_s
|
14
|
+
@elapsed_time = elapsed_time.to_i * 0.001
|
15
|
+
@backtrace = backtrace
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
require 'speed_gun/store'
|
3
|
+
require 'speed_gun/browser'
|
4
|
+
require 'speed_gun/hook'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'msgpack'
|
7
|
+
require 'multi_json'
|
8
|
+
|
9
|
+
class SpeedGun::Profiler
|
10
|
+
PROFILERS = {}
|
11
|
+
|
12
|
+
def self.load(id)
|
13
|
+
src = SpeedGun.store[id]
|
14
|
+
|
15
|
+
return nil unless src
|
16
|
+
|
17
|
+
data = MessagePack.unpack(src)
|
18
|
+
|
19
|
+
profiler = new({})
|
20
|
+
profiler.restore_by_hash(data)
|
21
|
+
|
22
|
+
profiler
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(env)
|
26
|
+
@id = SecureRandom.uuid
|
27
|
+
@path = env['PATH_INFO']
|
28
|
+
@query = env['QUERY_STRING']
|
29
|
+
@env = env
|
30
|
+
@requested_at = Time.now
|
31
|
+
@profiles = []
|
32
|
+
@browser = nil
|
33
|
+
@active = true
|
34
|
+
@now_profile = nil
|
35
|
+
end
|
36
|
+
attr_reader :id, :path, :query, :env, :requested_at, :profiles, :browser
|
37
|
+
attr_accessor :now_profile
|
38
|
+
|
39
|
+
def profile(type, *args, &block)
|
40
|
+
profiler = PROFILERS[type]
|
41
|
+
|
42
|
+
if profiler
|
43
|
+
profiler.profile(self, *args, &block)
|
44
|
+
else
|
45
|
+
yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def skip?
|
50
|
+
SpeedGun.config.skip_paths.any? { |prefix| prefix.match(@path) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def active?
|
54
|
+
@active && !skip?
|
55
|
+
end
|
56
|
+
|
57
|
+
def activate!
|
58
|
+
@active = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def deactivate!
|
62
|
+
@active = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def dump
|
66
|
+
SpeedGun.store[id] = to_msgpack
|
67
|
+
|
68
|
+
SpeedGun::Hook.invoke_all(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
def browser=(hash)
|
72
|
+
@browser = SpeedGun::Browser.new(hash)
|
73
|
+
end
|
74
|
+
|
75
|
+
def as_msgpack(*args)
|
76
|
+
{
|
77
|
+
id: @id,
|
78
|
+
path: @path,
|
79
|
+
query: @query,
|
80
|
+
env: msgpackable_env,
|
81
|
+
requested_at: @requested_at.to_i,
|
82
|
+
profiles: @profiles.map { |profile| profile.as_msgpack(*args) },
|
83
|
+
browser: @browser ? @browser.as_msgpack(*args) : nil,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_msgpack(*args)
|
88
|
+
as_msgpack(*args).to_msgpack(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_json(*args)
|
92
|
+
MultiJson.dump(as_msgpack(*args))
|
93
|
+
end
|
94
|
+
|
95
|
+
def restore_by_hash(hash)
|
96
|
+
hash.each_pair do |key, val|
|
97
|
+
instance_variable_set(:"@#{key}", restore_attribute(key, val))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def restore_attribute(key, val)
|
102
|
+
case key
|
103
|
+
when 'requested_at'
|
104
|
+
Time.at(val)
|
105
|
+
when 'profiles'
|
106
|
+
val.map { |profile| SpeedGun::Profiler::Base.load(profile) }
|
107
|
+
when 'browser'
|
108
|
+
val ? SpeedGun::Browser.new(val) : val
|
109
|
+
else
|
110
|
+
val
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def msgpackable_env
|
117
|
+
env = {}
|
118
|
+
@env.each_pair { |key, val| env[key] = val if key[0] =~ /[A-Z]/ }
|
119
|
+
env
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
require 'speed_gun/profiler/manual'
|
124
|
+
require 'speed_gun/profiler/js'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'speed_gun'
|
2
|
+
require 'speed_gun/store/file'
|
3
|
+
|
4
|
+
class SpeedGun::Railtie < ::Rails::Railtie
|
5
|
+
initializer 'speed_gun' do |app|
|
6
|
+
app.middleware.insert(0, SpeedGun::Middleware)
|
7
|
+
|
8
|
+
SpeedGun.config[:enable_if] = -> { Rails.env.development? }
|
9
|
+
SpeedGun.config[:backtrace_remove] = Rails.root.to_s + '/'
|
10
|
+
SpeedGun.config[:backtrace_includes] = [/^(app|config|lib|test|spec)/]
|
11
|
+
SpeedGun.config[:authorize_proc] = ->(request) { Rails.env.development? }
|
12
|
+
SpeedGun.config.skip_paths << /^#{Regexp.escape(app.config.assets.prefix)}/
|
13
|
+
SpeedGun.config[:store] =
|
14
|
+
SpeedGun::Store::File.new(path: Rails.root.join('tmp/speed_gun'))
|
15
|
+
|
16
|
+
ActiveSupport.on_load(:action_controller) do
|
17
|
+
require 'speed_gun/profiler/action_controller'
|
18
|
+
end
|
19
|
+
|
20
|
+
ActiveSupport.on_load(:action_view) do
|
21
|
+
require 'speed_gun/profiler/action_view'
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveSupport.on_load(:active_record) do
|
25
|
+
require 'speed_gun/profiler/active_record'
|
26
|
+
|
27
|
+
SpeedGun::Profiler::ActiveRecord.hook_method(
|
28
|
+
ActiveRecord::Base.connection.class,
|
29
|
+
:execute
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|