fiveruns_tuneup 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/History.rdoc +3 -0
  2. data/Manifest +57 -0
  3. data/README.rdoc +44 -0
  4. data/Rakefile +15 -0
  5. data/assets/images/arrows.gif +0 -0
  6. data/assets/images/edit.png +0 -0
  7. data/assets/images/fade.png +0 -0
  8. data/assets/images/fade_down.png +0 -0
  9. data/assets/images/head.gif +0 -0
  10. data/assets/images/logo.gif +0 -0
  11. data/assets/images/logo_clear.png +0 -0
  12. data/assets/images/magnify.png +0 -0
  13. data/assets/images/pip.gif +0 -0
  14. data/assets/images/pointer.gif +0 -0
  15. data/assets/images/schema.png +0 -0
  16. data/assets/images/signin.gif +0 -0
  17. data/assets/images/spinner.gif +0 -0
  18. data/assets/images/warning.gif +0 -0
  19. data/assets/javascripts/prototype.js +2515 -0
  20. data/assets/javascripts/tuneup.js +30 -0
  21. data/assets/stylesheets/tuneup.css +204 -0
  22. data/bin/fiveruns_tuneup +26 -0
  23. data/fiveruns_tuneup.gemspec +49 -0
  24. data/init.rb +2 -0
  25. data/install.rb +18 -0
  26. data/lib/bumpspark_helper.rb +52 -0
  27. data/lib/fiveruns/tuneup.rb +103 -0
  28. data/lib/fiveruns/tuneup/asset_tags.rb +39 -0
  29. data/lib/fiveruns/tuneup/custom_methods.rb +8 -0
  30. data/lib/fiveruns/tuneup/environment.rb +29 -0
  31. data/lib/fiveruns/tuneup/instrumentation/action_controller/base.rb +59 -0
  32. data/lib/fiveruns/tuneup/instrumentation/action_view/base.rb +77 -0
  33. data/lib/fiveruns/tuneup/instrumentation/active_record/base.rb +126 -0
  34. data/lib/fiveruns/tuneup/instrumentation/cgi/session.rb +30 -0
  35. data/lib/fiveruns/tuneup/instrumentation/utilities.rb +172 -0
  36. data/lib/fiveruns/tuneup/multipart.rb +75 -0
  37. data/lib/fiveruns/tuneup/runs.rb +86 -0
  38. data/lib/fiveruns/tuneup/schema.rb +43 -0
  39. data/lib/fiveruns/tuneup/step.rb +219 -0
  40. data/lib/fiveruns/tuneup/urls.rb +23 -0
  41. data/lib/fiveruns/tuneup/version.rb +80 -0
  42. data/lib/fiveruns_tuneup.rb +1 -0
  43. data/lib/tuneup_config.rb +29 -0
  44. data/lib/tuneup_controller.rb +140 -0
  45. data/lib/tuneup_helper.rb +185 -0
  46. data/rails/init.rb +20 -0
  47. data/tasks/assets.rake +32 -0
  48. data/test/test_helper.rb +3 -0
  49. data/test/tuneup_test.rb +0 -0
  50. data/uninstall.rb +6 -0
  51. data/views/tuneup/_data.html.erb +15 -0
  52. data/views/tuneup/_flash.html.erb +6 -0
  53. data/views/tuneup/_link.html.erb +1 -0
  54. data/views/tuneup/_schema.html.erb +17 -0
  55. data/views/tuneup/_sql.html.erb +23 -0
  56. data/views/tuneup/_step.html.erb +15 -0
  57. data/views/tuneup/panel/_registered.html.erb +4 -0
  58. data/views/tuneup/panel/_unregistered.html.erb +14 -0
  59. metadata +146 -0
@@ -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,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')
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,219 @@
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
+ unless leaf?
92
+ if disparity > 0
93
+ percentages[layer] += disparity / self.time
94
+ end
95
+ end
96
+ percentages[:other] ||= 0
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
+ time - child_total
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ class Step < RootStep
114
+
115
+ attr_reader :name, :layer, :file, :line, :sql
116
+ attr_accessor :table_name
117
+ attr_writer :time, :depth
118
+
119
+ def self.disparity(time, parent)
120
+ returning Step.new("Other", parent.layer) do |step|
121
+ step.time = time
122
+ end
123
+ end
124
+
125
+ def initialize(name, layer=nil, file=nil, line=nil, sql=nil)
126
+ @name = name
127
+ @layer = layer
128
+ @file = file
129
+ @line = line
130
+ @sql = sql
131
+ end
132
+
133
+ def time
134
+ @time || 0
135
+ end
136
+
137
+ def size
138
+ children.map(&:size).sum + 1
139
+ end
140
+
141
+ class SQL
142
+
143
+ attr_reader :query, :explain
144
+
145
+ def initialize(sql, connection)
146
+ @query = sql
147
+ @explain = explain_from(connection)
148
+ end
149
+
150
+ #######
151
+ private
152
+ #######
153
+
154
+ def explain_from(connection)
155
+ return nil unless @query =~ /^select\b/i
156
+ return nil unless connection.adapter_name == 'MySQL'
157
+ explain = Explain.new(@query, connection)
158
+ explain if explain.valid?
159
+ end
160
+
161
+ class Explain
162
+
163
+ attr_reader :fields, :rows
164
+
165
+ def initialize(sql, connection)
166
+ result = connection.execute("explain #{sql}")
167
+ @fields = fetch_fields_from(result)
168
+ @rows = fetch_rows_from(result)
169
+ result.free
170
+ add_schemas(connection)
171
+ @valid = true
172
+ rescue Exception
173
+ @valid = false
174
+ end
175
+
176
+ def valid?
177
+ @valid
178
+ end
179
+
180
+ def table_offset
181
+ @table_offset ||= @fields.index('table')
182
+ end
183
+
184
+ #######
185
+ private
186
+ #######
187
+
188
+ def fetch_fields_from(result)
189
+ result.fetch_fields.map(&:name)
190
+ end
191
+
192
+ def fetch_rows_from(result)
193
+ returning [] do |rows|
194
+ result.each do |row|
195
+ rows << row
196
+ end
197
+ end
198
+ end
199
+
200
+ def add_schemas(connection)
201
+ tables.each do |table|
202
+ Fiveruns::Tuneup.add_schema_for(table, connection)
203
+ end
204
+ end
205
+
206
+ def tables
207
+ return [] unless table_offset
208
+ @rows.map { |row| row[table_offset] }.compact
209
+ end
210
+
211
+ end
212
+
213
+ end
214
+
215
+ end
216
+
217
+ end
218
+ end
219
+
@@ -0,0 +1,23 @@
1
+ module Fiveruns
2
+ module Tuneup
3
+ module Urls
4
+
5
+ def collector_url
6
+ @collector_url ||= begin
7
+ url = ENV['TUNEUP_COLLECTOR'] || 'https://tuneup-collector.fiveruns.com'
8
+ url = "http://#{url}" unless url =~ /^http/
9
+ url
10
+ end
11
+ end
12
+
13
+ def frontend_url
14
+ @frontend_url ||= begin
15
+ url = ENV['TUNEUP_FRONTEND'] || 'https://tuneup.fiveruns.com'
16
+ url = "http://#{url}" unless url =~ /^http/
17
+ url
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end