query_reviewer 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +118 -0
  3. data/Rakefile +24 -0
  4. data/lib/query_reviewer/array_extensions.rb +29 -0
  5. data/lib/query_reviewer/controller_extensions.rb +65 -0
  6. data/lib/query_reviewer/mysql_adapter_extensions.rb +90 -0
  7. data/lib/query_reviewer/mysql_analyzer.rb +62 -0
  8. data/lib/query_reviewer/query_warning.rb +17 -0
  9. data/lib/query_reviewer/rails.rb +33 -0
  10. data/lib/query_reviewer/sql_query.rb +130 -0
  11. data/lib/query_reviewer/sql_query_collection.rb +103 -0
  12. data/lib/query_reviewer/sql_sub_query.rb +45 -0
  13. data/lib/query_reviewer/tasks.rb +8 -0
  14. data/lib/query_reviewer/views/_box.html.erb +11 -0
  15. data/lib/query_reviewer/views/_box_ajax.js +34 -0
  16. data/lib/query_reviewer/views/_box_body.html.erb +73 -0
  17. data/lib/query_reviewer/views/_box_disabled.html.erb +2 -0
  18. data/lib/query_reviewer/views/_box_header.html.erb +1 -0
  19. data/lib/query_reviewer/views/_box_includes.html.erb +234 -0
  20. data/lib/query_reviewer/views/_explain.html.erb +30 -0
  21. data/lib/query_reviewer/views/_js_includes.html.erb +68 -0
  22. data/lib/query_reviewer/views/_js_includes_new.html.erb +68 -0
  23. data/lib/query_reviewer/views/_profile.html.erb +26 -0
  24. data/lib/query_reviewer/views/_query_sql.html.erb +8 -0
  25. data/lib/query_reviewer/views/_query_trace.html.erb +31 -0
  26. data/lib/query_reviewer/views/_query_with_warning.html.erb +54 -0
  27. data/lib/query_reviewer/views/_spectrum.html.erb +10 -0
  28. data/lib/query_reviewer/views/_warning_no_query.html.erb +8 -0
  29. data/lib/query_reviewer/views/query_review_box_helper.rb +98 -0
  30. data/lib/query_reviewer.rb +54 -0
  31. data/query_reviewer_defaults.yml +39 -0
  32. metadata +96 -0
