solid_queue_heroku_autoscaler 0.1.0 → 0.2.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/CHANGELOG.md +14 -0
- data/README.md +79 -0
- data/lib/generators/solid_queue_heroku_autoscaler/dashboard_generator.rb +54 -0
- data/lib/generators/solid_queue_heroku_autoscaler/templates/create_solid_queue_autoscaler_events.rb.erb +24 -0
- data/lib/generators/solid_queue_heroku_autoscaler/templates/initializer.rb +6 -0
- data/lib/solid_queue_heroku_autoscaler/configuration.rb +24 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/engine.rb +136 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/views/layouts/solid_queue_heroku_autoscaler/dashboard/application.html.erb +206 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/dashboard/index.html.erb +138 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/events/index.html.erb +102 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/workers/index.html.erb +106 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/workers/show.html.erb +209 -0
- data/lib/solid_queue_heroku_autoscaler/dashboard.rb +99 -0
- data/lib/solid_queue_heroku_autoscaler/railtie.rb +31 -1
- data/lib/solid_queue_heroku_autoscaler/scale_event.rb +292 -0
- data/lib/solid_queue_heroku_autoscaler/scaler.rb +67 -0
- data/lib/solid_queue_heroku_autoscaler/version.rb +1 -1
- data/lib/solid_queue_heroku_autoscaler.rb +2 -0
- metadata +11 -1
|
@@ -109,7 +109,7 @@ module SolidQueueHerokuAutoscaler
|
|
|
109
109
|
|
|
110
110
|
desc 'Reset cooldown state for a worker (or all if WORKER=all). Use WORKER=name'
|
|
111
111
|
task reset_cooldown: :environment do
|
|
112
|
-
worker_name = ENV
|
|
112
|
+
worker_name = ENV.fetch('WORKER', nil)&.to_sym
|
|
113
113
|
|
|
114
114
|
if worker_name == :all || worker_name.nil?
|
|
115
115
|
# Reset all workers
|
|
@@ -129,6 +129,36 @@ module SolidQueueHerokuAutoscaler
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
desc 'Show recent scale events. Use LIMIT=n and WORKER=name'
|
|
133
|
+
task events: :environment do
|
|
134
|
+
worker_name = ENV.fetch('WORKER', nil)
|
|
135
|
+
limit = (ENV['LIMIT'] || 20).to_i
|
|
136
|
+
|
|
137
|
+
events = SolidQueueHerokuAutoscaler::ScaleEvent.recent(limit: limit, worker_name: worker_name)
|
|
138
|
+
|
|
139
|
+
if events.empty?
|
|
140
|
+
puts 'No events found'
|
|
141
|
+
puts '(Make sure to run: rails generate solid_queue_heroku_autoscaler:dashboard)'
|
|
142
|
+
else
|
|
143
|
+
puts "Recent Scale Events#{" for #{worker_name}" if worker_name} (#{events.size}):"
|
|
144
|
+
puts '-' * 100
|
|
145
|
+
events.each do |event|
|
|
146
|
+
action = event.action.ljust(10)
|
|
147
|
+
workers = "#{event.from_workers}->#{event.to_workers}".ljust(8)
|
|
148
|
+
dry_run = event.dry_run ? ' [DRY RUN]' : ''
|
|
149
|
+
time = event.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
|
150
|
+
puts "#{time} | #{event.worker_name.ljust(15)} | #{action} | #{workers} | #{event.reason}#{dry_run}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
desc 'Cleanup old scale events. Use KEEP_DAYS=n (default: 30)'
|
|
156
|
+
task cleanup_events: :environment do
|
|
157
|
+
keep_days = (ENV['KEEP_DAYS'] || 30).to_i
|
|
158
|
+
SolidQueueHerokuAutoscaler::ScaleEvent.cleanup!(keep_days: keep_days)
|
|
159
|
+
puts "Cleaned up events older than #{keep_days} days"
|
|
160
|
+
end
|
|
161
|
+
|
|
132
162
|
def print_scale_result(result, worker_name)
|
|
133
163
|
prefix = worker_name == :default ? '' : "[#{worker_name}] "
|
|
134
164
|
if result.success?
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueHerokuAutoscaler
|
|
4
|
+
# Lightweight model for recording autoscaler events.
|
|
5
|
+
# Does not inherit from ActiveRecord to avoid requiring it as a dependency.
|
|
6
|
+
# Uses raw SQL for compatibility with any database connection.
|
|
7
|
+
class ScaleEvent
|
|
8
|
+
TABLE_NAME = 'solid_queue_autoscaler_events'
|
|
9
|
+
|
|
10
|
+
ACTIONS = %w[scale_up scale_down no_change skipped error].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :worker_name, :action, :from_workers, :to_workers,
|
|
13
|
+
:reason, :queue_depth, :latency_seconds, :metrics_json,
|
|
14
|
+
:dry_run, :created_at
|
|
15
|
+
|
|
16
|
+
def initialize(attrs = {})
|
|
17
|
+
@id = attrs[:id]
|
|
18
|
+
@worker_name = attrs[:worker_name]
|
|
19
|
+
@action = attrs[:action]
|
|
20
|
+
@from_workers = attrs[:from_workers]
|
|
21
|
+
@to_workers = attrs[:to_workers]
|
|
22
|
+
@reason = attrs[:reason]
|
|
23
|
+
@queue_depth = attrs[:queue_depth]
|
|
24
|
+
@latency_seconds = attrs[:latency_seconds]
|
|
25
|
+
@metrics_json = attrs[:metrics_json]
|
|
26
|
+
@dry_run = attrs[:dry_run]
|
|
27
|
+
@created_at = attrs[:created_at]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def scaled?
|
|
31
|
+
%w[scale_up scale_down].include?(action)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def scale_up?
|
|
35
|
+
action == 'scale_up'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def scale_down?
|
|
39
|
+
action == 'scale_down'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def metrics
|
|
43
|
+
return nil unless metrics_json
|
|
44
|
+
|
|
45
|
+
JSON.parse(metrics_json, symbolize_names: true)
|
|
46
|
+
rescue JSON::ParserError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class << self
|
|
51
|
+
# Creates a new scale event record.
|
|
52
|
+
# @param attrs [Hash] Event attributes
|
|
53
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
54
|
+
# @return [ScaleEvent] The created event
|
|
55
|
+
def create!(attrs, connection: nil)
|
|
56
|
+
conn = connection || default_connection
|
|
57
|
+
return nil unless table_exists?(conn)
|
|
58
|
+
|
|
59
|
+
now = Time.current
|
|
60
|
+
sql = <<~SQL
|
|
61
|
+
INSERT INTO #{TABLE_NAME}
|
|
62
|
+
(worker_name, action, from_workers, to_workers, reason,
|
|
63
|
+
queue_depth, latency_seconds, metrics_json, dry_run, created_at)
|
|
64
|
+
VALUES
|
|
65
|
+
(#{conn.quote(attrs[:worker_name])},
|
|
66
|
+
#{conn.quote(attrs[:action])},
|
|
67
|
+
#{conn.quote(attrs[:from_workers])},
|
|
68
|
+
#{conn.quote(attrs[:to_workers])},
|
|
69
|
+
#{conn.quote(attrs[:reason])},
|
|
70
|
+
#{conn.quote(attrs[:queue_depth])},
|
|
71
|
+
#{conn.quote(attrs[:latency_seconds])},
|
|
72
|
+
#{conn.quote(attrs[:metrics_json])},
|
|
73
|
+
#{conn.quote(attrs[:dry_run])},
|
|
74
|
+
#{conn.quote(now)})
|
|
75
|
+
RETURNING id
|
|
76
|
+
SQL
|
|
77
|
+
|
|
78
|
+
result = conn.execute(sql)
|
|
79
|
+
id = result.first&.fetch('id', nil)
|
|
80
|
+
|
|
81
|
+
new(attrs.merge(id: id, created_at: now))
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
# Log but don't fail if event recording fails
|
|
84
|
+
Rails.logger.warn("[Autoscaler] Failed to record event: #{e.message}") if defined?(Rails)
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Finds recent events.
|
|
89
|
+
# @param limit [Integer] Maximum number of events to return
|
|
90
|
+
# @param worker_name [String, nil] Filter by worker name
|
|
91
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
92
|
+
# @return [Array<ScaleEvent>] Array of events
|
|
93
|
+
def recent(limit: 50, worker_name: nil, connection: nil)
|
|
94
|
+
conn = connection || default_connection
|
|
95
|
+
return [] unless table_exists?(conn)
|
|
96
|
+
|
|
97
|
+
filter = worker_name ? "WHERE worker_name = #{conn.quote(worker_name)}" : ''
|
|
98
|
+
|
|
99
|
+
sql = <<~SQL
|
|
100
|
+
SELECT id, worker_name, action, from_workers, to_workers, reason,
|
|
101
|
+
queue_depth, latency_seconds, metrics_json, dry_run, created_at
|
|
102
|
+
FROM #{TABLE_NAME}
|
|
103
|
+
#{filter}
|
|
104
|
+
ORDER BY created_at DESC
|
|
105
|
+
LIMIT #{limit.to_i}
|
|
106
|
+
SQL
|
|
107
|
+
|
|
108
|
+
conn.select_all(sql).map { |row| from_row(row) }
|
|
109
|
+
rescue StandardError
|
|
110
|
+
[]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Finds events by action type.
|
|
114
|
+
# @param action [String] Action type (scale_up, scale_down, etc.)
|
|
115
|
+
# @param limit [Integer] Maximum number of events
|
|
116
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
117
|
+
# @return [Array<ScaleEvent>] Array of events
|
|
118
|
+
def by_action(action, limit: 50, connection: nil)
|
|
119
|
+
conn = connection || default_connection
|
|
120
|
+
return [] unless table_exists?(conn)
|
|
121
|
+
|
|
122
|
+
sql = <<~SQL
|
|
123
|
+
SELECT id, worker_name, action, from_workers, to_workers, reason,
|
|
124
|
+
queue_depth, latency_seconds, metrics_json, dry_run, created_at
|
|
125
|
+
FROM #{TABLE_NAME}
|
|
126
|
+
WHERE action = #{conn.quote(action)}
|
|
127
|
+
ORDER BY created_at DESC
|
|
128
|
+
LIMIT #{limit.to_i}
|
|
129
|
+
SQL
|
|
130
|
+
|
|
131
|
+
conn.select_all(sql).map { |row| from_row(row) }
|
|
132
|
+
rescue StandardError
|
|
133
|
+
[]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Gets event statistics for a time period.
|
|
137
|
+
# @param since [Time] Start time for statistics
|
|
138
|
+
# @param worker_name [String, nil] Filter by worker name
|
|
139
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
140
|
+
# @return [Hash] Statistics hash
|
|
141
|
+
def stats(since: 24.hours.ago, worker_name: nil, connection: nil)
|
|
142
|
+
conn = connection || default_connection
|
|
143
|
+
return default_stats unless table_exists?(conn)
|
|
144
|
+
|
|
145
|
+
worker_filter = worker_name ? "AND worker_name = #{conn.quote(worker_name)}" : ''
|
|
146
|
+
|
|
147
|
+
sql = <<~SQL
|
|
148
|
+
SELECT
|
|
149
|
+
action,
|
|
150
|
+
COUNT(*) as count,
|
|
151
|
+
AVG(queue_depth) as avg_queue_depth,
|
|
152
|
+
AVG(latency_seconds) as avg_latency
|
|
153
|
+
FROM #{TABLE_NAME}
|
|
154
|
+
WHERE created_at >= #{conn.quote(since)}
|
|
155
|
+
#{worker_filter}
|
|
156
|
+
GROUP BY action
|
|
157
|
+
SQL
|
|
158
|
+
|
|
159
|
+
results = conn.select_all(sql).to_a
|
|
160
|
+
build_stats(results)
|
|
161
|
+
rescue StandardError
|
|
162
|
+
default_stats
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Cleans up old events.
|
|
166
|
+
# @param keep_days [Integer] Number of days to keep
|
|
167
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
168
|
+
# @return [Integer] Number of deleted records
|
|
169
|
+
def cleanup!(keep_days: 30, connection: nil)
|
|
170
|
+
conn = connection || default_connection
|
|
171
|
+
return 0 unless table_exists?(conn)
|
|
172
|
+
|
|
173
|
+
cutoff = Time.current - keep_days.days
|
|
174
|
+
|
|
175
|
+
sql = <<~SQL
|
|
176
|
+
DELETE FROM #{TABLE_NAME}
|
|
177
|
+
WHERE created_at < #{conn.quote(cutoff)}
|
|
178
|
+
SQL
|
|
179
|
+
|
|
180
|
+
result = conn.execute(sql)
|
|
181
|
+
result.cmd_tuples
|
|
182
|
+
rescue StandardError
|
|
183
|
+
0
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Checks if the events table exists.
|
|
187
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
188
|
+
# @return [Boolean] True if table exists
|
|
189
|
+
def table_exists?(connection = nil)
|
|
190
|
+
conn = connection || default_connection
|
|
191
|
+
conn.table_exists?(TABLE_NAME)
|
|
192
|
+
rescue StandardError
|
|
193
|
+
false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Counts events in a time period.
|
|
197
|
+
# @param since [Time] Start time
|
|
198
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection
|
|
199
|
+
# @return [Integer] Event count
|
|
200
|
+
def count(since: nil, connection: nil)
|
|
201
|
+
conn = connection || default_connection
|
|
202
|
+
return 0 unless table_exists?(conn)
|
|
203
|
+
|
|
204
|
+
time_filter = since ? "WHERE created_at >= #{conn.quote(since)}" : ''
|
|
205
|
+
|
|
206
|
+
sql = "SELECT COUNT(*) FROM #{TABLE_NAME} #{time_filter}"
|
|
207
|
+
conn.select_value(sql).to_i
|
|
208
|
+
rescue StandardError
|
|
209
|
+
0
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
def default_connection
|
|
215
|
+
ActiveRecord::Base.connection
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def from_row(row)
|
|
219
|
+
new(
|
|
220
|
+
id: row['id'],
|
|
221
|
+
worker_name: row['worker_name'],
|
|
222
|
+
action: row['action'],
|
|
223
|
+
from_workers: row['from_workers'].to_i,
|
|
224
|
+
to_workers: row['to_workers'].to_i,
|
|
225
|
+
reason: row['reason'],
|
|
226
|
+
queue_depth: row['queue_depth'].to_i,
|
|
227
|
+
latency_seconds: row['latency_seconds'].to_f,
|
|
228
|
+
metrics_json: row['metrics_json'],
|
|
229
|
+
dry_run: parse_boolean(row['dry_run']),
|
|
230
|
+
created_at: parse_time(row['created_at'])
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def parse_boolean(value)
|
|
235
|
+
case value
|
|
236
|
+
when true, 't', 'true', '1', 1
|
|
237
|
+
true
|
|
238
|
+
else
|
|
239
|
+
false
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def parse_time(value)
|
|
244
|
+
case value
|
|
245
|
+
when Time, DateTime
|
|
246
|
+
value.to_time
|
|
247
|
+
when String
|
|
248
|
+
Time.parse(value)
|
|
249
|
+
else
|
|
250
|
+
value
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def default_stats
|
|
255
|
+
{
|
|
256
|
+
total: 0,
|
|
257
|
+
scale_up_count: 0,
|
|
258
|
+
scale_down_count: 0,
|
|
259
|
+
no_change_count: 0,
|
|
260
|
+
skipped_count: 0,
|
|
261
|
+
error_count: 0,
|
|
262
|
+
avg_queue_depth: 0,
|
|
263
|
+
avg_latency: 0
|
|
264
|
+
}
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def build_stats(results)
|
|
268
|
+
stats = default_stats
|
|
269
|
+
|
|
270
|
+
results.each do |row|
|
|
271
|
+
action = row['action']
|
|
272
|
+
count = row['count'].to_i
|
|
273
|
+
|
|
274
|
+
stats[:total] += count
|
|
275
|
+
stats[:"#{action}_count"] = count
|
|
276
|
+
|
|
277
|
+
# Use weighted average for overall metrics
|
|
278
|
+
stats[:avg_queue_depth] += row['avg_queue_depth'].to_f * count if row['avg_queue_depth']
|
|
279
|
+
stats[:avg_latency] += row['avg_latency'].to_f * count if row['avg_latency']
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Calculate averages
|
|
283
|
+
if stats[:total].positive?
|
|
284
|
+
stats[:avg_queue_depth] /= stats[:total]
|
|
285
|
+
stats[:avg_latency] /= stats[:total]
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
stats
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
@@ -126,6 +126,7 @@ module SolidQueueHerokuAutoscaler
|
|
|
126
126
|
def apply_decision(decision, metrics)
|
|
127
127
|
@adapter.scale(decision.to)
|
|
128
128
|
record_scale_time(decision)
|
|
129
|
+
record_scale_event(decision, metrics)
|
|
129
130
|
|
|
130
131
|
log_scale_action(decision)
|
|
131
132
|
|
|
@@ -190,6 +191,9 @@ module SolidQueueHerokuAutoscaler
|
|
|
190
191
|
end
|
|
191
192
|
|
|
192
193
|
def success_result(decision, metrics)
|
|
194
|
+
# Record no_change events if configured
|
|
195
|
+
record_scale_event(decision, metrics) if decision&.no_change? && @config.record_all_events?
|
|
196
|
+
|
|
193
197
|
ScaleResult.new(
|
|
194
198
|
success: true,
|
|
195
199
|
decision: decision,
|
|
@@ -201,6 +205,9 @@ module SolidQueueHerokuAutoscaler
|
|
|
201
205
|
def skipped_result(reason, decision: nil, metrics: nil)
|
|
202
206
|
logger.debug("[Autoscaler] Skipped: #{reason}")
|
|
203
207
|
|
|
208
|
+
# Record skipped events
|
|
209
|
+
record_skipped_event(reason, decision, metrics)
|
|
210
|
+
|
|
204
211
|
ScaleResult.new(
|
|
205
212
|
success: true,
|
|
206
213
|
decision: decision,
|
|
@@ -213,6 +220,9 @@ module SolidQueueHerokuAutoscaler
|
|
|
213
220
|
def error_result(error)
|
|
214
221
|
logger.error("[Autoscaler] Error: #{error.class}: #{error.message}")
|
|
215
222
|
|
|
223
|
+
# Record error events
|
|
224
|
+
record_error_event(error)
|
|
225
|
+
|
|
216
226
|
ScaleResult.new(
|
|
217
227
|
success: false,
|
|
218
228
|
error: error,
|
|
@@ -223,5 +233,62 @@ module SolidQueueHerokuAutoscaler
|
|
|
223
233
|
def logger
|
|
224
234
|
@config.logger
|
|
225
235
|
end
|
|
236
|
+
|
|
237
|
+
def record_scale_event(decision, metrics)
|
|
238
|
+
return unless @config.record_events?
|
|
239
|
+
|
|
240
|
+
ScaleEvent.create!(
|
|
241
|
+
{
|
|
242
|
+
worker_name: @config.name.to_s,
|
|
243
|
+
action: decision.action.to_s,
|
|
244
|
+
from_workers: decision.from,
|
|
245
|
+
to_workers: decision.to,
|
|
246
|
+
reason: decision.reason,
|
|
247
|
+
queue_depth: metrics&.queue_depth || 0,
|
|
248
|
+
latency_seconds: metrics&.oldest_job_age_seconds || 0.0,
|
|
249
|
+
metrics_json: metrics&.to_h&.to_json,
|
|
250
|
+
dry_run: @config.dry_run?
|
|
251
|
+
},
|
|
252
|
+
connection: @config.connection
|
|
253
|
+
)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def record_skipped_event(reason, decision, metrics)
|
|
257
|
+
return unless @config.record_events?
|
|
258
|
+
|
|
259
|
+
ScaleEvent.create!(
|
|
260
|
+
{
|
|
261
|
+
worker_name: @config.name.to_s,
|
|
262
|
+
action: 'skipped',
|
|
263
|
+
from_workers: decision&.from || 0,
|
|
264
|
+
to_workers: decision&.to || 0,
|
|
265
|
+
reason: reason,
|
|
266
|
+
queue_depth: metrics&.queue_depth || 0,
|
|
267
|
+
latency_seconds: metrics&.oldest_job_age_seconds || 0.0,
|
|
268
|
+
metrics_json: metrics&.to_h&.to_json,
|
|
269
|
+
dry_run: @config.dry_run?
|
|
270
|
+
},
|
|
271
|
+
connection: @config.connection
|
|
272
|
+
)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def record_error_event(error)
|
|
276
|
+
return unless @config.record_events?
|
|
277
|
+
|
|
278
|
+
ScaleEvent.create!(
|
|
279
|
+
{
|
|
280
|
+
worker_name: @config.name.to_s,
|
|
281
|
+
action: 'error',
|
|
282
|
+
from_workers: 0,
|
|
283
|
+
to_workers: 0,
|
|
284
|
+
reason: "#{error.class}: #{error.message}",
|
|
285
|
+
queue_depth: 0,
|
|
286
|
+
latency_seconds: 0.0,
|
|
287
|
+
metrics_json: nil,
|
|
288
|
+
dry_run: @config.dry_run?
|
|
289
|
+
},
|
|
290
|
+
connection: @config.connection
|
|
291
|
+
)
|
|
292
|
+
end
|
|
226
293
|
end
|
|
227
294
|
end
|
|
@@ -12,6 +12,7 @@ require_relative 'solid_queue_heroku_autoscaler/advisory_lock'
|
|
|
12
12
|
require_relative 'solid_queue_heroku_autoscaler/metrics'
|
|
13
13
|
require_relative 'solid_queue_heroku_autoscaler/decision_engine'
|
|
14
14
|
require_relative 'solid_queue_heroku_autoscaler/cooldown_tracker'
|
|
15
|
+
require_relative 'solid_queue_heroku_autoscaler/scale_event'
|
|
15
16
|
require_relative 'solid_queue_heroku_autoscaler/scaler'
|
|
16
17
|
|
|
17
18
|
module SolidQueueHerokuAutoscaler
|
|
@@ -102,5 +103,6 @@ module SolidQueueHerokuAutoscaler
|
|
|
102
103
|
end
|
|
103
104
|
|
|
104
105
|
require_relative 'solid_queue_heroku_autoscaler/railtie' if defined?(Rails::Railtie)
|
|
106
|
+
require_relative 'solid_queue_heroku_autoscaler/dashboard'
|
|
105
107
|
|
|
106
108
|
require_relative 'solid_queue_heroku_autoscaler/autoscale_job' if defined?(ActiveJob::Base)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_queue_heroku_autoscaler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- reillyse
|
|
@@ -119,9 +119,11 @@ files:
|
|
|
119
119
|
- CHANGELOG.md
|
|
120
120
|
- LICENSE.txt
|
|
121
121
|
- README.md
|
|
122
|
+
- lib/generators/solid_queue_heroku_autoscaler/dashboard_generator.rb
|
|
122
123
|
- lib/generators/solid_queue_heroku_autoscaler/install_generator.rb
|
|
123
124
|
- lib/generators/solid_queue_heroku_autoscaler/migration_generator.rb
|
|
124
125
|
- lib/generators/solid_queue_heroku_autoscaler/templates/README
|
|
126
|
+
- lib/generators/solid_queue_heroku_autoscaler/templates/create_solid_queue_autoscaler_events.rb.erb
|
|
125
127
|
- lib/generators/solid_queue_heroku_autoscaler/templates/create_solid_queue_autoscaler_state.rb.erb
|
|
126
128
|
- lib/generators/solid_queue_heroku_autoscaler/templates/initializer.rb
|
|
127
129
|
- lib/solid_queue_heroku_autoscaler.rb
|
|
@@ -133,10 +135,18 @@ files:
|
|
|
133
135
|
- lib/solid_queue_heroku_autoscaler/autoscale_job.rb
|
|
134
136
|
- lib/solid_queue_heroku_autoscaler/configuration.rb
|
|
135
137
|
- lib/solid_queue_heroku_autoscaler/cooldown_tracker.rb
|
|
138
|
+
- lib/solid_queue_heroku_autoscaler/dashboard.rb
|
|
139
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/engine.rb
|
|
140
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/views/layouts/solid_queue_heroku_autoscaler/dashboard/application.html.erb
|
|
141
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/dashboard/index.html.erb
|
|
142
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/events/index.html.erb
|
|
143
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/workers/index.html.erb
|
|
144
|
+
- lib/solid_queue_heroku_autoscaler/dashboard/views/solid_queue_heroku_autoscaler/dashboard/workers/show.html.erb
|
|
136
145
|
- lib/solid_queue_heroku_autoscaler/decision_engine.rb
|
|
137
146
|
- lib/solid_queue_heroku_autoscaler/errors.rb
|
|
138
147
|
- lib/solid_queue_heroku_autoscaler/metrics.rb
|
|
139
148
|
- lib/solid_queue_heroku_autoscaler/railtie.rb
|
|
149
|
+
- lib/solid_queue_heroku_autoscaler/scale_event.rb
|
|
140
150
|
- lib/solid_queue_heroku_autoscaler/scaler.rb
|
|
141
151
|
- lib/solid_queue_heroku_autoscaler/version.rb
|
|
142
152
|
homepage: https://github.com/reillyse/solid_queue_heroku_autoscaler
|