rocketjob 2.0.0.rc3 → 2.0.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
  SHA1:
3
- metadata.gz: 272892209b4b987a90ec99fe808e7197588c1fea
4
- data.tar.gz: 560ead0b08ab42a2fc6cda4de2f3ed107199fce7
3
+ metadata.gz: 3bf52fdcaa423858a798fdd0da28cf9df7448170
4
+ data.tar.gz: a8cd888f37215dbe633dad2f13941e629c6463dc
5
5
  SHA512:
6
- metadata.gz: b7c162229dfaed223d3346f6a31613026fd8dff5dcbd590e11b9b4569e98b65ed9fa31850db395248d54be9005017cf7bb28c12517a559a38e84f44b90146656
7
- data.tar.gz: 0614a2ccfdcece55f6480d880eef065e8034629695f7678e21bc6212231351c24ebf1cab06e53553f7102479554eca419eadd208a4716b7a83a53e25ab56bc46
6
+ metadata.gz: 99b6ff7af94d554ff2107deaaade0b639703cf29cd714fcdbe0763f1e4217ae8b22e87a790fd87101a849d1172684a670fb2c6b80c6ec5689ca3991795a0bb53
7
+ data.tar.gz: 1d477d2acc562df2a16ebccf500e71d77c570feb486c38a01f8bdcabacc99ebcdf56394b05049b893b5237754633920643c8e8387058666bc0f710984f9058f1
@@ -8,7 +8,7 @@ begin
8
8
  RocketJob::CLI.new(ARGV).run
9
9
  rescue => exc
10
10
  # Failsafe logger that writes to STDERR
11
- SemanticLogger.add_appender(STDERR, :error, &SemanticLogger::Appender::Base.colorized_formatter)
11
+ SemanticLogger.add_appender(io: STDERR, level: :error, formatter: :color)
12
12
  SemanticLogger['RocketJob'].error('Rocket Job shutting down due to exception', exc)
13
13
  SemanticLogger.flush
14
14
  exit 1
@@ -1,91 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- require 'csv'
3
- require 'yaml'
4
2
  require 'rocketjob'
5
3
 
6
4
  # Log to console
7
- SemanticLogger.add_appender(STDOUT, &SemanticLogger::Appender::Base.colorized_formatter)
5
+ SemanticLogger.add_appender(io: STDOUT, formatter: :color)
8
6
 
9
- class RocketJobPerf
10
- attr_accessor :count, :worker_processes, :worker_threads, :version, :ruby, :environment, :mongo_config
11
-
12
- def initialize
13
- @version = RocketJob::VERSION
14
- @ruby = defined?(JRuby) ? "jruby_#{JRUBY_VERSION}" : "ruby_#{RUBY_VERSION}"
15
- @count = 100_000
16
- @worker_processes = 0
17
- @worker_threads = 0
18
- @environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
19
- @mongo_config = 'config/mongo.yml'
20
- end
21
-
22
- def run_test_case(count = self.count)
23
- self.worker_processes = RocketJob::Worker.count
24
- raise 'Please start workers before starting the performance test' if worker_processes == 0
25
-
26
- self.worker_processes = 0
27
- self.worker_threads = 0
28
- RocketJob::Worker.where(state: :running).each do |worker_process|
29
- unless worker_process.zombie?
30
- self.worker_processes += 1
31
- self.worker_threads += worker_process.heartbeat.current_threads
32
- end
33
- end
34
- puts "Running: #{worker_threads} workers, distributed across #{worker_processes} processes"
35
-
36
- puts 'Waiting for workers to pause'
37
- RocketJob::Worker.pause_all
38
- RocketJob::Jobs::SimpleJob.delete_all
39
- sleep 15
40
-
41
- puts 'Enqueuing jobs'
42
- first = RocketJob::Jobs::SimpleJob.create(priority: 1, destroy_on_complete: false)
43
- (count - 2).times { |i| RocketJob::Jobs::SimpleJob.create! }
44
- last = RocketJob::Jobs::SimpleJob.create(priority: 100, destroy_on_complete: false)
45
-
46
- puts 'Resuming workers'
47
- RocketJob::Worker.resume_all
48
-
49
- while (!last.reload.completed?)
50
- sleep 3
51
- end
52
-
53
- duration = last.reload.completed_at - first.reload.started_at
54
- {count: count, duration: duration, jobs_per_second: (count.to_f / duration).to_i}
55
- end
56
-
57
- # Export the Results hash to a CSV file
58
- def export_results(results)
59
- CSV.open("job_results_#{ruby}_#{worker_processes}p_#{threads}t_v#{version}.csv", 'wb') do |csv|
60
- csv << results.first.keys
61
- results.each { |result| csv << result.values }
62
- end
63
- end
64
-
65
- # Parse command line options
66
- def parse(argv)
67
- parser = OptionParser.new do |o|
68
- o.on('-c', '--count COUNT', 'Count of jobs to enqueue') do |arg|
69
- self.count = arg.to_i
70
- end
71
- o.on('-m', '--mongo MONGO_CONFIG_FILE_NAME', 'Location of mongo.yml config file') do |arg|
72
- self.mongo_config = arg
73
- end
74
- o.on('-e', '--environment ENVIRONMENT', 'The environment to run the app on (Default: RAILS_ENV || RACK_ENV || development)') do |arg|
75
- self.environment = arg
76
- end
77
- end
78
- parser.banner = 'rocketjob_perf <options>'
79
- parser.on_tail '-h', '--help', 'Show help' do
80
- puts parser
81
- exit 1
82
- end
83
- parser.parse! argv
84
- end
85
-
86
- end
87
-
88
- perf = RocketJobPerf.new
7
+ perf = RocketJob::Performance.new
89
8
  perf.parse(ARGV)
