如何调试无法忍受的Rails资产预编译

aro*_*roo 40 debugging rake ruby-on-rails asset-pipeline ruby-on-rails-3.2

我正在开发一个Rails 3.2项目,最近几个月资产增加了不少,尽管我不认为这个项目很大.资产包括JS(没有咖啡脚本)和SASS文件; 我们有很多图像,但是从早期开始它们就已经存在了很多,所以我不认为它们是一个重要的因素.我们可能有大约十二个库,大多数是小的,最大的是Jquery UI JS.部署是通过Capistrano完成的,并且开始显示部署到分段的速度明显快于生产.为了说明同时避免不同服务器和网络效果的因素,我只需在笔记本电脑上按顺序运行以下三个命令,如下所示:

$ time RAILS_ENV=production bundle exec rake assets:precompile
^Crake aborted!
[Note I aborted this run as I felt it was getting stupidly long...]
real    52m33.656s
user    50m48.993s
sys 1m42.165s

$ time RAILS_ENV=staging bundle exec rake assets:precompile
real    0m41.685s
user    0m38.808s
sys 0m2.803s

$ time RAILS_ENV=development bundle exec rake assets:precompile
real    0m12.157s
user    0m10.567s
sys 0m1.531s
Run Code Online (Sandbox Code Playgroud)

所以我一直在挠头.为什么各种环境之间存在如此巨大的差异?我可以理解开发和分期之间的差距,但我们对分期和生产的配置是相同的.(我应该指出生产编译将在大约2小时后完成!)

虽然最终的结果是让我的预编译更快,但我想通过了解所有时间的进展以及为什么Rails环境之间存在如此大的差异来实现这一目标.我已经看过其他关于使用不同压缩器等的帖子,但我找不到任何有关如何调试这些rake任务的信息,以确定花费时间的地方并确定哪些设置可能导致如此显着的差异.

我不知道人们可能需要哪些其他信息,因此会在评论提出时更新.TIA

更新:下面提供的其他信息

config/environments/production.rbconfig/environments/staging.rb(他们完全一样):

MyRailsApp::Application.configure do
  # Code is not reloaded between requests
  config.cache_classes = true

  # Full error reports are disabled and caching is turned on
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true

  # Disable Rails's static asset server (Apache or nginx will already do this)
  config.serve_static_assets = true
  config.static_cache_control = "public, max-age=31536000"
  config.action_controller.asset_host = "//#{MyRailsApp::CONFIG[:cdn]}"

  # Compress JavaScripts and CSS
  config.assets.compress = true

  # Don't fallback to assets pipeline if a precompiled asset is missed
  config.assets.compile = false

  # Generate digests for assets URLs
  config.assets.digest = true

  # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
  # the I18n.default_locale when a translation can not be found)
  config.i18n.fallbacks = true

  # Send deprecation notices to registered listeners
  config.active_support.deprecation = :notify
end
Run Code Online (Sandbox Code Playgroud)

base config/application.rb是:

require File.expand_path('../boot', __FILE__)

require 'rails/all'

if defined?(Bundler)
  # If you precompile assets before deploying to production, use this line
  Bundler.require(*Rails.groups(:assets => %w(development test)))
  # If you want your assets lazily compiled in production, use this line
  # Bundler.require(:default, :assets, Rails.env)
