rails_autoscale_agent 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 355209571af7f1f6c5fbe38ed112a2936903afcdd9cff56b9c68531be45c8d5e
4
- data.tar.gz: 9855ff92fcc103546a4fe3115942b4e94e0c5e3e626d6b14b26c1eeab0953bc8
3
+ metadata.gz: ecfcd2368450e449cfcb8b644621a19cd5f8d14a5d25bd2f6f85383c2e372b83
4
+ data.tar.gz: 61008635232925e88adaea78ecd33514e282dc678e5b6a8c46365bc5636402da
5
5
  SHA512:
6
- metadata.gz: 1e76f3c52649b97c5919ce06170414e0d4f2642bf133f781ee3e54e842f77e0264c37f625e1f4c1c681ce79aec162bb7749d133a663e7d580cfc5385813f591b
7
- data.tar.gz: c312bfb9654d06c25be2bda4da119e74e547fc24f0b71cd412fdeef63665154b37bf013beaae9ad1c4fb169469c0bff4cc0a601942bbe845bb739708ecc0d426
6
+ metadata.gz: 3ddbc592abd1d260081e764f5628a33d6c984d55229cde0da911fa4d56c49d45dc2cb8e89486882851bfc6041bca7797d6d3fea709b699edeb7c687bb4aa0adc
7
+ data.tar.gz: 47f1a329b95e49566725ba73e0266645a9966d657c5e64c7afec536a02fde4fcc6c3f258ccf373db909f47698ab47bac7bd1471b373485e31ae716436b267542
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/adamlogic/rails_autoscale_agent.svg?branch=master)](https://travis-ci.org/adamlogic/rails_autoscale_agent)
4
4
 
5
- This gem works together with the [Rails Autoscale](https://railsautoscale.com) Heroku add-on to automatically scale your web dynos as needed. It gathers a minimal set of metrics for each request, and periodically posts this data asynchronously to the Rails Autoscale service.
5
+ This gem works together with the [Rails Autoscale](https://railsautoscale.com) Heroku add-on to automatically scale your web and worker dynos as needed. It gathers a minimal set of metrics for each request (and job queue), and periodically posts this data asynchronously to the Rails Autoscale service.
6
6
 
7
7
  ## Requirements
8
8
 
@@ -26,9 +26,11 @@ You'll need to insert the `RailsAutoscaleAgent::Middleware` manually. Insert it
26
26
 
27
27
  ## Changing the logger
28
28
 
29
+ The Rails logger is used by default.
29
30
  If you wish to use a different logger you can set it on the configuration object:
30
31
 
31
32
  ```ruby
33
+ # config/initializers/rails_autoscale_agent.rb
32
34
  RailsAutoscaleAgent::Config.instance.logger = MyLogger.new
33
35
  ```
34
36
 
@@ -43,22 +45,29 @@ The middleware agent runs in its own thread so your web requests are not impacte
43
45
  - PID
44
46
  - Collection of queue time measurements (time and milliseconds)
45
47
 
46
- Rails Autoscale processes and stores this information in order to power the autoscaling algorithm and dashboard visualizations.
48
+ Rails Autoscale aggregates and stores this information to power the autoscaling algorithm and dashboard visualizations.
47
49
 
48
50
  ## Troubleshooting
49
51
 
50
- If your logger supports tagged logging (as the Rails logger does by default), all log output from this gem is prefixed with "[RailsAutoscale]".
51
-
52
- Once installed, you should see something like this in development:
52
+ Once installed, you should see something like this in your development log:
53
53
 
54
54
  > [RailsAutoscale] Reporter not started: RAILS_AUTOSCALE_URL is not set
55
55
 
56
- In production, you should see something like this:
56
+ In production, run `heroku logs -t | grep RailsAutoscale`, and you should see something like this:
57
57
 
58
58
  > [RailsAutoscale] Reporter starting, will report every 15 seconds
59
59
 
60
60
  If you don't see either of these, try running `bundle` again and restarting your Rails application.
61
61
 
62
+ You can see more detailed (debug) logging by setting the `RAILS_AUTOSCALE_DEBUG` env var on your Heroku app:
63
+
64
+ ```
65
+ heroku config:add RAILS_AUTOSCALE_DEBUG=true
66
+ ```
67
+
68
+ Debug logs are silenced by default because Rails apps default to a DEBUG log level in production,
69
+ and these can get very noisy with this gem.
70
+
62
71
  Reach out to help@railsautoscale.com if you run into any other problems.
63
72
 
64
73
  ## Development
@@ -10,6 +10,15 @@ module RailsAutoscaleAgent
10
10
  :dyno, :pid, :addon_name, :worker_adapters
11
11
 
12
12
  def initialize
13
+ require 'rails_autoscale_agent/worker_adapters/sidekiq'
14
+ require 'rails_autoscale_agent/worker_adapters/delayed_job'
15
+ require 'rails_autoscale_agent/worker_adapters/que'
16
+ @worker_adapters = [
17
+ WorkerAdapters::Sidekiq.instance,
18
+ WorkerAdapters::DelayedJob.instance,
19
+ WorkerAdapters::Que.instance,
20
+ ]
21
+
13
22
  # Allow the add-on name to be configured - needed for testing
14
23
  @addon_name = ENV['RAILS_AUTOSCALE_ADDON'] || 'RAILS_AUTOSCALE'
15
24
  @api_base_url = ENV["#{@addon_name}_URL"]
@@ -18,9 +27,6 @@ module RailsAutoscaleAgent
18
27
  @report_interval = 60 # this default will be overwritten during Reporter#register!
19
28
  @logger ||= defined?(Rails) ? Rails.logger : ::Logger.new(STDOUT)
20
29
  @dyno = ENV['DYNO']
21
- @worker_adapters = [
22
- WorkerAdapters::Sidekiq.new,
23
- ]
24
30
  end
25
31
 
26
32
  def to_s
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/core_ext/module/delegation'
4
3
  require 'rails_autoscale_agent/config'
4
+ require 'logger'
5
5
 
6
6
  module RailsAutoscaleAgent
7
7
  module Logger
@@ -11,23 +11,30 @@ module RailsAutoscaleAgent
11
11
  end
12
12
 
13
13
  class LoggerProxy < Struct.new(:logger)
14
- def tagged(*tags, &block)
15
- if logger.respond_to?(:tagged)
16
- logger.tagged *tags, &block
17
- else
18
- # NOTE: Quack like ActiveSupport::TaggedLogging, but don't reimplement
19
- yield self
14
+ TAG = '[RailsAutoscale]'
15
+
16
+ %w[info warn error].each do |name|
17
+ define_method name do |msg|
18
+ logger.send name, tag(msg)
20
19
  end
21
20
  end
22
21
 
23
- def debug(*args)
24
- # Rails logger defaults to DEBUG level in production, but I don't want
25
- # to be chatty by default.
26
- logger.debug(*args) if ENV['RAILS_AUTOSCALE_LOG_LEVEL'] == 'DEBUG'
22
+ def debug(msg)
23
+ # Silence debug logs by default to avoiding being overly chatty (Rails logger defaults
24
+ # to DEBUG level in production).
25
+ # This uses a separate logger so that RAILS_AUTOSCALE_DEBUG
26
+ # shows debug logs regardless of Rails log level.
27
+ debug_logger.debug tag(msg) if ENV['RAILS_AUTOSCALE_DEBUG'] == 'true'
28
+ end
29
+
30
+ private
31
+
32
+ def debug_logger
33
+ @debug_loggers ||= ::Logger.new(STDOUT)
27
34
  end
28
35
 
29
- def method_missing(name, *args, &block)
30
- logger.send name, *args, &block
36
+ def tag(msg)
37
+ "#{TAG} #{msg}"
31
38
  end
32
39
  end
33
40
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails_autoscale_agent/logger'
4
3
  require 'rails_autoscale_agent/store'
5
4
  require 'rails_autoscale_agent/reporter'
6
5
  require 'rails_autoscale_agent/config'
@@ -8,25 +7,21 @@ require 'rails_autoscale_agent/request'
8
7
 
9
8
  module RailsAutoscaleAgent
10
9
  class Middleware
11
- include Logger
12
-
13
10
  def initialize(app)
14
11
  @app = app
15
12
  end
16
13
 
17
14
  def call(env)
18
- logger.tagged 'RailsAutoscale' do
19
- config = Config.instance
20
- request = Request.new(env, config)
15
+ config = Config.instance
16
+ request = Request.new(env, config)
21
17
 
22
- store = Store.instance
23
- Reporter.start(config, store)
18
+ store = Store.instance
19
+ Reporter.start(config, store)
24
20
 
25
- if !request.ignore? && queue_time = request.queue_time
26
- # NOTE: Expose queue time to the app
27
- env['queue_time'] = queue_time
28
- store.push queue_time
29
- end
21
+ if !request.ignore? && queue_time = request.queue_time
22
+ # NOTE: Expose queue time to the app
23
+ env['queue_time'] = queue_time
24
+ store.push queue_time
30
25
  end
31
26
 
32
27
  @app.call(env)
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails_autoscale_agent/middleware'
4
+ require 'rails_autoscale_agent/logger'
4
5
 
5
6
  module RailsAutoscaleAgent
6
7
  class Railtie < Rails::Railtie
8
+ include Logger
9
+
7
10
  initializer "rails_autoscale_agent.middleware" do |app|
11
+ logger.info "Preparing middleware"
8
12
  app.middleware.insert_before Rack::Runtime, Middleware
9
13
  end
10
14
  end
@@ -5,7 +5,6 @@ require 'rails_autoscale_agent/logger'
5
5
  require 'rails_autoscale_agent/autoscale_api'
6
6
  require 'rails_autoscale_agent/time_rounder'
7
7
  require 'rails_autoscale_agent/registration'
8
- require 'rails_autoscale_agent/worker_adapters/sidekiq'
9
8
 
10
9
  # Reporter wakes up every minute to send metrics to the RailsAutoscale API
11
10
 
@@ -28,23 +27,21 @@ module RailsAutoscaleAgent
28
27
  end
29
28
 
30
29
  Thread.new do
31
- logger.tagged 'RailsAutoscale' do
32
- register!(config)
33
-
34
- loop do
35
- # Stagger reporting to spread out reports from many processes
36
- multiplier = 1 - (rand / 4) # between 0.75 and 1.0
37
- sleep config.report_interval * multiplier
38
-
39
- begin
40
- @worker_adapters.map { |a| a.collect!(store) }
41
- report!(config, store)
42
- rescue => ex
43
- # Exceptions in threads other than the main thread will fail silently
44
- # https://ruby-doc.org/core-2.2.0/Thread.html#class-Thread-label-Exception+handling
45
- logger.error "Reporter error: #{ex.inspect}"
46
- logger.error ex.backtrace.join("\n")
47
- end
30
+ loop do
31
+ register!(config) unless @registered
32
+
33
+ # Stagger reporting to spread out reports from many processes
34
+ multiplier = 1 - (rand / 4) # between 0.75 and 1.0
35
+ sleep config.report_interval * multiplier
36
+
37
+ begin
38
+ @worker_adapters.map { |a| a.collect!(store) }
39
+ report!(config, store)
40
+ rescue => ex
41
+ # Exceptions in threads other than the main thread will fail silently
42
+ # https://ruby-doc.org/core-2.2.0/Thread.html#class-Thread-label-Exception+handling
43
+ logger.error "Reporter error: #{ex.inspect}"
44
+ logger.error ex.backtrace.join("\n")
48
45
  end
49
46
  end
50
47
  end
@@ -80,6 +77,7 @@ module RailsAutoscaleAgent
80
77
 
81
78
  case result
82
79
  when AutoscaleApi::SuccessResponse
80
+ @registered = true
83
81
  config.report_interval = result.data['report_interval'] if result.data['report_interval']
84
82
  config.max_request_size = result.data['max_request_size'] if result.data['max_request_size']
85
83
  worker_adapters_msg = @worker_adapters.map { |a| a.class.name }.join(', ')
@@ -88,6 +86,5 @@ module RailsAutoscaleAgent
88
86
  logger.error "Reporter failed to register: #{result.failure_message}"
89
87
  end
90
88
  end
91
-
92
89
  end
93
90
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAutoscaleAgent
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_autoscale_agent/logger'
4
+
5
+ module WorkerAdapters
6
+ class DelayedJob
7
+ include RailsAutoscaleAgent::Logger
8
+ include Singleton
9
+
10
+ class << self
11
+ attr_accessor :queues
12
+ end
13
+
14
+ def initialize
15
+ # Track the known queues so we can continue reporting on queues that don't
16
+ # currently have enqueued jobs.
17
+ self.class.queues = Set.new
18
+
19
+ install if enabled?
20
+ end
21
+
22
+ def enabled?
23
+ defined? ::Delayed
24
+ end
25
+
26
+ def collect!(store)
27
+ log_msg = String.new('DelayedJob latency ')
28
+ t = Time.now
29
+
30
+ sql = 'SELECT queue, min(run_at) FROM delayed_jobs GROUP BY queue'
31
+ run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
32
+ queues = self.class.queues | run_at_by_queue.keys
33
+
34
+ queues.each do |queue|
35
+ run_at = run_at_by_queue[queue]
36
+ run_at = Time.parse(run_at) if run_at.is_a?(String)
37
+ latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
38
+ store.push latency_ms, t, queue
39
+ log_msg << "#{queue}=#{latency_ms} "
40
+ end
41
+
42
+ logger.debug log_msg
43
+ end
44
+
45
+ private
46
+
47
+ def install
48
+ plugin = Class.new(Delayed::Plugin) do
49
+ require 'delayed_job'
50
+
51
+ callbacks do |lifecycle|
52
+ lifecycle.before(:enqueue) do |job, &block|
53
+ queue = job.queue || 'default'
54
+ WorkerAdapters::DelayedJob.queues.add queue
55
+ end
56
+ end
57
+ end
58
+
59
+ Delayed::Worker.plugins << plugin
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_autoscale_agent/logger'
4
+ require 'time'
5
+
6
+ module WorkerAdapters
7
+ class Que
8
+ include RailsAutoscaleAgent::Logger
9
+ include Singleton
10
+
11
+ DEFAULT_QUEUES = ['default']
12
+
13
+ class << self
14
+ attr_accessor :queues
15
+ end
16
+
17
+ def initialize
18
+ self.class.queues = DEFAULT_QUEUES
19
+ end
20
+
21
+ def enabled?
22
+ defined? ::Que
23
+ end
24
+
25
+ def collect!(store)
26
+ log_msg = String.new('Que latency ')
27
+ t = Time.now
28
+
29
+ sql = 'SELECT queue, min(run_at) FROM que_jobs GROUP BY queue'
30
+ run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
31
+ self.class.queues |= run_at_by_queue.keys
32
+
33
+ self.class.queues.each do |queue|
34
+ run_at = run_at_by_queue[queue]
35
+ run_at = Time.parse(run_at) if run_at.is_a?(String)
36
+ latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
37
+ store.push latency_ms, t, queue
38
+ log_msg << "#{queue}=#{latency_ms} "
39
+ end
40
+
41
+ logger.debug log_msg
42
+ end
43
+ end
44
+ end
@@ -5,6 +5,7 @@ require 'rails_autoscale_agent/logger'
5
5
  module WorkerAdapters
6
6
  class Sidekiq
7
7
  include RailsAutoscaleAgent::Logger
8
+ include Singleton
8
9
 
9
10
  def enabled?
10
11
  require 'sidekiq/api'
@@ -13,7 +14,6 @@ module WorkerAdapters
13
14
  false
14
15
  end
15
16
 
16
- # TODO: specs
17
17
  def collect!(store)
18
18
  log_msg = String.new('Sidekiq latency ')
19
19
 
@@ -16,14 +16,15 @@ Gem::Specification.new do |spec|
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.add_dependency "activesupport", ">= 3.2"
20
-
21
19
  spec.add_development_dependency "bundler", "~> 2.0"
22
- spec.add_development_dependency "rake", "~> 10.0"
20
+ spec.add_development_dependency "rake", "~> 12.3.3"
23
21
  spec.add_development_dependency "rspec", "~> 3.0"
24
22
  spec.add_development_dependency "vcr", "~> 3.0"
25
23
  spec.add_development_dependency "webmock"
26
- spec.add_development_dependency "pry"
24
+ # pry 0.13.0 seems to be incompatible with pry-byebug 3.8.0
25
+ spec.add_development_dependency "pry", "0.12.2"
27
26
  spec.add_development_dependency "pry-byebug"
28
27
  spec.add_development_dependency "sidekiq", "~> 5.0"
28
+ spec.add_development_dependency "delayed_job"
29
+ spec.add_development_dependency "que"
29
30
  end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_autoscale_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam McCrea
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-04 00:00:00.000000000 Z
11
+ date: 2020-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '3.2'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '3.2'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +30,14 @@ dependencies:
44
30
  requirements:
45
31
  - - "~>"
46
32
  - !ruby/object:Gem::Version
47
- version: '10.0'
33
+ version: 12.3.3
48
34
  type: :development
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
52
38
  - - "~>"
53
39
  - !ruby/object:Gem::Version
54
- version: '10.0'
40
+ version: 12.3.3
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -98,16 +84,16 @@ dependencies:
98
84
  name: pry
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
- - - ">="
87
+ - - '='
102
88
  - !ruby/object:Gem::Version
103
- version: '0'
89
+ version: 0.12.2
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - ">="
94
+ - - '='
109
95
  - !ruby/object:Gem::Version
110
- version: '0'
96
+ version: 0.12.2
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: pry-byebug
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +122,34 @@ dependencies:
136
122
  - - "~>"
137
123
  - !ruby/object:Gem::Version
138
124
  version: '5.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: delayed_job
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: que
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  description:
140
154
  email:
141
155
  - adam@adamlogic.com
@@ -167,6 +181,8 @@ files:
167
181
  - lib/rails_autoscale_agent/store.rb
168
182
  - lib/rails_autoscale_agent/time_rounder.rb
169
183
  - lib/rails_autoscale_agent/version.rb
184
+ - lib/rails_autoscale_agent/worker_adapters/delayed_job.rb
185
+ - lib/rails_autoscale_agent/worker_adapters/que.rb
170
186
  - lib/rails_autoscale_agent/worker_adapters/sidekiq.rb
171
187
  - log/.gitkeep
172
188
  - rails_autoscale_agent.gemspec