90
9
  RocketJob::Config.load!(perf.environment, perf.mongo_config)
91
10
  results = perf.run_test_case
@@ -25,6 +25,7 @@ module RocketJob
25
25
  setup_environment
26
26
  setup_logger
27
27
  rails? ? boot_rails : boot_standalone
28
+ # setup_metrics
28
29
  write_pidfile
29
30
 
30
31
  opts = {}
@@ -33,6 +34,15 @@ module RocketJob
33
34
  Worker.run(opts)
34
35
  end
35
36
 
37
+ def setup_metrics
38
+ SemanticLogger.on_metric do |log|
39
+ if log.metric.start_with?('rocketjob/')
40
+ ap log
41
+ RocketJob::Stats::ShortTerm.increment_metric(log.time, log.name, log.duration, log.metric_amount)
42
+ end
43
+ end
44
+ end
45
+
36
46
  def rails?
37
47
  @rails ||= begin
38
48
  boot_file = Pathname.new(directory).join('config/environment.rb').expand_path
@@ -58,7 +68,7 @@ module RocketJob
58
68
  SemanticLogger.default_level = log_level.to_sym if log_level
59
69
 
60
70
  if Rails.configuration.eager_load
61
- RocketJob::Worker.logger.benchmark_info('Eager loaded Rails and all Engines') do
71
+ RocketJob::Worker.logger.measure_info('Eager loaded Rails and all Engines') do
62
72
  Rails.application.eager_load!
63
73
  Rails::Engine.subclasses.each(&:eager_load!)
64
74
  end
@@ -83,7 +93,7 @@ module RocketJob
83
93
  # Log to file except when booting rails, when it will add the log file path
84
94
  path = log_file ? Pathname.new(log_file) : Pathname.pwd.join("log/#{environment}.log")
85
95
  path.dirname.mkpath
86
- SemanticLogger.add_appender(path.to_s, &SemanticLogger::Appender::Base.colorized_formatter)
96
+ SemanticLogger.add_appender(file_name: path.to_s, formatter: :color)
87
97
 
88
98
  logger.info "Rails not detected. Running standalone: #{environment}"
89
99
  RocketJob::Config.load!(environment)
@@ -112,7 +122,7 @@ module RocketJob
112
122
  end
113
123
 
114
124
  def setup_logger
115
- SemanticLogger.add_appender(STDOUT, &SemanticLogger::Appender::Base.colorized_formatter) unless quiet
125
+ SemanticLogger.add_appender(io: STDOUT, formatter: :color) unless quiet
116
126
  SemanticLogger.default_level = log_level.to_sym if log_level
117
127
 
118
128
  # Enable SemanticLogger signal handling for this process
@@ -251,7 +251,7 @@ module RocketJob
251
251
 
252
252
  # Passes each filename [Pathname] found that matches the pattern into the supplied block
253
253
  def each(&block)
254
- logger.fast_tag("DirmonEntry:#{id}") do
254
+ logger.fast_tag("dirmon_entry:#{id}") do
255
255
  # Case insensitive filename matching
256
256
  Pathname.glob(pattern, File::FNM_CASEFOLD).each do |pathname|
257
257
  next if pathname.directory?
@@ -305,7 +305,7 @@ module RocketJob
305
305
  # Queues the job for the supplied pathname