@@ -0,0 +1,26 @@
1
+ <table>
2
+ <thead>
3
+ <tr>
4
+ <th>stage</th>
5
+ <th>duration</th>
6
+ <th>user cpu</th>
7
+ <th>context switches</th>
8
+ <th>block ops</th>
9
+ <th>messages</th>
10
+ <th>page faults</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <% query.profile.each do |profile| %>
15
+ <tr>
16
+ <td title="<%= h profile.Status %>"><%= h profile.Status %></td>
17
+ <td title="<%= h profile.Duration %> seconds"><%= h("%.4f" % profile.Duration.to_f) %>s</td>
18
+ <td title="USER: <%= profile.CPU_user.to_f %>s SYSTEM: <%= profile.CPU_system.to_f %>s"><%= h("%.4f" % profile.CPU_user.to_f) %>s</td>
19
+ <td title="Voluntary: <%= h profile.Context_voluntary %> Involuntary: <%= h profile.Context_involuntary %>"><%= h(profile.Context_voluntary.to_i + profile.Context_involuntary.to_i) %></td>
20
+ <td title="Ops in: <%= h profile.Block_ops_in %> Ops out: <%= h profile.Block_ops_out %>"><%= h(profile.Block_ops_in.to_i + profile.Block_ops_out.to_i) %></td>
21
+ <td title="Sent: <%= h profile.Messages_sent %> Received: <%= h profile.Messages_received %>"><%= h(profile.Messages_sent.to_i + profile.Messages_received.to_i) %></td>
22
+ <td title="Major: <%= h profile.Page_faults_major %> Minor: <%= h profile.Page_faults_minor %>"><%= h profile.Page_faults_major %></td>
23
+ <tr>
24
+ <% end %>
25
+ </tbody>
26
+ </table>
@@ -0,0 +1,8 @@
1
+ <span id="query_plain_sql_<%= query_sql.id %>" style="display: none;">
2
+ <%= syntax_highlighted_sql(query_sql.sql) %>
3
+ <% if query_sql.respond_to?(:subqueries) && query_sql.subqueries.length > 1 %>
4
+ <% end %>
5
+ </span>
6
+ <span id="query_sanitized_sql_<%= query_sql.id %>">
7
+ <%= syntax_highlighted_sql(query_sql.sanitized_sql) if query_sql.sanitized_sql %>
8
+ </span>
@@ -0,0 +1,31 @@
1
+ <div class="trace" id="trace_<%= query_id %>_abridged">
2
+ <code>
3
+ <% query_trace[0..(QueryReviewer::CONFIGURATION["stack_trace_lines"] - 1)].compact.each do |c| %>
4
+ <% if c.match(/:.*:/) %>
5
+ <% file = h(c.slice(0..(c.index(":", c.index(":")+1)-1))) %>
6
+ <span><%= file.split("/")[0..-2].join("/") %>/</span><span class="bold"><%= file.split("/").last %></span>
7
+ <br/>
8
+ <span class="indent"><%= h(c.slice((c.index(":", c.index(":")+1)+1)..-1)) %></span><br/>
9
+ <% else %>
10
+ <span><%= h c %></span><br/>
11
+ <% end %>
12
+ <% end %>
13
+ </code>
14
+ <a href="javascript: query_review_toggle('trace_<%= query_id %>_abridged'); query_review_toggle('trace_<%= query_id %>_full')" title="show full trace">FULL</a>
15
+ </div>
16
+
17
+ <div class="trace" style="display: none; max-height: 300px; overflow: scroll" id="trace_<%= query_id %>_full">
18
+ <code>
19
+ <% full_trace.compact.each do |c| %>
20
+ <% if c.match(/:.*:/) %>
21
+ <% file = h(c.slice(0..(c.index(":", c.index(":")+1)-1))) %>
22
+ <span><%= file.split("/")[0..-2].join("/") %>/</span><span class="bold"><%= file.split("/").last %></span>
23
+ <br/>
24
+ <span class="indent"><%= h(c.slice((c.index(":", c.index(":")+1)+1)..-1)) %></span><br/>
25
+ <% else %>
26
+ <span><%= h c %></span><br/>
27
+ <% end %>
28
+ <% end %>
29
+ </code>
30
+ <a href="javascript: query_review_toggle('trace_<%= query_id %>_abridged'); query_review_toggle('trace_<%= query_id %>_full')" title="show short trace">SHORT</a>
31
+ </div>
@@ -0,0 +1,54 @@
1
+ <li id="query_<%= query_with_warning.id %>">
2
+ <div>
3
+ <%= render :partial => "/spectrum", :locals => {:severity => query_with_warning.max_severity} %>
4
+ <% if QueryReviewer::CONFIGURATION["production_data"] %>
5
+ <div style="float: left; padding-right: 5px;">
6
+ <%= duration_with_color(query_with_warning) %>s
7
+ </div>
8
+ <% end %>
9
+ <p>
10
+ <% if query_with_warning.count > 1 %>
11
+ <b title="<%= query_with_warning.count %> queries were executed with the same stack trace and similar SQL structure">
12
+ <%= query_with_warning.count %> identical queries
13
+ </b>
14
+ <% end %>
15
+ <i>Table <%= (query_with_warning.warnings.detect {|w| !w.table.blank? } || query_with_warning.warnings.last).table %>:</i>
16
+ <% query_with_warning.warnings.sort{|a,b| a.severity <=> b.severity}.reverse.each_with_index do |warn, index| %>
17
+ <span style="color: <%= severity_color warn.severity%>;" title="<%= warn.desc%>"><%= warn.problem %></span><%= ", " if index < query_with_warning.warnings.length - 1 %>
18
+ <% end %>
19
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_desc')" title="show/hide warning message">MSG</a>
20
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_sql')" title="show/hide sql">SQL</a>
21
+ <% if query_with_warning.select? %>
22
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_explain')" title="show/hide explain output">EXPLN</a>
23
+ <% end %>
24
+ <% if QueryReviewer::CONFIGURATION["profiling"] && query_with_warning.profile %>
25
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_profile')" title="show/hide profile output">PROF</a>
26
+ <% end %>
27
+ <a href="javascript: query_review_toggle('warning_<%= query_with_warning.id %>_trace')" title="show/hide stack trace">TRACE</a>
28
+ <% if ignore_hash?(query_with_warning.to_hash) %>
29
+ <a href="javascript: remove_ignore_hash('<%= query_with_warning.to_hash %>'); query_review_hide('query_<%= query_with_warning.id %>')" title="stop ignore this query from now on">UNIGNR</a>
30
+ <% else %>
31
+ <a href="javascript: add_ignore_hash('<%= query_with_warning.to_hash %>'); query_review_hide('query_<%= query_with_warning.id %>')" title="ignoring this query from now on">IGNR</a>
32
+ <% end %>
33
+ </p>
34
+ </div>
35
+ <p style="display: none" id="warning_<%= query_with_warning.id %>_desc" class="indent">
36
+ <% query_with_warning.warnings.each do |warn| %>
37
+ <span style="color: <%= severity_color warn.severity%>"><%= warn.desc %></span><br/>
38
+ <% end %>
39
+ </p>
40
+ <p style="display: none" id="warning_<%= query_with_warning.id %>_sql" class="indent small tbpadded">
41
+ <%= render :partial => "/query_sql", :locals => {:query_sql => query_with_warning} %>
42
+ </p>
43
+ <div style="display: none" id="warning_<%= query_with_warning.id %>_explain" class="indent small tbpadded">
44
+ <%= render :partial => "/explain", :locals => {:query => query_with_warning} %>
45
+ </div>
46
+ <% if QueryReviewer::CONFIGURATION["profiling"] && query_with_warning.profile %>
47
+ <div style="display: none" id="warning_<%= query_with_warning.id %>_profile" class="indent small tbpadded">
48
+ <%= render :partial => "/profile", :locals => {:query => query_with_warning} %>
49
+ </div>
50
+ <% end %>
51
+ <div style="display: none" id="warning_<%= query_with_warning.id %>_trace" class="indent small">
52
+ <%= render :partial => "/query_trace", :locals => {:query_id => query_with_warning.id, :full_trace => query_with_warning.full_trace,:query_trace => query_with_warning.relevant_trace} %>
53
+ </div>
54
+ </li>
@@ -0,0 +1,10 @@
1
+ <div class="spectrum_container" title="Severity: <%= severity%> out of 10">
2
+ <% 0.upto(15) do |i| %>
3
+ <div class="spectrum_elem" style="background-color: <%= "##{i.to_s(16)}F0"%>"></div>
4
+ <% end %>
5
+ <% 0.upto(15) do |i| %>
6
+ <div class="spectrum_elem" style="background-color: <%= "#F#{((15-i)).to_s(16)}0"%>"></div>
7
+ <% end %>
8
+ <div class="spectrum_elem" style="background-color: #FF0000"></div>
9
+ <div class="spectrum_pointer" style="left: <%= severity * 3 + 2 %>px;"></div>
10
+ </div>
@@ -0,0 +1,8 @@
1
+ <li id="warning_<%= warning_no_query.id %>">
2
+ <div>
3
+ <%= render :partial => "/spectrum", :locals => {:severity => warning_no_query.severity} %>
4
+ <p>
5
+ <span style="color: <%= severity_color warning_no_query.severity%>" title="<%= warning_no_query.desc %>"><%= warning_no_query.problem %></span>
6
+ </p>
7
+ </div>
8
+ </li>
@@ -0,0 +1,98 @@
1
+ module QueryReviewer
2
+ module Views
3
+ module QueryReviewBoxHelper
4
+ def parent_div_class
5
+ "sql_#{parent_div_status.downcase}"
6
+ end
7
+
8
+ def parent_div_status
9
+ if !enabled_by_cookie
10
+ "DISABLED"
11
+ elsif overall_max_severity < (QueryReviewer::CONFIGURATION["warn_severity"] || 4)
12
+ "OK"
13
+ elsif overall_max_severity < (QueryReviewer::CONFIGURATION["critical_severity"] || 7)
14
+ # uh oh
15
+ "WARNING"
16
+ else
17
+ # oh @#&!
18
+ "CRITICAL"
19
+ end
20
+ end
21
+
22
+ def syntax_highlighted_sql(sql)
23
+ if QueryReviewer::CONFIGURATION["uv"]
24
+ uv_out = Uv.parse(sql, "xhtml", "sql_rails", false, "blackboard")
25
+ uv_out.gsub("<pre class=\"blackboard\">", "<code class=\"sql\">").gsub("</pre>", "</code>")
26
+ else
27
+ sql.gsub(/</, "&lt;").gsub(/>/, "&gt;")
28
+ end
29
+ end
30
+
31
+ def overall_max_severity
32
+ max = 0
33
+ max = queries_with_warnings_sorted_nonignored[0].max_severity unless queries_with_warnings_sorted_nonignored.empty?
34
+ max = warnings_no_query_sorted.first.severity unless warnings_no_query_sorted.empty? || warnings_no_query_sorted.first.severity < max
35
+ max
36
+ end
37
+
38
+ def severity_color(severity)
39
+ red = (severity * 16.0 / 10).to_i
40
+ green = ((10-severity) * 16.0 / 10).to_i
41
+ red = 8 if red > 8
42
+ red = 0 if red < 0
43
+ green = 8 if green > 8
44
+ green = 0 if green < 0
45
+ "##{red.to_s(16)}#{green.to_s(16)}0"
46
+ end
47
+
48
+ def ignore_hash?(h)
49
+ (controller.send(:cookies)["query_review_ignore_list"] || "").split(",").include?(h.to_s)
50
+ end
51
+
52
+ def queries_with_warnings
53
+ @queries.queries.select{|q| q.has_warnings?}
54
+ end
55
+
56
+ def queries_with_warnings_sorted
57
+ queries_with_warnings.sort{|a,b| (b.max_severity * 1000 + (b.duration || 0)) <=> (a.max_severity * 1000 + (a.duration || 0))}
58
+ end
59
+
60
+ def queries_with_warnings_sorted_nonignored
61
+ queries_with_warnings_sorted.select{|q| q.max_severity >= ::QueryReviewer::CONFIGURATION["warn_severity"] && !ignore_hash?(q.to_hash)}
62
+ end
63
+
64
+ def queries_with_warnings_sorted_ignored
65
+ queries_with_warnings_sorted.reject{|q| q.max_severity >= ::QueryReviewer::CONFIGURATION["warn_severity"] && !ignore_hash?(q.to_hash)}
66
+ end
67
+
68
+ def warnings_no_query_sorted
69
+ @queries.collection_warnings.sort{|a,b| a.severity <=> b.severity}.reverse
70
+ end
71
+
72
+ def warnings_no_query_sorted_ignored
73
+ warnings_no_query_sorted.select{|q| q.severity < ::QueryReviewer::CONFIGURATION["warn_severity"]}
74
+ end
75
+
76
+ def warnings_no_query_sorted_nonignored
77
+ warnings_no_query_sorted.select{|q| q.severity >= ::QueryReviewer::CONFIGURATION["warn_severity"]}
78
+ end
79
+
80
+ def enabled_by_cookie
81
+ controller.send(:cookies)["query_review_enabled"]
82
+ end
83
+
84
+ def duration_with_color(query)
85
+ title = query.duration_stats
86
+ duration = query.duration
87
+ span_html = if duration > QueryReviewer::CONFIGURATION["critical_duration_threshold"]
88
+ "<span style=\"color: #{severity_color(9)}\" title=\"#{title}\">#{"%.3f" % duration}</span>"
89
+ elsif duration > QueryReviewer::CONFIGURATION["warn_duration_threshold"]
90
+ "<span style=\"color: #{severity_color(QueryReviewer::CONFIGURATION["critical_severity"])}\" title=\"#{title}\">#{"%.3f" % duration}</span>"
91
+ else
92
+ "<span title=\"#{title}\">#{"%.3f" % duration}</span>"
93
+ end
94
+ span_html.respond_to?(:html_safe) ? span_html.html_safe : span_html
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,54 @@
1
+ # QueryReviewer
2
+ require "ostruct"
3
+ require 'erb'
4
+ require 'yaml'
5
+
6
+ module QueryReviewer
7
+ CONFIGURATION = {}
8
+
9
+ def self.load_configuration
10
+ default_config = YAML::load(ERB.new(IO.read(File.join(File.dirname(__FILE__), "..", "query_reviewer_defaults.yml"))).result)
11
+
12
+ CONFIGURATION.merge!(default_config["all"] || {})
13
+ CONFIGURATION.merge!(default_config[Rails.env || "test"] || {})
14
+
15
+ app_config_file = File.join(Rails.root.to_s, "config", "query_reviewer.yml")
16
+
17
+ if File.exist?(app_config_file)
18
+ app_config = YAML.load(ERB.new(IO.read(app_config_file)).result)
19
+ CONFIGURATION.merge!(app_config["all"] || {})
20
+ CONFIGURATION.merge!(app_config[Rails.env || "test"] || {})
21
+ end
22
+
23
+ if enabled?
24
+ begin
25
+ CONFIGURATION["uv"] ||= !Gem.searcher.find("uv").nil?
26
+ if CONFIGURATION["uv"]
27
+ require "uv"
28
+ end
29
+ rescue
30
+ CONFIGURATION["uv"] ||= false
31
+ end
32
+ end
33
+ end
34
+
35
+ def self.enabled?
36
+ CONFIGURATION["enabled"]
37
+ end
38
+ end
39
+
40
+ QueryReviewer.load_configuration
41
+
42
+ if QueryReviewer.enabled?
43
+ require "query_reviewer/query_warning"
44
+ require "query_reviewer/array_extensions"
45
+ require "query_reviewer/sql_query"
46
+ require "query_reviewer/mysql_analyzer"
47
+ require "query_reviewer/sql_sub_query"
48
+ require "query_reviewer/mysql_adapter_extensions"
49
+ require "query_reviewer/controller_extensions"
50
+ require "query_reviewer/sql_query_collection"
51
+ end
52
+
53
+ # Rails Integration
54
+ require 'query_reviewer/rails' if defined?(Rails)
@@ -0,0 +1,39 @@
1
+ all:
2
+ inject_view: true
3
+ stack_trace_lines: 3
4
+ trace_includes_vendor: false
5
+ trace_includes_lib: true
6
+ profiling: enabled
7
+ production_data: true
8
+ max_safe_key_length: 100
9
+ disable_sql_cache: true
10
+
11
+ warn_severity: 3
12
+ critical_severity: 7
13
+
14
+ warn_select_count: 20
15
+ critical_select_count: 50
16
+
17
+ warn_update_count: 5
18
+ critical_update_count: 10
19
+
20
+ warn_insert_count: 5
21
+ critical_insert_count: 10
22
+
23
+ warn_delete_count: 5
24
+ critical_delete_count: 10
25
+
26
+ warn_duration_threshold: 0.2
27
+ critical_duration_threshold: 1.0
28
+
29
+ warn_affected_rows: 10
30
+ critical_affected_rows: 100
31
+
32
+ development:
33
+ enabled: true
34
+
35
+ production:
36
+ enabled: false
37
+
38
+ test:
39
+ enabled: false
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: query_reviewer
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - dsboulder
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-19 00:00:00 -08:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Runs explain before each select query and displays results in an overlayed div
22
+ email: nesquena@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - MIT-LICENSE
31
+ - Rakefile
32
+ - README.md
33
+ - query_reviewer_defaults.yml
34
+ - lib/query_reviewer/array_extensions.rb
35
+ - lib/query_reviewer/controller_extensions.rb
36
+ - lib/query_reviewer/mysql_adapter_extensions.rb
37
+ - lib/query_reviewer/mysql_analyzer.rb
38
+ - lib/query_reviewer/query_warning.rb
39
+ - lib/query_reviewer/rails.rb
40
+ - lib/query_reviewer/sql_query.rb
41
+ - lib/query_reviewer/sql_query_collection.rb
42
+ - lib/query_reviewer/sql_sub_query.rb
43
+ - lib/query_reviewer/tasks.rb
44
+ - lib/query_reviewer/views/_box.html.erb
45
+ - lib/query_reviewer/views/_box_ajax.js
46
+ - lib/query_reviewer/views/_box_body.html.erb
47
+ - lib/query_reviewer/views/_box_disabled.html.erb
48
+ - lib/query_reviewer/views/_box_header.html.erb
49
+ - lib/query_reviewer/views/_box_includes.html.erb
50
+ - lib/query_reviewer/views/_explain.html.erb
51
+ - lib/query_reviewer/views/_js_includes.html.erb
52
+ - lib/query_reviewer/views/_js_includes_new.html.erb
53
+ - lib/query_reviewer/views/_profile.html.erb
54
+ - lib/query_reviewer/views/_query_sql.html.erb
55
+ - lib/query_reviewer/views/_query_trace.html.erb
56
+ - lib/query_reviewer/views/_query_with_warning.html.erb
57
+ - lib/query_reviewer/views/_spectrum.html.erb
58
+ - lib/query_reviewer/views/_warning_no_query.html.erb
59
+ - lib/query_reviewer/views/query_review_box_helper.rb
60
+ - lib/query_reviewer.rb
61
+ has_rdoc: true
62
+ homepage: https://github.com/nesquena/query_reviewer
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ hash: 3
76
+ segments:
77
+ - 0
78
+ version: "0"
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ requirements: []
89
+
90
+ rubyforge_project:
91
+ rubygems_version: 1.3.7
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Runs explain before each select query and displays results in an overlayed div
95
+ test_files: []
96
+