ghazel-fiveruns_tuneup 0.8.22
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +33 -0
- data/CONTRIBUTORS +5 -0
- data/README.rdoc +65 -0
- data/assets/images/arrows.gif +0 -0
- data/assets/images/edit.png +0 -0
- data/assets/images/fade.png +0 -0
- data/assets/images/fade_down.png +0 -0
- data/assets/images/head.gif +0 -0
- data/assets/images/logo.gif +0 -0
- data/assets/images/logo_clear.png +0 -0
- data/assets/images/magnify.png +0 -0
- data/assets/images/pin.png +0 -0
- data/assets/images/pip.gif +0 -0
- data/assets/images/pointer.gif +0 -0
- data/assets/images/pushed_pin.png +0 -0
- data/assets/images/schema.png +0 -0
- data/assets/images/signin.gif +0 -0
- data/assets/images/spinner.gif +0 -0
- data/assets/images/warning.gif +0 -0
- data/assets/javascripts/init.js +12 -0
- data/assets/javascripts/prototype.js +2515 -0
- data/assets/javascripts/tuneup.js +115 -0
- data/assets/stylesheets/tuneup.css +209 -0
- data/init.rb +2 -0
- data/install.rb +13 -0
- data/lib/bumpspark_helper.rb +52 -0
- data/lib/fiveruns/tuneup.rb +154 -0
- data/lib/fiveruns/tuneup/asset_tags.rb +54 -0
- data/lib/fiveruns/tuneup/configuration.rb +25 -0
- data/lib/fiveruns/tuneup/custom_methods.rb +8 -0
- data/lib/fiveruns/tuneup/environment.rb +29 -0
- data/lib/fiveruns/tuneup/instrumentation/action_controller/base.rb +59 -0
- data/lib/fiveruns/tuneup/instrumentation/action_view/base.rb +81 -0
- data/lib/fiveruns/tuneup/instrumentation/action_view/partial_template.rb +28 -0
- data/lib/fiveruns/tuneup/instrumentation/active_record/base.rb +126 -0
- data/lib/fiveruns/tuneup/instrumentation/cgi/session.rb +30 -0
- data/lib/fiveruns/tuneup/instrumentation/utilities.rb +187 -0
- data/lib/fiveruns/tuneup/multipart.rb +75 -0
- data/lib/fiveruns/tuneup/routing.rb +25 -0
- data/lib/fiveruns/tuneup/runs.rb +86 -0
- data/lib/fiveruns/tuneup/schema.rb +43 -0
- data/lib/fiveruns/tuneup/step.rb +221 -0
- data/lib/fiveruns/tuneup/version.rb +89 -0
- data/lib/fiveruns_tuneup.rb +11 -0
- data/lib/tuneup_controller.rb +46 -0
- data/lib/tuneup_helper.rb +181 -0
- data/rails/init.rb +14 -0
- data/tasks/assets.rake +32 -0
- data/test/test_helper.rb +3 -0
- data/test/tuneup_test.rb +0 -0
- data/uninstall.rb +6 -0
- data/views/tuneup/_data.html.erb +15 -0
- data/views/tuneup/_flash.html.erb +6 -0
- data/views/tuneup/_link.html.erb +1 -0
- data/views/tuneup/_schema.html.erb +17 -0
- data/views/tuneup/_sql.html.erb +23 -0
- data/views/tuneup/_step.html.erb +17 -0
- data/views/tuneup/panel/_show.html.erb +4 -0
- data/views/tuneup/sandbox.html.erb +6 -0
- metadata +124 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
module Fiveruns
|
2
|
+
module Tuneup
|
3
|
+
module Instrumentation
|
4
|
+
module Utilities
|
5
|
+
|
6
|
+
def stack
|
7
|
+
@stack ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def exclusion_stack
|
11
|
+
@exclusion_stack ||= [0]
|
12
|
+
end
|
13
|
+
|
14
|
+
def custom_methods
|
15
|
+
@custom_methods ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_custom_methods(target, *methods)
|
19
|
+
custom_methods[target] = [] unless custom_methods.key?(target)
|
20
|
+
custom_methods[target].push(*methods)
|
21
|
+
end
|
22
|
+
|
23
|
+
def stopwatch
|
24
|
+
start = Time.now.to_f
|
25
|
+
yield
|
26
|
+
(Time.now.to_f - start) * 1000
|
27
|
+
end
|
28
|
+
|
29
|
+
def exclude
|
30
|
+
result = nil
|
31
|
+
exclusion_stack[-1] += stopwatch { result = yield }
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def step(name, layer=nil, link=true, sql=nil, table_name=nil, &block)
|
36
|
+
if recording?
|
37
|
+
result = nil
|
38
|
+
caller_line = caller.detect { |l| l.include?(RAILS_ROOT) && l !~ /tuneup|vendor\/rails/ } if link
|
39
|
+
file, line = caller_line ? caller_line.split(':')[0, 2] : [nil, nil]
|
40
|
+
line = line.to_i if line
|
41
|
+
returning ::Fiveruns::Tuneup::Step.new(name, layer, file, line, sql, &block) do |step|
|
42
|
+
step.table_name = table_name
|
43
|
+
stack.last << step
|
44
|
+
stack << step
|
45
|
+
begin
|
46
|
+
handle_exclusions_in step do
|
47
|
+
step.time = stopwatch { result = yield(sql) }
|
48
|
+
end
|
49
|
+
ensure
|
50
|
+
stack.pop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
result
|
54
|
+
else
|
55
|
+
yield(sql)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Handle removal of excluded time from total for this step, and
|
60
|
+
# bubble the value up for removal from the parent step
|
61
|
+
def handle_exclusions_in(step)
|
62
|
+
exclusion_stack << 0
|
63
|
+
yield # Must set +step.time+
|
64
|
+
time_to_exclude = exclusion_stack.pop
|
65
|
+
step.time -= time_to_exclude
|
66
|
+
exclusion_stack[-1] += time_to_exclude unless exclusion_stack.blank?
|
67
|
+
end
|
68
|
+
|
69
|
+
def instrument(target, *mods)
|
70
|
+
mods.each do |mod|
|
71
|
+
# Change target for 'ClassMethods' module
|
72
|
+
real_target = mod.name.demodulize == 'ClassMethods' ? (class << target; self; end) : target
|
73
|
+
real_target.__send__(:include, mod)
|
74
|
+
# Find all the instrumentation hooks and chain them in
|
75
|
+
mod.instance_methods.each do |meth|
|
76
|
+
name = meth.to_s.sub('_with_fiveruns_tuneup', '')
|
77
|
+
real_target.alias_method_chain(name, :fiveruns_tuneup) rescue nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def instrument_action_methods(controller)
|
83
|
+
klass = controller.class
|
84
|
+
actions_for(klass).each do |meth|
|
85
|
+
format = alias_format_for(meth)
|
86
|
+
next if controller.respond_to?(format % :with, true)
|
87
|
+
wrap(klass, format, meth, "Invoke #{meth} action", :controller)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def instrument_filters(controller)
|
92
|
+
klass = controller.class
|
93
|
+
filters_for(klass).each do |filter|
|
94
|
+
format = alias_format_for(name_of_filter(filter))
|
95
|
+
next if controller.respond_to?(format % :with, true)
|
96
|
+
wrap(klass, format, name_of_filter(filter), "#{filter.type.to_s.titleize} filter #{name_of_filter(filter)}", :controller)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def instrument_custom_methods
|
101
|
+
custom_methods.each do |meth_target, meths|
|
102
|
+
lineage = meth_target.ancestors
|
103
|
+
layer = if lineage.include?(ActionController::Base)
|
104
|
+
:controller
|
105
|
+
elsif lineage.include?(ActiveRecord::Base)
|
106
|
+
:model
|
107
|
+
elsif lineage.include?(ActionView::Base)
|
108
|
+
:view
|
109
|
+
else
|
110
|
+
:other
|
111
|
+
end
|
112
|
+
meths.each do |meth|
|
113
|
+
format = alias_format_for(meth)
|
114
|
+
wrap(meth_target, format, meth, "Method #{meth}", layer)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#######
|
120
|
+
private
|
121
|
+
#######
|
122
|
+
|
123
|
+
def wrap(klass, format, meth, name, layer)
|
124
|
+
return if klass.instance_methods.include?(format % :with)
|
125
|
+
text = <<-EOC
|
126
|
+
def #{format % :with}(*args, &block)
|
127
|
+
Fiveruns::Tuneup.step "#{name}", :#{layer} do
|
128
|
+
#{format % :without}(*args, &block)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
alias_method_chain :#{meth}, :fiveruns_tuneup
|
132
|
+
EOC
|
133
|
+
begin
|
134
|
+
klass.class_eval text
|
135
|
+
rescue SyntaxError => e
|
136
|
+
# XXX: Catch-all for reports of oddly-named methods affecting dynamically generated code
|
137
|
+
log :warn, %[Bad syntax wrapping #{klass}##{meth}, "#{name}"]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def alias_format_for(name)
|
142
|
+
name.to_s =~ /^(.*?)(\?|!|=)$/ ? "#{$1}_%s_fiveruns_tuneup#{$2}" : "#{name}_%s_fiveruns_tuneup"
|
143
|
+
end
|
144
|
+
|
145
|
+
def actions_for(klass)
|
146
|
+
klass.action_methods.reject { |meth| meth.to_s.include?('fiveruns') }
|
147
|
+
end
|
148
|
+
|
149
|
+
def filters_for(klass)
|
150
|
+
klass.filter_chain.select { |f| name_of_filter(f).is_a?(Symbol) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def name_of_filter(filter)
|
154
|
+
if filter.respond_to?(:filter)
|
155
|
+
filter.filter
|
156
|
+
else
|
157
|
+
filter.method
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def install_instrumentation
|
162
|
+
instrumentation_path = File.dirname(__FILE__)
|
163
|
+
Dir[File.join(instrumentation_path, '/*/**/*.rb')].each do |filename|
|
164
|
+
constant_path = filename[(instrumentation_path.size + 1)..-4]
|
165
|
+
constant_name = path_to_constant_name(constant_path)
|
166
|
+
|
167
|
+
instrumentation = "Fiveruns::Tuneup::Instrumentation::#{constant_name}".constantize
|
168
|
+
next if instrumentation.respond_to?(:relevant?) && !instrumentation.relevant?
|
169
|
+
|
170
|
+
if (constant = constant_name.constantize rescue nil)
|
171
|
+
constant.__send__(:include, instrumentation)
|
172
|
+
else
|
173
|
+
log :debug, "#{constant_name} not found; skipping instrumentation."
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def path_to_constant_name(path)
|
179
|
+
parts = path.split(File::SEPARATOR)
|
180
|
+
parts.map(&:camelize).join('::').sub('Cgi', 'CGI')
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module Fiveruns
|
5
|
+
|
6
|
+
module Tuneup
|
7
|
+
|
8
|
+
class Multipart
|
9
|
+
|
10
|
+
BOUNDARY_ROOT = 'B0UND~F0R~UPL0AD'
|
11
|
+
|
12
|
+
attr_reader :file, :params
|
13
|
+
def initialize(file, params={})
|
14
|
+
@file = file
|
15
|
+
@params = params
|
16
|
+
end
|
17
|
+
|
18
|
+
def content_type
|
19
|
+
%(multipart/form-data, boundary="#{boundary}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
%(#{parts}\r\n#{separator}--)
|
24
|
+
end
|
25
|
+
|
26
|
+
#######
|
27
|
+
private
|
28
|
+
#######
|
29
|
+
|
30
|
+
def boundary
|
31
|
+
"#{BOUNDARY_ROOT}*#{nonce}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def parts
|
35
|
+
params.merge(:file => file).map do |name, value|
|
36
|
+
[
|
37
|
+
separator,
|
38
|
+
headers_for(name, value)
|
39
|
+
].flatten.join(crlf) + crlf + crlf + content_of(value)
|
40
|
+
end.flatten.join(crlf)
|
41
|
+
end
|
42
|
+
|
43
|
+
def separator
|
44
|
+
%(--#{boundary})
|
45
|
+
end
|
46
|
+
|
47
|
+
def crlf
|
48
|
+
@crlf ||= "\r\n"
|
49
|
+
end
|
50
|
+
|
51
|
+
def headers_for(name, value)
|
52
|
+
if value.respond_to?(:read)
|
53
|
+
[
|
54
|
+
%(Content-Disposition: form-data; name="#{name}"; filename="#{File.basename(value.path)}"),
|
55
|
+
%(Content-Transfer-Encoding: binary),
|
56
|
+
%(Content-Type: application/octet-stream)
|
57
|
+
]
|
58
|
+
else
|
59
|
+
[ %(Content-Disposition: form-data; name="#{name}") ]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def nonce
|
64
|
+
@nonce ||= (Time.now.utc.to_f * 1000).to_i
|
65
|
+
end
|
66
|
+
|
67
|
+
def content_of(value)
|
68
|
+
value.respond_to?(:read) ? value.read : value.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Fiveruns::Tuneup
|
2
|
+
|
3
|
+
module Routing
|
4
|
+
|
5
|
+
def self.install
|
6
|
+
ActionController::Routing::RouteSet.send(:include, self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.alias_method_chain :draw, :fiveruns_tuneup
|
11
|
+
end
|
12
|
+
def draw_with_fiveruns_tuneup(*args, &block)
|
13
|
+
draw_without_fiveruns_tuneup(*args) do |map|
|
14
|
+
map.connect '/tuneup', :controller => 'tuneup', :action => 'show'
|
15
|
+
map.connect '/tuneup/:action', :controller => 'tuneup'
|
16
|
+
yield map
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Fiveruns
|
2
|
+
module Tuneup
|
3
|
+
module Runs
|
4
|
+
|
5
|
+
def run_dir
|
6
|
+
@run_dir ||= File.join(RAILS_ROOT, 'tmp', 'tuneup', 'runs', RAILS_ENV)
|
7
|
+
end
|
8
|
+
|
9
|
+
def retrieve_run(run_id)
|
10
|
+
filename = filename_for(run_id)
|
11
|
+
if File.file?(filename)
|
12
|
+
load_from_file(filename)
|
13
|
+
else
|
14
|
+
log :error, "Couldn't find filename: #{filename}"
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_from_file(filename)
|
20
|
+
decompressed = Zlib::Inflate.inflate(File.open(filename, 'rb') { |f| f.read })
|
21
|
+
YAML.load(decompressed)
|
22
|
+
end
|
23
|
+
|
24
|
+
def last_filename_for_run_uri(uri)
|
25
|
+
filename_for(last_run_id_for(uri))
|
26
|
+
end
|
27
|
+
|
28
|
+
def last_run
|
29
|
+
last_file = sorted_run_files.last
|
30
|
+
load_from_file(last_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
#######
|
34
|
+
private
|
35
|
+
#######
|
36
|
+
|
37
|
+
def sorted_run_files
|
38
|
+
Dir[File.join(run_dir, '*/*.gz')].sort_by do |f|
|
39
|
+
File.basename(f).split('_').first.to_i
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def trend_for(run_id)
|
44
|
+
Dir[File.join(run_dir, File.dirname(run_id), "*.gz")].map do |filename|
|
45
|
+
Integer(File.basename(filename, '.yml.gz').split('_').last)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def last_run_id_for(url)
|
50
|
+
last_file = Dir[File.join(run_dir, stub(url), '*.gz')].last
|
51
|
+
if last_file
|
52
|
+
File.join(File.basename(File.dirname(last_file)), File.basename(last_file, '.yml.gz'))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Use Run ID, current timestamp, and total time (in microseconds)
|
57
|
+
def generate_run_id(url, time)
|
58
|
+
timestamp = '%d' % (Time.now.to_f * 1000)
|
59
|
+
File.join(stub(url), timestamp.to_s << "_#{(time * 1000).to_i}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def persist(run_id, environment, schemas, data)
|
63
|
+
log :info, "Persisting #{run_id}"
|
64
|
+
filename = filename_for(run_id)
|
65
|
+
FileUtils.mkdir_p File.dirname(filename)
|
66
|
+
compressed = Zlib::Deflate.deflate(package_for(run_id, environment, schemas, data).to_yaml)
|
67
|
+
File.open(filename, 'wb') { |f| f.write compressed }
|
68
|
+
end
|
69
|
+
|
70
|
+
def filename_for(run_id)
|
71
|
+
File.join(run_dir, run_id) << '.yml.gz'
|
72
|
+
end
|
73
|
+
|
74
|
+
def stub(url)
|
75
|
+
Digest::SHA1.hexdigest(url)
|
76
|
+
end
|
77
|
+
|
78
|
+
def package_for(run_id, environment, schemas, data)
|
79
|
+
{'id' => run_id, 'environment' => environment, 'schemas' => schemas, 'stack' => data}
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Fiveruns
|
2
|
+
module Tuneup
|
3
|
+
module Schema
|
4
|
+
|
5
|
+
def schemas
|
6
|
+
@schemas ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_schema_for(table, connection)
|
10
|
+
schemas[table] ||= begin
|
11
|
+
{
|
12
|
+
:columns => columns_for(table, connection),
|
13
|
+
:indexes => indexes_for(table, connection)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#######
|
19
|
+
private
|
20
|
+
#######
|
21
|
+
|
22
|
+
def columns_for(table, connection)
|
23
|
+
connection.columns(table).map do |column|
|
24
|
+
extract(column, :name, :sql_type)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def indexes_for(table, connection)
|
29
|
+
connection.indexes(table).map do |index|
|
30
|
+
extract(index, :name, :unique, :columns)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def extract(obj, *fields)
|
35
|
+
fields.inject({}) do |hash, field|
|
36
|
+
hash[field] = obj.__send__(field)
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module Fiveruns
|
2
|
+
module Tuneup
|
3
|
+
|
4
|
+
class RootStep
|
5
|
+
|
6
|
+
delegate :blank?, :to => :children
|
7
|
+
alias_method :id, :object_id # Avoid record identitication warnings
|
8
|
+
|
9
|
+
def self.layers
|
10
|
+
framework_layers + [:other]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.framework_layers
|
14
|
+
[:model, :view, :controller]
|
15
|
+
end
|
16
|
+
|
17
|
+
def schemas
|
18
|
+
@schemas ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def time
|
22
|
+
children.map(&:time).sum || 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def depth
|
26
|
+
@depth ||= 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def <<(child)
|
30
|
+
child.depth = depth + 1
|
31
|
+
children << child
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
children.map(&:size).sum || 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def children_with_disparity
|
39
|
+
children + [Step.disparity(disparity, self)]
|
40
|
+
end
|
41
|
+
|
42
|
+
def children
|
43
|
+
@children ||= []
|
44
|
+
end
|
45
|
+
|
46
|
+
def leaf?
|
47
|
+
children.blank?
|
48
|
+
end
|
49
|
+
|
50
|
+
def leaves
|
51
|
+
@leaves ||= begin
|
52
|
+
if children.blank?
|
53
|
+
[self]
|
54
|
+
else
|
55
|
+
children.map(&:leaves).flatten
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def child_times_by_layer
|
61
|
+
@child_times_by_layer ||= children.inject(Hash.new(0)) do |totals, child|
|
62
|
+
child.percentages_by_layer.each do |layer, percentage|
|
63
|
+
totals[layer] += child.time * percentage
|
64
|
+
end
|
65
|
+
totals
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def percentages_by_layer
|
70
|
+
@percentages_by_layer ||= begin
|
71
|
+
percentages = self.class.framework_layers.inject({}) do |map, layer|
|
72
|
+
map[layer] = if leaf?
|
73
|
+
self.layer == layer ? 1.0 : 0
|
74
|
+
else
|
75
|
+
result = child_times_by_layer[layer] / self.time
|
76
|
+
result = nil unless result.to_s =~ /\d/
|
77
|
+
result.is_a?(Numeric) ? result : 0 # TODO: Fix issue at source
|
78
|
+
end
|
79
|
+
map
|
80
|
+
end
|
81
|
+
fill percentages
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
#######
|
86
|
+
private
|
87
|
+
#######
|
88
|
+
|
89
|
+
def fill(percentages)
|
90
|
+
returning percentages do
|
91
|
+
percentages[:other] ||= 0
|
92
|
+
unless leaf?
|
93
|
+
if disparity > 0
|
94
|
+
percentages[layer] += disparity / self.time
|
95
|
+
end
|
96
|
+
end
|
97
|
+
total = percentages.values.sum
|
98
|
+
if total < 0.999
|
99
|
+
percentages[:other] += 1.0 - total
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def disparity
|
105
|
+
@disparity ||= begin
|
106
|
+
child_total = children.map(&:time).sum || 0
|
107
|
+
disparity = time - child_total
|
108
|
+
disparity > 0 ? disparity : 0
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
class Step < RootStep
|
115
|
+
|
116
|
+
attr_reader :name, :layer, :file, :line, :sql
|
117
|
+
attr_accessor :table_name
|
118
|
+
attr_writer :time, :depth
|
119
|
+
|
120
|
+
def self.disparity(time, parent)
|
121
|
+
returning Step.new("Other", parent.layer) do |step|
|
122
|
+
step.time = time
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize(name, layer=nil, file=nil, line=nil, sql=nil)
|
127
|
+
@name = name
|
128
|
+
@layer = layer
|
129
|
+
@file = file
|
130
|
+
@line = line
|
131
|
+
@sql = sql
|
132
|
+
end
|
133
|
+
|
134
|
+
def time
|
135
|
+
# FIXME: rank hack to get around weird JRuby YAML bug
|
136
|
+
@time.respond_to?(:value) ? @time.value.to_f : @time || 0
|
137
|
+
end
|
138
|
+
|
139
|
+
def size
|
140
|
+
children.map(&:size).sum + 1
|
141
|
+
end
|
142
|
+
|
143
|
+
class SQL
|
144
|
+
|
145
|
+
attr_reader :query, :explain
|
146
|
+
|
147
|
+
def initialize(sql, connection)
|
148
|
+
@query = sql
|
149
|
+
@explain = explain_from(connection)
|
150
|
+
end
|
151
|
+
|
152
|
+
#######
|
153
|
+
private
|
154
|
+
#######
|
155
|
+
|
156
|
+
def explain_from(connection)
|
157
|
+
return nil unless @query =~ /^select\b/i
|
158
|
+
return nil unless connection.adapter_name == 'MySQL'
|
159
|
+
explain = Explain.new(@query, connection)
|
160
|
+
explain if explain.valid?
|
161
|
+
end
|
162
|
+
|
163
|
+
class Explain
|
164
|
+
|
165
|
+
attr_reader :fields, :rows
|
166
|
+
|
167
|
+
def initialize(sql, connection)
|
168
|
+
result = connection.execute("explain #{sql}")
|
169
|
+
@fields = fetch_fields_from(result)
|
170
|
+
@rows = fetch_rows_from(result)
|
171
|
+
result.free
|
172
|
+
add_schemas(connection)
|
173
|
+
@valid = true
|
174
|
+
rescue Exception
|
175
|
+
@valid = false
|
176
|
+
end
|
177
|
+
|
178
|
+
def valid?
|
179
|
+
@valid
|
180
|
+
end
|
181
|
+
|
182
|
+
def table_offset
|
183
|
+
@table_offset ||= @fields.index('table')
|
184
|
+
end
|
185
|
+
|
186
|
+
#######
|
187
|
+
private
|
188
|
+
#######
|
189
|
+
|
190
|
+
def fetch_fields_from(result)
|
191
|
+
result.fetch_fields.map(&:name)
|
192
|
+
end
|
193
|
+
|
194
|
+
def fetch_rows_from(result)
|
195
|
+
returning [] do |rows|
|
196
|
+
result.each do |row|
|
197
|
+
rows << row
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def add_schemas(connection)
|
203
|
+
tables.each do |table|
|
204
|
+
Fiveruns::Tuneup.add_schema_for(table, connection)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def tables
|
209
|
+
return [] unless table_offset
|
210
|
+
@rows.map { |row| row[table_offset] }.compact
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|