From 901fe6897c41db550abbe8014ae938f459680116 Mon Sep 17 00:00:00 2001 From: Petrik Date: Mon, 6 May 2024 11:47:09 +0200 Subject: [PATCH] [ruby/padrino] Upgrade to Ruby 3.4 Also use ActiveRecord instead of DataMapper, as that is no longer maintained. +-------------------+-----+-----+-----+------+-------+---------+--------------+ | branch_name| json| db|query|update|fortune|plaintext|weighted_score| +-------------------+-----+-----+-----+------+-------+---------+--------------+ | master|16348| 4309| 6315| 3785| 4251| 12539| 439| |padrino/ruby-3.4-ar|35491|12931|12760| 7322| 10133| 17696| 880| +-------------------+-----+-----+-----+------+-------+---------+--------------+ --- frameworks/Ruby/padrino/Gemfile | 22 +++--- frameworks/Ruby/padrino/app/app.rb | 23 +++--- frameworks/Ruby/padrino/app/controllers.rb | 71 ++++++++++--------- frameworks/Ruby/padrino/app/helpers.rb | 15 +++- frameworks/Ruby/padrino/benchmark_config.json | 6 +- frameworks/Ruby/padrino/config/apps.rb | 4 +- frameworks/Ruby/padrino/config/boot.rb | 1 - frameworks/Ruby/padrino/config/database.rb | 45 ++++++++---- frameworks/Ruby/padrino/models/fortune.rb | 10 +-- frameworks/Ruby/padrino/models/world.rb | 19 +++-- .../Ruby/padrino/padrino-unicorn.dockerfile | 5 +- frameworks/Ruby/padrino/padrino.dockerfile | 7 +- 12 files changed, 136 insertions(+), 92 deletions(-) diff --git a/frameworks/Ruby/padrino/Gemfile b/frameworks/Ruby/padrino/Gemfile index 792046310c6..217e0b3649a 100644 --- a/frameworks/Ruby/padrino/Gemfile +++ b/frameworks/Ruby/padrino/Gemfile @@ -1,11 +1,17 @@ source 'https://rubygems.org' -gem 'mysql2', '~> 0.4' -gem "unicorn", '~> 6.1' -gem 'puma', '~> 6.4' -gem 'json', '~> 2.0' +gem 'mysql2', '> 0.5' +gem 'json' +gem 'activerecord', '>= 7.1', :require => 'active_record' + gem 'slim', '2.0.3' -gem 'dm-mysql-adapter', '1.2.0' -gem 'dm-core', '1.2.1' -gem 'padrino', '0.15.3' -gem 'rack', '~> 2.2' +gem 'padrino', git: 'https://github.com/padrino/padrino-framework.git' +gem 'rack' + +group :puma, optional: true do + gem 'puma', '~> 6.4', require: false +end + +group :unicorn, optional: true do + gem 'unicorn', '~> 6.1', platforms: [:ruby, :mswin], require: false +end diff --git a/frameworks/Ruby/padrino/app/app.rb b/frameworks/Ruby/padrino/app/app.rb index 1b991b42dca..8641baa8c39 100644 --- a/frameworks/Ruby/padrino/app/app.rb +++ b/frameworks/Ruby/padrino/app/app.rb @@ -14,11 +14,16 @@ class App < Padrino::Application # # You can customize caching store engines: # - # set :cache, Padrino::Cache::Store::Memcache.new(::Memcached.new('127.0.0.1:11211', :exception_retry_limit => 1)) - # set :cache, Padrino::Cache::Store::Memcache.new(::Dalli::Client.new('127.0.0.1:11211', :exception_retry_limit => 1)) - # set :cache, Padrino::Cache::Store::Redis.new(::Redis.new(:host => '127.0.0.1', :port => 6379, :db => 0)) - # set :cache, Padrino::Cache::Store::Memory.new(50) - # set :cache, Padrino::Cache::Store::File.new(Padrino.root('tmp', app_name.to_s, 'cache')) # default choice + # set :cache, Padrino::Cache.new(:LRUHash) # Keeps cached values in memory + # set :cache, Padrino::Cache.new(:Memcached) # Uses default server at localhost + # set :cache, Padrino::Cache.new(:Memcached, :server => '127.0.0.1:11211', :exception_retry_limit => 1) + # set :cache, Padrino::Cache.new(:Memcached, :backend => memcached_or_dalli_instance) + # set :cache, Padrino::Cache.new(:Redis) # Uses default server at localhost + # set :cache, Padrino::Cache.new(:Redis, :host => '127.0.0.1', :port => 6379, :db => 0) + # set :cache, Padrino::Cache.new(:Redis, :backend => redis_instance) + # set :cache, Padrino::Cache.new(:Mongo) # Uses default server at localhost + # set :cache, Padrino::Cache.new(:Mongo, :backend => mongo_client_instance) + # set :cache, Padrino::Cache.new(:File, :dir => Padrino.root('tmp', app_name.to_s, 'cache')) # default choice # ## @@ -32,8 +37,8 @@ class App < Padrino::Application # set :reload, false # Reload application files (default in development) # set :default_builder, 'foo' # Set a custom form builder (default 'StandardFormBuilder') # set :locale_path, 'bar' # Set path for I18n translations (default your_apps_root_path/locale) - # disable :sessions # Disabled sessions by default (enable if needed) - # disable :flash # Disables sinatra-flash (enabled by default if Sinatra::Flash is defined) + disable :sessions # Disabled sessions by default (enable if needed) + disable :flash # Disables sinatra-flash (enabled by default if Sinatra::Flash is defined) # layout :my_layout # Layout can be in views/layouts/foo.ext or views/foo.ext (default :application) # @@ -53,8 +58,8 @@ class App < Padrino::Application # render 'errors/404' # end # - # error 505 do - # render 'errors/505' + # error 500 do + # render 'errors/500' # end # end diff --git a/frameworks/Ruby/padrino/app/controllers.rb b/frameworks/Ruby/padrino/app/controllers.rb index 0d899ce97a9..c2a3291d797 100644 --- a/frameworks/Ruby/padrino/app/controllers.rb +++ b/frameworks/Ruby/padrino/app/controllers.rb @@ -1,62 +1,65 @@ -MAX_PK = 10_000 -QUERIES_MIN = 1 -QUERIES_MAX = 500 +QUERY_RANGE = (1..10_000).freeze +ALL_IDS = QUERY_RANGE.to_a HelloWorld::App.controllers do + + after do + response['Server'] = 'padrino' + end + + after do + response['Date'] = Time.now.httpdate + end if defined?(Puma) + get '/json', :provides => [:json] do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate {message: "Hello, World!"}.to_json end get '/db', :provides => [:json] do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate - id = Random.rand(MAX_PK) + 1 - World.get(id).attributes.to_json + world = ActiveRecord::Base.with_connection do + World.find(rand1).attributes + end + world.to_json end get '/queries', :provides => [:json] do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate - queries = params['queries'].to_i.clamp(QUERIES_MIN, QUERIES_MAX) - - results = (1..queries).map do - World.get(Random.rand(MAX_PK) + 1).attributes - end.to_json + worlds = ActiveRecord::Base.with_connection do + ALL_IDS.sample(bounded_queries).map do |id| + World.find(id).attributes + end + end + worlds.to_json end get '/fortunes' do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate - @fortunes = Fortune.all - @fortunes << Fortune.new(:id => 0, :message => "Additional fortune added at request time.") - @fortunes = @fortunes.sort_by { |x| x.message } + @fortunes = Fortune.all.to_a + @fortunes << Fortune.new( + id: 0, + message: "Additional fortune added at request time." + ) + @fortunes = @fortunes.sort_by(&:message) render 'fortunes', layout: "layout" end get '/updates', :provides => [:json] do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate - queries = params['queries'].to_i.clamp(QUERIES_MIN, QUERIES_MAX) - - worlds = (1..queries).map do - # get a random row from the database, which we know has 10000 - # rows with ids 1 - 10000 - world = World.get(Random.rand(MAX_PK) + 1) - world.update(randomNumber: Random.rand(MAX_PK) + 1) - world.attributes + worlds = [] + ActiveRecord::Base.with_connection do + worlds = ALL_IDS.sample(bounded_queries).map do |id| + world = World.find(id) + new_value = rand1 + new_value = rand1 while new_value == world.randomNumber + world.randomNumber = new_value + world + end + World.upsert_all(worlds) end worlds.to_json end get '/plaintext' do - response.headers['Server'] = 'padrino' - response.headers['Date'] = Time.now.httpdate content_type 'text/plain' "Hello, World!" end - end diff --git a/frameworks/Ruby/padrino/app/helpers.rb b/frameworks/Ruby/padrino/app/helpers.rb index 16774801dff..523fb246f77 100644 --- a/frameworks/Ruby/padrino/app/helpers.rb +++ b/frameworks/Ruby/padrino/app/helpers.rb @@ -1,7 +1,16 @@ # Helper methods defined here can be accessed in any controller or view in the application +MAX_PK = 10_000 +QUERIES_MIN = 1 +QUERIES_MAX = 500 + HelloWorld::App.helpers do - # def simple_helper_method - # ... - # end + def rand1 + rand(MAX_PK) + 1 + end + + def bounded_queries + queries = params[:queries].to_i + queries.clamp(QUERIES_MIN, QUERIES_MAX) + end end diff --git a/frameworks/Ruby/padrino/benchmark_config.json b/frameworks/Ruby/padrino/benchmark_config.json index db352531224..2fa32a70b47 100644 --- a/frameworks/Ruby/padrino/benchmark_config.json +++ b/frameworks/Ruby/padrino/benchmark_config.json @@ -25,10 +25,10 @@ }, "unicorn": { "json_url": "/json", - "db_url": "/db", - "query_url": "/queries?queries=", + "db_url": "/db", + "query_url": "/queries?queries=", "fortune_url": "/fortunes", - "update_url": "/updates?queries=", + "update_url": "/updates?queries=", "plaintext_url": "/plaintext", "port": 8080, "approach": "Realistic", diff --git a/frameworks/Ruby/padrino/config/apps.rb b/frameworks/Ruby/padrino/config/apps.rb index 4b36a36b656..2956874c751 100644 --- a/frameworks/Ruby/padrino/config/apps.rb +++ b/frameworks/Ruby/padrino/config/apps.rb @@ -28,8 +28,8 @@ Padrino.configure_apps do # enable :sessions set :session_secret, 'b941cbcb2d647360c0d1fb3c54a7039ed4f71cc0b7785d2aac689cc37d7757b7' - set :protection, true - set :protect_from_csrf, true + set :protection, false + set :protect_from_csrf, false end # Mounts the core application for this project diff --git a/frameworks/Ruby/padrino/config/boot.rb b/frameworks/Ruby/padrino/config/boot.rb index 0706fc35e87..6e01f2ed030 100644 --- a/frameworks/Ruby/padrino/config/boot.rb +++ b/frameworks/Ruby/padrino/config/boot.rb @@ -39,7 +39,6 @@ # Add your after (RE)load hooks here # Padrino.after_load do - DataMapper.finalize end Padrino.load! diff --git a/frameworks/Ruby/padrino/config/database.rb b/frameworks/Ruby/padrino/config/database.rb index b87a47f1d19..494a834d052 100644 --- a/frameworks/Ruby/padrino/config/database.rb +++ b/frameworks/Ruby/padrino/config/database.rb @@ -1,17 +1,32 @@ -## -# A MySQL connection: -# DataMapper.setup(:default, 'mysql://user:password@localhost/the_database_name') -# -# # A Postgres connection: -# DataMapper.setup(:default, 'postgres://user:password@localhost/the_database_name') -# -# # A Sqlite3 connection -# DataMapper.setup(:default, "sqlite3://" + Padrino.root('db', "development.db")) -# +Bundler.require('mysql') +opts = { + adapter: 'mysql2', + username: 'benchmarkdbuser', + password: 'benchmarkdbpass', + host: 'tfb-database', + database: 'hello_world' +} -DataMapper.logger = logger -DataMapper::Property::String.length(255) - -case Padrino.env - when :production then DataMapper.setup(:default, "mysql://benchmarkdbuser:benchmarkdbpass@tfb-database/hello_world") +# Determine threading/thread pool size and timeout +if defined?(Puma) && (threads = Puma.cli_config.options.fetch(:max_threads)) > 1 + opts[:pool] = (2 * Math.log(threads)).floor + opts[:checkout_timeout] = 10 +else + # TODO: ActiveRecord doesn't have a single-threaded mode? + opts[:pool] = 1 + opts[:checkout_timeout] = 0 end + + +# Setup our logger +ActiveRecord::Base.logger = logger + +# Use ISO 8601 format for JSON serialized times and dates. +ActiveSupport.use_standard_json_time_format = true + +# Don't escape HTML entities in JSON, leave that for the #json_escape helper +# if you're including raw JSON in an HTML page. +ActiveSupport.escape_html_entities_in_json = false + +# Now we can establish connection with our db. +ActiveRecord::Base.establish_connection(opts) diff --git a/frameworks/Ruby/padrino/models/fortune.rb b/frameworks/Ruby/padrino/models/fortune.rb index 98e91d0f9c5..61151fa4848 100644 --- a/frameworks/Ruby/padrino/models/fortune.rb +++ b/frameworks/Ruby/padrino/models/fortune.rb @@ -1,9 +1,3 @@ -class Fortune - include DataMapper::Resource - - storage_names[:default] = 'Fortune' - - # property , - property :id, Serial - property :message, String +class Fortune < ActiveRecord::Base + self.table_name = name end diff --git a/frameworks/Ruby/padrino/models/world.rb b/frameworks/Ruby/padrino/models/world.rb index 9d093ade56d..9e7b8d16fb7 100644 --- a/frameworks/Ruby/padrino/models/world.rb +++ b/frameworks/Ruby/padrino/models/world.rb @@ -1,9 +1,16 @@ -class World - include DataMapper::Resource +class World < ActiveRecord::Base + self.table_name = name - storage_names[:default] = 'World' + alias_attribute(:randomNumber, :randomnumber) \ + if connection.adapter_name.downcase.start_with?('postgres') - # property , - property :id, Serial - property :randomNumber, Integer, field: 'randomNumber' + if connection.adapter_name.downcase.start_with?('mysql') + def self.upsert_all(attributes, on_duplicate: :update, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil) + # On MySQL Batch updates verification isn't supported yet by TechEmpower. + # https://github.com/TechEmpower/FrameworkBenchmarks/issues/5983 + attributes.each do |attrs| + where(id: attrs[:id]).update_all(randomNumber: attrs[:randomNumber]) + end + end + end end diff --git a/frameworks/Ruby/padrino/padrino-unicorn.dockerfile b/frameworks/Ruby/padrino/padrino-unicorn.dockerfile index 77e083211e9..95a44be3678 100644 --- a/frameworks/Ruby/padrino/padrino-unicorn.dockerfile +++ b/frameworks/Ruby/padrino/padrino-unicorn.dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.0 +FROM ruby:3.4 WORKDIR /padrino COPY app app @@ -9,11 +9,14 @@ COPY config.ru config.ru COPY Gemfile Gemfile COPY Rakefile Rakefile +RUN bundle config set with 'unicorn' RUN bundle install --jobs=4 --gemfile=/padrino/Gemfile RUN apt-get update -yqq && apt-get install -yqq nginx EXPOSE 8080 +ENV RUBY_YJIT_ENABLE=1 + CMD nginx -c /padrino/config/nginx.conf && \ bundle exec unicorn -E production -c config/unicorn.rb diff --git a/frameworks/Ruby/padrino/padrino.dockerfile b/frameworks/Ruby/padrino/padrino.dockerfile index 78fd7774dea..b35254eb3e7 100644 --- a/frameworks/Ruby/padrino/padrino.dockerfile +++ b/frameworks/Ruby/padrino/padrino.dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.0 +FROM ruby:3.4 WORKDIR /padrino COPY app app @@ -9,8 +9,11 @@ COPY config.ru config.ru COPY Gemfile Gemfile COPY Rakefile Rakefile +RUN bundle config set with 'puma' RUN bundle install --jobs=4 --gemfile=/padrino/Gemfile EXPOSE 8080 -CMD ["bundle", "exec", "puma", "-C", "config/puma.rb", "-w", "8", "--preload"] +ENV RUBY_YJIT_ENABLE=1 + +CMD bundle exec puma -C config/puma.rb -w 8 --preload