Skip to content

Commit 1a19b3f

Browse files
committed
Cleanup
1 parent 968aa11 commit 1a19b3f

File tree

4 files changed

+242
-153
lines changed

4 files changed

+242
-153
lines changed

bin/bench

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,79 +3,127 @@ require 'fileutils'
33
require 'benchmark'
44
require 'pathname'
55
require 'shellwords'
6+
require 'English'
67

7-
ROOT = Pathname File.expand_path(['..', '..'].join(File::Separator), __FILE__)
8-
TMP_DIR = File.join(ROOT, 'tmp/bench')
8+
class Benchmarking
9+
ROOT = Pathname File.expand_path(['..', '..'].join(File::Separator), __FILE__)
10+
TMP_DIR = File.join(ROOT, 'tmp/bench')
911

10-
def temp_dir_empty?
11-
Dir[File.join(TMP_DIR, '*')].none?
12-
end
12+
attr_reader :prepend, :append
1313

14-
def empty_temp_dir
15-
FileUtils.mkdir_p(TMP_DIR)
16-
Dir[File.join(TMP_DIR, '*')].each do |file|
17-
FileUtils.rm(file)
14+
def initialize(prepend: '', append: '')
15+
@prepend = prepend
16+
@append = append
17+
refresh_temp_dir if temp_dir_empty?
1818
end
19-
end
2019

21-
def fill_temp_dir
22-
Dir[File.join(ROOT, 'test', 'benchmark', '*.rb')].each do |file|
23-
FileUtils.cp(file, File.join(TMP_DIR, File.basename(file)))
20+
def temp_dir_empty?
21+
Dir[File.join(TMP_DIR, '*')].none?
2422
end
25-
at_exit { empty_temp_dir }
26-
end
2723

28-
def refresh_temp_dir
29-
empty_temp_dir
30-
fill_temp_dir
31-
end
24+
def empty_temp_dir
25+
FileUtils.mkdir_p(TMP_DIR)
26+
Dir[File.join(TMP_DIR, '*')].each do |file|
27+
FileUtils.rm(file)
28+
end
29+
end
3230

33-
def benchmark_tests
34-
refresh_temp_dir if temp_dir_empty?
35-
system("bundle exec ruby -Ilib:test #{Shellwords.shellescape(TMP_DIR)}/*_benchmark.rb")
36-
end
31+
def fill_temp_dir
32+
Dir[File.join(ROOT, 'test', 'benchmark', '*.rb')].each do |file|
33+
FileUtils.cp(file, File.join(TMP_DIR, File.basename(file)))
34+
end
35+
at_exit { empty_temp_dir }
36+
end
3737

38-
def current_branch
39-
@current_branch ||= `cat .git/HEAD | cut -d/ -f3,4,5`.chomp
40-
end
38+
def refresh_temp_dir
39+
empty_temp_dir
40+
fill_temp_dir
41+
end
4142

42-
def checkout_ref(ref)
43-
puts `git checkout #{ref}`.chomp
44-
abort "Checkout failed: #{ref}, #{$?.exitstatus}" unless $?.success?
45-
end
43+
def benchmark_tests
44+
tmp_dir = Shellwords.shellescape(TMP_DIR)
45+
system("#{prepend} bundle exec ruby -Ilib:test #{tmp_dir}/*_benchmark.rb #{append}")
46+
end
4647

47-
def benchmark
48-
refresh_temp_dir
49-
ref = current_branch
48+
def current_branch
49+
@current_branch ||= `cat .git/HEAD | cut -d/ -f3,4,5`.chomp
50+
end
5051

51-
actual = run_benchmark_at_ref ref
52-
master = run_benchmark_at_ref 'master'
52+
def checkout_ref(ref)
53+
puts `git checkout #{ref}`.chomp
54+
abort "Checkout failed: #{ref}, #{$CHILD_STATUS.exitstatus}" unless $CHILD_STATUS.success?
55+
end
5356

54-
checkout_ref(ref)
57+
def benchmark_refs(ref1: nil, ref2: nil)
58+
ref0 = current_branch
59+
ref1 ||= current_branch
60+
ref2 ||= 'master'
5561

56-
puts "\n\nResults ============================\n"
57-
puts "------------------------------------~> (Branch) MASTER"
58-
puts master
59-
puts "------------------------------------\n\n"
62+
actual = run_benchmark_at_ref(ref1)
63+
master = run_benchmark_at_ref(ref2)
6064

