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