306
306
  def later(pathname)
307
307
  if klass = job_class
308
- logger.benchmark_info "Enqueued: #{name}, Job class: #{job_class_name}" do
308
+ logger.measure_info "Enqueued: #{name}, Job class: #{job_class_name}" do
309
309
  job = klass.new(properties.merge(arguments: arguments))
310
310
  upload_file(job, pathname)
311
311
  job.save!
@@ -16,7 +16,7 @@ module Mongo
16
16
  private
17
17
 
18
18
  def log_operation(name, payload, duration)
19
- MongoClient.logger.benchmark_trace(name, duration: (duration * 1000), payload: payload)
19
+ MongoClient.logger.measure_trace(name, duration: (duration * 1000), payload: payload)
20
20
  end
21
21
 
22
22
  end
@@ -0,0 +1,82 @@
1
+ require 'csv'
2
+ require 'yaml'
3
+ module RocketJob
4
+ class Performance
5
+ attr_accessor :count, :worker_processes, :worker_threads, :version, :ruby, :environment, :mongo_config
6
+
7
+ def initialize
8
+ @version = RocketJob::VERSION
9
+ @ruby = defined?(JRuby) ? "jruby_#{JRUBY_VERSION}" : "ruby_#{RUBY_VERSION}"
10
+ @count = 100_000
11
+ @worker_processes = 0
12
+ @worker_threads = 0
13
+ @environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
14
+ @mongo_config = 'config/mongo.yml'
15
+ end
16
+
17
+ def run_test_case(count = self.count)
18
+ self.worker_processes = RocketJob::Worker.count
19
+ raise 'Please start workers before starting the performance test' if worker_processes == 0
20
+
21
+ self.worker_processes = 0
22
+ self.worker_threads = 0
23
+ RocketJob::Worker.where(state: :running).each do |worker_process|
24
+ unless worker_process.zombie?
25
+ self.worker_processes += 1
26
+ self.worker_threads += worker_process.heartbeat.current_threads
27
+ end
28
+ end
29
+ puts "Running: #{worker_threads} workers, distributed across #{worker_processes} processes"
30
+
31
+ puts 'Waiting for workers to pause'
32
+ RocketJob::Worker.pause_all
33
+ RocketJob::Jobs::SimpleJob.delete_all
34
+ sleep 15
35
+
36
+ puts 'Enqueuing jobs'
37
+ first = RocketJob::Jobs::SimpleJob.create(priority: 1, destroy_on_complete: false)
38
+ (count - 2).times { |i| RocketJob::Jobs::SimpleJob.create! }
39
+ last = RocketJob::Jobs::SimpleJob.create(priority: 100, destroy_on_complete: false)
40
+
41
+ puts 'Resuming workers'
42
+ RocketJob::Worker.resume_all
43
+
44
+ while (!last.reload.completed?)
45
+ sleep 3
46
+ end
47
+
48
+ duration = last.reload.completed_at - first.reload.started_at
49
+ {count: count, duration: duration, jobs_per_second: (count.to_f / duration).to_i}
50
+ end
51
+
52
+ # Export the Results hash to a CSV file
53
+ def export_results(results)
54
+ CSV.open("job_results_#{ruby}_#{worker_processes}p_#{threads}t_v#{version}.csv", 'wb') do |csv|
55
+ csv << results.first.keys
56
+ results.each { |result| csv << result.values }
57
+ end
58
+ end
59
+
60
+ # Parse command line options
61
+ def parse(argv)
62
+ parser = OptionParser.new do |o|
63
+ o.on('-c', '--count COUNT', 'Count of jobs to enqueue') do |arg|
64
+ self.count = arg.to_i
65
+ end
66
+ o.on('-m', '--mongo MONGO_CONFIG_FILE_NAME', 'Location of mongo.yml config file') do |arg|
67
+ self.mongo_config = arg
68
+ end
69
+ o.on('-e', '--environment ENVIRONMENT', 'The environment to run the app on (Default: RAILS_ENV || RACK_ENV || development)') do |arg|
70
+ self.environment = arg
71
+ end
72
+ end
73
+ parser.banner = 'rocketjob_perf <options>'
74
+ parser.on_tail '-h', '--help', 'Show help' do
75
+ puts parser
76
+ exit 1
77
+ end
78
+ parser.parse! argv
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,130 @@
1
+ # encoding: UTF-8
2
+ require 'active_support/concern'
3
+
4
+ module RocketJob
5
+ module Plugins
6
+ # Automatically schedules the job to start based on the supplied `cron_schedule`.
7
+ # Once started the job will automatically restart on completion and will only run again
8
+ # according to the `cron_schedule`.
9
+ # Failed jobs are aborted so that they cannot be restarted since a new instance has already
10
+ # been enqueued.
11
+ #
12
+ # Include RocketJob::Plugins::Singleton to prevent multiple copies of the job from running at
13
+ # the same time.
14
+ #
15
+ # Unlike cron, if a job is already running, another one is not queued when the cron
16
+ # schedule needs another started, but rather on completion of the current job. This prevents
17
+ # multiple instances of the same job from running at the same time. The next instance of the
18
+ # job is only scheduled on completion of the current job instance.
19
+ #
20
+ # For example if the job takes 10 minutes to complete, and is scheduled to run every 5 minutes,
21
+ # it will only be run every 10 minutes.
22
+ #
23
+ # Their is no centralized scheduler or need to start schedulers anywhere, since the jobs
24
+ # can be picked up by any Rocket Job worker. Once processing is complete a new instance is then
25
+ # automatically scheduled based on the `cron_schedule`.
26
+ #
27
+ # A job is only queued to run at the specified `cron_schedule`, it will only run if there are workers
28
+ # available to run. For example if workers are busy working on higher priority jobs, then the job
29
+ # will only run once those jobs have completed, or their priority lowered. Additionally, while the
30
+ # job is queued no additional instances will be enqueued, even if the next cron interval has been reached.
31
+ #
32
+ # Note:
33
+ # - The job will not be restarted if:
34
+ # - A validation fails after cloning this job.
35
+ # - The job has expired.
36
+ #
37
+ # Example:
38
+ #
39
+ # class MyCronJob < RocketJob::Job
40
+ # include RocketJob::Plugins::Cron
41
+ #
42
+ # # Set the default cron_schedule
43
+ # rocket_job do |job|
44
+ # job.cron_schedule = "* 1 * * * UTC"
45
+ # end
46
+ #
47
+ # def perform
48
+ # puts "DONE"
49
+ # end
50
+ # end
51
+ #
52
+ # # Queue the job for processing using the default cron_schedule specified above
53
+ # MyCronJob.create!
54
+ #
55
+ # # Set the cron schedule:
56
+ # MyCronJob.create!(cron_schedule: '* 1 * * * America/New_York')
57
+ #
58
+ #
59
+ # Note:
60
+ #
61
+ # To prevent multiple instances of the job from running at the same time,
62
+ # add: "include RocketJob::Plugins::Singleton"
63
+ #
64
+ # Example: Only allow one instance of the cron job to run at a time:
65
+ #
66
+ # class MyCronJob < RocketJob::Job
67
+ # # Add `cron_schedule`
68
+ # include RocketJob::Plugins::Cron
69
+ # # Prevents mutiple instances from being queued or run at the same time
70
+ # include RocketJob::Plugins::Singleton
71
+ #
72
+ # # Set the default cron_schedule
73
+ # rocket_job do |job|
74
+ # job.cron_schedule = "* 1 * * * UTC"
75
+ # end
76
+ #
77
+ # def perform
78
+ # puts "DONE"
79
+ # end
80
+ # end
81
+ #
82
+ # Note: The cron_schedule field is formatted as follows:
83
+ #
84
+ # * * * * * *
85
+ # ┬ ┬ ┬ ┬ ┬ ┬
86
+ # │ │ │ │ │ │
87
+ # │ │ │ │ │ └ Optional: Timezone, for example: 'America/New_York', 'UTC'
88
+ # │ │ │ │ └───── day_of_week (0-7) (0 or 7 is Sun, or use 3-letter names)
89
+ # │ │ │ └────────── month (1-12, or use 3-letter names)
90
+ # │ │ └─────────────── day_of_month (1-31)
91
+ # │ └──────────────────── hour (0-23)
92
+ # └───────────────────────── minute (0-59)
93
+ #
94
+ # * When specifying day of week, both day 0 and day 7 is Sunday.
95
+ # * Ranges & Lists of numbers are allowed.
96
+ # * Ranges or lists of names are not allowed.
97
+ # * Ranges can include 'steps', so `1-9/2` is the same as `1,3,5,7,9`.
98
+ # * Months or days of the week can be specified by name.
99
+ # * Use the first three letters of the particular day or month (case doesn't matter).
100
+ # * The timezone is recommended to prevent any issues with possible default timezone
101
+ # differences across servers, or environments.
102
+ module Cron
103
+ extend ActiveSupport::Concern
104
+
105
+ included do
106
+ include Restart
107
+
108
+ key :cron_schedule, String
109
+
110
+ before_create :rocket_job_set_run_at
111
+
112
+ validates_presence_of :cron_schedule
113
+ validates_each :cron_schedule do |record, attr, value|
114
+ begin
115
+ RocketJob::Plugins::Rufus::CronLine.new(value)
116
+ rescue ArgumentError => exc
117
+ record.errors.add(attr, exc.message)
118
+ end
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def rocket_job_set_run_at
125
+ self.run_at = RocketJob::Plugins::Rufus::CronLine.new(cron_schedule).next_time
126
+ end
127
+
128
+ end
129
+ end
130
+ end
@@ -20,7 +20,7 @@ module RocketJob
20
20
  # - silence noisy jobs by raising log level
