rspec_stats/rspec_query.rb (144 lines of code) (raw):
# frozen_string_literal: true
require 'open3'
require 'csv'
require 'erb'
require 'json'
class RspecQuery
attr_reader :name, :title, :sql, :prepare, :description
def initialize(opts)
@name = opts[:name]
@title = opts[:title] || opts[:name]
@sql = opts[:query]
@prepare = opts[:prepare]
@hidden = opts[:hidden]
@description = opts[:description]
end
def write_csv
filename = filename(:csv)
if File.exist?(filename)
warn "#{filename} already exists, skipping"
return
end
with_time do
if sql.is_a?(Proc)
sql.call(self)
else
exec_query(filename)
end
end
end
def write_json
filename = filename(:json)
return if csv.empty?
with_time do
json = JSON.generate(data: csv.map(&:fields))
File.write(filename, json)
end
end
def filename(ext)
"tmp/rspec/#{basename(ext)}"
end
def basename(ext)
"#{name}.#{ext}"
end
def csv
options = {
headers: true,
skip_lines: /\(\d+ rows\)/ # Skip lines like "(1000 rows)"
}
csv_filename = filename(:csv)
@csv ||= CSV.read(csv_filename, **options)
rescue StandardError => e
warn "warning: failed to parse #{csv_filename}: #{e.message}"
[]
end
def query_id
name.tr(' ', '-')
end
def html_table_id
"#{query_id}_table"
end
def html
return if csv.empty?
template = File.join(File.dirname(__FILE__), 'templates/table.erb')
ERB.new(File.read(template)).result(binding)
end
def table_headers
headers = csv.headers
headers.append('') unless name == 'overall_time'
headers
end
def hidden?
@hidden == true
end
def avg_change_increase
avg_change do |csv, records|
top_sorted = records.sort_by { |rec| -rec[1] }.first(1000)
top_sorted.each { |rec| csv << rec }
end
end
def avg_change_decrease
avg_change do |csv, records|
top_sorted = records.sort_by { |rec| rec[1] }.first(1000)
top_sorted.each { |rec| csv << rec }
end
end
private
def avg_change
CSV.open(filename(:csv), 'w', write_headers: true) do |csv|
csv << %w[file pct_change_impact avg_time runtime_weight pct_change pct_change_by_day]
yield csv, avg_records
end
end
def avg_records
return @avg_records if @avg_records
q = SQL_QUERIES.find { |q| q[:name] == 'avg_time_per_file_per_day' }
orig_query = RspecQuery.new(q)
today = Date.today
days = (today - 7..today - 1).map(&:to_s)
by_file = orig_query.csv.group_by { |row| row['file'] }
last_day = days.last
@avg_records = by_file.filter_map do |file, rows|
next unless last = rows.find { |r| r['day'] == last_day }
first = rows.find { |r| r['day'] == days.first }
diff_pct = diff_percent(first, last).to_f
weight = last['sum_time'].to_f
pct_impact = (diff_pct * weight).to_i
[file, pct_impact, last['avg_time'], weight.to_i, diff_pct, pct_history(days, rows)]
end
end
def pct_history(days, rows)
last = nil
days.map do |day|
next_row = rows.find { |r| r['day'] == day }
pct = next_row && last ? diff_percent(last, next_row) : 'n/a'
last = next_row
pct
end.drop(1).join(', ')
end
def diff_percent(first, last)
return unless first && last
((last['avg_time'].to_f - first['avg_time'].to_f) * 100 / first['avg_time'].to_f).to_i
end
def with_time
stime = Time.now
yield
puts "time: #{Time.now - stime}"
end
def exec_query(csv_filename)
if ENV['PGURI'].nil?
env = ENV.slice('PGHOST', 'PGDATABASE', 'PGUSER', 'PGPASSWORD', 'PGSSLMODE', 'PGSSLCERT', 'PGSSLKEY',
'PGSSLROOTCERT')
cmd = ['psql', '-A', '-F', ',', '-o', csv_filename, '-f', '-']
# cmd = ['psql', '-f', '-']
else
env = {}
cmd = ['psql', ENV['PGURI'], '-A', '-F', ',', '-o', csv_filename, '-f', '-']
# cmd = ['psql', ENV['PGURI'], '-f', '-']
end
puts "creating #{csv_filename}"
Open3.popen2(env, *cmd) do |stdin, _stdout, thr|
stdin.puts sql
stdin.close
# puts stdout.read
exit_status = thr.value
raise "psql failed" unless exit_status.success?
end
end
end