diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index ea6a577..123d0dd 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -9,7 +9,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: fetch-depth: 0 # Fetch current tag as annotated. See https://github.com/actions/checkout/issues/290 - uses: ruby/setup-ruby@v1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f6e0d66..c1908bc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v1 + uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.0.0 @@ -24,4 +24,4 @@ jobs: with: rubocop_version: gemfile rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile - reporter: github-pr-review # Default is github-pr-check \ No newline at end of file + reporter: github-pr-review # Default is github-pr-check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6c81ced..491d248 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,16 +17,16 @@ jobs: strategy: fail-fast: false matrix: - rails: ["6.1", "7.0", "7.1", "7.2"] - ruby: ["3.1", "3.2", "3.3"] + rails: ["7.0", "7.1", "7.2", "8.0", "8.1"] + ruby: ["3.3", "3.4"] container: image: ruby:${{ matrix.ruby }} env: CI: true BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.rails }}.gemfile steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: vendor/bundle key: bundle-${{ matrix.ruby }}-${{ hashFiles('**/*.gemspec') }}-${{ hashFiles(format('**/gemfiles/rails_{0}.gemfile', matrix.rails)) }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a878b7..c0e6778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## Unreleased +- Add support for newer ActiveJob features such as scheduled jobs and bulk enqueued jobs. https://github.com/Fullscript/yabeda-activejob/pull/24 @lewispb + ## 0.6.0 - 2024-10-29 - Add support for Rails 7.1.4+ - Fix rubocop rules and configure Github actions to run the specs for supported Rails / Ruby versions. diff --git a/README.md b/README.md index 076a98a..ae69617 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,12 @@ Sidekiq.configure_server do |_config| end ``` -If using with resque: +If using with solid_queue or resque: ```ruby # config/initializers/yabeda.rb or elsewhere Yabeda::ActiveJob.install! ``` -If using resque you may need to use [yabeda-prometheus-mmap](https://github.com/yabeda-rb/yabeda-prometheus-mmap) or set your storage type to direct file store so that the metrics are available -to your collector. +If using solid_queue or resque you need to use [yabeda-prometheus-mmap](https://github.com/yabeda-rb/yabeda-prometheus-mmap) or set your storage type to direct file store so that the metrics are available to your collector. To set your storage type to direct file store you can do the following in your yabeda initializer: diff --git a/gemfiles/rails_6.1.gemfile b/gemfiles/rails_8.0.gemfile similarity index 86% rename from gemfiles/rails_6.1.gemfile rename to gemfiles/rails_8.0.gemfile index ab30978..7ab16e9 100644 --- a/gemfiles/rails_6.1.gemfile +++ b/gemfiles/rails_8.0.gemfile @@ -3,6 +3,6 @@ source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -gem "rails", "~> 6.1.0" +gem "rails", "~> 8.0.0" eval_gemfile "../Gemfile" diff --git a/gemfiles/rails_8.1.gemfile b/gemfiles/rails_8.1.gemfile new file mode 100644 index 0000000..58ae8b3 --- /dev/null +++ b/gemfiles/rails_8.1.gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +source "https://rubygems.org" +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +gem "rails", "~> 8.1.0" + +eval_gemfile "../Gemfile" diff --git a/lib/yabeda/activejob/event_handler.rb b/lib/yabeda/activejob/event_handler.rb index 059fab5..769c0f5 100644 --- a/lib/yabeda/activejob/event_handler.rb +++ b/lib/yabeda/activejob/event_handler.rb @@ -1,105 +1,110 @@ # frozen_string_literal: true -class Yabeda::ActiveJob::EventHandler - def initialize(*event_args) - @event = ActiveSupport::Notifications::Event.new(*event_args) - end +module Yabeda + module ActiveJob + class EventHandler + def initialize(*event_args) + @event = ActiveSupport::Notifications::Event.new(*event_args) + end - def handle_perform - labels = { - activejob: event.payload[:job].class.to_s, - queue: event.payload[:job].queue_name.to_s, - executions: event.payload[:job].executions.to_s, - } - if event.payload[:exception].present? - Yabeda.activejob_failed_total.increment( - labels.merge(failure_reason: event.payload[:exception].first.to_s), - ) - else - Yabeda.activejob_success_total.increment(labels) - end + def handle_perform + labels = { + activejob: event.payload[:job].class.to_s, + queue: event.payload[:job].queue_name.to_s, + executions: event.payload[:job].executions.to_s, + } + if event.payload[:exception].present? + Yabeda.activejob_failed_total.increment( + labels.merge(failure_reason: event.payload[:exception].first.to_s), + ) + else + Yabeda.activejob_success_total.increment(labels) + end - Yabeda.activejob_executed_total.increment(labels) - Yabeda.activejob_runtime.measure(labels, ms2s(event.duration)) - call_after_event_block - end + Yabeda.activejob_executed_total.increment(labels) + Yabeda.activejob_runtime.measure(labels, ms2s(event.duration)) + call_after_event_block + end - def handle_perform_start - labels = common_labels(event.payload[:job]) + def handle_perform_start + labels = common_labels(event.payload[:job]) - job_latency_value = job_latency - Yabeda.activejob_latency.measure(labels, job_latency_value) if job_latency_value.present? - call_after_event_block - end + job_latency_value = job_latency + Yabeda.activejob_latency.measure(labels, job_latency_value) if job_latency_value.present? + call_after_event_block + end - def handle_enqueue - labels = common_labels(event.payload[:job]) + def handle_enqueue + labels = common_labels(event.payload[:job]) - Yabeda.activejob_enqueued_total.increment(labels) + Yabeda.activejob_enqueued_total.increment(labels) - call_after_event_block - end + call_after_event_block + end - def handle_enqueue_at - labels = common_labels(event.payload[:job]) + def handle_enqueue_at + labels = common_labels(event.payload[:job]) - Yabeda.activejob_scheduled_total.increment(labels) + Yabeda.activejob_scheduled_total.increment(labels) - call_after_event_block - end + call_after_event_block + end - def handle_enqueue_all - event.payload[:jobs].each do |job| - labels = common_labels(job) + def handle_enqueue_all + event.payload[:jobs].each do |job| + labels = common_labels(job) - if job.scheduled_at - Yabeda.activejob_scheduled_total.increment(labels) - else - Yabeda.activejob_enqueued_total.increment(labels) + if job.scheduled_at + Yabeda.activejob_scheduled_total.increment(labels) + else + Yabeda.activejob_enqueued_total.increment(labels) + end + end + + call_after_event_block end - end - call_after_event_block - end + private - private - attr_reader :event + attr_reader :event + + def job_latency + return unless (enqueue_time = event.payload[:job].enqueued_at) - def job_latency - if enqueue_time = event.payload[:job].enqueued_at enqueue_time = parse_event_time(enqueue_time) perform_at_time = parse_event_time(event.end) perform_at_time - enqueue_time end - end - def ms2s(milliseconds) - (milliseconds.to_f / 1000).round(3) - end + def ms2s(milliseconds) + (milliseconds.to_f / 1000).round(3) + end - def parse_event_time(time) - case time - when Time then time - when String then Time.parse(time).utc - else - if time > 1e12 - Time.at(ms2s(time)).utc + def parse_event_time(time) + case time + when Time then time + when String then Time.parse(time).utc else - Time.at(time).utc + if time > 1e12 + Time.at(ms2s(time)).utc + else + Time.at(time).utc + end end end - end - def common_labels(job) - Yabeda.default_tags.reverse_merge( - activejob: job.class.to_s, - queue: job.queue_name, - executions: job.executions.to_s, - ) - end + def common_labels(job) + Yabeda.default_tags.reverse_merge( + activejob: job.class.to_s, + queue: job.queue_name, + executions: job.executions.to_s, + ) + end - def call_after_event_block - Yabeda::ActiveJob.after_event_block.call(event) if Yabeda::ActiveJob.after_event_block.respond_to?(:call) + def call_after_event_block + Yabeda::ActiveJob.after_event_block.call(event) if Yabeda::ActiveJob.after_event_block.respond_to?(:call) + end end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1ae5eb1..554f40c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,6 @@ require_relative "support/rails_app" require "rspec/rails" require "simplecov" -require "active_support" SimpleCov.start RSpec.configure do |config| diff --git a/spec/yabeda/activejob_spec.rb b/spec/yabeda/activejob_spec.rb index c9a57ec..17233fc 100644 --- a/spec/yabeda/activejob_spec.rb +++ b/spec/yabeda/activejob_spec.rb @@ -58,10 +58,13 @@ describe "job latency calculation" do # Rails 7.1.4 and above it "measures correct latency from end_time in seconds", queue_adapter: :test do - start_time = Time.now + base_time = Time.now + + # Mock the job's enqueued_at time to be exactly base_time + allow_any_instance_of(HelloJob).to receive(:enqueued_at).and_return(base_time) # rubocop:disable RSpec/AnyInstance # Mock the event end time to simulate 60 seconds later - allow_any_instance_of(ActiveSupport::Notifications::Event).to receive(:end).and_return(1.minute.from_now(start_time).to_f) # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(ActiveSupport::Notifications::Event).to receive(:end).and_return((base_time + 60.seconds).to_f) # rubocop:disable RSpec/AnyInstance expect { HelloJob.perform_later }.to have_enqueued_job.on_queue("default") expect { perform_enqueued_jobs }.to measure_yabeda_histogram(Yabeda.activejob.latency) @@ -71,10 +74,13 @@ # Rails 7.1.3 and below it "measures correct latency from end_time in milliseconds", queue_adapter: :test do - start_time = Time.now + base_time = Time.now + + # Mock the job's enqueued_at time to be exactly base_time + allow_any_instance_of(HelloJob).to receive(:enqueued_at).and_return(base_time) # rubocop:disable RSpec/AnyInstance # Mock the event end time to simulate 60 seconds later (in milliseconds) - allow_any_instance_of(ActiveSupport::Notifications::Event).to receive(:end).and_return(1.minute.from_now(start_time).to_f * 1000) # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(ActiveSupport::Notifications::Event).to receive(:end).and_return((base_time + 60.seconds).to_f * 1000) # rubocop:disable RSpec/AnyInstance expect { HelloJob.perform_later }.to have_enqueued_job.on_queue("default") expect { perform_enqueued_jobs }.to measure_yabeda_histogram(Yabeda.activejob.latency) @@ -197,7 +203,7 @@ end end - context "when jobs are bulk enqueued", skip: !ActiveJob.respond_to?(:perform_all_later), queue_adapter: :test do + context "when jobs are bulk enqueued", queue_adapter: :test, skip: !ActiveJob.respond_to?(:perform_all_later) do it "increments enqueued job counter for all jobs" do expect do ActiveJob.perform_all_later([HelloJob.new, HelloJob.new, LongJob.new]) @@ -213,10 +219,10 @@ it "increments scheduled job counter for scheduled jobs in bulk" do expect do ActiveJob.perform_all_later([ - HelloJob.new.set(wait: 1.hour), - HelloJob.new, - LongJob.new.set(wait: 2.hours), - ]) + HelloJob.new.set(wait: 1.hour), + HelloJob.new, + LongJob.new.set(wait: 2.hours), + ]) end.to increment_yabeda_counter(Yabeda.activejob.scheduled_total) .with_tags(queue: "default", activejob: "HelloJob", executions: "0") .by(1).and(