sidekiq-undertaker 1.0.0.rc01

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +6 -0
  3. data/.gitignore +22 -0
  4. data/.rubocop.yml +5 -0
  5. data/.rubocop_todo.yml +23 -0
  6. data/.travis.yml +53 -0
  7. data/Demo_Filter.png +0 -0
  8. data/Demo_Job_Filter.png +0 -0
  9. data/Demo_Morgue_1_Job.png +0 -0
  10. data/Demo_Morgue_all.png +0 -0
  11. data/Gemfile +11 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +95 -0
  14. data/Rakefile +34 -0
  15. data/lib/sidekiq/undertaker.rb +16 -0
  16. data/lib/sidekiq/undertaker/bucket.rb +29 -0
  17. data/lib/sidekiq/undertaker/dead_job.rb +64 -0
  18. data/lib/sidekiq/undertaker/job_distributor.rb +64 -0
  19. data/lib/sidekiq/undertaker/job_filter.rb +46 -0
  20. data/lib/sidekiq/undertaker/version.rb +7 -0
  21. data/lib/sidekiq/undertaker/web_extension.rb +37 -0
  22. data/lib/sidekiq/undertaker/web_extension/api_helpers.rb +120 -0
  23. data/sidekiq-undertaker.gemspec +55 -0
  24. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_filter_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +240 -0
  25. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_called/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
  26. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_filter/when_job_classbucket_page_is_polled/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +241 -0
  27. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_all_failures_and_errors/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +316 -0
  28. data/spec/fixtures/approvals/sidekiq_undertaker_webextension/show_morgue/when_job_classerrorbucket_is_called/with_specific_job_class_and_a_specific_error/behaves_like_a_page/the_displayed_page_is_correct.approved.txt +274 -0
  29. data/spec/sidekiq/undertaker/bucket_spec.rb +26 -0
  30. data/spec/sidekiq/undertaker/dead_jobs_spec.rb +90 -0
  31. data/spec/sidekiq/undertaker/job_distributor_spec.rb +104 -0
  32. data/spec/sidekiq/undertaker/job_filter_spec.rb +105 -0
  33. data/spec/sidekiq/undertaker/web_extension_spec.rb +270 -0
  34. data/spec/spec_helper.rb +76 -0
  35. data/web/locales/en.yml +5 -0
  36. data/web/views/filter.erb +34 -0
  37. data/web/views/filter_job_class.erb +35 -0
  38. data/web/views/morgue.erb +75 -0
  39. metadata +378 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 11a061d4335309c54a17ef8f34fc207c49a36ad772f74a145b15482d2dc4446e
