query_reviewer 0.1

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.
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
+