sql_reporter 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6f986dc946bf53f6bc27bb59019cce13ccbcf52d2b91d80f7893a966c4fdc737
4
+ data.tar.gz: 740bf0c3e7c8dc428ccba73d35e945174a5cf378c88d9ca7e0795c1efb1a59b1
5
+ SHA512:
6
+ metadata.gz: a79e634a5470eada47d5cc643e610c54e05e770688987fe349b65ed7d9b34b55c157d98d2b567fe5617f176ccbd9718db08d766e315d448d1b981c1e5556134d
7
+ data.tar.gz: c7f610440bdd33eedee6d076a001c26ae160be1ff02c77c13f43caa712142355331bd5f6a0c0d8cfd1a75879d78295d6a33bc0b974b86515fc7991f3f97a2e2c
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ .bundle
3
+ comparison.*
4
+ samples/*
5
+ Gemfile.lock
6
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 JanTaras29
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,66 @@
1
+ ## What this is?
2
+
3
+ This is a gem that can be used for comparing reports generated by excellent [sql_tracker](https://github.com/steventen/sql_tracker) gem for different branches. It can be useful for detecting SQL queries regressions or optimisation opportunities.
4
+
5
+ ## Setup
6
+
7
+ Simply run the following in terminal while in the repo directory (temporary solution while not in rubygems.org)
8
+ ```
9
+ gem build sql_reporter.gemspec
10
+ gem install sql_reporter
11
+ ```
12
+
13
+ or if you are using Bundler
14
+ ```
15
+ bundle
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ `sql_reporter original_branch.json improved_branch.json`
21
+
22
+
23
+ The above will generate a comparison.log file with content akin to:
24
+ ```
25
+ SQL Count Decreases between samples/master.json -> samples/637.json
26
+ ##########################################################
27
+
28
+ Queries killed: 0
29
+
30
+ Duration decrease[ms]: 0.0
31
+
32
+ SQL Count Increases between samples/master.json -> samples/637.json
33
+ ##########################################################
34
+
35
+ Queries killed: 0
36
+
37
+ Duration decrease[ms]: 0.0
38
+
39
+ SQL Spawned between samples/master.json -> samples/637.json
40
+ ##########################################################
41
+ +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------+-------------------------------------+
42
+ |Query |Count difference |Duration difference [ms] |
43
+ +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------+-------------------------------------+
44
+ |SELECT `apples`.* FROM `apples` WHERE `apples`.`apple_id` = xxx AND `apples`.`locale` = xxx AND `appler`.`apple_type` IN (xxx) |0 -> 1 |0.0 -> 1.76 |
45
+ +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------+-------------------------------------+
46
+ Queries spawned: 1
47
+
48
+ Duration gain[ms]: 1.76
49
+
50
+ SQL Gone between samples/master.json -> samples/637.json
51
+ ##########################################################
52
+ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+--------------------+
53
+ |Query |Count diff… |Duration differenc… |
54
+ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+--------------------+
55
+ |SELECT `apples`.* FROM `apples` WHERE `apples`.`apple_id` = xxx AND `apples`.`apple_type` = xxx AND `apples`.`locale` = xxx ORDER BY `email_default_messages`.`id` ASC LIMI… |7 -> 0 |13.78 -> 0.0 |
56
+ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+--------------------+
57
+ Queries killed: 7
58
+
59
+ Duration decrease[ms]: 13.78
60
+
61
+ ################## SUMMARY #####################
62
+
63
+ Queries killed: 6
64
+
65
+ Duration decrease[ms]: 12.02
66
+ ```
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sql_reporter'
4
+
5
+ begin
6
+ SqlReporter::ReporterFactory.new.for_format.generate_report
7
+ rescue StandardError => e
8
+ STDERR.puts('[ERROR] Unexpected Error occured')
9
+ puts e.message
10
+ puts e.backtrace
11
+ end
@@ -0,0 +1,7 @@
1
+ require 'sql_reporter/query'
2
+ require 'sql_reporter/total'
3
+ require 'sql_reporter/parser'
4
+ require 'sql_reporter/difference'
5
+ require 'sql_reporter/reporters/reporters'
6
+ require 'sql_reporter/reporter_factory'
7
+ require 'sql_reporter/version'
@@ -0,0 +1,28 @@
1
+ module SqlReporter
2
+ # Difference between 2 Query objects
3
+ class Difference
4
+ attr_reader :master, :feature, :query_name
5
+
6
+ def initialize(name, master, feature)
7
+ @query_name = name
8
+ @master = master
9
+ @feature = feature
10
+ end
11
+
12
+ def delta_count
13
+ (feature - master).count
14
+ end
15
+
16
+ def delta_cached_count
17
+ (feature - master).cached_count
18
+ end
19
+
20
+ def delta_time
21
+ (feature - master).duration_formatted
22
+ end
23
+
24
+ def sort_score(max_count)
25
+ (master - feature).count.abs + master.post_decimal_score(max_count)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+
6
+ module SqlReporter
7
+ class Parser
8
+ def self.parse
9
+ options = {}
10
+ OptionParser.new do |opts|
11
+ opts.banner = 'Usage: sql_reporter [options] file.json file2.json'
12
+
13
+ opts.on('-f', '--format FORMAT', String, 'Format of the output file (defaults to pdf, avaliable formats: log , json, png, pdf, xls )') do |f|
14
+ options[:format] = f
15
+ end
16
+
17
+ opts.on('--disable-console', 'Disable outputting the report to terminal') do |f|
18
+ options[:disable_console] = true
19
+ end
20
+
21
+ opts.on('-o', '--output FILE', String, 'File to write the report to - without extension') do |o|
22
+ options[:output] = o
23
+ end
24
+
25
+ opts.on_tail('--version', 'Show version') do
26
+ puts SqlReporter::VERSION
27
+ exit
28
+ end
29
+
30
+ opts.on("-h", "--help", "Prints this help") do
31
+ puts opts
32
+ exit
33
+ end
34
+ end.parse!
35
+
36
+ unless ARGV.size == 2
37
+ STDERR.puts "[ERROR] Incorrect number of parameters passed (2 files required)"
38
+ exit(1)
39
+ end
40
+
41
+ begin
42
+ f0 = File.read(ARGV[0])
43
+ f1 = File.read(ARGV[1])
44
+ rescue StandardError => e
45
+ puts e.message
46
+ exit(1)
47
+ end
48
+
49
+ begin
50
+ master = JSON.load(f0)['data'].values.map do |v|
51
+ [v['sql'], SqlReporter::Query.new(v['sql'], v['count'], v['duration'] || 0, v['cached_count'] || 0)]
52
+ end.to_h
53
+ feature = JSON.load(f1)['data'].values.map do |v|
54
+ [v['sql'], SqlReporter::Query.new(v['sql'], v['count'], v['duration'] || 0, v['cached_count'] || 0)]
55
+ end.to_h
56
+ rescue JSON::ParserError
57
+ STDERR.puts 'One of the files provided is not a correctly formatted JSON file'
58
+ exit(1)
59
+ end
60
+
61
+ master_key = ARGV[0]
62
+ feature_key = ARGV[0] == ARGV[1] ? ARGV[0] + '_copy' : ARGV[1]
63
+
64
+ { master_key => master, feature_key => feature, format: options[:format] }.merge(options)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Data struct for keeping the query data
4
+ module SqlReporter
5
+ class Query
6
+ attr_accessor :sql, :count, :duration, :cached_count
7
+
8
+ def self.null(query_name)
9
+ self.new(query_name, 0, 0, 0)
10
+ end
11
+
12
+ def initialize(s, c, d, cc)
13
+ @sql = s
14
+ @count = c
15
+ @duration = d
16
+ @cached_count = cc
17
+ end
18
+
19
+ def +(other)
20
+ self.class.new(sql, count + other.count, duration + other.duration, cached_count + other.cached_count)
21
+ end
22
+
23
+ def -(other)
24
+ self.class.new(sql, count - other.count, duration - other.duration, cached_count - other.cached_count)
25
+ end
26
+
27
+ def post_decimal_score(max_count)
28
+ count * (1 / (max_count + 1))
29
+ end
30
+
31
+ def duration_formatted
32
+ duration&.round(2)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ module SqlReporter
2
+ class ReporterFactory
3
+ attr_reader :parser_hsh
4
+
5
+ def initialize
6
+ @parser_hsh = SqlReporter::Parser.parse
7
+ end
8
+
9
+ def for_format
10
+ case parser_hsh[:format]
11
+ when 'log'
12
+ log_reporter
13
+ when 'json'
14
+ json_reporter
15
+ when 'png'
16
+ plot_reporter
17
+ when 'pdf'
18
+ pdf_reporter
19
+ when 'xls'
20
+ excel_reporter
21
+ else
22
+ pdf_reporter
23
+ end
24
+ end
25
+
26
+ def log_reporter
27
+ SqlReporter::Reporters::LogReporter.new(parser_hsh)
28
+ end
29
+
30
+ def json_reporter
31
+ SqlReporter::Reporters::JsonReporter.new(parser_hsh)
32
+ end
33
+
34
+ def plot_reporter
35
+ SqlReporter::Reporters::PlotReporter.new(parser_hsh)
36
+ end
37
+
38
+ def pdf_reporter
39
+ SqlReporter::Reporters::PdfReporter.new(parser_hsh)
40
+ end
41
+
42
+ def excel_reporter
43
+ SqlReporter::Reporters::ExcelReporter.new(parser_hsh)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,71 @@
1
+ #frozen_string_literal: true
2
+ require 'spreadsheet'
3
+
4
+ module SqlReporter
5
+ module Reporters
6
+ class ExcelReporter < LogReporter
7
+ EXTENSION = '.xls'
8
+ HEADERS = ['Query', 'Count [master]', 'Count [feature]', 'Total time [master]', 'Total time [feature]']
9
+
10
+ attr_reader :totals
11
+
12
+ protected
13
+
14
+ def generate_summary(totals, **kwargs)
15
+ @totals = totals
16
+ end
17
+
18
+ def generate_query_line(diff)
19
+ lines << diff
20
+ end
21
+
22
+ def before_generate_report
23
+ @lines = []
24
+ end
25
+
26
+ def after_generate_report
27
+ @totals = totals
28
+ @lines = lines.sort_by {|d| d.sort_score(master_max_count) }.reverse
29
+ book = Spreadsheet::Workbook.new
30
+ sheet = book.create_worksheet(name: "Comparison Report #{fname0} -> #{fname1}")
31
+ sheet.row(0).concat HEADERS
32
+ lines.each_with_index do |l, i|
33
+ sheet.row(i + 1).concat [l.query_name, l.master.count, l.feature.count, l.master.duration_formatted, l.feature.duration_formatted]
34
+ end
35
+ totals_row_no = lines.size + 1
36
+ accumulated_row = totals_row_no + 2
37
+ sheet[totals_row_no, 0] = 'Totals:'
38
+ sheet[totals_row_no, 1] = lines.reduce(0) {|acc, l| acc + l.master.count }
39
+ sheet[totals_row_no, 2] = lines.reduce(0) {|acc, l| acc + l.feature.count }
40
+ sheet[totals_row_no, 3] = lines.reduce(0) {|acc, l| acc + l.master.duration_formatted }
41
+ sheet[totals_row_no, 4] = lines.reduce(0) {|acc, l| acc + l.feature.duration_formatted }
42
+ sheet[accumulated_row, 1] = 'Count Increase:'
43
+ sheet[accumulated_row, 2] = sheet[totals_row_no, 2] - sheet[totals_row_no, 1]
44
+ sheet[accumulated_row, 3] = 'Time Increase:'
45
+ sheet[accumulated_row, 4] = sheet[totals_row_no, 4] - sheet[totals_row_no, 3]
46
+
47
+ bold = Spreadsheet::Format.new(weight: :bold)
48
+ [totals_row_no, accumulated_row].each do |row|
49
+ 5.times { |x| sheet.row(row).set_format(x, bold) }
50
+ end
51
+ book.write "./#{output_file}"
52
+ end
53
+
54
+ def before_increases
55
+ end
56
+
57
+ def before_decreases
58
+ end
59
+
60
+ def before_gone
61
+ end
62
+
63
+ def before_spawned
64
+ end
65
+
66
+ def before_summary
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,57 @@
1
+ module SqlReporter
2
+ module Reporters
3
+ class JsonReporter < Reporter
4
+ EXTENSION = '.json'
5
+
6
+ attr_reader :lines, :title, :body
7
+
8
+ protected
9
+
10
+ def generate_summary(totals, **kwargs)
11
+ hsh = { count_increase: totals.query_diff , duration_increase: totals.duration_diff.round(2) }
12
+ hsh[:queries] = lines unless lines.empty?
13
+ body[title] = hsh
14
+ @lines = []
15
+ end
16
+
17
+ def generate_query_line(diff)
18
+ hsh = {
19
+ name: diff.query_name,
20
+ count: {before: diff.master.count, after: diff.feature.count},
21
+ duration: {before: diff.master.duration_formatted, after: diff.feature.duration_formatted}
22
+ }
23
+ lines << hsh
24
+ end
25
+
26
+ def before_generate_report
27
+ @lines = []
28
+ @body = {}
29
+ end
30
+
31
+ def after_generate_report
32
+ io.write(body.to_json)
33
+ io.close
34
+ end
35
+
36
+ def before_increases
37
+ @title = 'increases'
38
+ end
39
+
40
+ def before_decreases
41
+ @title = 'decreases'
42
+ end
43
+
44
+ def before_gone
45
+ @title = 'gone'
46
+ end
47
+
48
+ def before_spawned
49
+ @title = 'spawned'
50
+ end
51
+
52
+ def before_summary
53
+ @title = 'total'
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,89 @@
1
+ #frozen_string_literal: true
2
+
3
+ require 'tty-table'
4
+
5
+ module SqlReporter
6
+ module Reporters
7
+ class LogReporter < Reporter
8
+ EXTENSION = '.log'
9
+ HEADERS = ['Query', 'Count difference', 'Cached SQLs difference', 'Duration difference [ms]']
10
+
11
+ attr_reader :lines
12
+
13
+ protected
14
+
15
+ def generate_summary(totals, **kwargs)
16
+ table = TTY::Table.new(HEADERS, lines).render(
17
+ :ascii,
18
+ column_widths: [100, 40, 40, 40],
19
+ multiline: true,
20
+ resize: true,
21
+ )
22
+ io.write(table)
23
+ io.write("Queries reduced: #{kwargs[:reduced]}\n") if kwargs.key? :reduced
24
+ io.write("Queries spawned: #{kwargs[:spawned]}\n") if kwargs.key? :spawned
25
+ io.write(totals.summary)
26
+ @lines = []
27
+ end
28
+
29
+ def generate_query_line(diff)
30
+ lines << [diff.query_name, "#{diff.master.count} -> #{diff.feature.count}", "#{diff.master.cached_count} -> #{diff.feature.cached_count}", "#{diff.master.duration_formatted} -> #{diff.feature.duration_formatted}"]
31
+ end
32
+
33
+ def before_generate_report
34
+ @lines = []
35
+ end
36
+
37
+ def after_generate_report
38
+ io.close
39
+ end
40
+
41
+ def before_increases
42
+ print_header('Count Increases')
43
+ end
44
+
45
+ def before_decreases
46
+ print_header('Count Decreases')
47
+ end
48
+
49
+ def before_gone
50
+ print_header('Gone')
51
+ end
52
+
53
+ def before_spawned
54
+ print_header('Spawned')
55
+ end
56
+
57
+ def before_summary
58
+ io.write("################## SUMMARY #####################\n\n")
59
+ end
60
+
61
+ def setup_io
62
+ @io = Class.new do
63
+ attr_reader :file, :console_disabled
64
+
65
+ def initialize(file, disable_console = false)
66
+ @file = File.open(file, "w")
67
+ @console_disabled = disable_console
68
+ end
69
+
70
+ def write(content)
71
+ file.write(content)
72
+ STDOUT.write(content) unless console_disabled
73
+ end
74
+
75
+ def close
76
+ file.close
77
+ end
78
+ end.new(output_file, disable_console)
79
+ end
80
+
81
+ private
82
+
83
+ def print_header(header_name)
84
+ io.write("SQL #{header_name} between #{fname0} -> #{fname1}\n")
85
+ io.write("##########################################################\n")
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,37 @@
1
+ require 'prawn'
2
+
3
+ module SqlReporter
4
+ module Reporters
5
+ class PdfReporter< Reporter
6
+ EXTENSION = '.pdf'
7
+
8
+ attr_reader :plot_report, :table_report
9
+
10
+ def initialize(parser_hsh)
11
+ super(parser_hsh)
12
+ @table_report = SqlReporter::Reporters::PdfTableReporter.new(parser_hsh)
13
+ @plot_report = SqlReporter::Reporters::PlotReporter.new(parser_hsh)
14
+ end
15
+
16
+ def after_generate_report
17
+ table_report.generate_report
18
+ plot_report.generate_report
19
+ Prawn::Document.generate(output_file) do |pdf|
20
+ pdf.font_size(20) { pdf.text "Comparison report of #{@fname0} -> #{@fname1}", align: :center, styles: [:bold] }
21
+ pdf.move_down 20
22
+ pdf.text 'Count changes:'
23
+ pdf.move_down 10
24
+ pdf.image @plot_report.output_file, position: :center, scale: 0.8
25
+ pdf.move_down 10
26
+ pdf.text 'Timing changes:'
27
+ pdf.move_down 10
28
+ pdf.image('time_' + @plot_report.output_file, position: :center, scale: 0.8)
29
+ pdf.start_new_page
30
+ pdf.font_size(25) { pdf.text 'Summary', align: :center, styles: [:bold] }
31
+ pdf.move_down 10
32
+ table_report.produce_table(pdf)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,51 @@
1
+ #frozen_string_literal: true
2
+ require 'prawn/table'
3
+
4
+ module SqlReporter
5
+ module Reporters
6
+ class PdfTableReporter < LogReporter
7
+
8
+ attr_reader :totals
9
+
10
+ def produce_table(pdf_context)
11
+ t = pdf_context.make_table([HEADERS, *lines])
12
+ t.draw
13
+ pdf_context.move_down(20)
14
+ pdf_context.font_size(12) { pdf_context.text(totals.summary, styles: [:bold]) }
15
+ end
16
+
17
+ protected
18
+
19
+ def generate_summary(totals, **kwargs)
20
+ @totals = totals
21
+ end
22
+
23
+ def generate_query_line(diff)
24
+ lines << [diff.query_name, "#{diff.master.count} -> #{diff.feature.count}", "#{diff.master.duration_formatted} -> #{diff.feature.duration_formatted}"]
25
+ end
26
+
27
+ def before_generate_report
28
+ @lines = []
29
+ end
30
+
31
+ def after_generate_report
32
+ end
33
+
34
+ def before_increases
35
+ end
36
+
37
+ def before_decreases
38
+ end
39
+
40
+ def before_gone
41
+ end
42
+
43
+ def before_spawned
44
+ end
45
+
46
+ def before_summary
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require 'gruff'
2
+
3
+ module SqlReporter
4
+ module Reporters
5
+ class PlotReporter < Reporter
6
+ EXTENSION = '.png'
7
+
8
+ attr_reader :count_plot, :time_plot, :diffs
9
+
10
+ protected
11
+
12
+ def before_generate_report
13
+ @count_plot = Gruff::Bar.new(400)
14
+ @time_plot = Gruff::Bar.new(400)
15
+ @diffs = []
16
+ count_plot.title = "Count #{fname0} > #{fname1}"
17
+ count_plot.marker_count = 0
18
+ count_plot.show_labels_for_bar_values = true
19
+ time_plot.title = "Timing #{fname0} > #{fname1}"
20
+ time_plot.marker_count = 0
21
+ time_plot.show_labels_for_bar_values = true
22
+ end
23
+
24
+ def generate_query_line(diff)
25
+ diffs << diff
26
+ end
27
+
28
+ def after_generate_report
29
+ instert_plot_data(count_plot, :count)
30
+ instert_plot_data(time_plot, :duration_formatted)
31
+ count_plot.write(output_file)
32
+ time_plot.write('time_' + output_file)
33
+ end
34
+
35
+ private
36
+
37
+ def instert_plot_data(plot, method)
38
+ diffs.each do |diff|
39
+ plot.data(diff.query_name[0..20] + ' BEFORE', diff.master.public_send(method), '#990000')
40
+ plot.data(diff.query_name[0..20] + ' AFTER', diff.feature.public_send(method), '#000099')
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,125 @@
1
+ module SqlReporter
2
+ module Reporters
3
+ class Reporter
4
+ EXTENSION=''
5
+
6
+ def initialize(parser_hsh)
7
+ @fname0, @master = parser_hsh.entries[0]
8
+ @fname1, @feature = parser_hsh.entries[1]
9
+ @master_max_count = @master.values.map(&:count).max
10
+ @output = parser_hsh[:output] if parser_hsh.key?(:output)
11
+ @disable_console = parser_hsh[:disable_console] if parser_hsh.key?(:disable_console)
12
+ end
13
+
14
+ attr_accessor :master, :feature, :fname0, :fname1, :output, :io
15
+ attr_reader :master_max_count, :disable_console
16
+
17
+ def generate_report
18
+ setup_io
19
+ before_generate_report
20
+ totals = []
21
+
22
+ before_decreases
23
+ totals << summary_for_selected_differences(master.keys | feature.keys) do |key|
24
+ master[key] && feature[key] && (
25
+ master[key].count > feature[key].count || (master[key].count == feature[key].count && master[key].cached_count > feature[key].cached_count)
26
+ )
27
+ end
28
+
29
+ before_increases
30
+ totals << summary_for_selected_differences(master.keys | feature.keys) do |key|
31
+ master[key] && feature[key] && (
32
+ master[key].count < feature[key].count || (master[key].count == feature[key].count && master[key].cached_count < feature[key].cached_count)
33
+ )
34
+ end
35
+
36
+ before_spawned
37
+ totals << summary_for_selected_differences(feature.keys - master.keys) { |key| feature[key] }
38
+
39
+ before_gone
40
+ totals << summary_for_selected_differences(master.keys - feature.keys) { |key| master[key] }
41
+
42
+ before_summary
43
+ totals_sum = totals.reduce(SqlReporter::Total.new(0,0,0)) {|acc, t| acc + t}
44
+ additional_data = {}
45
+ additional_data[:reduced] = totals.reduce(0) {|acc, t| acc + t.query_drop}
46
+ additional_data[:spawned] = totals.reduce(0) {|acc, t| acc + t.query_gain}
47
+ generate_summary(totals_sum, **additional_data)
48
+ after_generate_report
49
+ print_success_message
50
+ end
51
+
52
+ def output_file
53
+ (output || 'comparison') + self.class::EXTENSION
54
+ end
55
+
56
+ protected
57
+
58
+ def generate_summary(totals, **kwargs)
59
+ end
60
+
61
+ def generate_query_line(diff)
62
+ end
63
+
64
+ def summary_for_selected_differences(collection, &block)
65
+ duration_diff = 0
66
+ cached_count_diff = 0
67
+ count_diff = 0
68
+ process_differences(collection, &block).each do |diff|
69
+ generate_query_line(diff)
70
+ count_diff += diff.delta_count
71
+ cached_count_diff += diff.delta_cached_count
72
+ duration_diff += diff.delta_time
73
+ end
74
+ totals = SqlReporter::Total.new(count_diff, duration_diff, cached_count_diff)
75
+ generate_summary(totals)
76
+ totals
77
+ end
78
+
79
+ def process_differences(collection)
80
+ prefiltered_collection = collection.select { |key| yield(key) }
81
+ differences = prefiltered_collection.map do |key|
82
+ construct_difference_object(key)
83
+ end
84
+ differences.sort_by {|d| d.sort_score(master_max_count) }.reverse
85
+ end
86
+
87
+ def construct_difference_object(key)
88
+ m = master[key] || SqlReporter::Query.null(key)
89
+ f = feature[key] || SqlReporter::Query.null(key)
90
+ SqlReporter::Difference.new(key, m, f)
91
+ end
92
+
93
+ def before_generate_report
94
+ end
95
+
96
+ def after_generate_report
97
+ end
98
+
99
+ def before_increases
100
+ end
101
+
102
+ def before_decreases
103
+ end
104
+
105
+ def before_gone
106
+ end
107
+
108
+ def before_spawned
109
+ end
110
+
111
+ def before_summary
112
+ end
113
+
114
+ def setup_io
115
+ @io = File.open(output_file, "w")
116
+ end
117
+
118
+ private
119
+
120
+ def print_success_message
121
+ puts "[Comparison Successful] Comparison report written to: #{`pwd`.strip + '/' + output_file}"
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,7 @@
1
+ require 'sql_reporter/reporters/reporter'
2
+ require 'sql_reporter/reporters/log_reporter'
3
+ require 'sql_reporter/reporters/json_reporter'
4
+ require 'sql_reporter/reporters/plot_reporter'
5
+ require 'sql_reporter/reporters/pdf_table_reporter'
6
+ require 'sql_reporter/reporters/pdf_reporter'
7
+ require 'sql_reporter/reporters/excel_reporter'
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SqlReporter
4
+ class Total
5
+ attr_accessor :query_diff, :cached_query_diff, :duration_diff
6
+ def initialize
7
+ @query_diff = 0
8
+ @duration_diff = 0
9
+ @cached_query_diff = 0
10
+ end
11
+
12
+ def initialize(query, duration, cached)
13
+ @query_diff = query
14
+ @duration_diff = duration
15
+ @cached_query_diff = cached
16
+ end
17
+
18
+ def query_gain
19
+ query_diff > 0 ? query_diff : 0
20
+ end
21
+
22
+ def query_drop
23
+ query_diff > 0 ? 0 : -query_diff
24
+ end
25
+
26
+ def queries_msg
27
+ "\nQueries count change: #{query_diff}\n"
28
+ end
29
+
30
+ def duration_msg
31
+ "\nDuration #{duration_diff > 0 ? 'gain' : 'decrease' }[ms]: #{duration_diff.abs.round(2)}\n"
32
+ end
33
+
34
+ def summary
35
+ queries_msg + duration_msg + "\n"
36
+ end
37
+
38
+ def +(total)
39
+ self.class.new(query_diff + total.query_diff, duration_diff + total.duration_diff, cached_query_diff + total.cached_query_diff)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module SqlReporter
2
+ VERSION= '0.10.1'.freeze
3
+ end
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'sql_reporter/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.authors = ["Jan Taras"]
7
+ spec.email = ["jan.taras29@gmail.com"]
8
+ spec.name = "sql_reporter"
9
+ spec.version = SqlReporter::VERSION
10
+ spec.date = "2019-07-12"
11
+ spec.summary = "Supplementary Gem to sql_tracker allowing you to compare query data bewteen files"
12
+ spec.homepage = 'https://github.com/JanTaras29/sql_reporter'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = 'bin'
17
+ spec.executables = ['sql_reporter']
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency('tty-table', '0.10.0')
21
+ spec.add_dependency('gruff', '0.7.0')
22
+ spec.add_dependency('prawn', '2.2.0')
23
+ spec.add_dependency('prawn-table', '0.2.2')
24
+ spec.add_dependency('spreadsheet', '1.2.4')
25
+ spec.add_development_dependency('pry')
26
+ end
27
+
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sql_reporter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.10.1
5
+ platform: ruby
6
+ authors:
7
+ - Jan Taras
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-table
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: gruff
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: prawn
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: 2.2.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: 2.2.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: prawn-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.2.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.2.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: spreadsheet
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 1.2.4
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.2.4
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - jan.taras29@gmail.com
100
+ executables:
101
+ - sql_reporter
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - Gemfile
107
+ - LICENSE.TXT
108
+ - README.md
109
+ - bin/sql_reporter
110
+ - lib/sql_reporter.rb
111
+ - lib/sql_reporter/difference.rb
112
+ - lib/sql_reporter/parser.rb
113
+ - lib/sql_reporter/query.rb
114
+ - lib/sql_reporter/reporter_factory.rb
115
+ - lib/sql_reporter/reporters/excel_reporter.rb
116
+ - lib/sql_reporter/reporters/json_reporter.rb
117
+ - lib/sql_reporter/reporters/log_reporter.rb
118
+ - lib/sql_reporter/reporters/pdf_reporter.rb
119
+ - lib/sql_reporter/reporters/pdf_table_reporter.rb
120
+ - lib/sql_reporter/reporters/plot_reporter.rb
121
+ - lib/sql_reporter/reporters/reporter.rb
122
+ - lib/sql_reporter/reporters/reporters.rb
123
+ - lib/sql_reporter/total.rb
124
+ - lib/sql_reporter/version.rb
125
+ - sql_reporter.gemspec
126
+ homepage: https://github.com/JanTaras29/sql_reporter
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.0.3
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Supplementary Gem to sql_tracker allowing you to compare query data bewteen
149
+ files
150
+ test_files: []