diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 00000000..30a021f9 --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,16 @@ +env: + browser: true + node: true + es6: true +extends: 'eslint:recommended' +globals: + Atomics: readonly + SharedArrayBuffer: readonly +parserOptions: + ecmaFeatures: + jsx: true + ecmaVersion: 2018 + sourceType: module +plugins: + - react +rules: {} diff --git a/.gitignore b/.gitignore index df2840a6..cb93c022 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ coverage # Ignore node_modules node_modules/ +false/ # Generated js bundles /app/assets/javascripts/generated/* @@ -36,6 +37,7 @@ latest.dump # Ignore application configuration /config/application.yml +config/new_relic.yml /public/packs /public/packs-test /node_modules @@ -52,4 +54,4 @@ package-lock.json .idea/ #sitemap -/public/sitemap.xml.gz \ No newline at end of file +/public/sitemap.xml.gz diff --git a/Envfile b/Envfile index 944a3fac..bb96f2a0 100644 --- a/Envfile +++ b/Envfile @@ -148,6 +148,8 @@ variable :TRENDING_TAGS, :String, default: "git,beginners" variable :FACEBOOK_PIXEL_ID, :String, default: "Optional" variable :SMARTY_STREETS_WEB_KEY, :String, default: "Optional" +# New relic +variable :NEW_RELIC_LICENSE_KEY, :String, default: "Optional" group :production do variable :SECRET_KEY_BASE, :String diff --git a/Gemfile b/Gemfile index 1a5f4c0b..fb8083a3 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" @@ -117,7 +117,7 @@ group :development do gem "web-console", "~> 3.7" end -group :development, :test do +group :development, :lp, :test do gem "capybara", "~> 3.13" gem "derailed", "~> 0.1" gem "erb_lint", "~> 0.0", require: false @@ -133,6 +133,7 @@ group :development, :test do gem "spring", "~> 2.0" gem "spring-commands-rspec", "~> 1.0" gem "vcr", "~> 4.0" + gem 'meta_request' end group :test do @@ -157,3 +158,7 @@ group :test do gem "zonebie", "~> 0.6.1" end # rubocop:enable LineLength + +gem 'newrelic_rpm' +gem 'prometheus_exporter' +gem 'rack-mini-profiler', require: false diff --git a/Gemfile.lock b/Gemfile.lock index c7c050cf..1cfa3077 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,6 +137,7 @@ GEM descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) thread_safe (~> 0.3, >= 0.3.1) + base64 (0.1.1) bcrypt (3.1.12) benchmark-ips (2.7.2) better_errors (2.5.0) @@ -592,6 +593,9 @@ GEM memoizable (0.4.2) thread_safe (~> 0.3, >= 0.3.1) memory_profiler (0.9.12) + meta_request (0.7.4) + rack-contrib (>= 1.1, < 3) + railties (>= 3.0.0, < 7.1) method_source (0.9.2) mime-types (3.2.2) mime-types-data (~> 3.2015) @@ -612,6 +616,8 @@ GEM net-http-persistent (3.0.0) connection_pool (~> 2.2) netrc (0.11.0) + newrelic_rpm (9.6.0) + base64 nio4r (2.3.1) nokogiri (1.10.1) mini_portile2 (~> 2.4.0) @@ -651,6 +657,8 @@ GEM ast (~> 2.4.0) pg (1.1.4) powerpack (0.1.2) + prometheus_exporter (2.0.8) + webrick pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) @@ -675,8 +683,12 @@ GEM pusher-signature (0.1.8) raabro (1.1.6) rack (2.0.6) + rack-contrib (2.4.0) + rack (< 4) 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) @@ -932,6 +944,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) @@ -1012,7 +1025,9 @@ DEPENDENCIES libhoney (~> 1.11) liquid (~> 4.0) memory_profiler (~> 0.9) + meta_request nakayoshi_fork + newrelic_rpm nokogiri (~> 1.10) octokit (~> 4.13) omniauth (~> 1.9) @@ -1020,6 +1035,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) @@ -1029,6 +1045,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)! @@ -1057,7 +1074,7 @@ DEPENDENCIES simplecov (~> 0.16) sinatra (~> 2.0) sitemap_generator (~> 6.0) - skylight (~> 3.1) + skylight slack-notifier (~> 2.3) spring (~> 2.0) spring-commands-rspec (~> 1.0) diff --git a/Procfile.lp b/Procfile.lp new file mode 100644 index 00000000..8fec48ff --- /dev/null +++ b/Procfile.lp @@ -0,0 +1,4 @@ +web: bin/rails s -p 3000 -e lp +webpacker: ./bin/webpack-dev-server +job: bin/rake jobs:work +prometheus_exporter: bundle exec prometheus_exporter diff --git a/app/assets/images/sloan.png b/app/assets/images/sloan.png index adaea574..e69de29b 100644 Binary files a/app/assets/images/sloan.png and b/app/assets/images/sloan.png differ diff --git a/app/views/stories/_main_stories_feed.html.erb b/app/views/stories/_main_stories_feed.html.erb index b6cd0a65..3e175010 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/bin/startup b/bin/startup index c7e84efb..35ccec0e 100755 --- a/bin/startup +++ b/bin/startup @@ -11,6 +11,13 @@ def system!(*args) end chdir APP_ROOT do + env = ENV.fetch('RAILS_ENV', 'development') puts "== STARTING UP ==" - system! "foreman start -f Procfile.dev" + + if env == 'lp' + system! "bundle exec rake assets:precompile" + system! "foreman start -f Procfile.lp" + else + system! "foreman start -f Procfile.dev" + end end diff --git a/case-study.md b/case-study.md new file mode 100644 index 00000000..d613d420 --- /dev/null +++ b/case-study.md @@ -0,0 +1,19 @@ +# Оптимизация + +## Кэширование `_single_story.html.erb` в `_main_stories_feed.html.erb` + +Добавление кэширования по стори позволило ускорить загрузку главной страницы почти в 2 раза. + +### AB + +#### before + +Time per request: 212.047 ms + +https://gist.github.com/peresvetjke/22b0076f71bb49000aa3c3f082889d85 + +#### after + +Time per request: 114.685 ms + +https://gist.github.com/peresvetjke/5b0a328f332dda0c43c7d8466f927bde diff --git a/config/application.rb b/config/application.rb index 1b4c391f..c7d933f5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -53,5 +53,7 @@ class Application < Rails::Application end ReservedWords.all = [ReservedWords::BASE_WORDS + top_routes].flatten.compact.uniq end + + config.skylight.environments << 'lp' end end diff --git a/config/environments/lp.rb b/config/environments/lp.rb new file mode 100644 index 00000000..bc5390b3 --- /dev/null +++ b/config/environments/lp.rb @@ -0,0 +1,102 @@ +# rubocop:disable Metrics/BlockLength +# +def yarn_integrity_enabled? + ENV.fetch("YARN_INTEGRITY_ENABLED", "false") == "true" +end + +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 = yarn_integrity_enabled? + + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = true + + # Do not eager load code on boot. + config.eager_load = true + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + if Rails.root.join("tmp/caching-dev.txt").exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + "Cache-Control" => "public, max-age=172800" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + config.assets.debug = false + + # 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 = false + + # Supress logger output for asset requests. + config.assets.quiet = true + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = true + + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + config.assets.raise_runtime_errors = true + + config.action_mailer.perform_caching = false + + config.app_domain = "localhost:3000" + + config.action_mailer.default_url_options = { host: "localhost:3000" } + config.action_mailer.delivery_method = :smtp + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: config.app_domain } + config.action_mailer.smtp_settings = { + address: "smtp.gmail.com", + port: "587", + enable_starttls_auto: true, + user_name: '<%= ENV["DEVELOPMENT_EMAIL_USERNAME"] %>', + password: '<%= ENV["DEVELOPMENT_EMAIL_PASSWORD"] %>', + authentication: :plain, + domain: "localhost:3000" + } + + config.action_mailer.preview_path = "#{Rails.root}/spec/mailers/previews" + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + config.public_file_server.enabled = true + + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + + # 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) +end + +# rubocop:enable Metrics/BlockLength diff --git a/config/initializers/airbrake.rb b/config/initializers/airbrake.rb index 0a3fffc7..9f22b8f9 100644 --- a/config/initializers/airbrake.rb +++ b/config/initializers/airbrake.rb @@ -7,6 +7,7 @@ # # Configuration details: # https://github.com/airbrake/airbrake-ruby#configuration + Airbrake.configure do |c| # You must set both project_id & project_key. To find your project_id and # project_key navigate to your project's General Settings and copy the values @@ -41,7 +42,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 lp] # 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/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 9e191ac1..553e4e15 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -3,7 +3,7 @@ require "carrierwave/storage/fog" CarrierWave.configure do |config| - if Rails.env.development? || Rails.env.test? + if %w[development test lp].include?(Rails.env) config.storage = :file else # config.fog_provider = 'fog-aws' diff --git a/config/initializers/honeycomb.rb b/config/initializers/honeycomb.rb index 0ac4787a..dcab6603 100644 --- a/config/initializers/honeycomb.rb +++ b/config/initializers/honeycomb.rb @@ -3,7 +3,7 @@ key = ApplicationConfig["HONEYCOMB_API_KEY"] dataset = "dev.to-#{Rails.env}" -$libhoney = if Rails.env.development? || Rails.env.test? +$libhoney = if %w[development test lp].include?(Rails.env) Libhoney::NullClient.new else Libhoney::Client.new( diff --git a/config/initializers/prometheus.rb b/config/initializers/prometheus.rb new file mode 100644 index 00000000..40e3a5b2 --- /dev/null +++ b/config/initializers/prometheus.rb @@ -0,0 +1,6 @@ +if 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/config/initializers/rack_mini_profiler.rb b/config/initializers/rack_mini_profiler.rb new file mode 100644 index 00000000..0dfc8038 --- /dev/null +++ b/config/initializers/rack_mini_profiler.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +if %w[development lp].include?(Rails.env) + require "rack-mini-profiler" + + # The initializer was required late, so initialize it manually. + Rack::MiniProfilerRails.initialize!(Rails.application) + Rack::MiniProfiler.config.authorization_mode = :allow_all if Rails.env.lp? +end diff --git a/config/newrelic.yml b/config/newrelic.yml new file mode 100644 index 00000000..7d785da5 --- /dev/null +++ b/config/newrelic.yml @@ -0,0 +1,70 @@ +# +# This file configures the New Relic Agent. New Relic monitors Ruby, Java, +# .NET, PHP, Python, Node, and Go applications with deep visibility and low +# overhead. For more information, visit www.newrelic.com. +# +# Generated October 28, 2022 +# +# This configuration file is custom generated for NewRelic Administration +# +# For full documentation of agent configuration options, please refer to +# https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration + +common: &default_settings + # Required license key associated with your New Relic account. + license_key: <%= ENV.fetch('NEW_RELIC_LICENSE_KEY', '') %> + + # Your application name. Renaming here affects where data displays in New + # Relic. For more details, see https://docs.newrelic.com/docs/apm/new-relic-apm/maintenance/renaming-applications + app_name: 'Dev.to (local)' + + distributed_tracing: + enabled: true + + # To disable the agent regardless of other settings, uncomment the following: + + # agent_enabled: false + + # Logging level for log/newrelic_agent.log + log_level: info + + application_logging: + # If `true`, all logging-related features for the agent can be enabled or disabled + # independently. If `false`, all logging-related features are disabled. + enabled: true + forwarding: + # If `true`, the agent captures log records emitted by this application. + enabled: true + # Defines the maximum number of log records to buffer in memory at a time. + max_samples_stored: 10000 + metrics: + # If `true`, the agent captures metrics related to logging for this application. + enabled: true + local_decorating: + # If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans. + # This requires a log forwarder to send your log files to New Relic. + # This should not be used when forwarding is enabled. + enabled: false + +# Environment-specific settings are in this section. +# RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment. +# If your application has other named environments, configure them here. +development: + <<: *default_settings + app_name: 'Dev.to (local) (Development)' + +test: + <<: *default_settings + # It doesn't make sense to report to New Relic from automated test runs. + monitor_mode: false + +staging: + <<: *default_settings + app_name: 'Dev.to (local) (Staging)' + +production: + <<: *default_settings + +lp: + <<: *default_settings + app_name: 'Dev.to (local) (LocalProduction)' diff --git a/config/secrets.yml b/config/secrets.yml index 73f5e05c..3d0c70bc 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -22,3 +22,5 @@ test: production: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> +lp: + secret_key_base: a60edc976c913b19fd9fc8118936fbe1df2b07f4eecc5ad32f975e33cd4ea36b150c1ce933b681b90874a46568041629003dcbfc07238f7dca91741bcd1ec870 diff --git a/config/skylight.yml b/config/skylight.yml index d40c2c26..3ea62565 100644 --- a/config/skylight.yml +++ b/config/skylight.yml @@ -1,3 +1,3 @@ --- # The authentication token for the application. -authentication: <%= ENV['SKYLIGHT_AUTHENTICATION'] %> +authentication: <%= ENV["SKYLIGHT_AUTHENTICATION"] %> diff --git a/config/webpack/lp.js b/config/webpack/lp.js new file mode 100644 index 00000000..d7346478 --- /dev/null +++ b/config/webpack/lp.js @@ -0,0 +1,3 @@ +const environment = require('./environment'); + +module.exports = environment.toWebpackConfig(); diff --git a/config/webpacker.yml b/config/webpacker.yml index 2dfcd170..8875ffdd 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -33,7 +33,7 @@ development: <<: *default compile: true - dev_server: + dev_server: &dev_server host: localhost port: 3035 hmr: false @@ -54,3 +54,15 @@ production: # Cache manifest.json for performance cache_manifest: true + +lp: + <<: *default + + dev_server: + <<: *dev_server + + # Production depends on precompilation of packs prior to booting for performance. + compile: false + + # Cache manifest.json for performance + cache_manifest: true diff --git a/prometheus/docker-compose.yml b/prometheus/docker-compose.yml new file mode 100644 index 00000000..4b7e18ff --- /dev/null +++ b/prometheus/docker-compose.yml @@ -0,0 +1,24 @@ +version: '2' +services: + dockerhost: + image: qoomon/docker-host + cap_add: [ 'NET_ADMIN', 'NET_RAW' ] + mem_limit: 8M + restart: on-failure + prometheus: + depends_on: [ dockerhost ] + image: prom/prometheus:0.18.0 + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + command: + - '-config.file=/etc/prometheus/prometheus.yml' + ports: + - '9090:9090' + grafana: + image: grafana/grafana:3.0.0-beta7 + environment: + - GF_SECURITY_ADMIN_PASSWORD=pass + depends_on: + - prometheus + ports: + - "3030:3000" diff --git a/prometheus/prometheus.yml b/prometheus/prometheus.yml new file mode 100644 index 00000000..de2a3fe6 --- /dev/null +++ b/prometheus/prometheus.yml @@ -0,0 +1,11 @@ +global: + scrape_interval: 5s + external_labels: + monitor: 'my-monitor' +scrape_configs: + - job_name: 'prometheus' + target_groups: + - targets: ['localhost:9090'] + - job_name: 'devdev' + target_groups: + - targets: ['dockerhost:9394']