diff --git a/lessons/220/rails-app/.gitignore b/lessons/220/rails-app/.gitignore index 5ba3862bb..8cf556f84 100644 --- a/lessons/220/rails-app/.gitignore +++ b/lessons/220/rails-app/.gitignore @@ -31,3 +31,5 @@ # Ignore master key for decrypting credentials and more. /config/master.key + +/.idea diff --git a/lessons/220/rails-app/Dockerfile b/lessons/220/rails-app/Dockerfile index 6370ff492..f3d6601bd 100644 --- a/lessons/220/rails-app/Dockerfile +++ b/lessons/220/rails-app/Dockerfile @@ -60,4 +60,4 @@ ENTRYPOINT ["/rails/bin/docker-entrypoint"] # Start the server by default, this can be overwritten at runtime EXPOSE 8080 -CMD ["./bin/rails", "server"] +CMD ["./bin/rails", "server", "-b", "0.0.0.0", "-p", "8080"] diff --git a/lessons/220/rails-app/Gemfile b/lessons/220/rails-app/Gemfile index 673845cb9..f622fb28d 100644 --- a/lessons/220/rails-app/Gemfile +++ b/lessons/220/rails-app/Gemfile @@ -1,47 +1,12 @@ source "https://rubygems.org" -gem 'pg' +gem "falcon" +gem "oj" +gem "pg" +gem "prometheus-client" +gem "rage-rb" -gem 'prometheus-client' - -# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" -gem "rails", "~> 7.2.1", ">= 7.2.1.2" -# Use sqlite3 as the database for Active Record -gem "sqlite3", ">= 1.4" -# Use the Puma web server [https://github.com/puma/puma] -gem "puma", ">= 5.0" -# Build JSON APIs with ease [https://github.com/rails/jbuilder] -# gem "jbuilder" -# Use Redis adapter to run Action Cable in production -# gem "redis", ">= 4.0.1" - -# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] -# gem "kredis" - -# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" - -# Windows does not include zoneinfo files, so bundle the tzinfo-data gem -gem "tzinfo-data", platforms: %i[ windows jruby ] +gem "rails" # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", require: false - -# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] -# gem "image_processing", "~> 1.2" - -# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible -# gem "rack-cors" - -group :development, :test do - # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem - gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" - - # Static analysis for security vulnerabilities [https://brakemanscanner.org/] - gem "brakeman", require: false - - # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] - gem "rubocop-rails-omakase", require: false -end - - diff --git a/lessons/220/rails-app/app/channels/application_cable/channel.rb b/lessons/220/rails-app/app/channels/application_cable/channel.rb deleted file mode 100644 index d67269728..000000000 --- a/lessons/220/rails-app/app/channels/application_cable/channel.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ApplicationCable - class Channel < ActionCable::Channel::Base - end -end diff --git a/lessons/220/rails-app/app/channels/application_cable/connection.rb b/lessons/220/rails-app/app/channels/application_cable/connection.rb deleted file mode 100644 index 0ff5442f4..000000000 --- a/lessons/220/rails-app/app/channels/application_cable/connection.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ApplicationCable - class Connection < ActionCable::Connection::Base - end -end diff --git a/lessons/220/rails-app/app/controllers/application_controller.rb b/lessons/220/rails-app/app/controllers/application_controller.rb index 4ac8823b0..3425a35ce 100644 --- a/lessons/220/rails-app/app/controllers/application_controller.rb +++ b/lessons/220/rails-app/app/controllers/application_controller.rb @@ -1,2 +1,2 @@ -class ApplicationController < ActionController::API +class ApplicationController < ActionController::Metal end diff --git a/lessons/220/rails-app/app/controllers/devices_controller.rb b/lessons/220/rails-app/app/controllers/devices_controller.rb index 01aa9fdf3..edc6e89ac 100644 --- a/lessons/220/rails-app/app/controllers/devices_controller.rb +++ b/lessons/220/rails-app/app/controllers/devices_controller.rb @@ -1,56 +1,34 @@ -class DevicesController < ApplicationController - before_action :set_device, only: %i[ show update destroy ] +# frozen_string_literal: true +class DevicesController < ApplicationController # GET /devices def index devices = [ - Device.new(id: 1, uuid: "9add349c-c35c-4d32-ab0f-53da1ba40a2a", mac: "5F-33-CC-1F-43-82", firmware: "2.1.6", created_at: "2024-5-28T15:21:51.137Z", updated_at: "2024-5-28T15:21:51.137Z"), - Device.new(id: 2, uuid: "d2293412-36eb-46e7-9231-af7e9249fffe", mac: "E7-34-96-33-0C-4C", firmware: "1.0.3", created_at: "2024-01-28T15:20:51.137Z", updated_at: "2024-01-28T15:20:51.137Z"), - Device.new(id: 3, uuid: "eee58ca8-ca51-47a5-ab48-163fd0e44b77", mac: "68-93-9B-B5-33-B9", firmware: "4.3.1", created_at: "2024-8-28T15:18:21.137Z", updated_at: "2024-8-28T15:18:21.137Z") + Device.new(id: 1, uuid: "9add349c-c35c-4d32-ab0f-53da1ba40a2a", mac: "5F-33-CC-1F-43-82", firmware: "2.1.6", created_at: "2024-5-28T15:21:51.137Z", updated_at: "2024-5-28T15:21:51.137Z").attributes, + Device.new(id: 2, uuid: "d2293412-36eb-46e7-9231-af7e9249fffe", mac: "E7-34-96-33-0C-4C", firmware: "1.0.3", created_at: "2024-01-28T15:20:51.137Z", updated_at: "2024-01-28T15:20:51.137Z").attributes, + Device.new(id: 3, uuid: "eee58ca8-ca51-47a5-ab48-163fd0e44b77", mac: "68-93-9B-B5-33-B9", firmware: "4.3.1", created_at: "2024-8-28T15:18:21.137Z", updated_at: "2024-8-28T15:18:21.137Z").attributes ] - - render json: devices - end - # GET /devices/1 - def show - render json: @device + build_response(devices, 200) end # POST /devices def create - @device = Device.new(device_params) - @device.uuid = SecureRandom.uuid - - if @device.save - render json: @device, status: :created, location: @device - else - render json: @device.errors, status: :unprocessable_entity - end - end + device = Device.new(params.slice(:mac, :firmware)) + device.uuid = SecureRandom.uuid - # PATCH/PUT /devices/1 - def update - if @device.update(device_params) - render json: @device + if device.save + build_response(device, 201) else - render json: @device.errors, status: :unprocessable_entity + build_response(device.errors, 422) end end - # DELETE /devices/1 - def destroy - @device.destroy! - end - private - # Use callbacks to share common setup or constraints between actions. - def set_device - @device = Device.find(params[:id]) - end - # Only allow a list of trusted parameters through. - def device_params - params.require(:device).permit(:uuid, :mac, :firmware) - end + def build_response(body, status_code) + self.content_type = "application/json" + self.status = status_code + self.response_body = body.to_json + end end diff --git a/lessons/220/rails-app/app/jobs/application_job.rb b/lessons/220/rails-app/app/jobs/application_job.rb deleted file mode 100644 index d394c3d10..000000000 --- a/lessons/220/rails-app/app/jobs/application_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -class ApplicationJob < ActiveJob::Base - # Automatically retry jobs that encountered a deadlock - # retry_on ActiveRecord::Deadlocked - - # Most jobs are safe to ignore if the underlying records are no longer available - # discard_on ActiveJob::DeserializationError -end diff --git a/lessons/220/rails-app/app/mailers/application_mailer.rb b/lessons/220/rails-app/app/mailers/application_mailer.rb deleted file mode 100644 index 3c34c8148..000000000 --- a/lessons/220/rails-app/app/mailers/application_mailer.rb +++ /dev/null @@ -1,4 +0,0 @@ -class ApplicationMailer < ActionMailer::Base - default from: "from@example.com" - layout "mailer" -end diff --git a/lessons/220/rails-app/app/models/device.rb b/lessons/220/rails-app/app/models/device.rb index c0b432e61..e9442ae21 100644 --- a/lessons/220/rails-app/app/models/device.rb +++ b/lessons/220/rails-app/app/models/device.rb @@ -1,2 +1,9 @@ class Device < ApplicationRecord + def save + result = nil + + PrometheusClient.db_histogram.observe(Benchmark.realtime { result = super }) + + result + end end diff --git a/lessons/220/rails-app/app/subscribers/active_record_prometheus_subscriber.rb b/lessons/220/rails-app/app/subscribers/active_record_prometheus_subscriber.rb deleted file mode 100644 index b7e0cced3..000000000 --- a/lessons/220/rails-app/app/subscribers/active_record_prometheus_subscriber.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'prometheus/client' - -class Prom - include Singleton - - def client - @client ||= Prometheus::Client.registry - end - - def self.histograms(*args) - instance.histograms(*args) - end - - def histograms - @histograms ||= Hash.new do |hash, key| - hash[key] = client.histogram(:myapp_request_duration_seconds, docstring: 'Duration of the request.', labels: [:op], buckets: buckets) - end - end - - private - def buckets - # Unlike a Summary, buckets must be defined based on the expected application latency to capture as many distributions as possible. - # More buckets mean more load on your monitoring system, so adapt to your app!!! - hist_buckets = [ - 0.00001, 0.000015, 0.00002, 0.000025, 0.00003, 0.000035, 0.00004, 0.000045, - 0.00005, 0.000055, 0.00006, 0.000065, 0.00007, 0.000075, 0.00008, 0.000085, - 0.00009, 0.000095, 0.0001, 0.000101, 0.000102, 0.000103, 0.000104, 0.000105, - 0.000106, 0.000107, 0.000108, 0.000109, 0.00011, 0.000111, 0.000112, 0.000113, - 0.000114, 0.000115, 0.000116, 0.000117, 0.000118, 0.000119, 0.00012, 0.000121, - 0.000122, 0.000123, 0.000124, 0.000125, 0.000126, 0.000127, 0.000128, - 0.000129, 0.00013, 0.000131, 0.000132, 0.000133, 0.000134, 0.000135, 0.000136, - 0.000137, 0.000138, 0.000139, 0.00014, 0.000141, 0.000142, 0.000143, 0.000144, - 0.000145, 0.000146, 0.000147, 0.000148, 0.000149, 0.00015, 0.000151, 0.000152, - 0.000153, 0.000154, 0.000155, 0.000156, 0.000157, 0.000158, 0.000159, 0.00016, - 0.000161, 0.000162, 0.000163, 0.000164, 0.000165, 0.000166, 0.000167, - 0.000168, 0.000169, 0.00017, 0.000171, 0.000172, 0.000173, 0.000174, 0.000175, - 0.000176, 0.000177, 0.000178, 0.000179, 0.00018, 0.000181, 0.000182, 0.000183, - 0.000184, 0.000185, 0.000186, 0.000187, 0.000188, 0.000189, 0.00019, 0.000191, - 0.000192, 0.000193, 0.000194, 0.000195, 0.000196, 0.000197, 0.000198, - 0.000199, 0.0002, 0.00021, 0.00022, 0.00023, 0.00024, 0.00025, 0.00026, - 0.00027, 0.00028, 0.00029, 0.0003, 0.00031, 0.00032, 0.00033, 0.00034, - 0.00035, 0.00036, 0.00037, 0.00038, 0.00039, 0.0004, 0.00041, 0.00042, - 0.00043, 0.00044, 0.00045, 0.00046, 0.00047, 0.00048, 0.00049, 0.0005, - 0.00051, 0.00052, 0.00053, 0.00054, 0.00055, 0.00056, 0.00057, 0.00058, - 0.00059, 0.0006, 0.00061, 0.00062, 0.00063, 0.00064, 0.00065, 0.00066, - 0.00067, 0.00068, 0.00069, 0.0007, 0.00071, 0.00072, 0.00073, 0.00074, - 0.00075, 0.00076, 0.00077, 0.00078, 0.00079, 0.0008, 0.00081, 0.00082, - 0.00083, 0.00084, 0.00085, 0.00086, 0.00087, 0.00088, 0.00089, 0.0009, - 0.00091, 0.00092, 0.00093, 0.00094, 0.00095, 0.00096, 0.00097, 0.00098, - 0.00099, 0.001, 0.0015, 0.002, 0.0025, 0.003, 0.0035, 0.004, 0.0045, 0.005, - 0.0055, 0.006, 0.0065, 0.007, 0.0075, 0.008, 0.0085, 0.009, 0.0095, 0.01, - 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06, 0.065, 0.07, - 0.075, 0.08, 0.085, 0.09, 0.095, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, - 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0, 1.5, 2.0, 2.5, - 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, - 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5 - ] - end - -end - -class ActiveRecordPrometheusSubscriber < ActiveSupport::Subscriber - attach_to :active_record - - def sql(event) - if event.payload[:name] == "Device Create" - Prom.histograms['device'].observe(event.duration, labels: { op: 'db' }) - end - end - end diff --git a/lessons/220/rails-app/app/views/layouts/mailer.html.erb b/lessons/220/rails-app/app/views/layouts/mailer.html.erb deleted file mode 100644 index 3aac9002e..000000000 --- a/lessons/220/rails-app/app/views/layouts/mailer.html.erb +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - <%= yield %> - - diff --git a/lessons/220/rails-app/app/views/layouts/mailer.text.erb b/lessons/220/rails-app/app/views/layouts/mailer.text.erb deleted file mode 100644 index 37f0bddbd..000000000 --- a/lessons/220/rails-app/app/views/layouts/mailer.text.erb +++ /dev/null @@ -1 +0,0 @@ -<%= yield %> diff --git a/lessons/220/rails-app/bin/iodine b/lessons/220/rails-app/bin/iodine new file mode 100755 index 000000000..444451211 --- /dev/null +++ b/lessons/220/rails-app/bin/iodine @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'iodine' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rage-iodine", "iodine") diff --git a/lessons/220/rails-app/config.ru b/lessons/220/rails-app/config.ru index bb17d54e3..6ff717108 100644 --- a/lessons/220/rails-app/config.ru +++ b/lessons/220/rails-app/config.ru @@ -1,14 +1,9 @@ # This file is used by Rack-based servers to start the application. require 'rack' -require 'prometheus/middleware/collector' require 'prometheus/middleware/exporter' -use Rack::Deflater -use Prometheus::Middleware::Collector use Prometheus::Middleware::Exporter -run ->(_) { [200, {'content-type' => 'text/html'}, ['OK']] } - require_relative "config/environment" run Rails.application diff --git a/lessons/220/rails-app/config/application.rb b/lessons/220/rails-app/config/application.rb index a262a0041..03594b281 100644 --- a/lessons/220/rails-app/config/application.rb +++ b/lessons/220/rails-app/config/application.rb @@ -1,6 +1,7 @@ require_relative "boot" -require "rails/all" +require "active_record/railtie" +require "action_controller/railtie" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -28,5 +29,33 @@ class Application < Rails::Application # Middleware like session, flash, cookies can be added back manually. # Skip views, helpers and assets when generating a new resource. config.api_only = true + config.secret_key_base = 'secret' + + config.middleware.delete Rack::Sendfile + config.middleware.delete ActionDispatch::Static + config.middleware.delete ActionDispatch::Executor + config.middleware.delete Rack::Runtime + config.middleware.delete ActionDispatch::RequestId + config.middleware.delete ActionDispatch::RemoteIp + config.middleware.delete Rails::Rack::Logger + config.middleware.delete ActionDispatch::ShowExceptions + config.middleware.delete ActionDispatch::DebugExceptions + config.middleware.delete ActionDispatch::Callbacks + config.middleware.delete Rack::Head + config.middleware.delete Rack::ConditionalGet + config.middleware.delete Rack::ETag + + require 'rage/fiber' + require 'rage/fiber_scheduler' + require 'rage/middleware/fiber_wrapper' + config.middleware.use Rage::FiberWrapper + + Oj.default_options = { mode: :compat } + Oj.optimize_rails + + Iodine.workers = ENV.fetch('WORKERS_NUM', 2).to_i + Iodine.threads = 1 + + Iodine.on_state(:after_fork) { RubyVM::YJIT.enable } end end diff --git a/lessons/220/rails-app/config/cable.yml b/lessons/220/rails-app/config/cable.yml deleted file mode 100644 index a75d25a12..000000000 --- a/lessons/220/rails-app/config/cable.yml +++ /dev/null @@ -1,10 +0,0 @@ -development: - adapter: async - -test: - adapter: test - -production: - adapter: redis - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> - channel_prefix: my_rails_production diff --git a/lessons/220/rails-app/config/credentials.yml.enc b/lessons/220/rails-app/config/credentials.yml.enc deleted file mode 100644 index 76ee6f006..000000000 --- a/lessons/220/rails-app/config/credentials.yml.enc +++ /dev/null @@ -1 +0,0 @@ -e2CALB2bWwUeqUwX8KgQZbaunuG6eQBw6htvcl7MG+0eud9NW5zt1Yn52o+c7xpYB3JjGRfecJO1vt6suEUAdP7mTdB0tuo/6Hwfg7veyZbU7xbvjrKvKzVkiaCti1uiL9w5co/zgUQukf6808Y0GDRRcinYlx9zuHpxjS/0wflwHWGVK8UOinfu249e0SBPR9WfgkMi91C935l4LC8m8BC5DqyHremNwaBPkJzRGGPI5KooVnXsLwdllmNpYNeNwfGv8HaQbtvVnuEm88st1UbSPtEramxM59ufJPPVmAtu5xXgl3IFKxB7+LKzfE6qP2LInpRnZRzOk2wCTavPBHOca+8g/i7LtPfAlBLR9UzXyJy5byCnQst54wY+/sCG3kk3ouBVpfnHitJs5cXumTUkX0pT--qEXMV/EqYowkdupm--pokWm+k9iVzm7qjhSmpgbw== \ No newline at end of file diff --git a/lessons/220/rails-app/config/environments/development.rb b/lessons/220/rails-app/config/environments/development.rb index 98128ff29..d2efb0e31 100644 --- a/lessons/220/rails-app/config/environments/development.rb +++ b/lessons/220/rails-app/config/environments/development.rb @@ -29,16 +29,16 @@ end # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + # config.active_storage.service = :local # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = false + # config.action_mailer.raise_delivery_errors = false # Disable caching for Action Mailer templates even if Action Controller # caching is enabled. - config.action_mailer.perform_caching = false + # config.action_mailer.perform_caching = false - config.action_mailer.default_url_options = { host: "localhost", port: 3000 } + # config.action_mailer.default_url_options = { host: "localhost", port: 3000 } # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log @@ -56,7 +56,7 @@ config.active_record.verbose_query_logs = true # Highlight code that enqueued background job in logs. - config.active_job.verbose_enqueue_logs = true + # config.active_job.verbose_enqueue_logs = true # Raises error for missing translations. # config.i18n.raise_on_missing_translations = true diff --git a/lessons/220/rails-app/config/environments/production.rb b/lessons/220/rails-app/config/environments/production.rb index e7702059a..1f24c38a6 100644 --- a/lessons/220/rails-app/config/environments/production.rb +++ b/lessons/220/rails-app/config/environments/production.rb @@ -30,7 +30,7 @@ # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + # config.active_storage.service = :local # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil @@ -69,7 +69,7 @@ # Disable caching for Action Mailer templates even if Action Controller # caching is enabled. - config.action_mailer.perform_caching = false + # config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. diff --git a/lessons/220/rails-app/config/initializers/load_notification_subscribers.rb b/lessons/220/rails-app/config/initializers/load_notification_subscribers.rb deleted file mode 100644 index 9e12b2497..000000000 --- a/lessons/220/rails-app/config/initializers/load_notification_subscribers.rb +++ /dev/null @@ -1,3 +0,0 @@ -Dir[Rails.root.join('app', 'subscribers', '**', '*_subscriber.rb')].each do |subscriber_file| - require subscriber_file - end diff --git a/lessons/220/rails-app/config/initializers/prometheus_histogram.rb b/lessons/220/rails-app/config/initializers/prometheus_histogram.rb new file mode 100644 index 000000000..06e26aa73 --- /dev/null +++ b/lessons/220/rails-app/config/initializers/prometheus_histogram.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +Rails.application.config.after_initialize do + # https://github.com/prometheus/client_ruby/pull/316 + + require 'prometheus/client/metric' + + module Prometheus + module Client + class Histogram < Metric + def observe(value, labels: {}) + bucket = buckets.bsearch { |upper_limit| upper_limit >= value } + bucket = "+Inf" if bucket.nil? + + base_label_set = label_set_for(labels) + + # This is basically faster than doing `.merge` + bucket_label_set = base_label_set.dup + bucket_label_set[:le] = bucket.to_s + sum_label_set = base_label_set.dup + sum_label_set[:le] = "sum" + + @store.synchronize do + @store.increment(labels: bucket_label_set, by: 1) + @store.increment(labels: sum_label_set, by: value) + end + end + end + end + end +end diff --git a/lessons/220/rails-app/config/locales/en.yml b/lessons/220/rails-app/config/locales/en.yml deleted file mode 100644 index 6c349ae5e..000000000 --- a/lessons/220/rails-app/config/locales/en.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Files in the config/locales directory are used for internationalization and -# are automatically loaded by Rails. If you want to use locales other than -# English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more about the API, please read the Rails Internationalization guide -# at https://guides.rubyonrails.org/i18n.html. -# -# Be aware that YAML interprets the following case-insensitive strings as -# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings -# must be quoted to be interpreted as strings. For example: -# -# en: -# "yes": yup -# enabled: "ON" - -en: - hello: "Hello world" diff --git a/lessons/220/rails-app/config/puma.rb b/lessons/220/rails-app/config/puma.rb deleted file mode 100644 index aed85f405..000000000 --- a/lessons/220/rails-app/config/puma.rb +++ /dev/null @@ -1,35 +0,0 @@ -# This configuration file will be evaluated by Puma. The top-level methods that -# are invoked here are part of Puma's configuration DSL. For more information -# about methods provided by the DSL, see https://puma.io/puma/Puma/DSL.html. - -# Puma starts a configurable number of processes (workers) and each process -# serves each request in a thread from an internal thread pool. -# -# The ideal number of threads per worker depends both on how much time the -# application spends waiting for IO operations and on how much you wish to -# to prioritize throughput over latency. -# -# As a rule of thumb, increasing the number of threads will increase how much -# traffic a given process can handle (throughput), but due to CRuby's -# Global VM Lock (GVL) it has diminishing returns and will degrade the -# response time (latency) of the application. -# -# The default is set to 3 threads as it's deemed a decent compromise between -# throughput and latency for the average Rails application. -# -# Any libraries that use a connection pool or another resource pool should -# be configured to provide at least as many connections as the number of -# threads. This includes Active Record's `pool` parameter in `database.yml`. -workers Integer(ENV['WORKERS_NUM'] || 2) -threads_count = ENV.fetch("RAILS_MAX_THREADS", 3) -threads threads_count, threads_count - -# Specifies the `port` that Puma will listen on to receive requests; default is 3000. -port ENV.fetch("PORT", 8080) - -# Allow puma to be restarted by `bin/rails restart` command. -plugin :tmp_restart - -# Specify the PID file. Defaults to tmp/pids/server.pid in development. -# In other environments, only set the PID file if requested. -pidfile ENV["PIDFILE"] if ENV["PIDFILE"] diff --git a/lessons/220/rails-app/config/routes.rb b/lessons/220/rails-app/config/routes.rb index 132c74deb..2cd7795d1 100644 --- a/lessons/220/rails-app/config/routes.rb +++ b/lessons/220/rails-app/config/routes.rb @@ -1,7 +1,6 @@ Rails.application.routes.draw do - - scope(:path => '/api') do - resources :devices + scope(path: "/api") do + resources :devices, only: %i[index create] end # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. diff --git a/lessons/220/rails-app/config/storage.yml b/lessons/220/rails-app/config/storage.yml deleted file mode 100644 index 4942ab669..000000000 --- a/lessons/220/rails-app/config/storage.yml +++ /dev/null @@ -1,34 +0,0 @@ -test: - service: Disk - root: <%= Rails.root.join("tmp/storage") %> - -local: - service: Disk - root: <%= Rails.root.join("storage") %> - -# Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) -# amazon: -# service: S3 -# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> -# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> -# region: us-east-1 -# bucket: your_own_bucket-<%= Rails.env %> - -# Remember not to checkin your GCS keyfile to a repository -# google: -# service: GCS -# project: your_project -# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> -# bucket: your_own_bucket-<%= Rails.env %> - -# Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) -# microsoft: -# service: AzureStorage -# storage_account_name: your_account_name -# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> -# container: your_container_name-<%= Rails.env %> - -# mirror: -# service: Mirror -# primary: local -# mirrors: [ amazon, google, microsoft ] diff --git a/lessons/220/rails-app/lib/prometheus_client.rb b/lessons/220/rails-app/lib/prometheus_client.rb new file mode 100644 index 000000000..4a2f84424 --- /dev/null +++ b/lessons/220/rails-app/lib/prometheus_client.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class PrometheusClient + DB_OP = { op: "db" }.freeze + BUCKETS = [ + 0.00001, 0.000015, 0.00002, 0.000025, 0.00003, 0.000035, 0.00004, 0.000045, + 0.00005, 0.000055, 0.00006, 0.000065, 0.00007, 0.000075, 0.00008, 0.000085, + 0.00009, 0.000095, 0.0001, 0.000101, 0.000102, 0.000103, 0.000104, 0.000105, + 0.000106, 0.000107, 0.000108, 0.000109, 0.00011, 0.000111, 0.000112, 0.000113, + 0.000114, 0.000115, 0.000116, 0.000117, 0.000118, 0.000119, 0.00012, 0.000121, + 0.000122, 0.000123, 0.000124, 0.000125, 0.000126, 0.000127, 0.000128, + 0.000129, 0.00013, 0.000131, 0.000132, 0.000133, 0.000134, 0.000135, 0.000136, + 0.000137, 0.000138, 0.000139, 0.00014, 0.000141, 0.000142, 0.000143, 0.000144, + 0.000145, 0.000146, 0.000147, 0.000148, 0.000149, 0.00015, 0.000151, 0.000152, + 0.000153, 0.000154, 0.000155, 0.000156, 0.000157, 0.000158, 0.000159, 0.00016, + 0.000161, 0.000162, 0.000163, 0.000164, 0.000165, 0.000166, 0.000167, + 0.000168, 0.000169, 0.00017, 0.000171, 0.000172, 0.000173, 0.000174, 0.000175, + 0.000176, 0.000177, 0.000178, 0.000179, 0.00018, 0.000181, 0.000182, 0.000183, + 0.000184, 0.000185, 0.000186, 0.000187, 0.000188, 0.000189, 0.00019, 0.000191, + 0.000192, 0.000193, 0.000194, 0.000195, 0.000196, 0.000197, 0.000198, + 0.000199, 0.0002, 0.00021, 0.00022, 0.00023, 0.00024, 0.00025, 0.00026, + 0.00027, 0.00028, 0.00029, 0.0003, 0.00031, 0.00032, 0.00033, 0.00034, + 0.00035, 0.00036, 0.00037, 0.00038, 0.00039, 0.0004, 0.00041, 0.00042, + 0.00043, 0.00044, 0.00045, 0.00046, 0.00047, 0.00048, 0.00049, 0.0005, + 0.00051, 0.00052, 0.00053, 0.00054, 0.00055, 0.00056, 0.00057, 0.00058, + 0.00059, 0.0006, 0.00061, 0.00062, 0.00063, 0.00064, 0.00065, 0.00066, + 0.00067, 0.00068, 0.00069, 0.0007, 0.00071, 0.00072, 0.00073, 0.00074, + 0.00075, 0.00076, 0.00077, 0.00078, 0.00079, 0.0008, 0.00081, 0.00082, + 0.00083, 0.00084, 0.00085, 0.00086, 0.00087, 0.00088, 0.00089, 0.0009, + 0.00091, 0.00092, 0.00093, 0.00094, 0.00095, 0.00096, 0.00097, 0.00098, + 0.00099, 0.001, 0.0015, 0.002, 0.0025, 0.003, 0.0035, 0.004, 0.0045, 0.005, + 0.0055, 0.006, 0.0065, 0.007, 0.0075, 0.008, 0.0085, 0.009, 0.0095, 0.01, + 0.015, 0.02, 0.025, 0.03, 0.035, 0.04, 0.045, 0.05, 0.055, 0.06, 0.065, 0.07, + 0.075, 0.08, 0.085, 0.09, 0.095, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, + 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0, 1.5, 2.0, 2.5, + 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5, 16.0, 16.5, 17.0, 17.5 + ].freeze + + class << self + def client + @client ||= Prometheus::Client.registry + end + + def create_device_histogram + @create_device_histogram ||= client.histogram( + :myapp_request_duration_seconds, docstring: "Duration of the request.", labels: [ :op ], buckets: BUCKETS + ) + end + + def db_histogram + @db_histogram ||= create_device_histogram.with_labels(DB_OP) + end + end +end diff --git a/lessons/220/rails-app/lib/tasks/migrate_with_sql.rake b/lessons/220/rails-app/lib/tasks/migrate_with_sql.rake deleted file mode 100644 index 2bdd44cf6..000000000 --- a/lessons/220/rails-app/lib/tasks/migrate_with_sql.rake +++ /dev/null @@ -1,7 +0,0 @@ -namespace :db do - desc 'Make migration with output' - task(:migrate_with_sql => :environment) do - ActiveRecord::Base.logger = Logger.new(STDOUT) - Rake::Task['db:migrate'].invoke - end - end \ No newline at end of file diff --git a/lessons/220/ruby-app/Gemfile b/lessons/220/ruby-app/Gemfile index 251297ce2..eefee89d8 100644 --- a/lessons/220/ruby-app/Gemfile +++ b/lessons/220/ruby-app/Gemfile @@ -10,4 +10,5 @@ gem 'standalone_migrations', require: false gem 'oj' gem 'prometheus-client' gem 'rack' -gem 'rage-iodine' +gem 'rage-rb' +gem 'falcon' diff --git a/lessons/220/ruby-app/README.md b/lessons/220/ruby-app/README.md index cfe57e741..b3e238ae4 100644 --- a/lessons/220/ruby-app/README.md +++ b/lessons/220/ruby-app/README.md @@ -16,15 +16,13 @@ bin/iodine -p 8080 Defaults ```bash WORKERS_NUM=2 -RAILS_MAX_THREADS=2 - ``` ### Local benchmark #### `rails-app` ```bash -WORKERS_NUM=2 RAILS_MAX_THREADS=2 RAILS_ENV=production rs +WORKERS_NUM=2 RAILS_ENV=production rs wrk -c 10 -t 2 -d 10 --latency http://localhost:8080/api/devices Running 10s test @ http://localhost:8080/api/devices 2 threads and 10 connections @@ -43,7 +41,7 @@ Transfer/sec: 4.56MB #### `ruby-app` ```bash -WORKERS_NUM=2 RAILS_MAX_THREADS=2 bin/iodine -p 8080 config.ru +WORKERS_NUM=2 bin/iodine -p 8080 config.ru wrk -c 10 -t 2 -d 10 --latency http://localhost:8080/api/devices Running 10s test @ http://localhost:8080/api/devices 2 threads and 10 connections diff --git a/lessons/220/ruby-app/config.ru b/lessons/220/ruby-app/config.ru index 730bd7ec1..1961278b7 100644 --- a/lessons/220/ruby-app/config.ru +++ b/lessons/220/ruby-app/config.ru @@ -1,26 +1,31 @@ # frozen_string_literal: true require 'connection_pool' -require 'iodine' require 'oj' require 'pg' require 'prometheus/middleware/exporter' require 'rack' +require 'rage' +require 'rage/fiber' +require 'rage/fiber_scheduler' +require 'rage/middleware/fiber_wrapper' require 'yaml' require_relative 'src/app' RubyVM::YJIT.enable -Iodine.threads = ENV.fetch('RAILS_MAX_THREADS', 2).to_i +Iodine.threads = 1 Iodine.workers = ENV.fetch('WORKERS_NUM', 2).to_i + Oj.default_options = { mode: :compat } pg_conf = YAML.load(File.read('db/config.yml'), aliases: true)['production'] -PG_POOL = ConnectionPool.new(size: Iodine.threads * Iodine.workers, timeout: 5) do +PG_POOL = ConnectionPool.new(size: pg_conf['pool'], timeout: 5) do PG.connect(pg_conf['host'], pg_conf['port'], nil, nil, pg_conf['database'], pg_conf['username'], pg_conf['password']) end # Enable if http metrics are required # use Prometheus::Middleware::Collector +use Rage::FiberWrapper use Prometheus::Middleware::Exporter run App.new diff --git a/lessons/220/ruby-app/db/config.yml b/lessons/220/ruby-app/db/config.yml index 01535dbc2..d6a8c84bb 100644 --- a/lessons/220/ruby-app/db/config.yml +++ b/lessons/220/ruby-app/db/config.yml @@ -1,10 +1,13 @@ --- default: &default adapter: postgresql - host: localhost - database: putratest - username: appodeal - password: pass + encoding: unicode + timeout: 5000 + host: postgresql.antonputra.pvt + database: mydb + pool: 50 + username: rails + password: devops123 development: <<: *default diff --git a/lessons/220/ruby-app/src/prometheus_client.rb b/lessons/220/ruby-app/src/prometheus_client.rb index e925a6b08..05cdb26f9 100644 --- a/lessons/220/ruby-app/src/prometheus_client.rb +++ b/lessons/220/ruby-app/src/prometheus_client.rb @@ -2,6 +2,33 @@ require 'prometheus/client' +# https://github.com/prometheus/client_ruby/pull/316 +require 'prometheus/client/metric' + +module Prometheus + module Client + class Histogram < Metric + def observe(value, labels: {}) + bucket = buckets.bsearch { |upper_limit| upper_limit >= value } + bucket = "+Inf" if bucket.nil? + + base_label_set = label_set_for(labels) + + # This is basically faster than doing `.merge` + bucket_label_set = base_label_set.dup + bucket_label_set[:le] = bucket.to_s + sum_label_set = base_label_set.dup + sum_label_set[:le] = "sum" + + @store.synchronize do + @store.increment(labels: bucket_label_set, by: 1) + @store.increment(labels: sum_label_set, by: value) + end + end + end + end +end + class PrometheusClient BUCKETS = [ 0.00001, 0.000015, 0.00002, 0.000025, 0.00003, 0.000035, 0.00004, 0.000045,