Skip to content

Commit 15ad5ec

Browse files
authored
Merge pull request #434 from ruby/rmf-extract-benchmark-filter
Extract BenchmarkFilter class to its own file
2 parents bcc821a + f837290 commit 15ad5ec

File tree

5 files changed

+170
-191
lines changed

5 files changed

+170
-191
lines changed

lib/benchmark_filter.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
# Filters benchmarks based on categories and name patterns
4+
class BenchmarkFilter
5+
def initialize(categories:, name_filters:, metadata:)
6+
@categories = categories
7+
@name_filters = process_name_filters(name_filters)
8+
@metadata = metadata
9+
@category_cache = {}
10+
end
11+
12+
def match?(entry)
13+
name = entry.sub(/\.rb\z/, '')
14+
matches_category?(name) && matches_name_filter?(name)
15+
end
16+
17+
private
18+
19+
def matches_category?(name)
20+
return true if @categories.empty?
21+
22+
benchmark_categories = get_benchmark_categories(name)
23+
@categories.intersect?(benchmark_categories)
24+
end
25+
26+
def matches_name_filter?(name)
27+
return true if @name_filters.empty?
28+
29+
@name_filters.any? { |filter| filter === name }
30+
end
31+
32+
def get_benchmark_categories(name)
33+
@category_cache[name] ||= begin
34+
benchmark_metadata = @metadata[name] || {}
35+
categories = [benchmark_metadata.fetch('category', 'other')]
36+
categories << 'ractor' if benchmark_metadata['ractor']
37+
categories
38+
end
39+
end
40+
41+
# Process "/my_benchmark/i" into /my_benchmark/i
42+
def process_name_filters(name_filters)
43+
name_filters.map do |name_filter|
44+
if name_filter.start_with?("/")
45+
parse_regexp_filter(name_filter)
46+
else
47+
name_filter
48+
end
49+
end
50+
end
51+
52+
def parse_regexp_filter(filter)
53+
regexp_str = filter[1..-1].reverse.sub(/\A(\w*)\//, "")
54+
regexp_opts = ::Regexp.last_match(1).to_s
55+
regexp_str.reverse!
56+
57+
return Regexp.new(regexp_str) if regexp_opts.empty?
58+
59+
# Convert option string to Regexp option flags
60+
flags = 0
61+
flags |= Regexp::IGNORECASE if regexp_opts.include?('i')
62+
flags |= Regexp::MULTILINE if regexp_opts.include?('m')
63+
flags |= Regexp::EXTENDED if regexp_opts.include?('x')
64+
65+
Regexp.new(regexp_str, flags)
66+
end
67+
end

lib/benchmark_runner.rb

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,45 +16,6 @@ def free_file_no(directory)
1616
end
1717
end
1818

19-
# Get benchmark categories from metadata
20-
def benchmark_categories(name, metadata)
21-
benchmark_metadata = metadata[name] || {}
22-
categories = [benchmark_metadata.fetch('category', 'other')]
23-
categories << 'ractor' if benchmark_metadata['ractor']
24-
categories
25-
end
26-
27-
# Check if the name matches any of the names in a list of filters
28-
def match_filter(entry, categories:, name_filters:, metadata:)
29-
name_filters = process_name_filters(name_filters)
30-
name = entry.sub(/\.rb\z/, '')
31-
(categories.empty? || benchmark_categories(name, metadata).any? { |cat| categories.include?(cat) }) &&
32-
(name_filters.empty? || name_filters.any? { |filter| filter === name })
33-
end
34-
35-
# Process "/my_benchmark/i" into /my_benchmark/i
36-
def process_name_filters(name_filters)
37-
name_filters.map do |name_filter|
38-
if name_filter[0] == "/"
39-
regexp_str = name_filter[1..-1].reverse.sub(/\A(\w*)\//, "")
40-
regexp_opts = ::Regexp.last_match(1).to_s
41-
regexp_str.reverse!
42-
r = /#{regexp_str}/
43-
if !regexp_opts.empty?
44-
# Convert option string to Regexp option flags
45-
flags = 0
46-
flags |= Regexp::IGNORECASE if regexp_opts.include?('i')
47-
flags |= Regexp::MULTILINE if regexp_opts.include?('m')
48-
flags |= Regexp::EXTENDED if regexp_opts.include?('x')
49-
r = Regexp.new(regexp_str, flags)
50-
end
51-
r
52-
else
53-
name_filter
54-
end
55-
end
56-
end
57-
5819
# Resolve the pre_init file path into a form that can be required
5920
def expand_pre_init(path)
6021
require 'pathname'

run_benchmarks.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
require_relative 'misc/stats'
1414
require_relative 'lib/benchmark_runner'
1515
require_relative 'lib/table_formatter'
16+
require_relative 'lib/benchmark_filter'
1617

1718
# Checked system - error or return info if the command fails
1819
def check_call(command, env: {}, raise_error: true, quiet: false)
@@ -113,9 +114,14 @@ def stddev(values)
113114
Stats.new(values).stddev
114115
end
115116

116-
# Check if the name matches any of the names in a list of filters
117-
def match_filter(entry, categories:, name_filters:)
118-
BenchmarkRunner.match_filter(entry, categories: categories, name_filters: name_filters, metadata: benchmarks_metadata)
117+
def benchmark_filter(categories:, name_filters:)
118+
@benchmark_filter ||= {}
119+
key = [categories, name_filters]
120+
@benchmark_filter[key] ||= BenchmarkFilter.new(
121+
categories: categories,
122+
name_filters: name_filters,
123+
metadata: benchmarks_metadata
124+
)
119125
end
120126

121127
def benchmarks_metadata
@@ -147,16 +153,18 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat
147153
bench_file_grouping = {}
148154

149155
# Get the list of benchmark files/directories matching name filters
156+
filter = benchmark_filter(categories: categories, name_filters: name_filters)
150157
bench_file_grouping[bench_dir] = Dir.children(bench_dir).sort.filter do |entry|
151-
match_filter(entry, categories: categories, name_filters: name_filters)
158+
filter.match?(entry)
152159
end
153160

154161
if categories == ["ractor"]
155162
# We ignore the category filter here because everything in the
156163
# benchmarks-ractor directory should be included when we're benchmarking the
157164
# Ractor category
165+
ractor_filter = benchmark_filter(categories: [], name_filters: name_filters)
158166
bench_file_grouping[ractor_bench_dir] = Dir.children(ractor_bench_dir).sort.filter do |entry|
159-
match_filter(entry, categories: [], name_filters: name_filters)
167+
ractor_filter.match?(entry)
160168
end
161169
end
162170

test/benchmark_filter_test.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
require_relative 'test_helper'
2+
require_relative '../lib/benchmark_filter'
3+
require_relative '../lib/benchmark_runner'
4+
5+
describe BenchmarkFilter do
6+
before do
7+
@metadata = {
8+
'fib' => { 'category' => 'micro' },
9+
'railsbench' => { 'category' => 'headline' },
10+
'optcarrot' => { 'category' => 'headline' },
11+
'ractor_bench' => { 'category' => 'other', 'ractor' => true }
12+
}
13+
end
14+
15+
describe '#match?' do
16+
it 'matches when no filters provided' do
17+
filter = BenchmarkFilter.new(categories: [], name_filters: [], metadata: @metadata)
18+
19+
assert_equal true, filter.match?('fib.rb')
20+
end
21+
22+
it 'matches by category' do
23+
filter = BenchmarkFilter.new(categories: ['micro'], name_filters: [], metadata: @metadata)
24+
25+
assert_equal true, filter.match?('fib.rb')
26+
assert_equal false, filter.match?('railsbench.rb')
27+
end
28+
29+
it 'matches by name filter' do
30+
filter = BenchmarkFilter.new(categories: [], name_filters: ['fib'], metadata: @metadata)
31+
32+
assert_equal true, filter.match?('fib.rb')
33+
assert_equal false, filter.match?('railsbench.rb')
34+
end
35+
36+
it 'matches ractor category' do
37+
filter = BenchmarkFilter.new(categories: ['ractor'], name_filters: [], metadata: @metadata)
38+
39+
assert_equal true, filter.match?('ractor_bench.rb')
40+
end
41+
42+
it 'strips .rb extension from entry name' do
43+
filter = BenchmarkFilter.new(categories: [], name_filters: ['fib'], metadata: @metadata)
44+
45+
assert_equal true, filter.match?('fib.rb')
46+
end
47+
48+
it 'handles regex filters' do
49+
filter = BenchmarkFilter.new(categories: [], name_filters: ['/rails/'], metadata: @metadata)
50+
51+
assert_equal true, filter.match?('railsbench.rb')
52+
assert_equal false, filter.match?('fib.rb')
53+
end
54+
55+
it 'handles case-insensitive regex filters' do
56+
filter = BenchmarkFilter.new(categories: [], name_filters: ['/RAILS/i'], metadata: @metadata)
57+
58+
assert_equal true, filter.match?('railsbench.rb')
59+
end
60+
61+
it 'handles multiple categories' do
62+
filter = BenchmarkFilter.new(categories: ['micro', 'headline'], name_filters: [], metadata: @metadata)
63+
64+
assert_equal true, filter.match?('fib.rb')
65+
assert_equal true, filter.match?('railsbench.rb')
66+
end
67+
68+
it 'requires both category and name filter to match when both provided' do
69+
filter = BenchmarkFilter.new(categories: ['micro'], name_filters: ['rails'], metadata: @metadata)
70+
71+
assert_equal false, filter.match?('fib.rb') # matches category but not name
72+
assert_equal false, filter.match?('railsbench.rb') # matches name but not category
73+
end
74+
75+
it 'handles complex regex patterns' do
76+
filter = BenchmarkFilter.new(categories: [], name_filters: ['/opt.*rot/'], metadata: @metadata)
77+
78+
assert_equal true, filter.match?('optcarrot.rb')
79+
assert_equal false, filter.match?('fib.rb')
80+
end
81+
82+
it 'handles mixed string and regex filters' do
83+
filter = BenchmarkFilter.new(categories: [], name_filters: ['fib', '/rails/'], metadata: @metadata)
84+
85+
assert_equal true, filter.match?('fib.rb')
86+
assert_equal true, filter.match?('railsbench.rb')
87+
assert_equal false, filter.match?('optcarrot.rb')
88+
end
89+
end
90+
end

0 commit comments

Comments
 (0)