end
module MyRailsApp
  CONFIG = YAML.load_file(File.join(File.dirname(__FILE__), 'config.yml'))[Rails.env]

  class Application < Rails::Application

    # Custom directories with classes and modules you want to be autoloadable.
    config.autoload_paths += %W(#{config.root}/lib)
    config.autoload_paths += %W(#{config.root}/app/workers)

    # Configure the default encoding used in templates for Ruby 1.9.
    config.encoding = "utf-8"

    # Configure sensitive parameters which will be filtered from the log file.
    config.filter_parameters += [:password]

    # Enable the asset pipeline
    config.assets.enabled = true

    # Stop precompile from looking for the database
    config.assets.initialize_on_precompile = false

    # Version of your assets, change this if you want to expire all your assets
    config.assets.version = '1.0'

    # Fix fonts in assets pipeline
    # http://stackoverflow.com/questions/6510006/add-a-new-asset-path-in-rails-3-1
    config.assets.paths << Rails.root.join('app','assets','fonts')

    config.middleware.insert 0, 'Rack::Cache', {
      :verbose     => true,
      :metastore   => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"),
      :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body")
    } # unless Rails.env.production?  ## uncomment this 'unless' in Rails 3.1,
                                      ## because it already inserts Rack::Cache in production

    config.middleware.insert_after 'Rack::Cache', 'Dragonfly::Middleware', :images

    config.action_mailer.default_url_options = { :host => CONFIG[:email][:host] }
    config.action_mailer.asset_host = 'http://' + CONFIG[:email][:host]
  end
end
Run Code Online (Sandbox Code Playgroud)

宝石文件:

source 'http://rubygems.org'

gem 'rails', '3.2.13'   
gem 'mysql2'
gem 'dragonfly', '>= 0.9.14'
gem 'rack-cache', :require => 'rack/cache'
gem 'will_paginate'
gem 'dynamic_form'
gem 'amazon_product' # for looking up Amazon ASIN codes of books
gem 'geoip'
gem 'mobile-fu'
gem 'airbrake'
gem 'newrelic_rpm'
gem 'bartt-ssl_requirement', '~>1.4.0', :require => 'ssl_requirement'
gem 'dalli' # memcache for api_cache
gem 'api_cache'
gem 'daemons'
gem 'delayed_job_active_record'
gem 'attr_encrypted'
gem 'rest-client'
gem 'json', '>= 1.7.7'
gem 'carrierwave' # simplify file uploads
gem 'net-scp'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'therubyracer'
  gem 'sass-rails',   '~> 3.2.3'
  gem 'compass', '~> 0.12.alpha'
  gem 'uglifier', '>= 1.0.3'
  gem 'jquery-fileupload-rails'
end

gem 'jquery-rails'
gem 'api_bee', :git => 'git://github.com/ismasan/ApiBee.git', :ref => '3cff959fea5963cf46b3d5730d68927cebcc59a8'
gem 'httparty', '>= 0.10.2'
gem 'twitter'

# Auth providers
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
gem 'omniauth-identity'
gem 'omniauth-readmill'
gem 'bcrypt-ruby', "~> 3.0.0" # required for omniauth-identity
gem 'mail_view'

# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'

# Deploy with Capistrano
group :development do
  gem 'capistrano'
  gem 'capistrano-ext'
  gem 'capistrano_colors'
  gem 'rvm-capistrano'

  # requirement for Hoof, Linux equivalent of Pow
  gem 'unicorn'
end

group :test, :development do  
  gem 'rspec-rails'
  gem 'pry'
  gem 'pry-rails'
end

group :test do
  gem 'factory_girl_rails'
  gem 'capybara'
  gem 'cucumber-rails'
  gem 'database_cleaner'
  gem 'launchy'
  gem 'ruby-debug19'
  # Pretty printed test output
  gem 'shoulda-matchers'
  gem 'simplecov', :require => false
  gem 'email_spec'
  gem 'show_me_the_cookies'
  gem 'vcr'
  gem 'webmock', '1.6'
end
Run Code Online (Sandbox Code Playgroud)

Gla*_*eep 33

这可能不能完全回答你的问题,但我相信这是一个不错的开始.正如您将看到的,具体答案取决于具体应用,宝石版本等.

所以.对于与资产相关的工作,如你所知,Rails使用一个名为Sprockets的库,我相信在较新版本的Rails中,它作为一个Railtie挂钩到Rails.它初始化一个Sprockets"环境",它可以执行诸如查看资产清单,加载这些文件,压缩它们,为编译后的资产提供合理名称等操作.

默认情况下,它将Sprockets::Environment其活动记录到STDERR日志级别FATAL,这在这些情况下不是很有用.幸运的是,Sprockets::Environment(截至2.2.2)具有可写的logger属性,您可以使用初始化程序通过Rails进行修补.


所以,这就是我的建议,开始:

config/initializers,创建一个文件,如asset_logging.rb.在其中,放:

Rails.application.config.assets.logger = Logger.new($stdout)
Run Code Online (Sandbox Code Playgroud)

这将覆盖默认记录器,其中一个会向更多信息吐出STDOUT.完成此设置后,运行资产预编译任务:

rake RAILS_ENV=production assets:precompile
Run Code Online (Sandbox Code Playgroud)

你应该看到稍微更有趣的输出,如:

...
Compiled jquery.ui.core.js  (0ms)  (pid 66524)
Compiled jquery.ui.widget.js  (0ms)  (pid 66524)
Compiled jquery.ui.accordion.js  (10ms)  (pid 66524)
...
Run Code Online (Sandbox Code Playgroud)

但是,最终,最终答案将取决于:

  • 你想要记录这些资产的东西是多么"深刻"
  • 您正在使用的Rails,Sprockets等具体版本
  • 以及你在路上发现的东西

正如您已经了解到的那样,在Rake任务级别或甚至在Rails级别上记录日志,并没有提供太多信息.甚至使Sprockets本身冗长(见上文)并没有告诉你太多太多.

如果你想要比Sprockets更深入,你可以修补Sprockets整齐地链接在一起的各种引擎和处理器,以使资产管道工作.例如,您可以查看这些组件的日志记录功能:

  • Sass::Engine (将SASS转换为CSS)
  • Uglifier (JavaScript压缩包装器)
  • ExecJS (在Ruby中运行JavaScript; Sprockets和Uglifier的依赖关系)
  • therubyracer(嵌入在Ruby中的V8;用于ExecJS)
  • 等等

但我会把所有这些都视为"读者的练习".如果有一颗银弹,我当然想知道它!

  • 在最近的rails版本中,你必须添加一个额外的`.assets`:`Rails.application.config.assets.logger = Logger.new($ stdout)` (9认同)
  • 我无法获得那些"有趣的输出",其他人有运气吗? (3认同)

pho*_*oet 2

这个问题有很多可能的原因。

出于可能的原因,我想知道在您上次部署的几个环境中编译资产的时间是如何增加的。这可能表明问题是否仅存在于环境上或资产编译本身内。你可以用git bisect它。我通常通过 jenkins 或其他 ci 系统将我的应用程序部署到暂存阶段,以便我可以看到部署时间和引入时间的任何变化。

它可能归结为对 CPU、内存(任何交换?)、IO 资源的大量使用。如果您在生产系统上编译资产,它们可能会忙于满足您的应用程序请求。转到您的系统,top对资源进行操作,也许同时有太多文件句柄(lsof对此有好处)。

另一件事可能是您为应用程序加载或缓存一些数据。暂存和生产环境中的数据库通常比开发环境中的数据库大得多。你可以Rails.logger在你的初始化器中添加一些调用或者其他什么。