rocketjob 2.0.0.rc3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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