21
21
  def rocket_job_around_logger(&block)
22
22
  logger.info('Start #perform')
23
- logger.benchmark_info(
23
+ logger.measure_info(
24
24
  'Completed #perform',
25
25
  metric: "rocketjob/#{self.class.name.underscore}/perform",
26
26
  log_exception: :full,
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+ require 'active_support/concern'
3
+
4
+ module RocketJob
5
+ module Plugins
6
+ # Ensure that a job will only run between certain hours of the day, regardless of when it was
7
+ # created/enqueued. Useful for creating a job now that should only be processed later during a
8
+ # specific time window. If the time window is already active the job is able to be processed
9
+ # immediately.
10
+ #
11
+ # Example: Process this job on Monday’s between 8am and 10am.
12
+ #
13
+ # Example: Run this job on the 1st of every month from midnight for the entire day.
14
+ #
15
+ # Since the cron schedule supports time zones it is easy to setup jobs to run at UTC or any other time zone.
16
+ #
17
+ # Example:
18
+ # # Only run the job between the hours of 8:30am and 8:30pm. If it is after 8:30pm schedule
19
+ # # it to run at 8:30am the next day.
20
+ # class BusinessHoursJob < RocketJob::Job
21
+ # include RocketJob::Plugins::ProcessingWindow
22
+ #
23
+ # # Set the default processing_window
24
+ # rocket_job do |job|
25
+ # # The start of the processing window
26
+ # job.processing_schedule = "30 8 * * * America/New_York"
27
+ # # How long the processing window is:
28
+ # job.processing_duration = 12.hours
29
+ # end
30
+ #
31
+ # def perform
32
+ # # Job will only run between 8:30am and 8:30pm Eastern
33
+ # end
34
+ # end
35
+ #
36
+ # Note:
37
+ # If a job is created/enqueued during the processing window, but due to busy/unavailable workers
38
+ # is not processed during the window, the current job will be re-queued for the beginning
39
+ # of the next processing window.
40
+ module ProcessingWindow
41
+ extend ActiveSupport::Concern
42
+
43
+ included do
44
+ key :processing_schedule, String
45
+ key :processing_duration, Integer
46
+
47
+ before_create :rocket_job_processing_window_set_run_at
48
+ before_retry :rocket_job_processing_window_set_run_at
49
+ after_start :rocket_job_processing_window_check
50
+
51
+ validates_presence_of :processing_schedule, :processing_duration
52
+ validates_each :processing_schedule do |record, attr, value|
53
+ begin
54
+ RocketJob::Plugins::Rufus::CronLine.new(value)
55
+ rescue ArgumentError => exc
56
+ record.errors.add(attr, exc.message)
57
+ end
58
+ end
59
+ end
60
+
61
+ # Returns [true|false] whether this job is currently inside its processing window
62
+ def rocket_job_processing_window_active?
63
+ time = Time.now
64
+ previous_time = rocket_job_processing_schedule.previous_time(time)
65
+ # Inside previous processing window?
66
+ previous_time + processing_duration > time
67
+ end
68
+
69
+ private
70
+
71
+ # Only process this job if it is still in its processing window
72
+ def rocket_job_processing_window_check
73
+ return if rocket_job_processing_window_active?
74
+ logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{rocket_job_processing_schedule.next_time}")
75
+ self.worker_name ||= 'inline'
76
+ self.requeue!(worker_name)
77
+ end
78
+
79
+ def rocket_job_processing_window_set_run_at
80
+ self.run_at = rocket_job_processing_schedule.next_time unless rocket_job_processing_window_active?
81
+ end
82
+
83
+ def rocket_job_processing_schedule
84
+ RocketJob::Plugins::Rufus::CronLine.new(processing_schedule)
85
+ end
86
+ end
87
+ end
88
+ end