rails_autoscale_agent 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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