rails_autoscale_agent 0.8.3 → 0.9.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 +4 -4
- data/.vscode/tasks.json +6 -5
- data/Gemfile +13 -1
- data/README.md +26 -14
- data/lib/rails_autoscale_agent/autoscale_api.rb +12 -3
- data/lib/rails_autoscale_agent/config.rb +10 -3
- data/lib/rails_autoscale_agent/logger.rb +19 -12
- data/lib/rails_autoscale_agent/measurement.rb +5 -3
- data/lib/rails_autoscale_agent/registration.rb +3 -1
- data/lib/rails_autoscale_agent/report.rb +6 -8
- data/lib/rails_autoscale_agent/reporter.rb +8 -8
- data/lib/rails_autoscale_agent/request.rb +16 -11
- data/lib/rails_autoscale_agent/store.rb +2 -2
- data/lib/rails_autoscale_agent/version.rb +1 -1
- data/lib/rails_autoscale_agent/worker_adapters/delayed_job.rb +45 -52
- data/lib/rails_autoscale_agent/worker_adapters/que.rb +45 -34
- data/lib/rails_autoscale_agent/worker_adapters/resque.rb +43 -0
- data/lib/rails_autoscale_agent/worker_adapters/sidekiq.rb +39 -18
- data/rails_autoscale_agent.gemspec +0 -12
- metadata +5 -158
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd7ffb8cd27de3a23d5e76bd9a9c7768ce1fd397389291285022523fb398ee45
|
4
|
+
data.tar.gz: d8cf3fa7ab2994ef196742fe872170c069dc6f14bc0490b595ff175e48ce59e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0976e83784dd6a622b18a67491e727cbe9f0fb1a54e1974f50b61f441f642a296b5a4e3349e2f4d292acc35c844765527c16e097cf7ac914a7224b68e2ae8bd6'
|
7
|
+
data.tar.gz: 2de7d8a61d44fd408cfbab88b1be37327bd5836da8c125f4c61f80ac7503c0a44c5ee5f893919eb5487e4a34a817102a68aabd76699d34d6ce3086680b8aceb7
|
data/.vscode/tasks.json
CHANGED
@@ -6,7 +6,8 @@
|
|
6
6
|
{
|
7
7
|
"label": "test: all",
|
8
8
|
"group": "test",
|
9
|
-
"command": "
|
9
|
+
"command": "bundle",
|
10
|
+
"args": ["exec", "rspec"],
|
10
11
|
"runOptions": {
|
11
12
|
"reevaluateOnRerun": false
|
12
13
|
},
|
@@ -31,8 +32,8 @@
|
|
31
32
|
{
|
32
33
|
"label": "test: file",
|
33
34
|
"group": "test",
|
34
|
-
"command": "
|
35
|
-
"args": ["${relativeFile}"],
|
35
|
+
"command": "bundle",
|
36
|
+
"args": ["exec", "rspec", "${relativeFile}"],
|
36
37
|
"runOptions": {
|
37
38
|
"reevaluateOnRerun": false
|
38
39
|
},
|
@@ -57,8 +58,8 @@
|
|
57
58
|
{
|
58
59
|
"label": "test: line",
|
59
60
|
"group": "test",
|
60
|
-
"command": "
|
61
|
-
"args": ["${relativeFile}:${lineNumber}"],
|
61
|
+
"command": "bundle",
|
62
|
+
"args": ["exec", "rspec", "${relativeFile}:${lineNumber}"],
|
62
63
|
"runOptions": {
|
63
64
|
"reevaluateOnRerun": false
|
64
65
|
},
|
data/Gemfile
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in rails_autoscale_agent.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
gem "rake", ">= 12.3.3"
|
6
|
+
gem "rspec", ">= 3.0"
|
7
|
+
gem "vcr", ">= 3.0"
|
8
|
+
gem "webmock"
|
9
|
+
gem "pry-byebug"
|
10
|
+
gem "sidekiq", ">= 5.0"
|
11
|
+
gem "delayed_job"
|
12
|
+
gem "delayed_job_active_record"
|
13
|
+
gem "que"
|
14
|
+
gem "resque"
|
15
|
+
gem "activesupport"
|
16
|
+
gem "sqlite3", platforms: :ruby
|
data/README.md
CHANGED
@@ -24,16 +24,6 @@ The agent will only communicate with Rails Autoscale if a `RAILS_AUTOSCALE_URL`
|
|
24
24
|
|
25
25
|
You'll need to insert the `RailsAutoscaleAgent::Middleware` manually. Insert it before `Rack::Runtime` to ensure accuracy of request queue timings.
|
26
26
|
|
27
|
-
## Changing the logger
|
28
|
-
|
29
|
-
The Rails logger is used by default.
|
30
|
-
If you wish to use a different logger you can set it on the configuration object:
|
31
|
-
|
32
|
-
```ruby
|
33
|
-
# config/initializers/rails_autoscale_agent.rb
|
34
|
-
RailsAutoscaleAgent::Config.instance.logger = MyLogger.new
|
35
|
-
```
|
36
|
-
|
37
27
|
## What data is collected?
|
38
28
|
|
39
29
|
The middleware agent runs in its own thread so your web requests are not impacted. The following data is submitted periodically to the Rails Autoscale API:
|
@@ -59,22 +49,44 @@ In production, run `heroku logs -t | grep RailsAutoscale`, and you should see so
|
|
59
49
|
|
60
50
|
If you don't see either of these, try running `bundle` again and restarting your Rails application.
|
61
51
|
|
62
|
-
You can see more detailed (debug) logging by setting
|
52
|
+
You can see more detailed (debug) logging by setting `RAILS_AUTOSCALE_DEBUG` on your Heroku app:
|
63
53
|
|
64
54
|
```
|
65
55
|
heroku config:add RAILS_AUTOSCALE_DEBUG=true
|
66
56
|
```
|
67
57
|
|
68
|
-
|
69
|
-
and these can get very noisy with this gem.
|
58
|
+
See more in the [logging](#logging) section below.
|
70
59
|
|
71
60
|
Reach out to help@railsautoscale.com if you run into any other problems.
|
72
61
|
|
62
|
+
## Logging
|
63
|
+
|
64
|
+
The Rails logger is used by default.
|
65
|
+
If you wish to use a different logger you can set it on the configuration object:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
# config/initializers/rails_autoscale_agent.rb
|
69
|
+
RailsAutoscaleAgent::Config.instance.logger = MyLogger.new
|
70
|
+
```
|
71
|
+
|
72
|
+
Debug logs are silenced by default because Rails apps default to a DEBUG log level in production, and this gem has _very_ chatty debug logs. If you want to see the debug logs, set `RAILS_AUTOSCALE_DEBUG` on your Heroku app:
|
73
|
+
|
74
|
+
```
|
75
|
+
heroku config:add RAILS_AUTOSCALE_DEBUG=true
|
76
|
+
```
|
77
|
+
|
78
|
+
If you find the gem too chatty even without this, you can quiet it down further:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# config/initializers/rails_autoscale_agent.rb
|
82
|
+
RailsAutoscaleAgent::Config.instance.quiet = true
|
83
|
+
```
|
84
|
+
|
73
85
|
## Development
|
74
86
|
|
75
87
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
76
88
|
|
77
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
89
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, commit it, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
78
90
|
|
79
91
|
## Contributing
|
80
92
|
|
@@ -11,8 +11,8 @@ module RailsAutoscaleAgent
|
|
11
11
|
|
12
12
|
SUCCESS = 'success'
|
13
13
|
|
14
|
-
def initialize(
|
15
|
-
@
|
14
|
+
def initialize(config)
|
15
|
+
@config = config
|
16
16
|
end
|
17
17
|
|
18
18
|
def report_metrics!(report_params, timings_csv)
|
@@ -24,6 +24,10 @@ module RailsAutoscaleAgent
|
|
24
24
|
post_json '/registrations', registration: registration_params
|
25
25
|
end
|
26
26
|
|
27
|
+
def report_exception!(ex)
|
28
|
+
post_json '/exceptions', message: ex.inspect, backtrace: ex.backtrace.join("\n")
|
29
|
+
end
|
30
|
+
|
27
31
|
private
|
28
32
|
|
29
33
|
def post_json(path, data)
|
@@ -37,9 +41,14 @@ module RailsAutoscaleAgent
|
|
37
41
|
end
|
38
42
|
|
39
43
|
def post_raw(options)
|
40
|
-
uri = URI.parse("#{@
|
44
|
+
uri = URI.parse("#{@config.api_base_url}#{options.fetch(:path)}")
|
41
45
|
ssl = uri.scheme == 'https'
|
42
46
|
|
47
|
+
if @config.dev_mode
|
48
|
+
logger.debug "[DEV_MODE] Skipping request to #{uri}"
|
49
|
+
return SuccessResponse.new('{}')
|
50
|
+
end
|
51
|
+
|
43
52
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http|
|
44
53
|
request = Net::HTTP::Post.new(uri.request_uri, options[:headers] || {})
|
45
54
|
request.body = options.fetch(:body)
|
@@ -7,26 +7,30 @@ module RailsAutoscaleAgent
|
|
7
7
|
include Singleton
|
8
8
|
|
9
9
|
attr_accessor :report_interval, :logger, :api_base_url, :max_request_size,
|
10
|
-
:dyno, :pid, :addon_name, :worker_adapters
|
10
|
+
:dyno, :pid, :addon_name, :worker_adapters, :dev_mode, :debug, :quiet
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
require 'rails_autoscale_agent/worker_adapters/sidekiq'
|
14
14
|
require 'rails_autoscale_agent/worker_adapters/delayed_job'
|
15
15
|
require 'rails_autoscale_agent/worker_adapters/que'
|
16
|
+
require 'rails_autoscale_agent/worker_adapters/resque'
|
16
17
|
@worker_adapters = [
|
17
18
|
WorkerAdapters::Sidekiq.instance,
|
18
19
|
WorkerAdapters::DelayedJob.instance,
|
19
20
|
WorkerAdapters::Que.instance,
|
21
|
+
WorkerAdapters::Resque.instance,
|
20
22
|
]
|
21
23
|
|
22
24
|
# Allow the add-on name to be configured - needed for testing
|
23
25
|
@addon_name = ENV['RAILS_AUTOSCALE_ADDON'] || 'RAILS_AUTOSCALE'
|
24
26
|
@api_base_url = ENV["#{@addon_name}_URL"]
|
27
|
+
@dev_mode = ENV['RAILS_AUTOSCALE_DEV'] == 'true'
|
28
|
+
@debug = dev_mode? || ENV['RAILS_AUTOSCALE_DEBUG'] == 'true'
|
25
29
|
@pid = Process.pid
|
26
30
|
@max_request_size = 100_000 # ignore request payloads over 100k since they skew the queue times
|
27
|
-
@report_interval =
|
31
|
+
@report_interval = 10 # this default will be overwritten during Reporter#register!
|
28
32
|
@logger ||= defined?(Rails) ? Rails.logger : ::Logger.new(STDOUT)
|
29
|
-
@dyno = ENV['DYNO']
|
33
|
+
@dyno = dev_mode? ? 'dev.1' : ENV['DYNO']
|
30
34
|
end
|
31
35
|
|
32
36
|
def to_s
|
@@ -37,5 +41,8 @@ module RailsAutoscaleAgent
|
|
37
41
|
@max_request_size
|
38
42
|
end
|
39
43
|
|
44
|
+
alias_method :dev_mode?, :dev_mode
|
45
|
+
alias_method :debug?, :debug
|
46
|
+
alias_method :quiet?, :quiet
|
40
47
|
end
|
41
48
|
end
|
@@ -13,26 +13,33 @@ module RailsAutoscaleAgent
|
|
13
13
|
class LoggerProxy < Struct.new(:logger)
|
14
14
|
TAG = '[RailsAutoscale]'
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def error(msg)
|
17
|
+
logger.error tag(msg)
|
18
|
+
end
|
19
|
+
|
20
|
+
def warn(msg)
|
21
|
+
logger.warn tag(msg)
|
22
|
+
end
|
23
|
+
|
24
|
+
def info(msg)
|
25
|
+
logger.info tag(msg) unless Config.instance.quiet?
|
20
26
|
end
|
21
27
|
|
22
28
|
def debug(msg)
|
23
29
|
# Silence debug logs by default to avoiding being overly chatty (Rails logger defaults
|
24
|
-
# to DEBUG level in production).
|
25
|
-
#
|
26
|
-
|
27
|
-
|
30
|
+
# to DEBUG level in production). Setting RAILS_AUTOSCALE_DEBUG=true enables debug logs,
|
31
|
+
# even if the underlying logger severity level is INFO.
|
32
|
+
if Config.instance.debug?
|
33
|
+
if logger.respond_to?(:debug?) && logger.debug?
|
34
|
+
logger.debug tag(msg)
|
35
|
+
elsif logger.respond_to?(:info?) && logger.info?
|
36
|
+
logger.info tag("[DEBUG] #{msg}")
|
37
|
+
end
|
38
|
+
end
|
28
39
|
end
|
29
40
|
|
30
41
|
private
|
31
42
|
|
32
|
-
def debug_logger
|
33
|
-
@debug_loggers ||= ::Logger.new(STDOUT)
|
34
|
-
end
|
35
|
-
|
36
43
|
def tag(msg)
|
37
44
|
"#{TAG} #{msg}"
|
38
45
|
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RailsAutoscaleAgent
|
4
|
-
class Measurement < Struct.new(:time, :value, :queue_name)
|
5
|
-
|
6
|
-
|
4
|
+
class Measurement < Struct.new(:time, :value, :queue_name, :metric)
|
5
|
+
# No queue_name is assumed to be a web request measurement
|
6
|
+
# Metrics: qt = queue time (default), qd = queue depth (needed for Resque support)
|
7
|
+
def initialize(time, value, queue_name = nil, metric = nil)
|
8
|
+
super time.utc, value.to_i, queue_name, metric
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'rails_autoscale_agent/version'
|
4
4
|
|
5
5
|
module RailsAutoscaleAgent
|
6
|
-
class Registration < Struct.new(:config)
|
6
|
+
class Registration < Struct.new(:config, :worker_adapters)
|
7
7
|
|
8
8
|
def to_params
|
9
9
|
{
|
@@ -12,6 +12,8 @@ module RailsAutoscaleAgent
|
|
12
12
|
ruby_version: RUBY_VERSION,
|
13
13
|
rails_version: defined?(Rails) && Rails.version,
|
14
14
|
gem_version: VERSION,
|
15
|
+
# example: { worker_adapters: 'Sidekiq,Que' }
|
16
|
+
worker_adapters: worker_adapters.map { |o| o.class.name.split('::').last }.join(','),
|
15
17
|
}
|
16
18
|
end
|
17
19
|
end
|
@@ -19,14 +19,12 @@ module RailsAutoscaleAgent
|
|
19
19
|
def to_csv
|
20
20
|
String.new.tap do |result|
|
21
21
|
@measurements.each do |measurement|
|
22
|
-
result <<
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
result << measurement.queue_name
|
29
|
-
end
|
22
|
+
result << [
|
23
|
+
measurement.time.to_i,
|
24
|
+
measurement.value,
|
25
|
+
measurement.queue_name,
|
26
|
+
measurement.metric,
|
27
|
+
].join(',')
|
30
28
|
|
31
29
|
result << "\n"
|
32
30
|
end
|
@@ -21,14 +21,14 @@ module RailsAutoscaleAgent
|
|
21
21
|
@started = true
|
22
22
|
@worker_adapters = config.worker_adapters.select(&:enabled?)
|
23
23
|
|
24
|
-
if !config.api_base_url
|
24
|
+
if !config.api_base_url && !config.dev_mode?
|
25
25
|
logger.info "Reporter not started: #{config.addon_name}_URL is not set"
|
26
26
|
return
|
27
27
|
end
|
28
28
|
|
29
29
|
Thread.new do
|
30
30
|
loop do
|
31
|
-
register!(config) unless @registered
|
31
|
+
register!(config, @worker_adapters) unless @registered
|
32
32
|
|
33
33
|
# Stagger reporting to spread out reports from many processes
|
34
34
|
multiplier = 1 - (rand / 4) # between 0.75 and 1.0
|
@@ -41,7 +41,7 @@ module RailsAutoscaleAgent
|
|
41
41
|
# Exceptions in threads other than the main thread will fail silently
|
42
42
|
# https://ruby-doc.org/core-2.2.0/Thread.html#class-Thread-label-Exception+handling
|
43
43
|
logger.error "Reporter error: #{ex.inspect}"
|
44
|
-
|
44
|
+
AutoscaleApi.new(config.api_base_url).report_exception!(ex)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
@@ -58,7 +58,7 @@ module RailsAutoscaleAgent
|
|
58
58
|
logger.info "Reporting #{report.measurements.size} measurements"
|
59
59
|
|
60
60
|
params = report.to_params(config)
|
61
|
-
result = AutoscaleApi.new(config
|
61
|
+
result = AutoscaleApi.new(config).report_metrics!(params, report.to_csv)
|
62
62
|
|
63
63
|
case result
|
64
64
|
when AutoscaleApi::SuccessResponse
|
@@ -71,16 +71,16 @@ module RailsAutoscaleAgent
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
-
def register!(config)
|
75
|
-
params = Registration.new(config).to_params
|
76
|
-
result = AutoscaleApi.new(config
|
74
|
+
def register!(config, worker_adapters)
|
75
|
+
params = Registration.new(config, worker_adapters).to_params
|
76
|
+
result = AutoscaleApi.new(config).register_reporter!(params)
|
77
77
|
|
78
78
|
case result
|
79
79
|
when AutoscaleApi::SuccessResponse
|
80
80
|
@registered = true
|
81
81
|
config.report_interval = result.data['report_interval'] if result.data['report_interval']
|
82
82
|
config.max_request_size = result.data['max_request_size'] if result.data['max_request_size']
|
83
|
-
worker_adapters_msg =
|
83
|
+
worker_adapters_msg = worker_adapters.map { |a| a.class.name }.join(', ')
|
84
84
|
logger.info "Reporter starting, will report every #{config.report_interval} seconds or so. Worker adapters: [#{worker_adapters_msg}]"
|
85
85
|
when AutoscaleApi::FailureResponse
|
86
86
|
logger.error "Reporter failed to register: #{result.failure_message}"
|
@@ -4,17 +4,17 @@ module RailsAutoscaleAgent
|
|
4
4
|
class Request
|
5
5
|
include Logger
|
6
6
|
|
7
|
-
attr_reader :id, :entered_queue_at, :path, :method, :size
|
8
|
-
|
9
7
|
def initialize(env, config)
|
10
8
|
@config = config
|
11
9
|
@id = env['HTTP_X_REQUEST_ID']
|
12
|
-
@path = env['PATH_INFO']
|
13
|
-
@method = env['REQUEST_METHOD'].downcase
|
14
10
|
@size = env['rack.input'].respond_to?(:size) ? env['rack.input'].size : 0
|
11
|
+
@request_body_wait = env['puma.request_body_wait'].to_i
|
15
12
|
|
16
|
-
if unix_millis = env['HTTP_X_REQUEST_START']
|
17
|
-
|
13
|
+
@entered_queue_at = if unix_millis = env['HTTP_X_REQUEST_START']
|
14
|
+
Time.at(unix_millis.to_f / 1000)
|
15
|
+
elsif config.dev_mode?
|
16
|
+
# In dev mode, fake a queue time of 0-1000ms
|
17
|
+
Time.now - rand + @request_body_wait
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -23,12 +23,17 @@ module RailsAutoscaleAgent
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def queue_time
|
26
|
-
if entered_queue_at
|
27
|
-
queue_time = ((Time.now - entered_queue_at) * 1000).to_i
|
28
|
-
|
29
|
-
|
26
|
+
if @entered_queue_at
|
27
|
+
queue_time = ((Time.now - @entered_queue_at) * 1000).to_i
|
28
|
+
|
29
|
+
# Subtract the time Puma spent waiting on the request body. It's irrelevant to capacity-related queue time.
|
30
|
+
# Without this, slow clients and large request payloads will skew queue time.
|
31
|
+
queue_time -= @request_body_wait
|
32
|
+
|
33
|
+
logger.debug "Request queue_time=#{queue_time}ms body_wait=#{@request_body_wait}ms request_id=#{@id} size=#{@size}"
|
30
34
|
|
31
|
-
|
35
|
+
# Safeguard against negative queue times (should not happen in practice)
|
36
|
+
queue_time > 0 ? queue_time : 0
|
32
37
|
end
|
33
38
|
end
|
34
39
|
end
|
@@ -15,8 +15,8 @@ module RailsAutoscaleAgent
|
|
15
15
|
@measurements = []
|
16
16
|
end
|
17
17
|
|
18
|
-
def push(value, time = Time.now, queue_name = nil)
|
19
|
-
@measurements << Measurement.new(time, value, queue_name)
|
18
|
+
def push(value, time = Time.now, queue_name = nil, metric = nil)
|
19
|
+
@measurements << Measurement.new(time, value, queue_name, metric)
|
20
20
|
end
|
21
21
|
|
22
22
|
def pop_report
|
@@ -2,63 +2,56 @@
|
|
2
2
|
|
3
3
|
require 'rails_autoscale_agent/logger'
|
4
4
|
|
5
|
-
module
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
# Ignore failed jobs (they skew latency measurement due to the original run_at)
|
31
|
-
sql = 'SELECT queue, min(run_at) FROM delayed_jobs WHERE attempts = 0 GROUP BY queue'
|
32
|
-
run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
|
33
|
-
queues = self.class.queues | run_at_by_queue.keys
|
34
|
-
|
35
|
-
queues.each do |queue|
|
36
|
-
next if queue.nil? || queue.empty?
|
37
|
-
run_at = run_at_by_queue[queue]
|
38
|
-
run_at = Time.parse(run_at) if run_at.is_a?(String)
|
39
|
-
latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
|
40
|
-
store.push latency_ms, t, queue
|
41
|
-
log_msg << "#{queue}=#{latency_ms} "
|
5
|
+
module RailsAutoscaleAgent
|
6
|
+
module WorkerAdapters
|
7
|
+
class DelayedJob
|
8
|
+
include RailsAutoscaleAgent::Logger
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_writer :queues
|
12
|
+
|
13
|
+
def queues
|
14
|
+
# Track the known queues so we can continue reporting on queues that don't
|
15
|
+
# have enqueued jobs at the time of reporting.
|
16
|
+
# Assume a "default" queue so we always report *something*, even when nothing
|
17
|
+
# is enqueued.
|
18
|
+
@queues ||= Set.new(['default'])
|
42
19
|
end
|
43
20
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def install
|
50
|
-
plugin = Class.new(Delayed::Plugin) do
|
51
|
-
require 'delayed_job'
|
52
|
-
|
53
|
-
callbacks do |lifecycle|
|
54
|
-
lifecycle.before(:enqueue) do |job, &block|
|
55
|
-
queue = job.queue || 'default'
|
56
|
-
WorkerAdapters::DelayedJob.queues.add queue
|
57
|
-
end
|
21
|
+
def enabled?
|
22
|
+
if defined?(::Delayed::Job) && defined?(::Delayed::Backend::ActiveRecord)
|
23
|
+
logger.info "DelayedJob enabled (#{::ActiveRecord::Base.default_timezone})"
|
24
|
+
true
|
58
25
|
end
|
59
26
|
end
|
60
27
|
|
61
|
-
|
28
|
+
def collect!(store)
|
29
|
+
log_msg = String.new
|
30
|
+
t = Time.now.utc
|
31
|
+
sql = <<~SQL
|
32
|
+
SELECT COALESCE(queue, 'default'), min(run_at)
|
33
|
+
FROM delayed_jobs
|
34
|
+
WHERE locked_at IS NULL
|
35
|
+
AND failed_at IS NULL
|
36
|
+
GROUP BY queue
|
37
|
+
SQL
|
38
|
+
|
39
|
+
run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
|
40
|
+
self.queues |= run_at_by_queue.keys
|
41
|
+
|
42
|
+
queues.each do |queue|
|
43
|
+
run_at = run_at_by_queue[queue]
|
44
|
+
# DateTime.parse assumes a UTC string
|
45
|
+
run_at = DateTime.parse(run_at) if run_at.is_a?(String)
|
46
|
+
latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
|
47
|
+
latency_ms = 0 if latency_ms < 0
|
48
|
+
|
49
|
+
store.push latency_ms, t, queue
|
50
|
+
log_msg << "dj.#{queue}=#{latency_ms} "
|
51
|
+
end
|
52
|
+
|
53
|
+
logger.debug log_msg unless log_msg.empty?
|
54
|
+
end
|
62
55
|
end
|
63
56
|
end
|
64
57
|
end
|
@@ -1,46 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'rails_autoscale_agent/logger'
|
4
|
-
require 'time'
|
5
4
|
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
module RailsAutoscaleAgent
|
6
|
+
module WorkerAdapters
|
7
|
+
class Que
|
8
|
+
include RailsAutoscaleAgent::Logger
|
9
|
+
include Singleton
|
10
10
|
|
11
|
-
|
11
|
+
attr_writer :queues
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def enabled?
|
22
|
-
defined? ::Que
|
23
|
-
end
|
13
|
+
def queues
|
14
|
+
# Track the known queues so we can continue reporting on queues that don't
|
15
|
+
# have enqueued jobs at the time of reporting.
|
16
|
+
# Assume a "default" queue so we always report *something*, even when nothing
|
17
|
+
# is enqueued.
|
18
|
+
@queues ||= Set.new(['default'])
|
19
|
+
end
|
24
20
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
sql = 'SELECT queue, min(run_at) FROM que_jobs WHERE error_count = 0 GROUP BY queue'
|
31
|
-
run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
|
32
|
-
self.class.queues |= run_at_by_queue.keys
|
33
|
-
|
34
|
-
self.class.queues.each do |queue|
|
35
|
-
next if queue.nil? || queue.empty?
|
36
|
-
run_at = run_at_by_queue[queue]
|
37
|
-
run_at = Time.parse(run_at) if run_at.is_a?(String)
|
38
|
-
latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
|
39
|
-
store.push latency_ms, t, queue
|
40
|
-
log_msg << "#{queue}=#{latency_ms} "
|
21
|
+
def enabled?
|
22
|
+
if defined?(::Que)
|
23
|
+
logger.info "Que enabled (#{::ActiveRecord::Base.default_timezone})"
|
24
|
+
true
|
25
|
+
end
|
41
26
|
end
|
42
27
|
|
43
|
-
|
28
|
+
def collect!(store)
|
29
|
+
log_msg = String.new
|
30
|
+
t = Time.now.utc
|
31
|
+
sql = <<~SQL
|
32
|
+
SELECT queue, min(run_at)
|
33
|
+
FROM que_jobs
|
34
|
+
WHERE finished_at IS NULL
|
35
|
+
AND expired_at IS NULL
|
36
|
+
AND error_count = 0
|
37
|
+
GROUP BY 1
|
38
|
+
SQL
|
39
|
+
|
40
|
+
run_at_by_queue = Hash[ActiveRecord::Base.connection.select_rows(sql)]
|
41
|
+
self.queues |= run_at_by_queue.keys
|
42
|
+
|
43
|
+
queues.each do |queue|
|
44
|
+
run_at = run_at_by_queue[queue]
|
45
|
+
run_at = DateTime.parse(run_at) if run_at.is_a?(String)
|
46
|
+
latency_ms = run_at ? ((t - run_at)*1000).ceil : 0
|
47
|
+
latency_ms = 0 if latency_ms < 0
|
48
|
+
|
49
|
+
store.push latency_ms, t, queue
|
50
|
+
log_msg << "que.#{queue}=#{latency_ms} "
|
51
|
+
end
|
52
|
+
|
53
|
+
logger.debug log_msg unless log_msg.empty?
|
54
|
+
end
|
44
55
|
end
|
45
56
|
end
|
46
57
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_autoscale_agent/logger'
|
4
|
+
|
5
|
+
module RailsAutoscaleAgent
|
6
|
+
module WorkerAdapters
|
7
|
+
class Resque
|
8
|
+
include RailsAutoscaleAgent::Logger
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_writer :queues
|
12
|
+
|
13
|
+
def queues
|
14
|
+
@queues ||= ['default']
|
15
|
+
end
|
16
|
+
|
17
|
+
def enabled?
|
18
|
+
require 'resque'
|
19
|
+
logger.info "Resque enabled"
|
20
|
+
true
|
21
|
+
rescue LoadError
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def collect!(store)
|
26
|
+
log_msg = String.new
|
27
|
+
|
28
|
+
# Ensure we continue to collect metrics for known queue names, even when nothing is
|
29
|
+
# enqueued at the time. Without this, it will appears that the agent is no longer reporting.
|
30
|
+
self.queues |= ::Resque.queues
|
31
|
+
|
32
|
+
queues.each do |queue|
|
33
|
+
next if queue.nil? || queue.empty?
|
34
|
+
depth = ::Resque.size(queue)
|
35
|
+
store.push depth, Time.now, queue, :qd
|
36
|
+
log_msg << "resque-qd.#{queue}=#{depth} "
|
37
|
+
end
|
38
|
+
|
39
|
+
logger.debug log_msg
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -2,28 +2,49 @@
|
|
2
2
|
|
3
3
|
require 'rails_autoscale_agent/logger'
|
4
4
|
|
5
|
-
module
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
true
|
13
|
-
rescue LoadError
|
14
|
-
false
|
15
|
-
end
|
5
|
+
module RailsAutoscaleAgent
|
6
|
+
module WorkerAdapters
|
7
|
+
class Sidekiq
|
8
|
+
include RailsAutoscaleAgent::Logger
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
attr_writer :known_queue_names
|
16
12
|
|
17
|
-
|
18
|
-
|
13
|
+
def known_queue_names
|
14
|
+
@known_queue_names ||= ['default']
|
15
|
+
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def enabled?
|
18
|
+
require 'sidekiq/api'
|
19
|
+
logger.info "Sidekiq enabled"
|
20
|
+
true
|
21
|
+
rescue LoadError
|
22
|
+
false
|
24
23
|
end
|
25
24
|
|
26
|
-
|
25
|
+
def collect!(store)
|
26
|
+
log_msg = String.new
|
27
|
+
queues_by_name = ::Sidekiq::Queue.all.each_with_object({}) do |queue, obj|
|
28
|
+
obj[queue.name] = queue
|
29
|
+
end
|
30
|
+
|
31
|
+
# Ensure we continue to collect metrics for known queue names, even when nothing is
|
32
|
+
# enqueued at the time. Without this, it will appears that the agent is no longer reporting.
|
33
|
+
known_queue_names.each do |queue_name|
|
34
|
+
queues_by_name[queue_name] ||= ::Sidekiq::Queue.new(queue_name)
|
35
|
+
end
|
36
|
+
self.known_queue_names = queues_by_name.keys
|
37
|
+
|
38
|
+
queues_by_name.each do |queue_name, queue|
|
39
|
+
latency_ms = (queue.latency * 1000).ceil
|
40
|
+
depth = queue.size
|
41
|
+
store.push latency_ms, Time.now, queue.name, :qt
|
42
|
+
store.push depth, Time.now, queue.name, :qd
|
43
|
+
log_msg << "sidekiq-qt.#{queue.name}=#{latency_ms} sidekiq-qd.#{queue.name}=#{depth} "
|
44
|
+
end
|
45
|
+
|
46
|
+
logger.debug log_msg
|
47
|
+
end
|
27
48
|
end
|
28
49
|
end
|
29
50
|
end
|
@@ -15,16 +15,4 @@ Gem::Specification.new do |spec|
|
|
15
15
|
|
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
|
-
|
19
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
20
|
-
spec.add_development_dependency "rake", "~> 12.3.3"
|
21
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
22
|
-
spec.add_development_dependency "vcr", "~> 3.0"
|
23
|
-
spec.add_development_dependency "webmock"
|
24
|
-
spec.add_development_dependency "pry"
|
25
|
-
spec.add_development_dependency "pry-byebug"
|
26
|
-
spec.add_development_dependency "sidekiq", "~> 5.0"
|
27
|
-
spec.add_development_dependency "delayed_job"
|
28
|
-
spec.add_development_dependency "que"
|
29
|
-
spec.add_development_dependency "activesupport"
|
30
18
|
end
|
metadata
CHANGED
@@ -1,169 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_autoscale_agent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.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: 2020-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.0'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '2.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rake
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: 12.3.3
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: 12.3.3
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: vcr
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '3.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: webmock
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: pry
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
|
-
- !ruby/object:Gem::Dependency
|
98
|
-
name: pry-byebug
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
100
|
-
requirements:
|
101
|
-
- - ">="
|
102
|
-
- !ruby/object:Gem::Version
|
103
|
-
version: '0'
|
104
|
-
type: :development
|
105
|
-
prerelease: false
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
107
|
-
requirements:
|
108
|
-
- - ">="
|
109
|
-
- !ruby/object:Gem::Version
|
110
|
-
version: '0'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: sidekiq
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '5.0'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
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'
|
153
|
-
- !ruby/object:Gem::Dependency
|
154
|
-
name: activesupport
|
155
|
-
requirement: !ruby/object:Gem::Requirement
|
156
|
-
requirements:
|
157
|
-
- - ">="
|
158
|
-
- !ruby/object:Gem::Version
|
159
|
-
version: '0'
|
160
|
-
type: :development
|
161
|
-
prerelease: false
|
162
|
-
version_requirements: !ruby/object:Gem::Requirement
|
163
|
-
requirements:
|
164
|
-
- - ">="
|
165
|
-
- !ruby/object:Gem::Version
|
166
|
-
version: '0'
|
11
|
+
date: 2020-07-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
167
13
|
description:
|
168
14
|
email:
|
169
15
|
- adam@adamlogic.com
|
@@ -198,6 +44,7 @@ files:
|
|
198
44
|
- lib/rails_autoscale_agent/version.rb
|
199
45
|
- lib/rails_autoscale_agent/worker_adapters/delayed_job.rb
|
200
46
|
- lib/rails_autoscale_agent/worker_adapters/que.rb
|
47
|
+
- lib/rails_autoscale_agent/worker_adapters/resque.rb
|
201
48
|
- lib/rails_autoscale_agent/worker_adapters/sidekiq.rb
|
202
49
|
- log/.gitkeep
|
203
50
|
- rails_autoscale_agent.gemspec
|
@@ -220,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
67
|
- !ruby/object:Gem::Version
|
221
68
|
version: '0'
|
222
69
|
requirements: []
|
223
|
-
rubygems_version: 3.
|
70
|
+
rubygems_version: 3.1.4
|
224
71
|
signing_key:
|
225
72
|
specification_version: 4
|
226
73
|
summary: This gem works with the Rails Autoscale Heroku add-on to automatically scale
|