61-
puts "------------------------------------~> (Actual Branch) #{ref}"
62-
puts actual
63-
puts "------------------------------------"
64-
end
65+
checkout_ref(ref0)
6566

66-
def run_benchmark
67-
response = Benchmark.realtime {
68-
benchmark_tests
69-
}
70-
benchmark_tests
71-
response
72-
end
67+
<<-REPORT
7368
74-
def run_benchmark_at_ref(ref)
75-
checkout_ref(ref)
76-
run_benchmark
69+
70+
Results ============================
71+
------------------------------------~> (Branch) #{ref2.upcase}
72+
#{master} (seconds)
73+
------------------------------------
74+
75+
76+
77+
------------------------------------~> (Actual Branch) #{ref1.upcase}
78+
#{actual} (seconds)
79+
------------------------------------
80+
REPORT
81+
rescue Exception # rubocop:disable Lint/RescueException
82+
checkout_ref(ref0)
83+
raise
84+
end
85+
86+
def run_benchmark
87+
bundle
88+
benchmark_tests # warmup
89+
parse_measurement Benchmark.measure {
90+
benchmark_tests
91+
}
92+
end
93+
94+
def run_benchmark_at_ref(ref)
95+
checkout_ref(ref)
96+
run_benchmark
97+
end
98+
99+
def bundle
100+
system('rm -f Gemfile.lock; bundle check || bundle --local --quiet || bundle --quiet')
101+
end
102+
103+
def parse_measurement(measurement)
104+
user = measurement.utime
105+
system = measurement.stime
106+
total = measurement.total
107+
real = measurement.real
108+
{
109+
:real => real,
110+
:total => total,
111+
:user => user,
112+
:system => system
113+
}
114+
end
77115
end
78116

79-
if $0 == __FILE__
80-
benchmark
117+
if $PROGRAM_NAME == __FILE__
118+
benchmarking = Benchmarking.new(prepend: 'TIMES=1000', append: '> /dev/null')
119+
test_type = ARGV[0]
120+
case test_type
121+
when 'current'
122+
puts "Ran in #{benchmarking.run_benchmark} seconds."
123+
else
124+
# Default: Compare current_branch to master
125+
# Optionally: pass in two refs as args to `bin/bench`
126+
# TODO: Consider checking across more revisions, to automatically find problems.
127+
puts benchmarking.benchmark_refs(ref1: ARGV[0], ref2: ARGV[1])
128+
end
81129
end

test/benchmark/benchmark_helper.rb

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
1+
# https://github.com/rails-api/active_model_serializers/pull/872
2+
# approx ref 792fb8a9053f8db3c562dae4f40907a582dd1720 to test against
3+
# require 'test_helper'
14
require 'bundler/setup'
25

36
require 'rails'
4-
abort "Rails application already defined: #{Rails.application.class}" if Rails.application
7+
require 'active_model'
8+
require 'active_support'
9+
require 'active_support/json'
510
require 'action_controller'
611
require 'action_controller/test_case'
712
require 'action_controller/railtie'
8-
require 'active_support/json'
13+
abort "Rails application already defined: #{Rails.application.class}" if Rails.application
914
require 'minitest/autorun'
1015
# Ensure backward compatibility with Minitest 4
1116
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
1217

18+
# ref: https://gist.github.com/bf4/8744473
1319
class BenchmarkApp < Rails::Application
14-
if Rails.version.to_s.start_with? '4'
15-
config.action_controller.perform_caching = true
16-
config.active_support.test_order = :random
17-
ActionController::Base.cache_store = :memory_store
18-
config.eager_load = false
19-
config.secret_key_base = 'abc123'
20-
end
20+
config.action_controller.perform_caching = true
21+
ActionController::Base.cache_store = :memory_store
22+
23+
# Set up production configuration
24+
config.eager_load = true
25+
config.cache_classes = true
26+
27+
config.active_support.test_order = :random
28+
config.secret_token = '1234'
29+
config.secret_key_base = 'abc123'
30+
config.logger = Logger.new(IO::NULL)
2131
end
2232

2333
require 'active_model_serializers'
@@ -38,5 +48,11 @@ def setup
3848
end
3949
end
4050

