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
@@ -0,0 +1,73 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
module ProfilingMethods
|
4
|
+
|
5
|
+
def record_sql(query, elapsed_ms)
|
6
|
+
c = current
|
7
|
+
return unless c
|
8
|
+
c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace) if (c && c.current_timer)
|
9
|
+
end
|
10
|
+
|
11
|
+
# perform a profiling step on given block
|
12
|
+
def step(name)
|
13
|
+
if current
|
14
|
+
parent_timer = current.current_timer
|
15
|
+
result = nil
|
16
|
+
current.current_timer = current_timer = current.current_timer.add_child(name)
|
17
|
+
begin
|
18
|
+
result = yield if block_given?
|
19
|
+
ensure
|
20
|
+
current_timer.record_time
|
21
|
+
current.current_timer = parent_timer
|
22
|
+
end
|
23
|
+
result
|
24
|
+
else
|
25
|
+
yield if block_given?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def unprofile_method(klass, method)
|
30
|
+
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
31
|
+
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
32
|
+
|
33
|
+
if klass.send :method_defined?, with_profiling
|
34
|
+
klass.send :alias_method, method, without_profiling
|
35
|
+
klass.send :remove_method, with_profiling
|
36
|
+
klass.send :remove_method, without_profiling
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def profile_method(klass, method, &blk)
|
41
|
+
default_name = klass.to_s + " " + method.to_s
|
42
|
+
with_profiling = (method.to_s + "_with_mini_profiler").intern
|
43
|
+
without_profiling = (method.to_s + "_without_mini_profiler").intern
|
44
|
+
|
45
|
+
if klass.send :method_defined?, with_profiling
|
46
|
+
return # dont double profile
|
47
|
+
end
|
48
|
+
|
49
|
+
klass.send :alias_method, without_profiling, method
|
50
|
+
klass.send :define_method, with_profiling do |*args, &orig|
|
51
|
+
return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
|
52
|
+
|
53
|
+
name = default_name
|
54
|
+
name = blk.bind(self).call(*args) if blk
|
55
|
+
|
56
|
+
parent_timer = Rack::MiniProfiler.current.current_timer
|
57
|
+
page_struct = Rack::MiniProfiler.current.page_struct
|
58
|
+
result = nil
|
59
|
+
|
60
|
+
Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
|
61
|
+
begin
|
62
|
+
result = self.send without_profiling, *args, &orig
|
63
|
+
ensure
|
64
|
+
current_timer.record_time
|
65
|
+
Rack::MiniProfiler.current.current_timer = parent_timer
|
66
|
+
end
|
67
|
+
result
|
68
|
+
end
|
69
|
+
klass.send :alias_method, method, with_profiling
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -6,14 +6,14 @@ module Rack
|
|
6
6
|
class RequestTimerStruct < TimerStruct
|
7
7
|
|
8
8
|
def self.createRoot(name, page)
|
9
|
-
rt = RequestTimerStruct.new(name, page)
|
9
|
+
rt = RequestTimerStruct.new(name, page, nil)
|
10
10
|
rt["IsRoot"]= true
|
11
11
|
rt
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
attr_accessor :children_duration
|
15
15
|
|
16
|
-
def initialize(name, page)
|
16
|
+
def initialize(name, page, parent)
|
17
17
|
super("Id" => MiniProfiler.generate_id,
|
18
18
|
"Name" => name,
|
19
19
|
"DurationMilliseconds" => 0,
|
@@ -30,34 +30,65 @@ module Rack
|
|
30
30
|
"SqlTimingsDurationMilliseconds"=> 0,
|
31
31
|
"IsTrivial"=> false,
|
32
32
|
"IsRoot"=> false,
|
33
|
-
"Depth"=> 0,
|
33
|
+
"Depth"=> parent ? parent.depth + 1 : 0,
|
34
34
|
"ExecutedReaders"=> 0,
|
35
35
|
"ExecutedScalars"=> 0,
|
36
36
|
"ExecutedNonQueries"=> 0)
|
37
37
|
@children_duration = 0
|
38
|
+
@start = Time.now
|
39
|
+
@parent = parent
|
40
|
+
@page = page
|
38
41
|
end
|
39
42
|
|
40
|
-
def
|
43
|
+
def duration_ms
|
44
|
+
self['DurationMilliseconds']
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_ms
|
48
|
+
self['StartMilliseconds']
|
49
|
+
end
|
50
|
+
|
51
|
+
def start
|
52
|
+
@start
|
53
|
+
end
|
54
|
+
|
55
|
+
def depth
|
56
|
+
self['Depth']
|
57
|
+
end
|
58
|
+
|
59
|
+
def children
|
60
|
+
self['Children']
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_child(name)
|
64
|
+
request_timer = RequestTimerStruct.new(name, @page, self)
|
41
65
|
self['Children'].push(request_timer)
|
42
66
|
self['HasChildren'] = true
|
43
67
|
request_timer['ParentTimingId'] = self['Id']
|
44
68
|
request_timer['Depth'] = self['Depth'] + 1
|
45
|
-
|
69
|
+
request_timer
|
46
70
|
end
|
47
71
|
|
48
|
-
def add_sql(query, elapsed_ms, page, skip_backtrace = false)
|
49
|
-
timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace)
|
72
|
+
def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
|
73
|
+
timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
|
50
74
|
timer['ParentTimingId'] = self['Id']
|
51
75
|
self['SqlTimings'].push(timer)
|
52
76
|
self['HasSqlTimings'] = true
|
53
77
|
self['SqlTimingsDurationMilliseconds'] += elapsed_ms
|
54
78
|
page['DurationMillisecondsInSql'] += elapsed_ms
|
79
|
+
timer
|
55
80
|
end
|
56
81
|
|
57
|
-
def record_time(milliseconds)
|
82
|
+
def record_time(milliseconds = nil)
|
83
|
+
milliseconds ||= (Time.now - @start) * 1000
|
58
84
|
self['DurationMilliseconds'] = milliseconds
|
59
85
|
self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
|
60
86
|
self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
|
87
|
+
|
88
|
+
if @parent
|
89
|
+
@parent.children_duration += milliseconds
|
90
|
+
end
|
91
|
+
|
61
92
|
end
|
62
93
|
end
|
63
94
|
end
|
@@ -5,7 +5,7 @@ module Rack
|
|
5
5
|
|
6
6
|
# Timing system for a SQL query
|
7
7
|
class SqlTimerStruct < TimerStruct
|
8
|
-
def initialize(query, duration_ms, page, skip_backtrace = false)
|
8
|
+
def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
|
9
9
|
|
10
10
|
stack_trace = nil
|
11
11
|
unless skip_backtrace
|
@@ -13,24 +13,35 @@ module Rack
|
|
13
13
|
stack_trace = ""
|
14
14
|
# Clean up the stack trace if there are options to do so
|
15
15
|
Kernel.caller.each do |ln|
|
16
|
-
ln.gsub!(Rack::MiniProfiler.
|
17
|
-
if Rack::MiniProfiler.
|
16
|
+
ln.gsub!(Rack::MiniProfiler.config.backtrace_remove, '') if Rack::MiniProfiler.config.backtrace_remove and !full_backtrace
|
17
|
+
if full_backtrace or Rack::MiniProfiler.config.backtrace_filter.nil? or ln =~ Rack::MiniProfiler.config.backtrace_filter
|
18
18
|
stack_trace << ln << "\n"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
@parent = parent
|
24
|
+
@page = page
|
25
|
+
|
23
26
|
super("ExecuteType" => 3, # TODO
|
24
27
|
"FormattedCommandString" => query,
|
25
28
|
"StackTraceSnippet" => stack_trace,
|
26
|
-
"StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
|
29
|
+
"StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
|
27
30
|
"DurationMilliseconds" => duration_ms,
|
28
|
-
"FirstFetchDurationMilliseconds" =>
|
31
|
+
"FirstFetchDurationMilliseconds" => duration_ms,
|
29
32
|
"Parameters" => nil,
|
30
33
|
"ParentTimingId" => nil,
|
31
34
|
"IsDuplicate" => false)
|
32
35
|
end
|
33
36
|
|
37
|
+
def report_reader_duration(elapsed_ms)
|
38
|
+
return if @reported
|
39
|
+
@reported = true
|
40
|
+
self["DurationMilliseconds"] += elapsed_ms
|
41
|
+
@parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
|
42
|
+
@page["DurationMillisecondsInSql"] += elapsed_ms
|
43
|
+
end
|
44
|
+
|
34
45
|
end
|
35
46
|
|
36
47
|
end
|
@@ -1,30 +1,82 @@
|
|
1
1
|
module MiniProfilerRails
|
2
|
+
|
2
3
|
class Railtie < ::Rails::Railtie
|
3
4
|
|
4
5
|
initializer "rack_mini_profiler.configure_rails_initialization" do |app|
|
6
|
+
c = Rack::MiniProfiler.config
|
7
|
+
|
8
|
+
# By default, only show the MiniProfiler in development mode, in production allow profiling if post_authorize_cb is set
|
9
|
+
c.pre_authorize_cb = lambda { |env|
|
10
|
+
Rails.env.development? || Rails.env.production?
|
11
|
+
}
|
12
|
+
|
13
|
+
if Rails.env.development?
|
14
|
+
c.skip_paths ||= []
|
15
|
+
c.skip_paths << "/assets/"
|
16
|
+
c.skip_schema_queries = true
|
17
|
+
end
|
5
18
|
|
6
|
-
|
7
|
-
|
19
|
+
if Rails.env.production?
|
20
|
+
c.authorization_mode = :whitelist
|
21
|
+
end
|
22
|
+
|
23
|
+
# The file store is just so much less flaky
|
8
24
|
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
9
25
|
Dir::mkdir(tmp) unless File.exists?(tmp)
|
10
|
-
Rack::MiniProfiler.configuration[:storage_options] = {:path => tmp}
|
11
|
-
Rack::MiniProfiler.configuration[:storage] = Rack::MiniProfiler::FileStore
|
12
26
|
|
13
|
-
|
14
|
-
|
15
|
-
Rack::MiniProfiler.configuration[:backtrace_filter] = /^\/?(app|config|lib|test)/
|
27
|
+
c.storage_options = {:path => tmp}
|
28
|
+
c.storage = Rack::MiniProfiler::FileStore
|
16
29
|
|
17
|
-
#
|
30
|
+
# Quiet the SQL stack traces
|
31
|
+
c.backtrace_remove = Rails.root.to_s + "/"
|
32
|
+
c.backtrace_filter = /^\/?(app|config|lib|test)/
|
33
|
+
c.skip_schema_queries = Rails.env != 'production'
|
18
34
|
|
19
35
|
# Install the Middleware
|
20
|
-
app.middleware.
|
36
|
+
app.middleware.insert(0, Rack::MiniProfiler)
|
21
37
|
|
22
38
|
# Attach to various Rails methods
|
23
39
|
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
24
40
|
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
25
41
|
|
26
|
-
|
27
42
|
end
|
28
43
|
|
44
|
+
# TODO: Implement something better here
|
45
|
+
# config.after_initialize do
|
46
|
+
#
|
47
|
+
# class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
|
48
|
+
# alias_method :asset_tag_orig, :asset_tag
|
49
|
+
# def asset_tag(source,options)
|
50
|
+
# current = Rack::MiniProfiler.current
|
51
|
+
# return asset_tag_orig(source,options) unless current
|
52
|
+
# wrapped = ""
|
53
|
+
# unless current.mpt_init
|
54
|
+
# current.mpt_init = true
|
55
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
56
|
+
# end
|
57
|
+
# name = source.split('/')[-1]
|
58
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
59
|
+
# wrapped
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
|
63
|
+
# class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
|
64
|
+
# alias_method :asset_tag_orig, :asset_tag
|
65
|
+
# def asset_tag(source,options)
|
66
|
+
# current = Rack::MiniProfiler.current
|
67
|
+
# return asset_tag_orig(source,options) unless current
|
68
|
+
# wrapped = ""
|
69
|
+
# unless current.mpt_init
|
70
|
+
# current.mpt_init = true
|
71
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
72
|
+
# end
|
73
|
+
# name = source.split('/')[-1]
|
74
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
75
|
+
# wrapped
|
76
|
+
# end
|
77
|
+
# end
|
78
|
+
|
79
|
+
# end
|
80
|
+
|
29
81
|
end
|
30
82
|
end
|
data/lib/patches/sql_patches.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
|
1
|
+
class SqlPatches
|
2
|
+
|
3
|
+
def self.patched?
|
4
|
+
@patched
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.patched=(val)
|
8
|
+
@patched = val
|
9
|
+
end
|
10
|
+
|
2
11
|
def self.class_exists?(name)
|
3
12
|
eval(name + ".class").to_s.eql?('Class')
|
4
13
|
rescue NameError
|
@@ -6,12 +15,116 @@ module SqlPatches
|
|
6
15
|
end
|
7
16
|
end
|
8
17
|
|
9
|
-
|
18
|
+
# The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
|
19
|
+
if SqlPatches.class_exists? "Mysql2::Client"
|
20
|
+
|
21
|
+
class Mysql2::Result
|
22
|
+
alias_method :each_without_profiling, :each
|
23
|
+
def each(*args, &blk)
|
24
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
25
|
+
|
26
|
+
start = Time.now
|
27
|
+
result = each_without_profiling(*args,&blk)
|
28
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
29
|
+
|
30
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
31
|
+
result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Mysql2::Client
|
36
|
+
alias_method :query_without_profiling, :query
|
37
|
+
def query(*args,&blk)
|
38
|
+
current = ::Rack::MiniProfiler.current
|
39
|
+
return query_without_profiling(*args,&blk) unless current
|
40
|
+
|
41
|
+
start = Time.now
|
42
|
+
result = query_without_profiling(*args,&blk)
|
43
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
44
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
45
|
+
|
46
|
+
result
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
SqlPatches.patched = true
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
|
56
|
+
if SqlPatches.class_exists? "PG::Result"
|
57
|
+
|
58
|
+
class PG::Result
|
59
|
+
alias_method :each_without_profiling, :each
|
60
|
+
alias_method :values_without_profiling, :values
|
61
|
+
|
62
|
+
def values(*args, &blk)
|
63
|
+
return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
64
|
+
|
65
|
+
start = Time.now
|
66
|
+
result = values_without_profiling(*args,&blk)
|
67
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
68
|
+
|
69
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
70
|
+
result
|
71
|
+
end
|
72
|
+
|
73
|
+
def each(*args, &blk)
|
74
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
75
|
+
|
76
|
+
start = Time.now
|
77
|
+
result = each_without_profiling(*args,&blk)
|
78
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
79
|
+
|
80
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class PG::Connection
|
86
|
+
alias_method :exec_without_profiling, :exec
|
87
|
+
alias_method :async_exec_without_profiling, :async_exec
|
88
|
+
|
89
|
+
def exec(*args,&blk)
|
90
|
+
current = ::Rack::MiniProfiler.current
|
91
|
+
return exec_without_profiling(*args,&blk) unless current
|
92
|
+
|
93
|
+
start = Time.now
|
94
|
+
result = exec_without_profiling(*args,&blk)
|
95
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
96
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
97
|
+
|
98
|
+
result
|
99
|
+
end
|
100
|
+
|
101
|
+
def async_exec(*args,&blk)
|
102
|
+
current = ::Rack::MiniProfiler.current
|
103
|
+
return exec_without_profiling(*args,&blk) unless current
|
104
|
+
|
105
|
+
start = Time.now
|
106
|
+
result = exec_without_profiling(*args,&blk)
|
107
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
108
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
109
|
+
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :query, :exec
|
114
|
+
end
|
115
|
+
|
116
|
+
SqlPatches.patched = true
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
# Fallback for sequel
|
122
|
+
if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
|
10
123
|
module Sequel
|
11
124
|
class Database
|
12
125
|
alias_method :log_duration_original, :log_duration
|
13
126
|
def log_duration(duration, message)
|
14
|
-
::Rack::MiniProfiler.
|
127
|
+
::Rack::MiniProfiler.record_sql(message, duration)
|
15
128
|
log_duration_original(duration, message)
|
16
129
|
end
|
17
130
|
end
|
@@ -20,6 +133,7 @@ end
|
|
20
133
|
|
21
134
|
|
22
135
|
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
136
|
+
## fallback for alls sorts of weird dbs
|
23
137
|
module Rack
|
24
138
|
class MiniProfiler
|
25
139
|
module ActiveRecordInstrumentation
|
@@ -34,19 +148,18 @@ module Rack
|
|
34
148
|
end
|
35
149
|
|
36
150
|
def log_with_miniprofiler(*args, &block)
|
151
|
+
current = ::Rack::MiniProfiler.current
|
152
|
+
return log_without_miniprofiler(*args, &block) unless current
|
153
|
+
|
37
154
|
sql, name, binds = args
|
38
155
|
t0 = Time.now
|
39
156
|
rval = log_without_miniprofiler(*args, &block)
|
40
|
-
|
41
|
-
# Get our MP Instance
|
42
|
-
instance = ::Rack::MiniProfiler.instance
|
43
|
-
return rval unless instance
|
44
|
-
|
157
|
+
|
45
158
|
# Don't log schema queries if the option is set
|
46
|
-
return rval if
|
159
|
+
return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
|
47
160
|
|
48
161
|
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
|
49
|
-
|
162
|
+
Rack::MiniProfiler.record_sql(sql, elapsed_time)
|
50
163
|
rval
|
51
164
|
end
|
52
165
|
end
|
@@ -58,15 +171,8 @@ module Rack
|
|
58
171
|
end
|
59
172
|
end
|
60
173
|
|
61
|
-
if defined?(::Rails)
|
62
|
-
|
63
|
-
# in theory this is the right thing to do for rails 3 ... but it seems to work anyway
|
64
|
-
#Rails.configuration.after_initialize do
|
65
|
-
insert_instrumentation
|
66
|
-
#end
|
67
|
-
else
|
68
|
-
insert_instrumentation
|
69
|
-
end
|
174
|
+
if defined?(::Rails) && !SqlPatches.patched?
|
175
|
+
insert_instrumentation
|
70
176
|
end
|
71
177
|
end
|
72
178
|
|