active-record-query-count 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +126 -0
- data/bin/setup +8 -0
- data/images/bar_chart.png +0 -0
- data/images/html.png +0 -0
- data/images/terminal.png +0 -0
- data/lib/active_record_query_count/compare/comparator.rb +33 -0
- data/lib/active_record_query_count/configuration.rb +12 -0
- data/lib/active_record_query_count/middleware.rb +37 -0
- data/lib/active_record_query_count/printer/base.rb +42 -0
- data/lib/active_record_query_count/printer/console.rb +30 -0
- data/lib/active_record_query_count/printer/html.rb +68 -0
- data/lib/active_record_query_count/printer/html_compare.rb +64 -0
- data/lib/active_record_query_count/printer/templates/bar_chart.js +153 -0
- data/lib/active_record_query_count/printer/templates/comparing.html.erb +66 -0
- data/lib/active_record_query_count/printer/templates/style.css +41 -0
- data/lib/active_record_query_count/printer/templates/template.html.erb +9 -0
- data/lib/active_record_query_count/printer/templates/template_base_query_counter.html.erb +51 -0
- data/lib/active_record_query_count/printer/templates/template_for_inject.html.erb +64 -0
- data/lib/active_record_query_count/recording/base.rb +28 -0
- data/lib/active_record_query_count/recording/tracker.rb +40 -0
- data/lib/active_record_query_count/version.rb +5 -0
- data/lib/active_record_query_count.rb +37 -0
- data/scripts_for_testing/example_script_optimize.yaml +217 -0
- data/scripts_for_testing/example_script_unoptimize.yaml +142 -0
- data/scripts_for_testing/script_compare_html.rb +16 -0
- data/scripts_for_testing/script_printer_html.rb +8 -0
- data/scripts_for_testing/scripts_console.rb.rb +8 -0
- metadata +266 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 832a3feb47d50f2629fe629a61d8b373e1e34f657c5367af3a414faed1e3b26e
|
4
|
+
data.tar.gz: 00a7ba247067342cbb64939cee6d2ad3023e022ef7e64103edd789bd087a247f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d63ff812445352062bd2118bd12dd1f6d2a9f07745d702de1075953f27b457a59c41c19c1404981cbc95d7707a48911e354a4526118ac3d6ac3327fae179b500
|
7
|
+
data.tar.gz: f70978b3f812a631a3814792572d8502351c2b1e2c8a52c8e617d72f4936331944370b10d53bdc48afff5722734ab49e5f33662c4b4583e3ff0593d9aa00c22e
|
data/README.md
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# ActiveRecordQueryCount
|
2
|
+
|
3
|
+
`ActiveRecordQueryCount` is a Ruby gem designed to help you track and analyze SQL queries executed by your ActiveRecord models. By subscribing to ActiveSupport notifications, it provides detailed insights into the queries being run, including the tables involved and the locations in your code where the queries are generated.
|
4
|
+
There are three things this gem allows you to do
|
5
|
+
|
6
|
+
1. You can compare two codes to view the difference in SQL counts on locations with a graph or a table.
|
7
|
+
2. You can view an overview of the SQL that a code does in a graph, a table or in the console.
|
8
|
+
3. (In progress) You can see an overview of the current request on a controller action
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add this line to your application's Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'active_record_query_tracker'
|
16
|
+
```
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
bundle install
|
22
|
+
```
|
23
|
+
|
24
|
+
Or install it yourself as:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
gem install active_record_query_tracker
|
28
|
+
```
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
There are four ways of using this gem:
|
33
|
+
|
34
|
+
1. With a block of code
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'active_record_query_tracker'
|
38
|
+
ActiveRecordQueryCount.start_with_block(printer: :html) do
|
39
|
+
# your code goes here
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
this will open up a html table with the SQL stats of your code
|
44
|
+
|
45
|
+
2. Starting recording manually
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
require 'active_record_query_tracker'
|
49
|
+
|
50
|
+
ActiveRecordQueryCount.start_recording
|
51
|
+
# your code goes here
|
52
|
+
ActiveRecordQueryCount.end_recording(printer: :html)
|
53
|
+
```
|
54
|
+
|
55
|
+
3. Comparing two blocks of code (only available for html printer)
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
require 'active_record_query_tracker'
|
59
|
+
ActiveRecordQueryCount.compare do |bench|
|
60
|
+
bench.code('script1') do
|
61
|
+
end
|
62
|
+
bench.code('script2') do
|
63
|
+
end
|
64
|
+
bench.compare!
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
this will open up a graph comparing the quantity of SQL of the two codes
|
69
|
+
|
70
|
+
4. (In progress) Enabling a middleware to see an overview of the current request SQL's in Rack Application.
|
71
|
+
On `config/development.rb` or the initializer of the application.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
config.after_initialize do
|
75
|
+
ActiveRecordQueryCount.configure do |configuration|
|
76
|
+
configuration.enable_middleware = true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
### Printing options
|
82
|
+
|
83
|
+
There are two ways of displaying the collected queries data, `:console` and `:html`, to select one pass the printer argument to `start_with_block` o `end_recording` methods.
|
84
|
+
|
85
|
+
If you use `html` with WSL enviroment, you need to set on your enviroments variables the `WSL_DISTRIBUTION` that you are using, so the dependency Launchy work as expected.
|
86
|
+
|
87
|
+
### Configuration options
|
88
|
+
|
89
|
+
When visualizing the html table or the console output, tables with less than `ignore_table_count` will not be shown. Also, the ammount of locations to show is given by `max_locations_per_table`
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
config.after_initialize do
|
93
|
+
ActiveRecordQueryCount.configure do |configuration|
|
94
|
+
configuration.ignore_table_count = 1
|
95
|
+
configuration.max_locations_per_table = 4
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
## Examples of visualization
|
102
|
+
|
103
|
+
1. Console output
|
104
|
+
![Console output](images/console.png)
|
105
|
+
2. HTML output
|
106
|
+
![HTML output](images/html.png)
|
107
|
+
3. Bar chart output
|
108
|
+
![Bar chart output](images/bar_chart.png)
|
109
|
+
|
110
|
+
## Development
|
111
|
+
|
112
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
113
|
+
|
114
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
115
|
+
|
116
|
+
## Contributing
|
117
|
+
|
118
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/[USERNAME]/active_record_query_tracker>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/active_record_query_tracker/blob/master/CODE_OF_CONDUCT.md).
|
119
|
+
|
120
|
+
## License
|
121
|
+
|
122
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
123
|
+
|
124
|
+
## Code of Conduct
|
125
|
+
|
126
|
+
Everyone interacting in the ActiveRecordQueryCount project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/active_record_query_tracker/blob/master/CODE_OF_CONDUCT.md).
|
data/bin/setup
ADDED
Binary file
|
data/images/html.png
ADDED
Binary file
|
data/images/terminal.png
ADDED
Binary file
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module ActiveRecordQueryCount
|
2
|
+
module Compare
|
3
|
+
class Comparator
|
4
|
+
attr_accessor :results, :scripts_loaded
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@results = {}
|
8
|
+
@scripts_loaded = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def code(name)
|
12
|
+
@scripts_loaded += 1
|
13
|
+
|
14
|
+
ActiveRecordQueryCount.start_with_block(printer: :none) do
|
15
|
+
yield
|
16
|
+
@results[name] = ActiveRecordQueryCount.tracker.active_record_query_tracker.dup
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def compare!
|
21
|
+
raise 'Exactly two code blocks are required' if @scripts_loaded != 2
|
22
|
+
|
23
|
+
ActiveRecordQueryCount::Printer::HtmlCompare.new(data_1: results.slice(results.keys[0]),
|
24
|
+
data_2: results.slice(results.keys[1])).print
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.compare
|
29
|
+
comparator = Comparator.new
|
30
|
+
yield(comparator)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
3
|
+
|
4
|
+
module ActiveRecordQueryCount
|
5
|
+
module Configuration
|
6
|
+
mattr_accessor :ignore_table_count, :max_locations_per_table, :enable_middleware
|
7
|
+
|
8
|
+
self.ignore_table_count = ENV['QUERY_COUNTER_IGNORE_TABLE_COUNT'] || 10
|
9
|
+
self.max_locations_per_table = ENV['MAX_LOCATIONS_PER_TABLE'] || 3
|
10
|
+
self.enable_middleware = false
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module ActiveRecordQueryCount
|
5
|
+
class Middleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
return @app.call(env) unless Configuration.enable_middleware
|
12
|
+
|
13
|
+
status, headers, response, query_count_data = nil
|
14
|
+
ActiveRecordQueryCount.start_recording
|
15
|
+
status, headers, response = @app.call(env)
|
16
|
+
query_count_data = ActiveRecordQueryCount.tracker.active_record_query_tracker.clone
|
17
|
+
ActiveRecordQueryCount.end_recording(printer: :none)
|
18
|
+
if headers['Content-Type']&.include?('text/html') && response.respond_to?(:body) && response.body['<body']
|
19
|
+
response_body = inject_query_counter_js(response.body, query_count_data)
|
20
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
21
|
+
response = [response_body]
|
22
|
+
end
|
23
|
+
[status, headers, response]
|
24
|
+
end
|
25
|
+
|
26
|
+
def inject_query_counter_js(response_body, query_count_data)
|
27
|
+
injected_html = ::ActiveRecordQueryCount::Printer::Html.new(data: query_count_data).inject_in_html
|
28
|
+
# Parse the response body with Nokogiri
|
29
|
+
doc = Nokogiri::HTML(response_body)
|
30
|
+
|
31
|
+
# Inject the query counter JS before the closing </body> tag
|
32
|
+
doc.at('body').add_child(injected_html)
|
33
|
+
|
34
|
+
doc.to_html
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ActiveRecordQueryCount
|
2
|
+
module Printer
|
3
|
+
class Base
|
4
|
+
def filter_data data
|
5
|
+
data = data.select { |_, v| v[:count] >= Configuration.ignore_table_count }
|
6
|
+
data = data.sort_by { |_, v| -v[:count] }.each do |_category, info|
|
7
|
+
info[:location] = info[:location].sort_by do |_, detail|
|
8
|
+
-detail[:count]
|
9
|
+
end.first(Configuration.max_locations_per_table).to_h
|
10
|
+
end
|
11
|
+
data.to_h
|
12
|
+
end
|
13
|
+
|
14
|
+
def sort_data data
|
15
|
+
data = data.sort_by { |_, v| -v[:count] }.each do |_category, info|
|
16
|
+
info[:location] = info[:location].sort_by do |_, detail|
|
17
|
+
-detail[:count]
|
18
|
+
end.to_h
|
19
|
+
end
|
20
|
+
data.to_h
|
21
|
+
end
|
22
|
+
|
23
|
+
def open_file html_dest
|
24
|
+
if ENV['WSL_DISTRIBUTION']
|
25
|
+
Launchy.open("file://wsl%24/#{ENV['WSL_DISTRIBUTION']}#{html_dest}")
|
26
|
+
else
|
27
|
+
Launchy.open(html_dest)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# this js is not used as erb, it could be changed
|
32
|
+
def js_content
|
33
|
+
File.read(@js_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
# this js is not used as erb, it could be changed
|
37
|
+
def css_content
|
38
|
+
File.read(@css_path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module ActiveRecordQueryCount
|
4
|
+
module Printer
|
5
|
+
class Console < Base
|
6
|
+
def initialize data:
|
7
|
+
super()
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def print
|
12
|
+
data = filter_data(@data)
|
13
|
+
puts '[ActiveRecordQueryCount] Query count per table:'.colorize(:blue)
|
14
|
+
puts "Total query count: #{data.values.sum { |v| v[:count] }}\n\n"
|
15
|
+
puts "All tables with less than #{Configuration.ignore_table_count} queries are ignored. \n\n"
|
16
|
+
puts "For each table, the top #{Configuration.max_locations_per_table} locations with the most queries will be shown.\n\n"
|
17
|
+
data.each do |category, info|
|
18
|
+
puts "Table #{category.colorize(:cyan)}"
|
19
|
+
puts " Total query count: #{info[:count].to_s.colorize(:blue)}"
|
20
|
+
puts ' Locations where the table was called:'
|
21
|
+
info[:location].each do |loc, details|
|
22
|
+
puts " - File location: #{loc}"
|
23
|
+
puts " Query count: #{details[:count].to_s.colorize(:blue)}"
|
24
|
+
end
|
25
|
+
puts
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'launchy'
|
4
|
+
require 'pry-byebug'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module ActiveRecordQueryCount
|
8
|
+
module Printer
|
9
|
+
class Html < Base
|
10
|
+
attr_accessor :data
|
11
|
+
|
12
|
+
def initialize data:
|
13
|
+
@template_path = File.join(__dir__, 'templates', 'template.html.erb')
|
14
|
+
@base_query_counter_path = File.join(__dir__, 'templates', 'template_base_query_counter.html.erb')
|
15
|
+
@inject_template_path = File.join(__dir__, 'templates', 'template_for_inject.html.erb')
|
16
|
+
@css_path = File.join(__dir__, 'templates', 'style.css')
|
17
|
+
# este no falla, quizá pueda poner lo mismo aquí con chart.js y así no hacer lo del nonce y csp
|
18
|
+
@js_path = File.join(__dir__, 'templates', 'bar_chart.js')
|
19
|
+
@data = data
|
20
|
+
end
|
21
|
+
|
22
|
+
def chart_data
|
23
|
+
@chart_data ||= generate_chart_data(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
# maybe this should not be filtered
|
27
|
+
def total_query_count
|
28
|
+
@total_query_count ||= data.values.sum { |v| v[:count] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def inject_in_html
|
32
|
+
ERB.new(File.read(@inject_template_path)).result(binding)
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_query_counter_base_div
|
36
|
+
ERB.new(File.read(@base_query_counter_path)).result(binding)
|
37
|
+
end
|
38
|
+
|
39
|
+
def print
|
40
|
+
html_dest = generate_html(binding)
|
41
|
+
open_file(html_dest)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def generate_chart_data(data)
|
47
|
+
chart_data = { labels: [], data: [], locations: {} }
|
48
|
+
data.each do |table, info|
|
49
|
+
chart_data[:labels] << table
|
50
|
+
chart_data[:data] << info[:count]
|
51
|
+
chart_data[:locations][table] = info[:location].map do |loc, detail|
|
52
|
+
{ location: loc, count: detail[:count] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
chart_data
|
56
|
+
end
|
57
|
+
|
58
|
+
def generate_html binding
|
59
|
+
template = ERB.new(File.read(@template_path))
|
60
|
+
html_content = template.result(binding)
|
61
|
+
temp_dir = Dir.mktmpdir
|
62
|
+
html_dest = File.join(temp_dir, 'query_counter_report.html')
|
63
|
+
File.write(html_dest, html_content)
|
64
|
+
html_dest
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'launchy'
|
4
|
+
require 'pry-byebug'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module ActiveRecordQueryCount
|
8
|
+
module Printer
|
9
|
+
class HtmlCompare < Base
|
10
|
+
def initialize data_1:, data_2:
|
11
|
+
super()
|
12
|
+
@template_path = File.join(__dir__, 'templates', 'comparing.html.erb')
|
13
|
+
@css_path = File.join(__dir__, 'templates', 'style.css')
|
14
|
+
@js_path = File.join(__dir__, 'templates', 'bar_chart.js')
|
15
|
+
@script_1_name = data_1.keys.first
|
16
|
+
@script_2_name = data_2.keys.first
|
17
|
+
@data_1 = data_1[@script_1_name]
|
18
|
+
@data_2 = data_2[@script_2_name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def print
|
22
|
+
# used by binding on erb templates
|
23
|
+
data_1 = sort_data(@data_1)
|
24
|
+
data_2 = sort_data(@data_2)
|
25
|
+
tables = data_1.keys | data_2.keys
|
26
|
+
total_query_count_1 = data_1.values.sum { |v| v[:count] }
|
27
|
+
total_query_count_2 = data_2.values.sum { |v| v[:count] }
|
28
|
+
chart_data = generate_chart_data_compare(data_1, data_2)
|
29
|
+
# end
|
30
|
+
html_dest = generate_html(binding)
|
31
|
+
open_file(html_dest)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def generate_chart_data_compare(data_1, data_2)
|
37
|
+
labels = (data_1.keys | data_2.keys).sort
|
38
|
+
chart_data = { labels: [], data_1: {}, data_2: {}, locations: {} }
|
39
|
+
chart_data[:data_1][:name] = @script_1_name
|
40
|
+
chart_data[:data_2][:name] = @script_2_name
|
41
|
+
chart_data[:data_1][:data] = []
|
42
|
+
chart_data[:data_2][:data] = []
|
43
|
+
labels.each do |label|
|
44
|
+
chart_data[:labels] << label
|
45
|
+
chart_data[:data_1][:data] << (data_1[label] ? data_1[label][:count] : 0)
|
46
|
+
chart_data[:data_2][:data] << (data_2[label] ? data_2[label][:count] : 0)
|
47
|
+
chart_data[:locations][label] =
|
48
|
+
(data_1[label] ? data_1[label][:location] : {}).merge(data_2[label] ? data_2[label][:location] : {})
|
49
|
+
end
|
50
|
+
chart_data
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_html binding
|
54
|
+
template = ERB.new(File.read(@template_path))
|
55
|
+
html_content = template.result(binding)
|
56
|
+
|
57
|
+
temp_dir = Dir.mktmpdir
|
58
|
+
html_dest = File.join(temp_dir, 'query_counter_report.html')
|
59
|
+
File.write(html_dest, html_content)
|
60
|
+
html_dest
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
function toggleView() {
|
3
|
+
const chart = document.getElementById('queryCountChart');
|
4
|
+
const chartContainer = chart.parentElement;
|
5
|
+
const table = document.getElementById('queryTable');
|
6
|
+
const button = document.getElementById('toggleButton');
|
7
|
+
if (chartContainer.style.display === 'none') {
|
8
|
+
chartContainer.style.display = 'block';
|
9
|
+
table.style.display = 'none';
|
10
|
+
button.textContent = 'Show Table View';
|
11
|
+
} else {
|
12
|
+
chartContainer.style.display = 'none';
|
13
|
+
table.style.display = 'table';
|
14
|
+
button.textContent = 'Show chartContainer View';
|
15
|
+
};
|
16
|
+
};
|
17
|
+
|
18
|
+
function toggleColumnContent() {
|
19
|
+
const columnHeader = document.getElementById('columnHeader');
|
20
|
+
const toggleContents = document.querySelectorAll('.toggle-content');
|
21
|
+
const showSQL = columnHeader.textContent === 'File Path';
|
22
|
+
|
23
|
+
columnHeader.textContent = showSQL ? 'SQL' : 'File Path';
|
24
|
+
|
25
|
+
toggleContents.forEach(content => {
|
26
|
+
const filepath = content.querySelector('.filepath');
|
27
|
+
const sql = content.querySelector('.sql');
|
28
|
+
|
29
|
+
if (showSQL) {
|
30
|
+
filepath.style.display = 'none';
|
31
|
+
sql.style.display = 'block';
|
32
|
+
} else {
|
33
|
+
filepath.style.display = 'block';
|
34
|
+
sql.style.display = 'none';
|
35
|
+
}
|
36
|
+
});
|
37
|
+
}
|
38
|
+
|
39
|
+
function initializeChart(chartData) {
|
40
|
+
console.log(chartData);
|
41
|
+
const ctx = document.getElementById('queryCountChart').getContext('2d');
|
42
|
+
const queryCountChart = new Chart(ctx, {
|
43
|
+
type: 'bar',
|
44
|
+
data: {
|
45
|
+
labels: chartData.labels,
|
46
|
+
datasets: [
|
47
|
+
{
|
48
|
+
label: 'Dataset 1',
|
49
|
+
data: chartData.data,
|
50
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
51
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
52
|
+
borderWidth: 1
|
53
|
+
}
|
54
|
+
]
|
55
|
+
},
|
56
|
+
options: {
|
57
|
+
scales: {
|
58
|
+
y: {
|
59
|
+
type: 'logarithmic',
|
60
|
+
beginAtZero: true,
|
61
|
+
title: {
|
62
|
+
display: true,
|
63
|
+
text: 'Logarithmic Scale'
|
64
|
+
},
|
65
|
+
ticks: {
|
66
|
+
callback: function(value, index, values) {
|
67
|
+
if (value === 0 || value === 10 || value === 100 || value === 1000 || value === 10000) {
|
68
|
+
return value.toString();
|
69
|
+
}
|
70
|
+
return null;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
74
|
+
},
|
75
|
+
plugins: {
|
76
|
+
tooltip: {
|
77
|
+
callbacks: {
|
78
|
+
label: function(tooltipItem) {
|
79
|
+
const index = tooltipItem.dataIndex;
|
80
|
+
const table = chartData.labels[index];
|
81
|
+
// const locations = chartData.locations[table];
|
82
|
+
// Aquí se podría hacer el stackgraph
|
83
|
+
return [
|
84
|
+
`Total Queries: ${tooltipItem.raw}`,
|
85
|
+
];
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
});
|
92
|
+
document.getElementById('queryCountChart').parentElement.style.display = 'none';
|
93
|
+
}
|
94
|
+
|
95
|
+
function initializeChartCompare(chartData) {
|
96
|
+
const ctx = document.getElementById('queryCountChart').getContext('2d');
|
97
|
+
const queryCountChart = new Chart(ctx, {
|
98
|
+
type: 'bar',
|
99
|
+
data: {
|
100
|
+
labels: chartData.labels,
|
101
|
+
datasets: [
|
102
|
+
{
|
103
|
+
label: chartData.data_1.name,
|
104
|
+
data: chartData.data_1.data,
|
105
|
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
106
|
+
borderColor: 'rgba(75, 192, 192, 1)',
|
107
|
+
borderWidth: 1
|
108
|
+
},
|
109
|
+
{
|
110
|
+
label: chartData.data_2.name,
|
111
|
+
data: chartData.data_2.data,
|
112
|
+
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
113
|
+
borderColor: 'rgba(255, 99, 132, 1)',
|
114
|
+
borderWidth: 1
|
115
|
+
}
|
116
|
+
]
|
117
|
+
},
|
118
|
+
options: {
|
119
|
+
scales: {
|
120
|
+
y: {
|
121
|
+
type: 'logarithmic',
|
122
|
+
beginAtZero: true,
|
123
|
+
title: {
|
124
|
+
display: true,
|
125
|
+
text: 'Logarithmic Scale'
|
126
|
+
},
|
127
|
+
ticks: {
|
128
|
+
callback: function(value, index, values) {
|
129
|
+
if (value === 0 || value === 10 || value === 100 || value === 1000 || value === 10000) {
|
130
|
+
return value.toString();
|
131
|
+
}
|
132
|
+
return null;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
},
|
137
|
+
plugins: {
|
138
|
+
tooltip: {
|
139
|
+
callbacks: {
|
140
|
+
label: function(tooltipItem) {
|
141
|
+
const index = tooltipItem.dataIndex;
|
142
|
+
const table = chartData.labels[index];
|
143
|
+
return [
|
144
|
+
`Total Queries: ${tooltipItem.raw}`,
|
145
|
+
];
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}
|
149
|
+
}
|
150
|
+
}
|
151
|
+
});
|
152
|
+
document.getElementById('queryCountChart').parentElement.style.display = 'none';
|
153
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Query Counter Report</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<script>
|
8
|
+
<%= js_content %>
|
9
|
+
</script>
|
10
|
+
<style>
|
11
|
+
<%= css_content %>
|
12
|
+
</style>
|
13
|
+
<div id='query_counter_report_gem'>
|
14
|
+
<h2>Query Counter Report</h2>
|
15
|
+
<p>Total query count on <%= @script_1_name %>: <%= total_query_count_1 %></p>
|
16
|
+
<p>Total query count on <%= @script_2_name %>: <%= total_query_count_2 %></p>
|
17
|
+
<button id="toggleButton" onclick="toggleView()">Show Chart View</button>
|
18
|
+
<button id="toggleColumnButton" onclick="toggleColumnContent()">Show SQL</button>
|
19
|
+
|
20
|
+
<div class="center-container">
|
21
|
+
<canvas id="queryCountChart"></canvas>
|
22
|
+
</div>
|
23
|
+
<table id="queryTable">
|
24
|
+
<tr>
|
25
|
+
<th>Table</th>
|
26
|
+
<th>Total Query Count <%= @script_1_name %></th>
|
27
|
+
<th>Total Query Count <%= @script_2_name %></th>
|
28
|
+
<th id="columnHeader">File Path</th>
|
29
|
+
<th>Method</th>
|
30
|
+
<th>Location Count <%= @script_1_name %></th>
|
31
|
+
<th>Location Count <%= @script_2_name %></th>
|
32
|
+
</tr>
|
33
|
+
<% tables.each do |table_name| %>
|
34
|
+
<% locations = (data_1.dig(table_name, :location)&.keys || []) | (data_2.dig(table_name, :location)&.keys || [])%>
|
35
|
+
<tr>
|
36
|
+
<td rowspan="<%= locations.count + 1 %>"><%= table_name %></td>
|
37
|
+
<td rowspan="<%= locations.count + 1 %>"><%= data_1.dig(table_name, :count) || 0%></td>
|
38
|
+
<td rowspan="<%= locations.count + 1 %>"><%= data_2.dig(table_name, :count) || 0 %></td>
|
39
|
+
</tr>
|
40
|
+
<% locations.each do |loc| %>
|
41
|
+
<% details_1 = data_1.dig(table_name, :location, loc) || {}%>
|
42
|
+
<% details_2 = data_2.dig(table_name, :location, loc) || {}%>
|
43
|
+
<% present_detail = details_1 || details_2 %>
|
44
|
+
<tr class="sub-row">
|
45
|
+
<% match = loc&.match(/^(?<file_path>.*):in.*`(?<method>.*)'$/) %>
|
46
|
+
<% file_path = match ? match[:file_path] : loc %>
|
47
|
+
<% method = match ? match[:method] : nil %>
|
48
|
+
<td class="toggle-content">
|
49
|
+
<span class="filepath"><%= file_path %></span>
|
50
|
+
<span class="sql" style="display: none;"><%= present_detail[:sql]&.gsub('"', '') %></span>
|
51
|
+
</td>
|
52
|
+
<td class="method-column"><%= method %></td>
|
53
|
+
<td><%= details_1[:count] || 0 %></td>
|
54
|
+
<td><%= details_2[:count] || 0 %></td>
|
55
|
+
</tr>
|
56
|
+
<% end %>
|
57
|
+
<% end %>
|
58
|
+
</table>
|
59
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
60
|
+
<script>
|
61
|
+
const chartData = <%= chart_data.to_json %>;
|
62
|
+
initializeChartCompare(chartData);
|
63
|
+
</script>
|
64
|
+
</div>
|
65
|
+
</body>
|
66
|
+
</html>
|