From 3a5304f14af2bdc160049fc119db8c45e1161961 Mon Sep 17 00:00:00 2001 From: Beglov Sergey Date: Wed, 29 May 2024 12:49:43 +0300 Subject: [PATCH 1/2] Optimization StoriesController#index --- .dev_to/compose.yml | 1 + Gemfile | 3 +- Gemfile.lock | 19 +-- app/controllers/application_controller.rb | 12 ++ app/views/stories/_main_stories_feed.html.erb | 4 +- config/application.rb | 3 + config/environments/local_production.rb | 131 ++++++++++++++++++ config/initializers/airbrake.rb | 2 +- config/secrets.yml | 3 + config/webpacker.yml | 9 ++ 10 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 config/environments/local_production.rb diff --git a/.dev_to/compose.yml b/.dev_to/compose.yml index ec0af700..dfb76e9c 100644 --- a/.dev_to/compose.yml +++ b/.dev_to/compose.yml @@ -45,6 +45,7 @@ x-backend: &backend PSQL_HISTFILE: /usr/local/hist/.psql_history IRB_HISTFILE: /usr/local/hist/.irb_history EDITOR: vi + SKYLIGHT_AUTHENTICATION: $SKYLIGHT_AUTHENTICATION depends_on: &backend_depends_on postgres: condition: service_healthy diff --git a/Gemfile b/Gemfile index d55a1cad..7d7eb416 100644 --- a/Gemfile +++ b/Gemfile @@ -89,7 +89,7 @@ gem "sdoc", "~> 1.0", group: :doc gem "serviceworker-rails", "~> 0.5" gem "share_meow_client", "~> 0.1" gem "sitemap_generator", "~> 6.0" -gem "skylight", "~> 3.1" +gem "skylight" gem "slack-notifier", "~> 2.3" gem "sprockets", "~> 3.7" gem "staccato", "~> 0.5" @@ -102,6 +102,7 @@ gem "uglifier", "~> 4.1" gem "validate_url", "~> 1.0" gem "webpacker", "~> 3.5" gem "webpush", "~> 0.3" +gem "rack-mini-profiler" group :development do gem "better_errors", "~> 2.5" diff --git a/Gemfile.lock b/Gemfile.lock index 84c23147..ac3243e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -197,7 +197,7 @@ GEM coffee-script-source execjs coffee-script-source (1.12.2) - concurrent-ruby (1.1.5) + concurrent-ruby (1.2.3) connection_pool (2.2.2) counter_culture (2.1.2) activerecord (>= 3.0.0) @@ -523,7 +523,7 @@ GEM mime-types (~> 3.0) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.6.0) + i18n (1.14.5) concurrent-ruby (~> 1.0) ice_nine (0.11.2) inflecto (0.0.2) @@ -585,7 +585,7 @@ GEM mime-types-data (3.2018.0812) mini_mime (1.0.1) mini_portile2 (2.4.0) - minitest (5.11.3) + minitest (5.23.1) momentjs-rails (2.20.1) railties (>= 3.1) msgpack (1.2.4) @@ -664,6 +664,8 @@ GEM rack (2.0.6) rack-host-redirect (1.3.0) rack + rack-mini-profiler (3.1.1) + rack (>= 1.2.0) rack-protection (2.0.4) rack rack-proxy (0.6.5) @@ -830,9 +832,9 @@ GEM tilt (~> 2.0) sitemap_generator (6.0.2) builder (~> 3.0) - skylight (3.1.4) - skylight-core (= 3.1.4) - skylight-core (3.1.4) + skylight (4.3.2) + skylight-core (= 4.3.2) + skylight-core (4.3.2) activesupport (>= 4.2.0) slack-notifier (2.3.2) smart_properties (1.13.1) @@ -878,7 +880,7 @@ GEM multipart-post (~> 2.0) naught (~> 1.0) simple_oauth (~> 0.3.0) - tzinfo (1.2.5) + tzinfo (1.2.11) thread_safe (~> 0.1) uber (0.1.0) uglifier (4.1.20) @@ -1012,6 +1014,7 @@ DEPENDENCIES pusher (~> 1.3) pusher-push-notifications (~> 1.0) rack-host-redirect (~> 1.3) + rack-mini-profiler rack-timeout (~> 0.5) rails (~> 5.1.6) rails-assets-airbrake-js-client (~> 1.5)! @@ -1040,7 +1043,7 @@ DEPENDENCIES simplecov (~> 0.16) sinatra (~> 2.0) sitemap_generator (~> 6.0) - skylight (~> 3.1) + skylight slack-notifier (~> 2.3) sprockets (~> 3.7) staccato (~> 0.5) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1531dc97..b5a9c8a1 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,6 +4,8 @@ class ApplicationController < ActionController::Base include Pundit include Instrumentation + before_action :authorize_rmp, if: :authorize_rmp? + def require_http_auth authenticate_or_request_with_http_basic do |username, password| username == ApplicationConfig["APP_NAME"] && password == ApplicationConfig["APP_PASSWORD"] @@ -78,4 +80,14 @@ def append_info_to_payload(payload) super(payload) append_to_honeycomb(request, self.class.name) end + + def authorize_rmp + Rack::MiniProfiler.authorize_request + end + + def authorize_rmp? + # return true unless Rails.env.production? + + params[:rmp_token] == 'secret' + end end diff --git a/app/views/stories/_main_stories_feed.html.erb b/app/views/stories/_main_stories_feed.html.erb index b6cd0a65..9fb395e7 100644 --- a/app/views/stories/_main_stories_feed.html.erb +++ b/app/views/stories/_main_stories_feed.html.erb @@ -55,7 +55,9 @@ <% if !user_signed_in? && i == 4 %> <%= render "stories/sign_in_invitation" %> <% end %> - <%= render "articles/single_story", story: story %> + <% cache(story) do %> + <%= render "articles/single_story", story: story %> + <% end %> <% end %> <% end %> <% if @stories.size > 1 %> diff --git a/config/application.rb b/config/application.rb index 1b4c391f..20e7f455 100644 --- a/config/application.rb +++ b/config/application.rb @@ -22,6 +22,9 @@ class Application < Rails::Application # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de + config.skylight.environments << "development" + config.skylight.environments << "local_production" + config.autoload_paths += Dir["#{config.root}/app/labor/"] config.autoload_paths += Dir["#{config.root}/app/decorators/"] config.autoload_paths += Dir["#{config.root}/app/services/"] diff --git a/config/environments/local_production.rb b/config/environments/local_production.rb new file mode 100644 index 00000000..cb1777ec --- /dev/null +++ b/config/environments/local_production.rb @@ -0,0 +1,131 @@ +# rubocop:disable Metrics/BlockLength + +Rails.application.configure do + # Verifies that versions and hashed value of the package contents in the project's package.json + config.webpacker.check_yarn_integrity = false + + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like + # NGINX, varnish or squid. + # config.action_dispatch.rack_cache = true + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.headers = { + "Cache-Control" => "public, s-maxage=2592000, max-age=86400" + } + + # Compress JavaScripts and CSS. + config.assets.js_compressor = Uglifier.new(harmony: true) + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` + # have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + config.action_controller.asset_host = ENV["FASTLY_CDN_URL"] + 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. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = [I18n.default_locale] + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + # config.log_formatter = ::Logger::Formatter.new + config.log_formatter = ::Logger::Formatter.new + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Install the Timber.io logger + send_logs_to_timber = ENV["SEND_LOGS_TO_TIMBER"] || "false" # <---- set to false to stop sending dev logs to Timber.io + log_device = send_logs_to_timber == "true" ? Timber::LogDevices::HTTP.new(ENV["TIMBER"]) : STDOUT + logger = Timber::Logger.new(log_device) + logger.level = config.log_level + config.logger = ActiveSupport::TaggedLogging.new(logger) + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + config.cache_store = :dalli_store, + (ENV["MEMCACHIER_SERVERS"] || "").split(","), + { username: ENV["MEMCACHIER_USERNAME"], + password: ENV["MEMCACHIER_PASSWORD"], + failover: true, + socket_timeout: 1.5, + socket_failure_delay: 0.2 } + + config.app_domain = "dev.to" + + config.action_mailer.delivery_method = :smtp + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: config.app_domain } + ActionMailer::Base.smtp_settings = { + address: "smtp.sendgrid.net", + port: "587", + authentication: :plain, + user_name: ENV["SENDGRID_USERNAME_ACCEL"], + password: ENV["SENDGRID_PASSWORD_ACCEL"], + domain: "dev.to", + enable_starttls_auto: true + } + + config.middleware.use Rack::HostRedirect, + "practicaldev.herokuapp.com" => "dev.to" +end + +# rubocop:enable Metrics/BlockLength diff --git a/config/initializers/airbrake.rb b/config/initializers/airbrake.rb index 0a3fffc7..c530ddc8 100644 --- a/config/initializers/airbrake.rb +++ b/config/initializers/airbrake.rb @@ -41,7 +41,7 @@ # environments. # NOTE: This option *does not* work if you don't set the 'environment' option. # https://github.com/airbrake/airbrake-ruby#ignore_environments - c.ignore_environments = %w[test development] + c.ignore_environments = %w[test development local_production] # A list of parameters that should be filtered out of what is sent to # Airbrake. By default, all "password" attributes will have their contents diff --git a/config/secrets.yml b/config/secrets.yml index 73f5e05c..8bf3b1cf 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -13,6 +13,9 @@ development: secret_key_base: a60edc976c913b19fd9fc8118936fbe1df2b07f4eecc5ad32f975e33cd4ea36b150c1ce933b681b90874a46568041629003dcbfc07238f7dca91741bcd1ec870 +local_production: + secret_key_base: a60edc976c913b19fd9fc8118936fbe1df2b07f4eecc5ad32f975e33cd4ea36b150c1ce933b681b90874a46568041629003dcbfc07238f7dca91741bcd1ec870 + test: secret_key_base: 42dd7834039ebbea271af22635a6782ee15e519b14629c5276bfcdd4cff841e9926994784bb43a335a8f8c9739bb254ea3afe831839d4dc65654ec7516ec25f0 diff --git a/config/webpacker.yml b/config/webpacker.yml index 2dfcd170..546a0171 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -54,3 +54,12 @@ production: # Cache manifest.json for performance cache_manifest: true + +local_production: + <<: *default + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Cache manifest.json for performance + cache_manifest: true From aba353d82b5da242dfc355c7135da994bbe4fd3d Mon Sep 17 00:00:00 2001 From: Beglov Sergey Date: Thu, 30 May 2024 18:09:40 +0300 Subject: [PATCH 2/2] Setup Prometheus & Grafana --- .dev_to/compose.yml | 26 ++++++++++++++++++++++++++ .dev_to/prometheus.yml | 11 +++++++++++ Gemfile | 1 + Gemfile.lock | 4 ++++ config/initializers/prometheus.rb | 6 ++++++ prometheus/custom_collector.rb | 15 +++++++++++++++ 6 files changed, 63 insertions(+) create mode 100644 .dev_to/prometheus.yml create mode 100644 config/initializers/prometheus.rb create mode 100644 prometheus/custom_collector.rb diff --git a/.dev_to/compose.yml b/.dev_to/compose.yml index dfb76e9c..00ce450c 100644 --- a/.dev_to/compose.yml +++ b/.dev_to/compose.yml @@ -46,6 +46,7 @@ x-backend: &backend IRB_HISTFILE: /usr/local/hist/.irb_history EDITOR: vi SKYLIGHT_AUTHENTICATION: $SKYLIGHT_AUTHENTICATION + PROMETHEUS_EXPORTER_HOST: exporter depends_on: &backend_depends_on postgres: condition: service_healthy @@ -72,6 +73,12 @@ services: # <<: *backend # command: bundle exec sidekiq -C config/sidekiq.yml + exporter: + <<: *backend + command: bundle exec prometheus_exporter -b 0.0.0.0 -a prometheus/custom_collector.rb + ports: + - "9394:9394" + postgres: image: postgres:14 volumes: @@ -99,6 +106,24 @@ services: timeout: 3s retries: 30 + prometheus: + image: dockerhub.timeweb.cloud/prom/prometheus + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + - prom_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - '9090:9090' + grafana: + image: dockerhub.timeweb.cloud/grafana/grafana + environment: + - GF_SECURITY_ADMIN_PASSWORD=pass + depends_on: + - prometheus + ports: + - "3030:3000" + webpacker: <<: *app command: bundle exec ./bin/webpack-dev-server @@ -124,3 +149,4 @@ volumes: redis: packs: packs-test: + prom_data: diff --git a/.dev_to/prometheus.yml b/.dev_to/prometheus.yml new file mode 100644 index 00000000..6fc70d5d --- /dev/null +++ b/.dev_to/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 5s + external_labels: + monitor: 'my-monitor' +scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + - job_name: 'devdev' + static_configs: + - targets: ['exporter:9394'] diff --git a/Gemfile b/Gemfile index 7d7eb416..c9ec8008 100644 --- a/Gemfile +++ b/Gemfile @@ -103,6 +103,7 @@ gem "validate_url", "~> 1.0" gem "webpacker", "~> 3.5" gem "webpush", "~> 0.3" gem "rack-mini-profiler" +gem "prometheus_exporter" group :development do gem "better_errors", "~> 2.5" diff --git a/Gemfile.lock b/Gemfile.lock index ac3243e0..e05609de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -638,6 +638,8 @@ GEM ast (~> 2.4.0) pg (1.1.4) powerpack (0.1.2) + prometheus_exporter (2.1.0) + webrick pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -917,6 +919,7 @@ GEM webpush (0.3.2) hkdf (~> 0.2) jwt + webrick (1.8.1) websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) @@ -1005,6 +1008,7 @@ DEPENDENCIES omniauth-twitter (~> 1.4) parallel_tests (~> 2.27) pg (~> 1.1) + prometheus_exporter pry (~> 0.12) pry-byebug (~> 3.7) pry-rails (~> 0.3) diff --git a/config/initializers/prometheus.rb b/config/initializers/prometheus.rb new file mode 100644 index 00000000..e7a9245e --- /dev/null +++ b/config/initializers/prometheus.rb @@ -0,0 +1,6 @@ +unless Rails.env.test? + require 'prometheus_exporter/middleware' + + # This reports stats per request like HTTP status and timings + Rails.application.middleware.unshift PrometheusExporter::Middleware +end diff --git a/prometheus/custom_collector.rb b/prometheus/custom_collector.rb new file mode 100644 index 00000000..bc6ea53c --- /dev/null +++ b/prometheus/custom_collector.rb @@ -0,0 +1,15 @@ +class CustomCollector < PrometheusExporter::Server::TypeCollector + unless defined? Rails + require File.expand_path("../../config/environment", __FILE__) + end + + def type + "mariela_posts" + end + + def metrics + mariela_posts_gague = PrometheusExporter::Metric::Gauge.new('mariela_posts', 'number of mariela posts') + mariela_posts_gague.observe User.find_by_name('Mariela Ledner').articles.count + [mariela_posts_gague] + end +end