rocketjob 3.4.3 → 3.5.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 +5 -5
- data/README.md +27 -0
- data/bin/rocketjob +1 -1
- data/lib/rocket_job/active_worker.rb +4 -3
- data/lib/rocket_job/cli.rb +13 -12
- data/lib/rocket_job/config.rb +17 -13
- data/lib/rocket_job/dirmon_entry.rb +88 -91
- data/lib/rocket_job/extensions/mongo/logging.rb +8 -4
- data/lib/rocket_job/extensions/mongoid/factory.rb +8 -6
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -4
- data/lib/rocket_job/heartbeat.rb +7 -8
- data/lib/rocket_job/job_exception.rb +6 -5
- data/lib/rocket_job/jobs/dirmon_job.rb +5 -7
- data/lib/rocket_job/jobs/housekeeping_job.rb +3 -2
- data/lib/rocket_job/jobs/on_demand_job.rb +104 -0
- data/lib/rocket_job/jobs/simple_job.rb +0 -2
- data/lib/rocket_job/jobs/upload_file_job.rb +100 -0
- data/lib/rocket_job/performance.rb +15 -10
- data/lib/rocket_job/plugins/cron.rb +7 -124
- data/lib/rocket_job/plugins/document.rb +8 -10
- data/lib/rocket_job/plugins/job/callbacks.rb +0 -1
- data/lib/rocket_job/plugins/job/logger.rb +0 -1
- data/lib/rocket_job/plugins/job/model.rb +15 -20
- data/lib/rocket_job/plugins/job/persistence.rb +3 -13
- data/lib/rocket_job/plugins/job/state_machine.rb +1 -2
- data/lib/rocket_job/plugins/job/throttle.rb +16 -12
- data/lib/rocket_job/plugins/job/worker.rb +15 -19
- data/lib/rocket_job/plugins/processing_window.rb +2 -2
- data/lib/rocket_job/plugins/restart.rb +3 -4
- data/lib/rocket_job/plugins/retry.rb +2 -3
- data/lib/rocket_job/plugins/singleton.rb +2 -3
- data/lib/rocket_job/plugins/state_machine.rb +19 -23
- data/lib/rocket_job/rocket_job.rb +4 -5
- data/lib/rocket_job/server.rb +35 -41
- data/lib/rocket_job/version.rb +2 -2
- data/lib/rocket_job/worker.rb +22 -21
- data/lib/rocketjob.rb +2 -0
- data/test/config/mongoid.yml +2 -2
- data/test/config_test.rb +0 -2
- data/test/dirmon_entry_test.rb +161 -134
- data/test/dirmon_job_test.rb +80 -78
- data/test/job_test.rb +0 -2
- data/test/jobs/housekeeping_job_test.rb +0 -1
- data/test/jobs/on_demand_job_test.rb +59 -0
- data/test/jobs/upload_file_job_test.rb +99 -0
- data/test/plugins/cron_test.rb +1 -3
- data/test/plugins/job/callbacks_test.rb +8 -13
- data/test/plugins/job/defaults_test.rb +0 -1
- data/test/plugins/job/logger_test.rb +2 -4
- data/test/plugins/job/model_test.rb +1 -2
- data/test/plugins/job/persistence_test.rb +0 -2
- data/test/plugins/job/state_machine_test.rb +0 -2
- data/test/plugins/job/throttle_test.rb +6 -8
- data/test/plugins/job/worker_test.rb +1 -2
- data/test/plugins/processing_window_test.rb +0 -2
- data/test/plugins/restart_test.rb +0 -1
- data/test/plugins/retry_test.rb +1 -2
- data/test/plugins/singleton_test.rb +0 -2
- data/test/plugins/state_machine_event_callbacks_test.rb +1 -2
- data/test/plugins/state_machine_test.rb +0 -2
- data/test/plugins/transaction_test.rb +5 -7
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +2 -1
- metadata +42 -36
@@ -4,22 +4,26 @@ module Mongo
|
|
4
4
|
class Monitoring
|
5
5
|
class CommandLogSubscriber
|
6
6
|
include SemanticLogger::Loggable
|
7
|
-
|
7
|
+
logger.name = 'Mongo'
|
8
8
|
|
9
9
|
def started(event)
|
10
10
|
@event_command = event.command
|
11
11
|
end
|
12
12
|
|
13
13
|
def succeeded(event)
|
14
|
-
logger.debug(message:
|
14
|
+
logger.debug(message: prefix(event),
|
15
|
+
duration: (event.duration * 1000),
|
16
|
+
payload: @event_command)
|
15
17
|
end
|
16
18
|
|
17
19
|
def failed(event)
|
18
|
-
logger.debug(message:
|
20
|
+
logger.debug(message: "#{prefix(event)} Failed: #{event.message}",
|
21
|
+
duration: (event.duration * 1000),
|
22
|
+
payload: @event_command)
|
19
23
|
end
|
20
24
|
|
21
25
|
def prefix(event)
|
22
|
-
"#{event.address
|
26
|
+
"#{event.address} | #{event.database_name}.#{event.command_name}"
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
require 'mongoid/factory'
|
2
2
|
|
3
3
|
module RocketJob
|
4
|
-
module
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
module Mongoid
|
5
|
+
module Factory
|
6
|
+
def from_db(*args)
|
7
|
+
super(*args)
|
8
|
+
rescue NameError
|
9
|
+
RocketJob::Job.instantiate(attributes, selected_fields)
|
10
|
+
end
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
|
-
::Mongoid::Factory.
|
15
|
+
::Mongoid::Factory.extend(RocketJob::Mongoid::Factory)
|
@@ -70,20 +70,18 @@ module ActiveJob
|
|
70
70
|
job
|
71
71
|
end
|
72
72
|
|
73
|
-
private
|
74
|
-
|
75
73
|
def self.active_job_params(active_job)
|
76
74
|
params = {
|
77
75
|
description: active_job.class.name,
|
78
76
|
data: active_job.serialize,
|
79
77
|
active_job_id: active_job.job_id,
|
80
78
|
active_job_class: active_job.class.name,
|
81
|
-
active_job_queue: active_job.queue_name
|
79
|
+
active_job_queue: active_job.queue_name
|
82
80
|
}
|
83
81
|
params[:priority] = active_job.priority if active_job.respond_to?(:priority) && active_job.priority
|
84
82
|
params
|
85
83
|
end
|
86
|
-
|
84
|
+
private_class_method :active_job_params
|
87
85
|
end
|
88
86
|
end
|
89
87
|
end
|
data/lib/rocket_job/heartbeat.rb
CHANGED
@@ -16,23 +16,22 @@ module RocketJob
|
|
16
16
|
#
|
17
17
|
|
18
18
|
# Percentage utilization for the server process alone
|
19
|
-
#field :process_cpu, type: Integer
|
19
|
+
# field :process_cpu, type: Integer
|
20
20
|
# Kilo Bytes used by the server process (Virtual & Physical)
|
21
|
-
#field :process_mem_phys_kb, type: Integer
|
22
|
-
#field :process_mem_virt_kb, type: Integer
|
21
|
+
# field :process_mem_phys_kb, type: Integer
|
22
|
+
# field :process_mem_virt_kb, type: Integer
|
23
23
|
|
24
24
|
#
|
25
25
|
# System Information. Future.
|
26
26
|
#
|
27
27
|
|
28
28
|
# Percentage utilization for the host machine
|
29
|
-
#field :host_cpu, type: Integer
|
29
|
+
# field :host_cpu, type: Integer
|
30
30
|
# Kilo Bytes Available on the host machine (Physical)
|
31
|
-
#field :host_mem_avail_phys_kbytes, type: Float
|
32
|
-
#field :host_mem_avail_virt_kbytes, type: Float
|
31
|
+
# field :host_mem_avail_phys_kbytes, type: Float
|
32
|
+
# field :host_mem_avail_virt_kbytes, type: Float
|
33
33
|
|
34
34
|
# If available
|
35
|
-
#field :load_average, type: Float
|
35
|
+
# field :load_average, type: Float
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
@@ -22,13 +22,14 @@ module RocketJob
|
|
22
22
|
field :record_number, type: Integer
|
23
23
|
|
24
24
|
# Returns [JobException] built from the supplied exception
|
25
|
-
def self.from_exception(exc)
|
25
|
+
def self.from_exception(exc, **args)
|
26
26
|
new(
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
args.merge(
|
28
|
+
class_name: exc.class.name,
|
29
|
+
message: exc.message,
|
30
|
+
backtrace: exc.backtrace || []
|
31
|
+
)
|
30
32
|
)
|
31
33
|
end
|
32
|
-
|
33
34
|
end
|
34
35
|
end
|
@@ -40,7 +40,7 @@ module RocketJob
|
|
40
40
|
# Start a new job when this one completes, fails, or aborts
|
41
41
|
include RocketJob::Plugins::Restart
|
42
42
|
|
43
|
-
self.priority =
|
43
|
+
self.priority = 30
|
44
44
|
|
45
45
|
# Number of seconds between directory scans. Default 5 mins
|
46
46
|
field :check_seconds, type: Float, default: 300.0, copy_on_restart: true
|
@@ -72,11 +72,10 @@ module RocketJob
|
|
72
72
|
DirmonEntry.enabled.each do |entry|
|
73
73
|
entry.each do |pathname|
|
74
74
|
# BSON Keys cannot contain periods
|
75
|
-
key
|
76
|
-
previous_size
|
77
|
-
|
78
|
-
|
79
|
-
end
|
75
|
+
key = pathname.to_s.tr('.', '_')
|
76
|
+
previous_size = previous_file_names[key]
|
77
|
+
size = check_file(entry, pathname, previous_size)
|
78
|
+
new_file_names[key] = size if size
|
80
79
|
end
|
81
80
|
end
|
82
81
|
self.previous_file_names = new_file_names
|
@@ -96,7 +95,6 @@ module RocketJob
|
|
96
95
|
size
|
97
96
|
end
|
98
97
|
end
|
99
|
-
|
100
98
|
end
|
101
99
|
end
|
102
100
|
end
|
@@ -54,12 +54,13 @@ module RocketJob
|
|
54
54
|
end
|
55
55
|
|
56
56
|
RocketJob::Job.aborted.where(completed_at: {'$lte' => aborted_retention.seconds.ago}).destroy_all if aborted_retention
|
57
|
-
|
57
|
+
if completed_retention
|
58
|
+
RocketJob::Job.completed.where(completed_at: {'$lte' => completed_retention.seconds.ago}).destroy_all
|
59
|
+
end
|
58
60
|
RocketJob::Job.failed.where(completed_at: {'$lte' => failed_retention.seconds.ago}).destroy_all if failed_retention
|
59
61
|
RocketJob::Job.paused.where(completed_at: {'$lte' => paused_retention.seconds.ago}).destroy_all if paused_retention
|
60
62
|
RocketJob::Job.queued.where(created_at: {'$lte' => queued_retention.seconds.ago}).destroy_all if queued_retention
|
61
63
|
end
|
62
|
-
|
63
64
|
end
|
64
65
|
end
|
65
66
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Generalized Job.
|
2
|
+
#
|
3
|
+
# Create or schedule a generalized job for one off fixes or cleanups.
|
4
|
+
#
|
5
|
+
# Example: Iterate over all rows in a table:
|
6
|
+
# code = <<~CODE
|
7
|
+
# User.unscoped.all.order('updated_at DESC').each |user|
|
8
|
+
# user.cleanse_attributes!
|
9
|
+
# user.save!
|
10
|
+
# end
|
11
|
+
# CODE
|
12
|
+
#
|
13
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
14
|
+
# code: code,
|
15
|
+
# description: 'Cleanse users'
|
16
|
+
# )
|
17
|
+
#
|
18
|
+
# Example: Test job in a console:
|
19
|
+
# code = <<~CODE
|
20
|
+
# User.unscoped.all.order('updated_at DESC').each |user|
|
21
|
+
# user.cleanse_attributes!
|
22
|
+
# user.save!
|
23
|
+
# end
|
24
|
+
# CODE
|
25
|
+
#
|
26
|
+
# job = RocketJob::Jobs::OnDemandJob.new(code: code, description: 'cleanse users')
|
27
|
+
# job.perform_now
|
28
|
+
#
|
29
|
+
# Example: Pass input data:
|
30
|
+
# code = <<~CODE
|
31
|
+
# puts data['a'] * data['b']
|
32
|
+
# CODE
|
33
|
+
#
|
34
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
35
|
+
# code: code,
|
36
|
+
# data: {'a' => 10, 'b' => 2}
|
37
|
+
# )
|
38
|
+
#
|
39
|
+
# Example: Retain output:
|
40
|
+
# code = <<~CODE
|
41
|
+
# {'value' => data['a'] * data['b']}
|
42
|
+
# CODE
|
43
|
+
#
|
44
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
45
|
+
# code: code,
|
46
|
+
# collect_output: true,
|
47
|
+
# data: {'a' => 10, 'b' => 2}
|
48
|
+
# )
|
49
|
+
#
|
50
|
+
# Example: Schedule the job to run nightly at 2am Eastern:
|
51
|
+
#
|
52
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
53
|
+
# cron_schedule: '0 2 * * * America/New_York',
|
54
|
+
# code: code
|
55
|
+
# )
|
56
|
+
#
|
57
|
+
# Example: Change the job priority, description, etc.
|
58
|
+
#
|
59
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
60
|
+
# code: code,
|
61
|
+
# description: 'Cleanse users',
|
62
|
+
# priority: 30
|
63
|
+
# )
|
64
|
+
#
|
65
|
+
# Example: Automatically retry up to 5 times on failure:
|
66
|
+
#
|
67
|
+
# RocketJob::Jobs::OnDemandJob.create!(
|
68
|
+
# retry_limit: 5
|
69
|
+
# code: code
|
70
|
+
# )
|
71
|
+
module RocketJob
|
72
|
+
module Jobs
|
73
|
+
class OnDemandJob < RocketJob::Job
|
74
|
+
include RocketJob::Plugins::Cron
|
75
|
+
include RocketJob::Plugins::Retry
|
76
|
+
|
77
|
+
self.priority = 90
|
78
|
+
self.description = 'Generalized Job'
|
79
|
+
self.destroy_on_complete = false
|
80
|
+
self.retry_limit = 0
|
81
|
+
|
82
|
+
# Be sure to store key names only as Strings, not Symbols
|
83
|
+
field :data, type: Hash, default: {}
|
84
|
+
field :code, type: String
|
85
|
+
|
86
|
+
validates :code, presence: true
|
87
|
+
validates_each :code do |job, attr, _value|
|
88
|
+
begin
|
89
|
+
job.send(:load_code)
|
90
|
+
rescue Exception => exc
|
91
|
+
job.errors.add(attr, "Failed to parse :code, #{exc.inspect}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
before_perform :load_code
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def load_code
|
100
|
+
instance_eval("def perform\n#{code}\nend", __FILE__, __LINE__)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
begin
|
3
|
+
require 'iostreams'
|
4
|
+
rescue LoadError
|
5
|
+
# Optional dependency
|
6
|
+
end
|
7
|
+
|
8
|
+
module RocketJob
|
9
|
+
module Jobs
|
10
|
+
# Job to upload a file into another job.
|
11
|
+
#
|
12
|
+
# Intended for use by DirmonJob to upload a file into a specified job.
|
13
|
+
#
|
14
|
+
# Can be used directly for any job class, as long as that job responds
|
15
|
+
# to `#upload`.
|
16
|
+
class UploadFileJob < RocketJob::Job
|
17
|
+
self.priority = 30
|
18
|
+
|
19
|
+
# Name of the job class to instantiate and upload the file into.
|
20
|
+
field :job_class_name, type: String, user_editable: true
|
21
|
+
|
22
|
+
# Properties to assign to the job when it is created.
|
23
|
+
field :properties, type: Hash, default: {}, user_editable: true
|
24
|
+
|
25
|
+
# File to upload
|
26
|
+
field :upload_file_name, type: String, user_editable: true
|
27
|
+
|
28
|
+
# The original Input file name.
|
29
|
+
# Used by #upload to extract the IOStreams when present.
|
30
|
+
field :original_file_name, type: String, user_editable: true
|
31
|
+
|
32
|
+
# Optionally set the job id for the downstream job
|
33
|
+
# Useful when for example the archived file should contain the job id for the downstream job.
|
34
|
+
field :job_id, type: BSON::ObjectId
|
35
|
+
|
36
|
+
validates_presence_of :upload_file_name, :job_class_name
|
37
|
+
validate :job_is_a_rocket_job
|
38
|
+
validate :job_implements_upload
|
39
|
+
validate :file_exists
|
40
|
+
|
41
|
+
# Create the job and upload the file into it.
|
42
|
+
def perform
|
43
|
+
job = job_class.new(properties)
|
44
|
+
job.id = job_id if job_id
|
45
|
+
upload_file(job)
|
46
|
+
job.save!
|
47
|
+
rescue StandardError => exc
|
48
|
+
# Prevent partial uploads
|
49
|
+
job&.cleanup! if job.respond_to?(:cleanup!)
|
50
|
+
raise(exc)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def job_class
|
56
|
+
@job_class ||= job_class_name.constantize
|
57
|
+
rescue NameError
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def upload_file(job)
|
62
|
+
if job.respond_to?(:upload)
|
63
|
+
if original_file_name && defined?(IOStreams)
|
64
|
+
streams = IOStreams.streams_for_file_name(original_file_name)
|
65
|
+
job.upload(upload_file_name, streams: streams)
|
66
|
+
else
|
67
|
+
job.upload(upload_file_name)
|
68
|
+
end
|
69
|
+
elsif job.respond_to?(:upload_file_name=)
|
70
|
+
job.upload_file_name = upload_file_name
|
71
|
+
elsif job.respond_to?(:full_file_name=)
|
72
|
+
job.full_file_name = upload_file_name
|
73
|
+
else
|
74
|
+
raise(ArgumentError, "Model #{job_class_name} must implement '#upload', or have attribute 'upload_file_name' or 'full_file_name'")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Validates job_class is a Rocket Job
|
79
|
+
def job_is_a_rocket_job
|
80
|
+
klass = job_class
|
81
|
+
return if klass.nil? || klass.ancestors&.include?(RocketJob::Job)
|
82
|
+
errors.add(:job_class_name, "Model #{job_class_name} must be defined and inherit from RocketJob::Job")
|
83
|
+
end
|
84
|
+
|
85
|
+
VALID_INSTANCE_METHODS = %i[upload upload_file_name= full_file_name=].freeze
|
86
|
+
|
87
|
+
# Validates job_class is a Rocket Job
|
88
|
+
def job_implements_upload
|
89
|
+
klass = job_class
|
90
|
+
return if klass.nil? || klass.instance_methods.any? { |m| VALID_INSTANCE_METHODS.include?(m) }
|
91
|
+
errors.add(:job_class_name, "#{job_class} must implement any one of: :#{VALID_INSTANCE_METHODS.join(' :')} instance methods")
|
92
|
+
end
|
93
|
+
|
94
|
+
def file_exists
|
95
|
+
return if upload_file_name.nil? || File.exist?(upload_file_name)
|
96
|
+
errors.add(:upload_file_name, "Upload file: #{upload_file_name} does not exist.")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -18,7 +18,9 @@ module RocketJob
|
|
18
18
|
# Loads the queue with jobs to be processed once the queue is loaded.
|
19
19
|
# Retain the first and last job for timings, all others are destroyed on completion.
|
20
20
|
def run_test_case(count = self.count)
|
21
|
-
|
21
|
+
if RocketJob::Server.where(:state.in => %w[running paused]).count.zero?
|
22
|
+
raise 'Please start servers before starting the performance test'
|
23
|
+
end
|
22
24
|
|
23
25
|
count_running_workers
|
24
26
|
|
@@ -33,21 +35,19 @@ module RocketJob
|
|
33
35
|
running += server.heartbeat.workers unless server.zombie?
|
34
36
|
end
|
35
37
|
puts "Waiting for #{running} workers"
|
36
|
-
break if running
|
38
|
+
break if running.zero?
|
37
39
|
sleep 1
|
38
40
|
end
|
39
41
|
|
40
42
|
puts 'Enqueuing jobs'
|
41
43
|
first = RocketJob::Jobs::SimpleJob.create!(priority: 1, destroy_on_complete: false)
|
42
|
-
(count - 2).times {
|
44
|
+
(count - 2).times { RocketJob::Jobs::SimpleJob.create! }
|
43
45
|
last = RocketJob::Jobs::SimpleJob.create!(priority: 100, destroy_on_complete: false)
|
44
46
|
|
45
47
|
puts 'Resuming workers'
|
46
48
|
RocketJob::Server.resume_all
|
47
49
|
|
48
|
-
|
49
|
-
sleep 3
|
50
|
-
end
|
50
|
+
sleep 3 until last.reload.completed?
|
51
51
|
|
52
52
|
duration = last.reload.completed_at - first.reload.started_at
|
53
53
|
first.destroy
|
@@ -67,13 +67,19 @@ module RocketJob
|
|
67
67
|
# Parse command line options
|
68
68
|
def parse(argv)
|
69
69
|
parser = OptionParser.new do |o|
|
70
|
-
o.on('-c',
|
70
|
+
o.on('-c',
|
71
|
+
'--count COUNT',
|
72
|
+
'Count of jobs to enqueue') do |arg|
|
71
73
|
self.count = arg.to_i
|
72
74
|
end
|
73
|
-
o.on('-m',
|
75
|
+
o.on('-m',
|
76
|
+
'--mongo MONGO_CONFIG_FILE_NAME',
|
77
|
+
'Path and filename of config file. Default: config/mongoid.yml') do |arg|
|
74
78
|
self.mongo_config = arg
|
75
79
|
end
|
76
|
-
o.on('-e',
|
80
|
+
o.on('-e',
|
81
|
+
'--environment ENVIRONMENT',
|
82
|
+
'The environment to run the app on (Default: RAILS_ENV || RACK_ENV || development)') do |arg|
|
77
83
|
self.environment = arg
|
78
84
|
end
|
79
85
|
end
|
@@ -95,6 +101,5 @@ module RocketJob
|
|
95
101
|
end
|
96
102
|
puts "Running: #{workers} workers, distributed across #{servers} servers"
|
97
103
|
end
|
98
|
-
|
99
104
|
end
|
100
105
|
end
|