|
# spec/support/spec_metrics/listener.rb |
|
require_relative './counter' |
|
require_relative './git_output' |
|
|
|
module SpecMetrics |
|
# Listener plugged into RSpec. Events to be triggered are setup in the |
|
# RSpec config object. See usage below. |
|
# |
|
# Taken from https://www.foraker.com/blog/profiling-your-rspec-suite |
|
# See https://github.com/foraker/rspec_profiling too |
|
# |
|
# Usage: |
|
# |
|
# listener = SpecMetrics::Listener.new(aws_credentials) |
|
# config.reporter.register_listener listener, |
|
# :start, :example_started, :example_passed, |
|
# :example_failed, :dump_summary, :seed |
|
# |
|
# Available events are defined in RSpec::Core::Reporter::RSPEC_NOTIFICATIONS |
|
# See https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/reporter.rb |
|
class Listener |
|
|
|
LISTENED_EVENTS = %i( |
|
start dump_summary seed |
|
example_started example_passed example_failed |
|
).freeze |
|
|
|
EXAMPLE_FIELDS = %w( |
|
file line_number description result time query_count |
|
query_time request_count request_time |
|
).freeze |
|
|
|
IGNORED_QUERIES_PATTERN = %r{( |
|
pg_table| |
|
pg_attribute| |
|
pg_namespace| |
|
show\stables| |
|
pragma| |
|
sqlite_master/rollback| |
|
^TRUNCATE TABLE| |
|
^ALTER TABLE| |
|
^BEGIN| |
|
^COMMIT| |
|
^ROLLBACK| |
|
^RELEASE| |
|
^SAVEPOINT |
|
)}xi |
|
|
|
# @param rspec_config [RSpec::Config]: RSpec configuration used to setup |
|
# listener methods |
|
# @param collector_config [Hash]: an hash providing configuration for the output |
|
# store. Currently, only available store is AWS S3, so the expected config |
|
# contains :access_key_id, :secret_access_key and :region. |
|
def self.setup(rspec_config, collector_config) |
|
listener = new(collector_config) |
|
rspec_config.reporter.register_listener listener, *LISTENED_EVENTS |
|
end |
|
|
|
# @param collector_config [Hash] |
|
def initialize(collector_config) |
|
@aws_credentials = Aws::Credentials.new( |
|
collector_config[:access_key_id], |
|
collector_config[:secret_access_key] |
|
) |
|
@aws_region = collector_config[:region] |
|
@around_counter = Counter.new |
|
end |
|
|
|
def finalize |
|
output[:git] = GitOutput.build |
|
output[:system] = system_info |
|
output[:around] = @around_counter.output |
|
send_to_s3(output) |
|
end |
|
|
|
def start(_notification) |
|
start_counting_queries |
|
start_counting_requests |
|
end |
|
|
|
def seed(notification) |
|
output[:seed] = notification.seed |
|
output[:seed_used] = notification.seed_used? |
|
end |
|
|
|
def dump_summary(notification) |
|
%i(duration example_count failure_count pending_count).each do |key| |
|
output[key] = notification.send(key) |
|
end |
|
finalize |
|
end |
|
|
|
def example_started(_notification) |
|
@example_counter = Counter.new |
|
end |
|
|
|
def example_finished(notification) |
|
output[:examples].push(ExampleOutput.build(notification.example.metadata, @example_counter)) |
|
@example_counter = nil |
|
end |
|
|
|
alias example_passed example_finished |
|
alias example_failed example_finished |
|
|
|
def start_counting_queries |
|
ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, query| |
|
unless query[:sql] =~ IGNORED_QUERIES_PATTERN |
|
counter.new_query(start, finish) |
|
end |
|
end |
|
end |
|
|
|
def start_counting_requests |
|
ActiveSupport::Notifications.subscribe('process_action.action_controller') do |_, _, _, _, request| |
|
counter.new_request(request) |
|
end |
|
end |
|
|
|
def counter |
|
@example_counter ? @example_counter : @around_counter |
|
end |
|
|
|
private |
|
|
|
def send_to_s3(output) |
|
client = Aws::S3::Client.new( |
|
credentials: @aws_credentials, |
|
region: @aws_region |
|
) |
|
bucket = Aws::S3::Bucket.new('jt-spec-metrics', client: client) |
|
bucket.put_object(key: key(output), body: output.to_json) |
|
|
|
rescue Aws::S3::Errors::ServiceError => e |
|
# rescues all errors returned by Amazon Simple Storage Service |
|
puts("Failed to send spec-metrics data to S3 (#{e})") |
|
# TODO: dump locally and display a message on how to retry |
|
# the S3 dump. |
|
end |
|
|
|
def key(output) |
|
remote = output[:git][:remote_origin].gsub(/[^\w]/, '-') |
|
branch = output[:git][:branch] |
|
sha = output[:git][:sha][0...8] |
|
time = Time.now.strftime('%Y%m%d%H%M%S%L') |
|
|
|
key = [ |
|
'raw', |
|
remote, |
|
"#{branch}-#{time}-#{sha}.json" |
|
].join('/') |
|
key |
|
end |
|
|
|
def output |
|
@output ||= { |
|
examples: [] |
|
} |
|
end |
|
|
|
def system_info |
|
{ |
|
current_dir: File.expand_path('.').split('/').last, |
|
hostname: `hostname`.strip |
|
} |
|
end |
|
|
|
end |
|
end |