diff --git a/.github/workflows/tests-real-data-stageportal.yml b/.github/workflows/tests-real-data-stageportal.yml index 53a9d112d6..9168398af3 100644 --- a/.github/workflows/tests-real-data-stageportal.yml +++ b/.github/workflows/tests-real-data-stageportal.yml @@ -27,10 +27,10 @@ jobs: - "3306:3306" env: MYSQL_ROOT_PASSWORD: root - memcached: - image: memcached:1.6 + redis: + image: redis:latest ports: - - 11211:11211 + - 6379:6379 steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/tests-real-data-testportal.yml b/.github/workflows/tests-real-data-testportal.yml index baa017f4e6..e7c3ce9573 100644 --- a/.github/workflows/tests-real-data-testportal.yml +++ b/.github/workflows/tests-real-data-testportal.yml @@ -27,10 +27,10 @@ jobs: - "3306:3306" env: MYSQL_ROOT_PASSWORD: root - memcached: - image: memcached:1.6 + redis: + image: redis:latest ports: - - 11211:11211 + - 6379:6379 steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/.github/workflows/tests-system.yml b/.github/workflows/tests-system.yml index 8da36c9e61..27eee7f14e 100644 --- a/.github/workflows/tests-system.yml +++ b/.github/workflows/tests-system.yml @@ -28,10 +28,10 @@ jobs: - "3306:3306" env: MYSQL_ROOT_PASSWORD: root - memcached: - image: memcached:1.6 + redis: + image: redis:latest ports: - - 11211:11211 + - 6379:6379 chrome-server: image: selenium/standalone-chrome:112.0-chromedriver-112.0-grid-4.9.0-20230421 options: "--shm-size=2g" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9214bc4de2..5d80c71d73 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,10 +28,11 @@ jobs: - "3306:3306" env: MYSQL_ROOT_PASSWORD: root - memcached: - image: memcached:1.6 + redis: + image: redis:latest ports: - - 11211:11211 + - 6379:6379 + steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/Gemfile b/Gemfile index 781e6379e2..0fe059471a 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ gem 'puma', '~> 5.0' # Use JavaScript with ESM import maps # [https://github.com/rails/importmap-rails] -gem 'importmap-rails' +gem 'importmap-rails', '2.0.1' # Hotwire's SPA-like page accelerator # [https://turbo.hotwired.dev] @@ -55,8 +55,9 @@ gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', require: false -# Memcached client for Ruby -gem 'dalli' +# For Rails cache store support +gem 'redis' +gem 'redis-rails' # GraphQL client for Ruby gem 'graphql-client' @@ -65,7 +66,7 @@ gem 'graphql-client' gem 'haml', '~> 5.1' # Internationalization (i18n) -gem 'i18n' +gem 'i18n', '~> 1.14.6' gem 'rails-i18n', '~> 7.0.0' # MySQL database adapter @@ -96,7 +97,7 @@ gem 'flag-icons-rails', '~> 3.4' gem 'iso-639', '~> 0.3.6' # Custom API client -gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'development' +gem 'ontologies_api_client', git: 'https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git', branch: 'feature/add-rails-performance' # Ruby 2.7.8 pinned gems (to remove when migrating to Ruby >= 3.0) gem 'ffi', '~> 1.16.3' @@ -114,6 +115,9 @@ gem 'omniauth-orcid' # Used to generate colors randomly gem "color", "~> 1.8" +# Application performance monitoring +gem 'rails_performance' + group :staging, :production, :appliance do # Application performance monitoring gem 'newrelic_rpm' diff --git a/Gemfile.lock b/Gemfile.lock index 31fd0e01d0..25d80728ae 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ GIT remote: https://github.com/ontoportal-lirmm/ontologies_api_ruby_client.git - revision: 670ce6adfe77aeda97974414ab0ed4b6c5dc9469 - branch: development + revision: c473aae92493c2e0de08f0af8ed5812349db9621 + branch: feature/add-rails-performance specs: ontologies_api_client (2.2.0) activesupport (~> 7.0.4) @@ -13,6 +13,7 @@ GIT multi_json oj parallel + rails_performance request_store spawnling (= 2.1.5) @@ -104,6 +105,7 @@ GEM popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) brakeman (5.4.1) + browser (5.3.1) bugsnag (6.27.1) concurrent-ruby (~> 1.0) builder (3.3.0) @@ -139,25 +141,25 @@ GEM coderay (1.1.3) color (1.8) concurrent-ruby (1.3.4) + connection_pool (2.4.1) crack (1.0.0) bigdecimal rexml crass (1.0.6) css_parser (1.17.1) addressable - dalli (3.2.8) date (3.3.4) debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - deepl-rb (2.5.3) + deepl-rb (3.0.2) diff-lcs (1.5.1) docile (1.4.1) domain_name (0.6.20240107) ed25519 (1.3.0) erubi (1.13.0) erubis (2.7.0) - excon (0.111.0) + excon (0.112.0) execjs (2.9.1) faraday (2.0.1) faraday-net_http (~> 2.0) @@ -176,7 +178,7 @@ GEM sass-rails globalid (1.2.1) activesupport (>= 6.1) - graphql (2.3.14) + graphql (2.3.17) base64 fiber-storage graphql-client (0.23.0) @@ -203,7 +205,7 @@ GEM http-accept (1.7.0) http-cookie (1.0.7) domain_name (~> 0.5) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) i18n-tasks (0.9.37) activesupport (>= 4.0.2) @@ -225,7 +227,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.14.0) + irb (1.14.1) rdoc (>= 4.0.0) reline (>= 0.4.2) iso-639 (0.3.6) @@ -245,7 +247,7 @@ GEM bindata faraday (~> 2.0) faraday-follow_redirects - jwt (2.8.2) + jwt (2.9.3) base64 language_server-protocol (3.17.0.3) launchy (3.0.1) @@ -287,12 +289,13 @@ GEM marcel (1.0.4) matrix (0.4.2) method_source (1.1.0) - mime-types (3.5.2) + mime-types (3.6.0) + logger mime-types-data (~> 3.2015) - mime-types-data (3.2024.0903) + mime-types-data (3.2024.1001) mini_mime (1.1.5) minitest (5.25.1) - msgpack (1.7.2) + msgpack (1.7.3) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.4.1) @@ -302,7 +305,7 @@ GEM time net-http (0.3.2) uri - net-imap (0.4.15) + net-imap (0.4.16) date net-protocol net-pop (0.1.2) @@ -315,9 +318,9 @@ GEM net-ssh (>= 5.0.0, < 8.0.0) net-smtp (0.5.0) net-protocol - net-ssh (7.2.3) + net-ssh (7.3.0) netrc (0.11.0) - newrelic_rpm (9.13.0) + newrelic_rpm (9.14.0) nio4r (2.7.3) nokogiri (1.15.6-x86_64-linux) racc (~> 1.4) @@ -328,7 +331,7 @@ GEM rack (>= 1.2, < 4) snaky_hash (~> 2.0) version_gem (~> 1.1) - oj (3.16.5) + oj (3.16.6) bigdecimal (>= 3.0) ostruct (>= 0.2) omniauth (2.1.2) @@ -338,8 +341,8 @@ GEM omniauth-github (2.0.1) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) - omniauth-google-oauth2 (1.1.3) - jwt (>= 2.0) + omniauth-google-oauth2 (1.2.0) + jwt (>= 2.9) oauth2 (~> 2.0) omniauth (~> 2.0) omniauth-oauth2 (~> 1.8) @@ -369,13 +372,15 @@ GEM psych (5.1.2) stringio public_suffix (5.1.1) - puma (5.6.8) + puma (5.6.9) nio4r (~> 2.0) racc (1.8.1) rack (2.2.9) rack-protection (3.2.0) base64 (>= 0.1.0) rack (~> 2.2, >= 2.2.4) + rack-session (1.0.2) + rack (< 3) rack-test (2.1.0) rack (>= 1.3) rails (7.0.4) @@ -402,6 +407,11 @@ GEM rails-i18n (7.0.9) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) + rails_performance (1.2.2) + browser + railties + redis + redis-namespace railties (7.0.4) actionpack (= 7.0.4) activesupport (= 7.0.4) @@ -419,6 +429,28 @@ GEM recaptcha (5.9.0) json redcarpet (3.6.0) + redis (5.3.0) + redis-client (>= 0.22.0) + redis-actionpack (5.4.0) + actionpack (>= 5, < 8) + redis-rack (>= 2.1.0, < 4) + redis-store (>= 1.1.0, < 2) + redis-activesupport (5.3.0) + activesupport (>= 3, < 8) + redis-store (>= 1.3, < 2) + redis-client (0.22.2) + connection_pool + redis-namespace (1.11.0) + redis (>= 4) + redis-rack (3.0.0) + rack-session (>= 0.2.0) + redis-store (>= 1.2, < 2) + redis-rails (5.0.2) + redis-actionpack (>= 5.0, < 6) + redis-activesupport (>= 5.0, < 6) + redis-store (>= 1.2, < 2) + redis-store (1.11.0) + redis (>= 4, < 6) regexp_parser (2.9.2) reline (0.5.10) io-console (~> 0.5) @@ -429,14 +461,14 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.3.7) - rouge (4.3.0) + rexml (3.3.8) + rouge (4.4.0) rspec-core (3.13.1) rspec-support (~> 3.13.0) rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (7.0.1) @@ -458,7 +490,7 @@ GEM rubocop-ast (>= 1.32.2, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.2) + rubocop-ast (1.32.3) parser (>= 3.3.1.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) @@ -521,13 +553,12 @@ GEM time (0.4.0) date timeout (0.4.1) - turbo-rails (2.0.6) + turbo-rails (2.0.10) actionpack (>= 6.0.0) - activejob (>= 6.0.0) railties (>= 6.0.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) + unicode-display_width (2.6.0) uri (0.13.1) version_gem (1.1.4) view_component (2.83.0) @@ -539,7 +570,7 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webmock (3.23.1) + webmock (3.24.0) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -572,7 +603,6 @@ DEPENDENCIES capybara chart-js-rails color (~> 1.8) - dalli debug deepl-rb ed25519 (>= 1.2, < 2.0) @@ -582,10 +612,10 @@ DEPENDENCIES haml (~> 5.1) haml-rails html2haml - i18n + i18n (~> 1.14.6) i18n-tasks i18n-tasks-csv (~> 1.1) - importmap-rails + importmap-rails (= 2.0.1) inline_svg iso-639 (~> 0.3.6) jquery-rails @@ -610,7 +640,10 @@ DEPENDENCIES puma (~> 5.0) rails (= 7.0.4) rails-i18n (~> 7.0.0) + rails_performance recaptcha (~> 5.9.0) + redis + redis-rails rest-client rspec-rails rubocop @@ -629,4 +662,4 @@ DEPENDENCIES will_paginate (~> 3.0) BUNDLED WITH - 2.4.22 + 2.3.23 diff --git a/app/views/admin/_monitoring.html.haml b/app/views/admin/_monitoring.html.haml new file mode 100644 index 0000000000..5225d714f9 --- /dev/null +++ b/app/views/admin/_monitoring.html.haml @@ -0,0 +1,2 @@ +%div + %iframe.embed-responsive.w-100.border{src: 'rails/performance', style: 'height:100vh;'} \ No newline at end of file diff --git a/app/views/admin/index.html.haml b/app/views/admin/index.html.haml index da8f0cbdee..c360f84d3b 100644 --- a/app/views/admin/index.html.haml +++ b/app/views/admin/index.html.haml @@ -11,7 +11,7 @@ %div %div.mx-1 - - sections = [t('admin.index.analytics'), t('admin.index.site_administration'),t('admin.index.ontology_administration'), t('admin.index.licensing'), t('admin.index.users'), t('admin.index.metadata_administration'), t('admin.index.groups'), t('admin.index.categories'), t('admin.index.persons_and_organizations'), t('admin.index.sparql'), t('admin.index.search')] + - sections = [t('admin.index.analytics'), t('admin.index.site_administration'), "Performance Monitoring", t('admin.index.ontology_administration'), t('admin.index.licensing'), t('admin.index.users'), t('admin.index.metadata_administration'), t('admin.index.groups'), t('admin.index.categories'), t('admin.index.persons_and_organizations'), t('admin.index.sparql'), t('admin.index.search')] - selected = params[:section] || sections.first.downcase = render Layout::VerticalTabsComponent.new(header: t('admin.index.administration_console'), titles: sections, selected: selected, url_parameter: 'section') do |t| - t.item_content do @@ -22,6 +22,8 @@ = render 'analytics' - t.item_content do = render 'main' + - t.item_content do + = render 'monitoring' - t.item_content do %div %table{:style => "float:left;"} diff --git a/config/cable.yml b/config/cable.yml index 4878660357..93745406b5 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -6,5 +6,5 @@ test: production: adapter: redis - url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> + url: <%= "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1" %> channel_prefix: bioportal_web_ui_production diff --git a/config/environments/appliance.rb b/config/environments/appliance.rb index ec5f5e79f9..4c00e23505 100644 --- a/config/environments/appliance.rb +++ b/config/environments/appliance.rb @@ -78,7 +78,7 @@ require Rails.root.join('config', "bioportal_config_#{Rails.env}.rb") # Use a different cache store in the appliance. - config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211", { :namespace => 'bioportal_web_ui', :expires_in => 1.day } + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Disable serving static files from the `/public` folder by default since diff --git a/config/environments/development.rb b/config/environments/development.rb index 7e0593176d..4ac537b21a 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -34,7 +34,7 @@ config.action_controller.perform_caching = true config.action_controller.enable_fragment_cache_logging = true - config.cache_store = :memory_store + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } config.public_file_server.headers = { "Cache-Control" => "public, max-age=#{2.days.to_i}" } @@ -70,8 +70,8 @@ # Suppress logger output for asset requests. config.assets.quiet = true - # memcache setup - config.cache_store = ActiveSupport::Cache::MemCacheStore.new('cache:11211', namespace: 'BioPortal') + # cache setup + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Silence cache output config.cache_store.logger = Logger.new("/dev/null") if config.cache_store.respond_to?(:logger) diff --git a/config/environments/production.rb b/config/environments/production.rb index 4327447229..7c10c49bec 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -59,7 +59,7 @@ config.log_tags = [:request_id] # Use a different cache store in production. - # config.cache_store = :mem_cache_store + # config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Use a real queuing backend for Active Job (and separate queues per environment). # config.active_job.queue_adapter = :resque @@ -97,8 +97,7 @@ end # Use a different cache store in production. - config.cache_store = :mem_cache_store, ENV['MEMCACHE_SERVERS'] || 'localhost:11211', - { namespace: 'bioportal_web_ui', expires_in: 1.day } + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Add custom data attributes to sanitize allowed list config.action_view.sanitized_allowed_attributes = %w[id class style data-cls data-ont] diff --git a/config/environments/staging.rb b/config/environments/staging.rb index c24addeaf1..ad282bf0fb 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -78,7 +78,7 @@ require Rails.root.join('config', "bioportal_config_#{Rails.env}.rb") # Use a different cache store in staging. - config.cache_store = :mem_cache_store, ENV["MEMCACHE_SERVERS"] || "localhost:11211", { namespace: 'bioportal_web_ui', expires_in: 1.day } + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. diff --git a/config/environments/test.rb b/config/environments/test.rb index d35b8d0f72..1a340d66af 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -27,7 +27,8 @@ # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - config.cache_store = ActiveSupport::Cache::MemCacheStore.new('localhost:11211', namespace: 'BioPortal') + + config.cache_store = :redis_cache_store, { url: "redis://#{ENV.fetch("CACHE_HOST", "localhost")}:6379/1", namespace: 'bioportal_web_ui' } # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false diff --git a/config/initializers/rails_performance.rb b/config/initializers/rails_performance.rb new file mode 100644 index 0000000000..639b621645 --- /dev/null +++ b/config/initializers/rails_performance.rb @@ -0,0 +1,39 @@ +require 'rails_performance' +RailsPerformance.setup do |config| + config.redis = Redis::Namespace.new("#{Rails.env}-rails-performance", redis: Rails.cache.redis) + config.duration = 7.days + + config.debug = false # currently not used> + config.enabled = true + + # default path where to mount gem + config.mount_at = '/rails/performance' + + # protect your Performance Dashboard with HTTP BASIC password + config.http_basic_authentication_enabled = false + config.http_basic_authentication_user_name = 'rails_performance' + config.http_basic_authentication_password = 'password12' + + # if you need an additional rules to check user permissions + config.verify_access_proc = proc { |controller| true } + # for example when you have `current_user` + # config.verify_access_proc = proc { |controller| controller.current_user && controller.current_user.admin? } + + # You can ignore endpoints with Rails standard notation controller#action + # config.ignored_endpoints = ['HomeController#contact'] + + # store custom data for the request + # config.custom_data_proc = proc do |env| + # request = Rack::Request.new(env) + # { + # email: request.env['warden'].user&.email, # if you are using Devise for example + # user_agent: request.env['HTTP_USER_AGENT'] + # } + # end + + # config home button link + config.home_link = '/' + config.skipable_rake_tasks = ['webpacker:compile'] + config.include_rake_tasks = false + config.include_custom_events = true +end if defined?(RailsPerformance) diff --git a/config/routes.rb b/config/routes.rb index b28c26ec02..6ae212853f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,15 @@ root to: 'home#index' mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? - get'/tools', to: 'home#tools' + constraints(lambda { |req| req.session[:user].admin? }) do + mount RailsPerformance::Engine, at: '/rails/performance' + end + + constraints(lambda { |req| !req.session[:user].admin? }) do + get '/rails/performance', to: redirect('/login?redirect=/rails/performance') + end + + get '/tools', to: 'home#tools' get 'auth/:provider/callback', to: 'login#create_omniauth' get 'locale/:language', to: 'language#set_locale_language' get 'metadata_export/index' @@ -44,12 +52,10 @@ resources :concepts - scope :ontologies do get ':ontology/concepts' => 'concepts#index' get ':ontology/concepts/show', to: 'concepts#show' - get ':ontology/instances', to: 'instances#index' get ':ontology/instances/show', to: 'instances#show' @@ -63,7 +69,6 @@ get ':ontology/collections/show', to: 'collections#show' end - resources :ontologies do resources :submissions do get 'edit_properties' @@ -74,8 +79,6 @@ get 'subscriptions' end - - resources :login resources :admin, only: [:index] @@ -115,7 +118,6 @@ get '/annotatorplus', to: 'annotator#annotator_plus' get '/ncbo_annotatorplus', to: 'annotator#ncbo_annotator_plus' - resources :virtual_appliance get 'change_requests/create_synonym' @@ -166,7 +168,6 @@ match '/ontologies/:acronym/submissions/:id/edit_metadata' => 'submissions#edit_metadata', via: [:get, :post] get '/ontologies_filter', to: 'ontologies#ontologies_filter' - get 'ontologies_selector', to: 'ontologies#ontologies_selector' get 'ontologies_selector/results', to: 'ontologies#ontologies_selector_results' diff --git a/docker-compose.yml b/docker-compose.yml index 3c70bbe887..462fb09c7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,13 +43,11 @@ services: retries: 3 cache: - image: memcached:latest - restart: unless-stopped - command: ["-m", "1024"] - networks: - - default + image: "redis:latest" ports: - - "11211:11211" + - "6379:6379" + volumes: + - redis_data:/data node: <<: *default-app command: "yarn build --watch" @@ -120,3 +118,4 @@ volumes: assets: node: app_ui: + redis_data: