Module: StableProfile

Defined in:
lib/stable_profile.rb,
lib/stable_profile/version.rb

Defined Under Namespace

Classes: Error, String

Constant Summary collapse

TOP_SLOWEST_EXAMPLES =

How many items to output in each category.

5
ITERATIONS =

The more iterations you run, the more accurate the results will be. 20 seems like plenty, but it could take a while depending on the size of your test suite.

4
MINIMUM_SAMPLE_SIZE =

It’s a slow one if it showed up in at least half the profile runs.

ITERATIONS / 2
DECIMAL_PLACES =
4
OUTPUT_DIR =
'tmp/multi_profile'
VERSION =
"0.1.0"

Class Method Summary collapse

Class Method Details

.runObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/stable_profile.rb', line 39

def run
  # Erase and Create the output directory
  FileUtils.rm_rf(OUTPUT_DIR)
  FileUtils.mkdir_p(OUTPUT_DIR)

  # Run the specs ITERATIONS times, each time with a different random seed
  progressbar = ProgressBar.create(title: 'Running profiles', total: ITERATIONS, format: '%t: |%B| %p%% %a')
  ITERATIONS.times do |i|
    system("rspec --profile 50 --order random --format json > #{OUTPUT_DIR}/multi_profile_#{i+1}.json")
    progressbar.increment
  end

  # Read the results from the JSON files into an array.
  outputs = Dir.glob("#{OUTPUT_DIR}/*.json").map { |file| JSON.parse(File.read(file)) }

  # Extract the JSON profile structures from the full results.
  profile_blocks = outputs.map { |output| output.fetch('profile') }
  examples       = profile_blocks.map { |profile| profile.fetch('examples') }.flatten
  groups         = profile_blocks.map { |profile| profile.fetch('groups') }.flatten

  # Create a hash, keyed by "id", with the times spent in each example.
  example_times = examples.each_with_object({}) do |example, hash|
    hash[example.fetch('id')] ||= {run_times: [], average_time: 0, example: example}
    hash[example.fetch('id')][:run_times] << example.fetch('run_time')
  end

  # Create a hash, keyed by "location", with the times spent in each group.
  group_times = groups.each_with_object({}) do |group, hash|
    hash[group.fetch('location')] ||= {run_times: [], average_time: 0, group: group}
    hash[group.fetch('location')][:run_times] << group.fetch('total_time')
  end

  # Calculate the average time spent in each example.
  example_times.each do |id, record|
    example_times[id][:average_time] = (record[:run_times].inject(:+) / record[:run_times].size).round(DECIMAL_PLACES)
  end

  # Calculate the average time spent in each group.
  group_times.each do |id, record|
    group_times[id][:average_time] = (record[:run_times].inject(:+) / record[:run_times].size).round(DECIMAL_PLACES)
  end

  # Mimic RSpec profile output
  puts
  puts "Top #{TOP_SLOWEST_EXAMPLES} slowest examples:"
  count = 0
  example_times.sort_by { |id, record| record[:average_time] }.reverse.each do |id, record|
    next if count == TOP_SLOWEST_EXAMPLES
    next if record[:run_times].size < MINIMUM_SAMPLE_SIZE
    count += 1

    example = record.fetch(:example)

    puts "  #{example['full_description']}"
    puts "    #{record[:average_time]} seconds".bold.ljust(27) + " (N=#{record[:run_times].size})".ljust(7) + " #{example['file_path']}:#{example['line_number']}"
  end

  puts
  puts "Top #{TOP_SLOWEST_EXAMPLES} slowest example groups:"
  count = 0
  group_times.sort_by { |id, record| record[:average_time] }.reverse.each do |id, record|
    next if count == TOP_SLOWEST_EXAMPLES
    next if record[:run_times].size < MINIMUM_SAMPLE_SIZE
    count += 1

    group = record.fetch(:group)

    puts "  #{group['description']}"
    puts "    #{record[:average_time]} seconds".bold.ljust(27) + " (N=#{record[:run_times].size})".ljust(7) + " #{group['location']}"
  end
end