51+
# Needs to initialize app before any serializes are defined, for sanity's sake.
52+
# Otherwise, you have to manually set perform caching.
53+
Rails.application.initialize!
54+
4155
require_relative 'fixtures'
42-
BenchmarkApp.initialize!
56+
57+
# Uncomment the below to test that cache is in use.
58+
# ActiveSupport::Cache::Store.logger = Logger.new(STDERR)

test/benchmark/fixtures.rb

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ class BlogSerializer < ActiveModel::Serializer
1212
class CommentSerializer < ActiveModel::Serializer
1313
attributes :id, :body
1414

15-
def custom_options
16-
options
17-
end
15+
belongs_to :post
16+
belongs_to :author
1817
end
1918

2019
class PostSerializer < ActiveModel::Serializer
@@ -27,67 +26,81 @@ class PostSerializer < ActiveModel::Serializer
2726
def blog
2827
Blog.new(id: 999, name: 'Custom blog')
2928
end
30-
31-
def custom_options
32-
options
33-
end
3429
end
3530

3631
class CachingAuthorSerializer < AuthorSerializer
37-
cache key: 'writer'
32+
cache key: 'writer', skip_digest: true
3833
end
3934

4035
class CachingCommentSerializer < CommentSerializer
41-
cache expires_in: 1.day
36+
cache expires_in: 1.day, skip_digest: true
4237
end
4338

4439
class CachingPostSerializer < PostSerializer
45-
cache key: 'post', expires_in: 0.1
40+
cache key: 'post', expires_in: 0.1, skip_digest: true
4641
belongs_to :blog, serializer: BlogSerializer
4742
belongs_to :author, serializer: CachingAuthorSerializer
4843
has_many :comments, serializer: CachingCommentSerializer
4944
end
5045

51-
class Model
52-
def initialize(hash = {})
53-
@attributes = hash
46+
# ActiveModelSerializers::Model is a convenient
47+
# serializable class to inherit from when making
48+
# serializable non-activerecord objects.
49+
class BenchmarkModel
50+
include ActiveModel::Model
51+
include ActiveModel::Serializers::JSON
52+
53+
attr_reader :attributes
54+
55+
def initialize(attributes = {})
56+
@attributes = attributes
57+
super
5458
end
5559

60+
# Defaults to the downcased model name.
61+
def id
62+
attributes.fetch(:id) { self.class.name.downcase }
63+
end
64+
65+
# Defaults to the downcased model name and updated_at
5666
def cache_key
57-
"#{self.class.name.downcase}/#{id}-#{updated_at}"
67+
attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime("%Y%m%d%H%M%S%9N")}" }
5868
end
5969

70+
# Defaults to the time the serializer file was modified.
6071
def updated_at
61-
@attributes[:updated_at] ||= Time.current.to_i
72+
attributes.fetch(:updated_at) { File.mtime(__FILE__) }
6273
end
6374

64-
def read_attribute_for_serialization(name)
65-
if name == :id || name == 'id'
66-
id
75+
def read_attribute_for_serialization(key)
76+
if key == :id || key == 'id'
77+
attributes.fetch(key) { id }
6778
else
68-
@attributes[name]
79+
attributes[key]
6980
end
7081
end
82+
end
7183

72-
def id
73-
@attributes[:id] || @attributes['id'] || object_id
74-
end
84+
class Comment < BenchmarkModel
85+
attr_accessor :id, :body
7586

76-
def to_param
77-
id
87+
def cache_key
88+
"#{self.class.name.downcase}/#{id}"
7889
end
90+
end
7991

80-
def method_missing(meth, *args)
81-
if meth.to_s =~ /^(.*)=$/
82-
@attributes[Regexp.last_match(1).to_sym] = args[0]
83-
elsif @attributes.key?(meth)
84-
@attributes[meth]
85-
else
86-
super
87-
end
92+
class Author < BenchmarkModel
93+
attr_accessor :id, :name, :posts
94+
end
95+
96+
class Post < BenchmarkModel
97+
attr_accessor :id, :title, :body, :comments, :blog, :author
98+
99+
def cache_key
100+
'benchmarking::post/1-20151215212620000000000'
88101
end
89102
end
90-
class Comment < Model; end
91-
class Author < Model; end
92-
class Post < Model; end
93-
class Blog < Model; end
103+
104+
class Blog < BenchmarkModel
105+
attr_accessor :id, :name
106+
end

0 commit comments

Comments
 (0)