diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6a05caa79d749c81ad396eb16c2ff6f53dab7f51..a01c992410e20766fad8d589ae78a0d2831fa766 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -206,6 +206,9 @@ variables: RSPEC_AUTO_EXPLAIN_LOG_PATH: auto_explain/auto_explain.ndjson.gz TMP_TEST_FOLDER: "${CI_PROJECT_DIR}/tmp/tests" TMP_TEST_GITLAB_WORKHORSE_PATH: "${TMP_TEST_FOLDER}/${GITLAB_WORKHORSE_FOLDER}" + EVENT_PROF_REPORT_PATH: rspec/event_prof.json + EVENT_PROF_TOP: 100000 + FPROF_REPORT_PATH: rspec/factory_prof.json ES_JAVA_OPTS: "-Xms256m -Xmx256m" ELASTIC_URL: "http://elastic:changeme@elasticsearch:9200" diff --git a/.gitlab/ci/rails/shared.gitlab-ci.yml b/.gitlab/ci/rails/shared.gitlab-ci.yml index 9453bdd9b8db9c3578b6bbee3c5e207e6d18776b..3426a3cc247ad21350d9ef59477f35733ddab90a 100644 --- a/.gitlab/ci/rails/shared.gitlab-ci.yml +++ b/.gitlab/ci/rails/shared.gitlab-ci.yml @@ -92,7 +92,10 @@ include: RECORD_DEPRECATIONS: "true" GEO_SECONDARY_PROXY: 0 SUCCESSFULLY_RETRIED_TEST_EXIT_CODE: 137 - EVENT_PROF: "sql.active_record" + FPROF: 'json' + EVENT_PROF: 'factory.create,sql.active_record' + EVENT_PROF_FORMAT: 'json' + EVENT_PROF_JSON_FILE: "event_prof_${CI_JOB_NAME}.json" needs: - !reference [.rspec-base-needs, needs] - job: "detect-tests" diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh index afe41a06bdc0561c3359f6fa4110cda4d4ea5d47..501fbea75b08612a493eea37446e1e9dbf48103f 100644 --- a/scripts/rspec_helpers.sh +++ b/scripts/rspec_helpers.sh @@ -47,6 +47,12 @@ function update_tests_metadata() { # in this pipeline, so that first_flaky_at for tests that are still flaky is maintained. scripts/flaky_examples/prune-old-flaky-examples "${FLAKY_RSPEC_SUITE_REPORT_PATH:-unknown_file}" + echo "{}" > "${EVENT_PROF_REPORT_PATH}" + scripts/merge-reports "${EVENT_PROF_REPORT_PATH}" rspec/test_prof/event_prof*.json + + echo "{}" > "${FPROF_REPORT_PATH}" + scripts/merge-reports "${FPROF_REPORT_PATH}" rspec/test_prof/factory_prof_*.json + if [[ "$CI_PIPELINE_SOURCE" == "schedule" ]]; then if [[ -n "$RSPEC_PROFILING_PGSSLKEY" ]]; then chmod 0600 $RSPEC_PROFILING_PGSSLKEY diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c3e935f50edea4fe92dae22f52117bf8e0ceb89e..26cb85f7c249f4c4fdaceb234ad45a0ad917baaf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -557,6 +557,9 @@ # Initialize FactoryDefault to use create_default helper TestProf::FactoryDefault.init +TestProf.configure do |config| + config.output_dir = ENV['CI'] ? 'rspec/test_prof' : 'tmp/test_prof' +end # Set the start of ID sequence for records initialized by `build_stubbed` to prevent conflicts FactoryBot::Strategy::Stub.next_id = 1_000_000_000 diff --git a/spec/support/event_prof.rb b/spec/support/event_prof.rb new file mode 100644 index 0000000000000000000000000000000000000000..adbeed7f75a0b7cde28fdce8060fdbcda2ab14f5 --- /dev/null +++ b/spec/support/event_prof.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +return unless ENV['EVENT_PROF'] +return unless ENV['EVENT_PROF_FORMAT'] == 'json' + +require 'json' + +module Support + module EventProf + module JsonPrinter + def print + super + + # group_location => { + # event => result + # } + json = Hash.new { |hash, location| hash[location] = {} } + + @profiler.each do |profile| + collect_data(json, profile) + end + + write_json(json) + end + + private + + def collect_data(json, profile) + profile.results[:groups].each do |group| + metadata = group[:id].metadata + path = metadata[:file_path] + json[path][:description] = metadata[:description] + json[path][:type] = metadata[:type] || '?' + json[path][:examples] = group[:id].descendant_filtered_examples.size + + json[path][profile.event] = { + time: group[:time], + run_time: group[:run_time], + count: group[:count] + } + end + end + + def write_json(json) + filename = ENV['EVENT_PROF_JSON_FILE'] || 'event_prof' + filename = filename.gsub(/\W/, '_') + filename = "#{filename}.json" + + path = TestProf.artifact_path(filename) + + File.write(path, JSON.generate(json)) # rubocop:disable Gitlab/Json -- This is fine. + + log :info, "EventProf results written to #{path}" + end + end + end +end + +TestProf::EventProf::RSpecListener.prepend Support::EventProf::JsonPrinter diff --git a/spec/support/factory_prof.rb b/spec/support/factory_prof.rb new file mode 100644 index 0000000000000000000000000000000000000000..a56c2e085fb18e4ea2565cdd9a26f4aba3d4b73c --- /dev/null +++ b/spec/support/factory_prof.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +return unless ENV['FPROF'] == 'json' + +require 'json' + +module Support + module FactoryProfPrinter + class << self + include TestProf::Logging + + def dump(result, start_time:) # rubocop:disable Lint/UnusedMethodArgument -- really unused. + json = convert_stats(result) + write_json(json) + end + + private + + def convert_stats(result) + { job_name => result.stats } + end + + def write_json(json) + filename = "factory_prof_#{job_name}" + filename = filename.gsub(/\W/, '_') + filename = "#{filename}.json" + + path = TestProf.artifact_path(filename) + + File.write(path, JSON.generate(json)) # rubocop:disable Gitlab/Json -- This is fine. + + log :info, "FactoryProf results written to #{path}" + end + + def job_name + ENV['CI_JOB_NAME'] || '__main__' + end + end + end +end + +TestProf::FactoryProf.configure do |config| + config.printer = Support::FactoryProfPrinter +end