rack-mini-profiler 0.1.3 → 0.1.8
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.
- data/CHANGELOG +15 -0
- data/README.md +18 -0
- data/lib/html/includes.css +75 -1
- data/lib/html/includes.js +14 -2
- data/lib/html/includes.less +1 -1
- data/lib/html/profile_handler.js +9 -9
- data/lib/mini_profiler/client_timer_struct.rb +38 -3
- data/lib/mini_profiler/config.rb +2 -1
- data/lib/mini_profiler/context.rb +10 -0
- data/lib/mini_profiler/page_timer_struct.rb +7 -3
- data/lib/mini_profiler/profiler.rb +161 -144
- data/lib/mini_profiler/profiling_methods.rb +73 -0
- data/lib/mini_profiler/request_timer_struct.rb +39 -8
- data/lib/mini_profiler/sql_timer_struct.rb +13 -2
- data/lib/mini_profiler/storage/file_store.rb +8 -7
- data/lib/mini_profiler/storage/memory_store.rb +0 -4
- data/lib/mini_profiler_rails/railtie.rb +41 -2
- data/lib/patches/sql_patches.rb +154 -34
- data/lib/rack-mini-profiler.rb +3 -3
- data/rack-mini-profiler.gemspec +3 -4
- metadata +6 -6
- data/lib/html/MiniProfilerHandler.cs +0 -419
- data/lib/mini_profiler/body_add_proxy.rb +0 -45
@@ -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
72
|
def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
|
49
|
-
timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace, full_backtrace)
|
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, full_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
|
@@ -20,17 +20,28 @@ module Rack
|
|
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
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
|
@@ -39,9 +39,14 @@ module Rack
|
|
39
39
|
|
40
40
|
me = self
|
41
41
|
Thread.new do
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
begin
|
43
|
+
while true do
|
44
|
+
# TODO: a sane retry count before bailing
|
45
|
+
me.cleanup_cache
|
46
|
+
sleep(3600)
|
47
|
+
end
|
48
|
+
rescue
|
49
|
+
# don't crash the thread, we can clean up next time
|
45
50
|
end
|
46
51
|
end
|
47
52
|
end
|
@@ -83,10 +88,6 @@ module Rack
|
|
83
88
|
}
|
84
89
|
end
|
85
90
|
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
|
90
91
|
def cleanup_cache
|
91
92
|
files = Dir.entries(@path)
|
92
93
|
@timer_struct_lock.synchronize {
|
@@ -1,4 +1,7 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module MiniProfilerRails
|
4
|
+
|
2
5
|
class Railtie < ::Rails::Railtie
|
3
6
|
|
4
7
|
initializer "rack_mini_profiler.configure_rails_initialization" do |app|
|
@@ -21,7 +24,7 @@ module MiniProfilerRails
|
|
21
24
|
|
22
25
|
# The file store is just so much less flaky
|
23
26
|
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
24
|
-
|
27
|
+
FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
|
25
28
|
|
26
29
|
c.storage_options = {:path => tmp}
|
27
30
|
c.storage = Rack::MiniProfiler::FileStore
|
@@ -38,8 +41,44 @@ module MiniProfilerRails
|
|
38
41
|
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
39
42
|
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
40
43
|
|
41
|
-
|
42
44
|
end
|
43
45
|
|
46
|
+
# TODO: Implement something better here
|
47
|
+
# config.after_initialize do
|
48
|
+
#
|
49
|
+
# class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
|
50
|
+
# alias_method :asset_tag_orig, :asset_tag
|
51
|
+
# def asset_tag(source,options)
|
52
|
+
# current = Rack::MiniProfiler.current
|
53
|
+
# return asset_tag_orig(source,options) unless current
|
54
|
+
# wrapped = ""
|
55
|
+
# unless current.mpt_init
|
56
|
+
# current.mpt_init = true
|
57
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
58
|
+
# end
|
59
|
+
# name = source.split('/')[-1]
|
60
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
61
|
+
# wrapped
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
|
65
|
+
# class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
|
66
|
+
# alias_method :asset_tag_orig, :asset_tag
|
67
|
+
# def asset_tag(source,options)
|
68
|
+
# current = Rack::MiniProfiler.current
|
69
|
+
# return asset_tag_orig(source,options) unless current
|
70
|
+
# wrapped = ""
|
71
|
+
# unless current.mpt_init
|
72
|
+
# current.mpt_init = true
|
73
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
|
74
|
+
# end
|
75
|
+
# name = source.split('/')[-1]
|
76
|
+
# wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
|
77
|
+
# wrapped
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
|
81
|
+
# end
|
82
|
+
|
44
83
|
end
|
45
84
|
end
|
data/lib/patches/sql_patches.rb
CHANGED
@@ -1,17 +1,136 @@
|
|
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
|
5
14
|
false
|
6
15
|
end
|
16
|
+
|
17
|
+
def self.module_exists?(name)
|
18
|
+
eval(name + ".class").to_s.eql?('Module')
|
19
|
+
rescue NameError
|
20
|
+
false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
|
25
|
+
if SqlPatches.class_exists? "Mysql2::Client"
|
26
|
+
|
27
|
+
class Mysql2::Result
|
28
|
+
alias_method :each_without_profiling, :each
|
29
|
+
def each(*args, &blk)
|
30
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
31
|
+
|
32
|
+
start = Time.now
|
33
|
+
result = each_without_profiling(*args,&blk)
|
34
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
35
|
+
|
36
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Mysql2::Client
|
42
|
+
alias_method :query_without_profiling, :query
|
43
|
+
def query(*args,&blk)
|
44
|
+
current = ::Rack::MiniProfiler.current
|
45
|
+
return query_without_profiling(*args,&blk) unless current
|
46
|
+
|
47
|
+
start = Time.now
|
48
|
+
result = query_without_profiling(*args,&blk)
|
49
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
50
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
51
|
+
|
52
|
+
result
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
SqlPatches.patched = true
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
|
62
|
+
if SqlPatches.class_exists? "PG::Result"
|
63
|
+
|
64
|
+
class PG::Result
|
65
|
+
alias_method :each_without_profiling, :each
|
66
|
+
alias_method :values_without_profiling, :values
|
67
|
+
|
68
|
+
def values(*args, &blk)
|
69
|
+
return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
70
|
+
|
71
|
+
start = Time.now
|
72
|
+
result = values_without_profiling(*args,&blk)
|
73
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
74
|
+
|
75
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
76
|
+
result
|
77
|
+
end
|
78
|
+
|
79
|
+
def each(*args, &blk)
|
80
|
+
return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
|
81
|
+
|
82
|
+
start = Time.now
|
83
|
+
result = each_without_profiling(*args,&blk)
|
84
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
85
|
+
|
86
|
+
@miniprofiler_sql_id.report_reader_duration(elapsed_time)
|
87
|
+
result
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class PG::Connection
|
92
|
+
alias_method :exec_without_profiling, :exec
|
93
|
+
alias_method :async_exec_without_profiling, :async_exec
|
94
|
+
|
95
|
+
def exec(*args,&blk)
|
96
|
+
current = ::Rack::MiniProfiler.current
|
97
|
+
return exec_without_profiling(*args,&blk) unless current
|
98
|
+
|
99
|
+
start = Time.now
|
100
|
+
result = exec_without_profiling(*args,&blk)
|
101
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
102
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
103
|
+
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
def async_exec(*args,&blk)
|
108
|
+
current = ::Rack::MiniProfiler.current
|
109
|
+
return exec_without_profiling(*args,&blk) unless current
|
110
|
+
|
111
|
+
start = Time.now
|
112
|
+
result = exec_without_profiling(*args,&blk)
|
113
|
+
elapsed_time = ((Time.now - start).to_f * 1000).round(1)
|
114
|
+
result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
|
115
|
+
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
alias_method :query, :exec
|
120
|
+
end
|
121
|
+
|
122
|
+
SqlPatches.patched = true
|
7
123
|
end
|
8
124
|
|
9
|
-
|
125
|
+
|
126
|
+
|
127
|
+
# Fallback for sequel
|
128
|
+
if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
|
10
129
|
module Sequel
|
11
130
|
class Database
|
12
131
|
alias_method :log_duration_original, :log_duration
|
13
132
|
def log_duration(duration, message)
|
14
|
-
::Rack::MiniProfiler.
|
133
|
+
::Rack::MiniProfiler.record_sql(message, duration)
|
15
134
|
log_duration_original(duration, message)
|
16
135
|
end
|
17
136
|
end
|
@@ -20,46 +139,47 @@ end
|
|
20
139
|
|
21
140
|
|
22
141
|
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
142
|
+
## fallback for alls sorts of weird dbs
|
143
|
+
if SqlPatches.module_exists?('ActiveRecord')
|
144
|
+
module Rack
|
145
|
+
class MiniProfiler
|
146
|
+
module ActiveRecordInstrumentation
|
147
|
+
def self.included(instrumented_class)
|
148
|
+
instrumented_class.class_eval do
|
149
|
+
unless instrumented_class.method_defined?(:log_without_miniprofiler)
|
150
|
+
alias_method :log_without_miniprofiler, :log
|
151
|
+
alias_method :log, :log_with_miniprofiler
|
152
|
+
protected :log
|
153
|
+
end
|
32
154
|
end
|
33
155
|
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def log_with_miniprofiler(*args, &block)
|
37
|
-
sql, name, binds = args
|
38
|
-
t0 = Time.now
|
39
|
-
rval = log_without_miniprofiler(*args, &block)
|
40
156
|
|
41
|
-
|
42
|
-
|
43
|
-
|
157
|
+
def log_with_miniprofiler(*args, &block)
|
158
|
+
current = ::Rack::MiniProfiler.current
|
159
|
+
return log_without_miniprofiler(*args, &block) unless current
|
44
160
|
|
45
|
-
|
46
|
-
|
161
|
+
sql, name, binds = args
|
162
|
+
t0 = Time.now
|
163
|
+
rval = log_without_miniprofiler(*args, &block)
|
164
|
+
|
165
|
+
# Don't log schema queries if the option is set
|
166
|
+
return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
|
47
167
|
|
48
|
-
|
49
|
-
|
50
|
-
|
168
|
+
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
|
169
|
+
Rack::MiniProfiler.record_sql(sql, elapsed_time)
|
170
|
+
rval
|
171
|
+
end
|
51
172
|
end
|
52
173
|
end
|
53
|
-
end
|
54
174
|
|
55
|
-
|
56
|
-
|
57
|
-
|
175
|
+
def self.insert_instrumentation
|
176
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
177
|
+
include ::Rack::MiniProfiler::ActiveRecordInstrumentation
|
178
|
+
end
|
58
179
|
end
|
59
|
-
end
|
60
180
|
|
61
|
-
|
62
|
-
|
181
|
+
if defined?(::Rails) && !SqlPatches.patched?
|
182
|
+
insert_instrumentation
|
183
|
+
end
|
63
184
|
end
|
64
185
|
end
|
65
|
-
|