4
+ data.tar.gz: b82c5d0d70efd96d4e32e9e1ef6e402f2ccce912bfe50ad4b7ea1ddf97b1c463
5
+ SHA512:
6
+ metadata.gz: dba278326f094e4a3498f727791bc484e2e960be1d751f044a209a8f18583890e3cae95368692618eff8d4b315046806d94c24887a24b64b1b000d09f4f8f0d9
7
+ data.tar.gz: 48b3d817160a12606fec4a7f160c7b29062aca67bebcafc54ba8953a668c75ecf9b3cacf81f8e7315a2fd5abfa5a723d41e70e5b7b6fb50fd234bc7b5cb148fa
@@ -0,0 +1,6 @@
1
+ version: "2"
2
+ plugins:
3
+ git-legal:
4
+ enabled: true
5
+ # rubocop:
6
+ # enabled: true
@@ -0,0 +1,22 @@
1
+ _yardoc
2
+ .approvals
3
+ .bundle
4
+ .config
5
+ .idea/
6
+ .ruby-version
7
+ .rvmrc
8
+ .yardoc
9
+ *.gem
10
+ *.rbc
11
+ *.received.*
12
+ coverage
13
+ doc/
14
+ Gemfile.lock
15
+ InstalledFiles
16
+ lib/bundler/man
17
+ pkg
18
+ rdoc
19
+ spec/reports
20
+ test/tmp
21
+ test/version_tmp
22
+ tmp
@@ -0,0 +1,5 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rt_rubocop_defaults
4
+
5
+ inherit_from: .rubocop_todo.yml
@@ -0,0 +1,23 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2019-12-06 21:33:12 +0100 using RuboCop version 0.77.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: Severity.
11
+ Metrics/AbcSize:
12
+ Max: 22
13
+
14
+ # Offense count: 3
15
+ # Configuration parameters: CountComments, ExcludedMethods, Severity.
16
+ Metrics/MethodLength:
17
+ Max: 19
18
+
19
+ # Offense count: 5
20
+ # Configuration parameters: .
21
+ # SupportedStyles: have_received, receive
22
+ RSpec/MessageSpies:
23
+ EnforcedStyle: receive
@@ -0,0 +1,53 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.4.9
5
+ - 2.5.7
6
+ - 2.6.5
7
+ - jruby-9.1.17.0
8
+ - jruby-9.2.9.0
9
+ jdk:
10
+ - oraclejdk11
11
+ env:
12
+ global:
13
+ - CC_TEST_REPORTER_ID=9fd6c503f8bb89483410c1fc0578e5845c3fe0d3e773a32eb2788713fb101a93
14
+ matrix:
15
+ - "JRUBY_OPTS='--dev --debug'"
16
+ - "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
17
+
18
+ matrix:
19
+ exclude:
20
+ - rvm: 2.4.9
21
+ jdk: oraclejdk8
22
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
23
+ - rvm: 2.5.7
24
+ jdk: oraclejdk8
25
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
26
+ - rvm: 2.6.5
27
+ jdk: oraclejdk8
28
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
29
+ - rvm: truffleruby
30
+ jdk: oraclejdk8
31
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
32
+ allow_failures:
33
+ - rvm: jruby-9.1.17.0
34
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
35
+ - rvm: jruby-9.2.9.0
36
+ env: "JRUBY_OPTS='-Xcompile.invokedynamic=true --debug'"
37
+
38
+ services:
39
+ - redis-server
40
+
41
+ before_install:
42
+ - gem update --system
43
+ - gem install bundler -v "~> 1.0"
44
+ - gem install bundler -v "~> 2.0"
45
+
46
+ before_script:
47
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
48
+ - chmod +x ./cc-test-reporter
49
+ - ./cc-test-reporter before-build
50
+ script:
51
+ - COVERAGE=true bundle exec rspec
52
+ after_script:
53
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
Binary file
Binary file
Binary file
Binary file
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in sidekiq-undertaker.gemspec
6
+ gemspec
7
+
8
+ # Good for debuging on travis-ci.org
9
+ # group :development, :test do
10
+ # gem "approvals", git: "https://github.com/br/approvals", branch: "diff-preview"
11
+ # end
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Thomas Koppensteiner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,95 @@
1
+ # Sidekiq::Undertaker
2
+
3
+ [![Build Status](https://travis-ci.org/ThomasKoppensteiner/sidekiq-under.svg?branch=master)](https://travis-ci.org/ThomasKoppensteiner/sidekiq-undertaker)
4
+ [![Code Climate](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker.svg)](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/9d75b8b7d3ff076e1ef1/test_coverage)](https://codeclimate.com/github/ThomasKoppensteiner/sidekiq-undertaker/test_coverage)
6
+
7
+ ## About
8
+
9
+ Sidekiq::Undertaker is a plugin for [Sidekiq](https://rubygems.org/gems/sidekiq).
10
+ It allows exploring, reviving (retrying) or burying (deleting) dead jobs.
11
+ For easy exploring the dead-jobs queue is broken down into time windows (buckets) of hours, days and weeks.
12
+
13
+ ## Installation
14
+
15
+ #### Install the Gem
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ````ruby
20
+ gem "sidekiq-undertaker"
21
+ ````
22
+
23
+ And then execute:
24
+ ````sh
25
+ $ bundle
26
+ ````
27
+
28
+ Or install it yourself as:
29
+
30
+ ````sh
31
+ $ gem install sidekiq-undertaker
32
+ ````
33
+
34
+ #### Install the Rubocop Pre-Commit Hook
35
+
36
+ ````sh
37
+ $ rake rubocop:install
38
+ ````
39
+
40
+ ## Impressions
41
+
42
+ #### Filter View
43
+
44
+ The filter page shows a table with time-buckets as columns and rows for each job class.
45
+
46
+ ![Sidekiq Undertaker](Demo_Filter.png)
47
+
48
+ #### Job Filter View
49
+
50
+ For each job class, you can drill down to view error distribution based on
51
+ error class.
52
+
53
+ ![Sidekiq Undertaker](Demo_Job_Filter.png)
54
+
55
+ #### Morgue View
56
+ Finally, click on the individual error counts to display details of the
57
+ errors in a list form.
58
+
59
+ ![Sidekiq Undertaker](Demo_Morgue_1_Job.png)
60
+
61
+ The morgue view can, for example, also show an error distribution over all job classes.
62
+
63
+ ![Sidekiq Undertaker](Demo_Morgue_all.png)
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it ( https://github.com/ThomasKoppensteiner/sidekiq-undertaker/fork )
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
72
+
73
+ ## Naming
74
+
75
+ As another gem with the name `sidekiq-cleaner` is already released on rubygems.org,
76
+ this fork was renamed to `sidekiq-undertaker`.
77
+
78
+ ## Thanks
79
+
80
+ The [Sidekiq-Cleaner](https://github.com/HackingHabits/sidekiq-cleaner) gem was originally created by [Madan Thangavelu](https://github.com/HackingHabits).
81
+ [Tout](https://github.com/Tout/sidekiq-cleaner) and [TheWudu](https://github.com/TheWudu/sidekiq-cleaner) also contributed to it.
82
+ For the complete list of network members have a look at the [fork overview](https://github.com/ThomasKoppensteiner/sidekiq-under/network/members).
83
+
84
+ ## Alternative Projects
85
+
86
+ * [sidekiq-cleaner](https://rubygems.org/gems/sidekiq-cleaner)
87
+ * [sidekiq_cleaner](https://rubygems.org/gems/sidekiq_cleaner)
88
+
89
+ ## Author
90
+
91
+ Thomas Koppensteiner | [Github](https://github.com/ThomasKoppensteiner) | [RubyGems](https://rubygems.org/profiles/thomaskoppensteiner) | [@koppensteiner_t](https://twitter.com/koppensteiner_t)
92
+
93
+ ## License
94
+
95
+ See the [License](https://github.com/ThomasKoppensteiner/sidekiq-under/blob/master/LICENSE.txt) file.
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ $LOAD_PATH.unshift("lib")
6
+
7
+ desc "setup gem for development"
8
+ task :init do
9
+ Rake::Task["rubocop:install"].execute
10
+ end
11
+
12
+ #
13
+ # RubocopRunner
14
+ #
15
+ begin
16
+ require "rubocop_runner/rake_task"
17
+ RubocopRunner::RakeTask.new
18
+ rescue LoadError
19
+ puts "RubocopRunner not available!"
20
+ end
21
+
22
+ #
23
+ # RSpec
24
+ #
25
+ begin
26
+ require "rspec/core/rake_task"
27
+
28
+ RSpec::Core::RakeTask.new(:spec)
29
+
30
+ task default: :spec
31
+ task test: :spec
32
+ rescue LoadError
33
+ puts "RSpec not available!"
34
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/version"
4
+ require "sidekiq/undertaker/web_extension"
5
+
6
+ begin
7
+ require "sidekiq/web"
8
+ rescue LoadError # rubocop:disable Lint/SuppressedException
9
+ # client-only usage
10
+ end
11
+
12
+ if defined?(Sidekiq::Web)
13
+ Sidekiq::Web.register Sidekiq::Undertaker::WebExtension
14
+ Sidekiq::Web.tabs["Undertaker"] = "undertaker/filter"
15
+ Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "../../web/locales")
16
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module Undertaker
5
+ class Bucket
6
+ class << self
7
+ def bucket_names
8
+ %w[1_hour 3_hours 1_day 3_days 1_week older]
9
+ end
10
+
11
+ def for_elapsed_time(elapsed_time)
12
+ return "1_hour" if elapsed_time <= ONE_HOUR
13
+ return "3_hours" if elapsed_time <= THREE_HOURS
14
+ return "1_day" if elapsed_time <= ONE_DAY
15
+ return "3_days" if elapsed_time <= THREE_DAYS
16
+ return "1_week" if elapsed_time <= ONE_WEEK
17
+
18
+ "older"
19
+ end
20
+
21
+ ONE_HOUR = 60 * 60 * 1
22
+ THREE_HOURS = ONE_HOUR * 3
23
+ ONE_DAY = ONE_HOUR * 24
24
+ THREE_DAYS = ONE_DAY * 3
25
+ ONE_WEEK = ONE_DAY * 7
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/bucket"
4
+
5
+ module Sidekiq
6
+ module Undertaker
7
+ class DeadJob
8
+ class << self
9
+ def for_each
10
+ Sidekiq::DeadSet.new.each do |job|
11
+ yield to_dead_job(job)
12
+ end
13
+ end
14
+
15
+ def to_dead_job(job)
16
+ job_failed_at = parsed_failed_at(job)
17
+ job_time_elapsed_since_failure = Time.now.to_i - job_failed_at.to_i
18
+ job_bucket_name = Bucket.for_elapsed_time(job_time_elapsed_since_failure)
19
+
20
+ new(job: job,
21
+ time_elapsed_since_failure: job_time_elapsed_since_failure,
22
+ bucket_name: job_bucket_name)
23
+ end
24
+
25
+ def parsed_failed_at(job)
26
+ job.item["failed_at"].is_a?(String) ? Time.parse(job.item["failed_at"]) : job.item["failed_at"]
27
+ end
28
+ end
29
+
30
+ attr_reader :job_class, :time_elapsed_since_failure, :error_class, :bucket_name, :job
31
+
32
+ def initialize(args)
33
+ @job = args.fetch(:job)
34
+ @time_elapsed_since_failure = args.fetch(:time_elapsed_since_failure)
35
+ @bucket_name = args.fetch(:bucket_name)
36
+ @job_class = args.fetch(:job_class, job.item["class"])
37
+ @error_class = args.fetch(:error_class, job.item["error_class"])
38
+ end
39
+
40
+ def ==(other)
41
+ job_class == other.job_class &&
42
+ time_elapsed_since_failure == other.time_elapsed_since_failure &&
43
+ error_class == other.error_class &&
44
+ bucket_name == other.bucket_name &&
45
+ job_eql?(other.job)
46
+ end
47
+ alias eql? ==
48
+
49
+ private
50
+
51
+ attr_writer :job_class, :time_elapsed_since_failure, :error_class, :bucket_name, :job
52
+
53
+ def job_eql?(other_job) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
54
+ job.jid == other_job.jid &&
55
+ job.item == other_job.item &&
56
+ job.args == other_job.args &&
57
+ job.queue == other_job.queue &&
58
+ job.score == other_job.score &&
59
+ job.parent.name == other_job.parent.name &&
60
+ job.parent.size == other_job.parent.size
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sidekiq/undertaker/bucket"
4
+
5
+ module Sidekiq
6
+ module Undertaker
7
+ class JobDistributor
8
+ attr_reader :dead_jobs
9
+
10
+ def initialize(dead_jobs)
11
+ @dead_jobs = dead_jobs
12
+ end
13
+
14
+ def group_by_job_class
15
+ group_by(:job_class)
16
+ end
17
+
18
+ def group_by_error_class
19
+ group_by(:error_class)
20
+ end
21
+
22
+ private
23
+
24
+ def distribute
25
+ distribution = init_counters({}, "all")
26
+
27
+ dead_jobs.each do |dead_job|
28
+ bucket_name = dead_job.bucket_name
29
+
30
+ incr_counters(distribution, "all", bucket_name)
31
+ yield distribution, dead_job, bucket_name if block_given?
32
+ end
33
+
34
+ sort_by_total_dead(distribution)
35
+ end
36
+
37
+ def group_by(attribute)
38
+ distribute do |distribution, dead_job, bucket_name|
39
+ attr = dead_job.public_send(attribute)
40
+ init_counters(distribution, attr) unless distribution[attr]
41
+ incr_counters(distribution, attr, bucket_name)
42
+ end
43
+ end
44
+
45
+ def sort_by_total_dead(distribution)
46
+ distribution.map { |k, v| [k, v] }.sort do |x, y|
47
+ x[1]["total_dead"] <=> y[1]["total_dead"]
48
+ end.reverse
49
+ end
50
+
51
+ def init_counters(distribution, attr)
52
+ distribution[attr] = {}
53
+ Bucket.bucket_names.each { |bucket| distribution[attr][bucket] = 0 }
54
+ distribution[attr]["total_dead"] = 0
55
+ distribution
56
+ end
57
+
58
+ def incr_counters(distribution, attr, bucket_name, value = 1)
59
+ distribution[attr][bucket_name] += value
60
+ distribution[attr]["total_dead"] += value
61
+ end
62
+ end
63
+ end
64
+ end