From 3d2e60cbd1249b00d2afe50e211959860be4cfe1 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 8 Feb 2022 12:19:22 -0600 Subject: [PATCH 1/3] Remove everything so next commit is just what is new --- .github/ISSUE_TEMPLATE/bug_report.md | 20 - .github/contributing.md | 47 - .github/workflows/ci.yml | 39 - .gitignore | 13 - .standard.yml | 15 - 3.0-Upgrade.md | 70 - 4.0-Upgrade.md | 53 - 5.0-Upgrade.md | 56 - 6.0-Upgrade.md | 72 - COMM-LICENSE.txt | 97 - Changes.md | 1864 ----------------- Ent-2.0-Upgrade.md | 37 - Ent-Changes.md | 333 --- Gemfile | 27 - Gemfile.lock | 217 -- LICENSE | 9 - Pro-2.0-Upgrade.md | 138 -- Pro-3.0-Upgrade.md | 44 - Pro-4.0-Upgrade.md | 35 - Pro-5.0-Upgrade.md | 25 - Pro-Changes.md | 851 -------- README.md | 98 - Rakefile | 10 - bin/sidekiq | 42 - bin/sidekiqload | 155 -- bin/sidekiqmon | 8 - code_of_conduct.md | 50 - examples/busy-ui.png | Bin 70667 -> 0 bytes examples/complex_batch_workflow.png | Bin 29012 -> 0 bytes examples/complex_batch_workflow.svg | 1 - examples/config.yml | 23 - examples/ent-bucket.png | Bin 125347 -> 0 bytes examples/ent-concurrent.png | Bin 148292 -> 0 bytes examples/ent-periodic.png | Bin 149863 -> 0 bytes examples/por.rb | 17 - examples/systemd/sidekiq.service | 89 - examples/upstart/sidekiq.conf | 75 - examples/upstart/workers.conf | 37 - examples/web-ui.png | Bin 78578 -> 0 bytes lib/generators/sidekiq/job_generator.rb | 57 - lib/generators/sidekiq/templates/job.rb.erb | 9 - .../sidekiq/templates/job_spec.rb.erb | 6 - .../sidekiq/templates/job_test.rb.erb | 8 - lib/sidekiq.rb | 270 --- lib/sidekiq/api.rb | 1008 --------- lib/sidekiq/cli.rb | 427 ---- lib/sidekiq/client.rb | 240 --- lib/sidekiq/delay.rb | 43 - lib/sidekiq/exception_handler.rb | 27 - lib/sidekiq/extensions/action_mailer.rb | 48 - lib/sidekiq/extensions/active_record.rb | 43 - lib/sidekiq/extensions/class_methods.rb | 43 - lib/sidekiq/extensions/generic_proxy.rb | 33 - lib/sidekiq/fetch.rb | 89 - lib/sidekiq/job.rb | 13 - lib/sidekiq/job_logger.rb | 51 - lib/sidekiq/job_retry.rb | 261 --- lib/sidekiq/job_util.rb | 65 - lib/sidekiq/launcher.rb | 263 --- lib/sidekiq/logger.rb | 170 -- lib/sidekiq/manager.rb | 133 -- lib/sidekiq/middleware/chain.rb | 162 -- lib/sidekiq/middleware/current_attributes.rb | 57 - lib/sidekiq/middleware/i18n.rb | 40 - lib/sidekiq/monitor.rb | 133 -- lib/sidekiq/paginator.rb | 47 - lib/sidekiq/processor.rb | 280 --- lib/sidekiq/rails.rb | 61 - lib/sidekiq/redis_connection.rb | 144 -- lib/sidekiq/scheduled.rb | 208 -- lib/sidekiq/sd_notify.rb | 149 -- lib/sidekiq/systemd.rb | 24 - lib/sidekiq/testing.rb | 342 --- lib/sidekiq/testing/inline.rb | 30 - lib/sidekiq/util.rb | 108 - lib/sidekiq/version.rb | 5 - lib/sidekiq/web.rb | 169 -- lib/sidekiq/web/action.rb | 93 - lib/sidekiq/web/application.rb | 366 ---- lib/sidekiq/web/csrf_protection.rb | 180 -- lib/sidekiq/web/helpers.rb | 342 --- lib/sidekiq/web/router.rb | 104 - lib/sidekiq/worker.rb | 362 ---- myapp/.gitignore | 30 - myapp/Gemfile | 11 - myapp/Rakefile | 6 - myapp/app/assets/config/manifest.js | 0 .../app/controllers/application_controller.rb | 2 - myapp/app/controllers/work_controller.rb | 44 - myapp/app/helpers/application_helper.rb | 2 - myapp/app/jobs/application_job.rb | 2 - myapp/app/jobs/exit_job.rb | 11 - myapp/app/jobs/some_job.rb | 8 - myapp/app/mailers/.gitkeep | 0 myapp/app/mailers/user_mailer.rb | 9 - myapp/app/models/.gitkeep | 0 myapp/app/models/exiter.rb | 9 - myapp/app/models/post.rb | 9 - myapp/app/views/layouts/application.html.erb | 15 - .../app/views/user_mailer/greetings.html.erb | 3 - myapp/app/views/work/index.html.erb | 1 - myapp/app/workers/exit_worker.rb | 11 - myapp/app/workers/hard_worker.rb | 10 - myapp/app/workers/lazy_worker.rb | 6 - myapp/bin/bundle | 3 - myapp/bin/rails | 9 - myapp/bin/rake | 9 - myapp/bin/setup | 36 - myapp/config.ru | 5 - myapp/config/application.rb | 34 - myapp/config/boot.rb | 3 - myapp/config/database.yml | 25 - myapp/config/environment.rb | 5 - myapp/config/environments/development.rb | 61 - myapp/config/environments/production.rb | 94 - myapp/config/environments/test.rb | 46 - myapp/config/initializers/assets.rb | 11 - .../initializers/backtrace_silencers.rb | 7 - .../config/initializers/cookies_serializer.rb | 5 - .../initializers/filter_parameter_logging.rb | 4 - myapp/config/initializers/inflections.rb | 16 - myapp/config/initializers/mime_types.rb | 4 - myapp/config/initializers/secret_token.rb | 7 - myapp/config/initializers/session_store.rb | 3 - myapp/config/initializers/sidekiq.rb | 44 - myapp/config/initializers/wrap_parameters.rb | 14 - myapp/config/locales/en.yml | 23 - myapp/config/routes.rb | 15 - myapp/config/secrets.yml | 22 - myapp/config/sidekiq.yml | 3 - .../db/migrate/20120123214055_create_posts.rb | 10 - myapp/db/schema.rb | 22 - myapp/db/seeds.rb | 7 - myapp/lib/assets/.gitkeep | 0 myapp/lib/tasks/.gitkeep | 0 myapp/log/.gitkeep | 0 myapp/script/rails | 6 - myapp/simple.ru | 19 - sidekiq.gemspec | 28 - test/config.yml | 7 - test/config__FILE__and__dir__.yml | 5 - test/config_empty.yml | 1 - test/config_environment.yml | 10 - test/config_queues_without_weights.yml | 4 - test/config_string.yml | 8 - test/config_with_alias.yml | 12 - test/config_with_internal_options.yml | 11 - test/dummy/config/application.rb | 19 - test/dummy/config/database.yml | 11 - test/dummy/config/environment.rb | 3 - test/dummy/config/sidekiq.yml | 3 - test/dummy/tmp/.keep | 0 test/fake_env.rb | 1 - test/fixtures/en.yml | 2 - test/helper.rb | 46 - test/test_actors.rb | 139 -- test/test_api.rb | 680 ------ test/test_cli.rb | 591 ------ test/test_client.rb | 446 ---- test/test_csrf.rb | 115 - test/test_current_attributes.rb | 64 - test/test_dead_set.rb | 46 - test/test_exception_handler.rb | 56 - test/test_extensions.rb | 112 - test/test_fetch.rb | 67 - test/test_job.rb | 13 - test/test_job_generator.rb | 56 - test/test_job_logger.rb | 96 - test/test_launcher.rb | 165 -- test/test_logger.rb | 147 -- test/test_manager.rb | 47 - test/test_middleware.rb | 169 -- test/test_processor.rb | 367 ---- test/test_rails.rb | 51 - test/test_redis_connection.rb | 300 --- test/test_retry.rb | 373 ---- test/test_retry_exhausted.rb | 151 -- test/test_scheduled.rb | 139 -- test/test_scheduling.rb | 69 - test/test_sidekiq.rb | 117 -- test/test_sidekiqmon.rb | 105 - test/test_systemd.rb | 40 - test/test_testing.rb | 133 -- test/test_testing_fake.rb | 365 ---- test/test_testing_inline.rb | 91 - test/test_util.rb | 25 - test/test_web.rb | 801 ------- test/test_web_helpers.rb | 127 -- test/test_worker.rb | 148 -- web/assets/images/apple-touch-icon.png | Bin 15594 -> 0 bytes web/assets/images/favicon.ico | Bin 5430 -> 0 bytes web/assets/images/logo.png | Bin 4143 -> 0 bytes web/assets/images/status.png | Bin 2271 -> 0 bytes web/assets/javascripts/application.js | 111 - web/assets/javascripts/dashboard.js | 296 --- web/assets/stylesheets/application-dark.css | 143 -- web/assets/stylesheets/application-rtl.css | 242 --- web/assets/stylesheets/application.css | 957 --------- web/assets/stylesheets/bootstrap-rtl.min.css | 9 - web/assets/stylesheets/bootstrap.css | 5 - web/locales/ar.yml | 87 - web/locales/cs.yml | 78 - web/locales/da.yml | 68 - web/locales/de.yml | 81 - web/locales/el.yml | 68 - web/locales/en.yml | 86 - web/locales/es.yml | 86 - web/locales/fa.yml | 80 - web/locales/fr.yml | 85 - web/locales/he.yml | 79 - web/locales/hi.yml | 75 - web/locales/it.yml | 69 - web/locales/ja.yml | 86 - web/locales/ko.yml | 68 - web/locales/lt.yml | 83 - web/locales/nb.yml | 77 - web/locales/nl.yml | 68 - web/locales/pl.yml | 59 - web/locales/pt-br.yml | 68 - web/locales/pt.yml | 67 - web/locales/ru.yml | 82 - web/locales/sv.yml | 68 - web/locales/ta.yml | 75 - web/locales/uk.yml | 76 - web/locales/ur.yml | 80 - web/locales/vi.yml | 83 - web/locales/zh-cn.yml | 68 - web/locales/zh-tw.yml | 68 - web/views/_footer.erb | 20 - web/views/_job_info.erb | 89 - web/views/_nav.erb | 52 - web/views/_paging.erb | 23 - web/views/_poll_link.erb | 4 - web/views/_status.erb | 4 - web/views/_summary.erb | 40 - web/views/busy.erb | 132 -- web/views/dashboard.erb | 83 - web/views/dead.erb | 34 - web/views/layout.erb | 42 - web/views/morgue.erb | 78 - web/views/queue.erb | 55 - web/views/queues.erb | 38 - web/views/retries.erb | 83 - web/views/retry.erb | 34 - web/views/scheduled.erb | 57 - web/views/scheduled_job_info.erb | 8 - 246 files changed, 23480 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/contributing.md delete mode 100644 .github/workflows/ci.yml delete mode 100644 .gitignore delete mode 100644 .standard.yml delete mode 100644 3.0-Upgrade.md delete mode 100644 4.0-Upgrade.md delete mode 100644 5.0-Upgrade.md delete mode 100644 6.0-Upgrade.md delete mode 100644 COMM-LICENSE.txt delete mode 100644 Changes.md delete mode 100644 Ent-2.0-Upgrade.md delete mode 100644 Ent-Changes.md delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 LICENSE delete mode 100644 Pro-2.0-Upgrade.md delete mode 100644 Pro-3.0-Upgrade.md delete mode 100644 Pro-4.0-Upgrade.md delete mode 100644 Pro-5.0-Upgrade.md delete mode 100644 Pro-Changes.md delete mode 100644 README.md delete mode 100644 Rakefile delete mode 100755 bin/sidekiq delete mode 100755 bin/sidekiqload delete mode 100755 bin/sidekiqmon delete mode 100644 code_of_conduct.md delete mode 100644 examples/busy-ui.png delete mode 100644 examples/complex_batch_workflow.png delete mode 100644 examples/complex_batch_workflow.svg delete mode 100644 examples/config.yml delete mode 100644 examples/ent-bucket.png delete mode 100644 examples/ent-concurrent.png delete mode 100644 examples/ent-periodic.png delete mode 100644 examples/por.rb delete mode 100644 examples/systemd/sidekiq.service delete mode 100644 examples/upstart/sidekiq.conf delete mode 100644 examples/upstart/workers.conf delete mode 100644 examples/web-ui.png delete mode 100644 lib/generators/sidekiq/job_generator.rb delete mode 100644 lib/generators/sidekiq/templates/job.rb.erb delete mode 100644 lib/generators/sidekiq/templates/job_spec.rb.erb delete mode 100644 lib/generators/sidekiq/templates/job_test.rb.erb delete mode 100644 lib/sidekiq.rb delete mode 100644 lib/sidekiq/api.rb delete mode 100644 lib/sidekiq/cli.rb delete mode 100644 lib/sidekiq/client.rb delete mode 100644 lib/sidekiq/delay.rb delete mode 100644 lib/sidekiq/exception_handler.rb delete mode 100644 lib/sidekiq/extensions/action_mailer.rb delete mode 100644 lib/sidekiq/extensions/active_record.rb delete mode 100644 lib/sidekiq/extensions/class_methods.rb delete mode 100644 lib/sidekiq/extensions/generic_proxy.rb delete mode 100644 lib/sidekiq/fetch.rb delete mode 100644 lib/sidekiq/job.rb delete mode 100644 lib/sidekiq/job_logger.rb delete mode 100644 lib/sidekiq/job_retry.rb delete mode 100644 lib/sidekiq/job_util.rb delete mode 100644 lib/sidekiq/launcher.rb delete mode 100644 lib/sidekiq/logger.rb delete mode 100644 lib/sidekiq/manager.rb delete mode 100644 lib/sidekiq/middleware/chain.rb delete mode 100644 lib/sidekiq/middleware/current_attributes.rb delete mode 100644 lib/sidekiq/middleware/i18n.rb delete mode 100644 lib/sidekiq/monitor.rb delete mode 100644 lib/sidekiq/paginator.rb delete mode 100644 lib/sidekiq/processor.rb delete mode 100644 lib/sidekiq/rails.rb delete mode 100644 lib/sidekiq/redis_connection.rb delete mode 100644 lib/sidekiq/scheduled.rb delete mode 100644 lib/sidekiq/sd_notify.rb delete mode 100644 lib/sidekiq/systemd.rb delete mode 100644 lib/sidekiq/testing.rb delete mode 100644 lib/sidekiq/testing/inline.rb delete mode 100644 lib/sidekiq/util.rb delete mode 100644 lib/sidekiq/version.rb delete mode 100644 lib/sidekiq/web.rb delete mode 100644 lib/sidekiq/web/action.rb delete mode 100644 lib/sidekiq/web/application.rb delete mode 100644 lib/sidekiq/web/csrf_protection.rb delete mode 100644 lib/sidekiq/web/helpers.rb delete mode 100644 lib/sidekiq/web/router.rb delete mode 100644 lib/sidekiq/worker.rb delete mode 100644 myapp/.gitignore delete mode 100644 myapp/Gemfile delete mode 100644 myapp/Rakefile delete mode 100644 myapp/app/assets/config/manifest.js delete mode 100644 myapp/app/controllers/application_controller.rb delete mode 100644 myapp/app/controllers/work_controller.rb delete mode 100644 myapp/app/helpers/application_helper.rb delete mode 100644 myapp/app/jobs/application_job.rb delete mode 100644 myapp/app/jobs/exit_job.rb delete mode 100644 myapp/app/jobs/some_job.rb delete mode 100644 myapp/app/mailers/.gitkeep delete mode 100644 myapp/app/mailers/user_mailer.rb delete mode 100644 myapp/app/models/.gitkeep delete mode 100644 myapp/app/models/exiter.rb delete mode 100644 myapp/app/models/post.rb delete mode 100644 myapp/app/views/layouts/application.html.erb delete mode 100644 myapp/app/views/user_mailer/greetings.html.erb delete mode 100644 myapp/app/views/work/index.html.erb delete mode 100644 myapp/app/workers/exit_worker.rb delete mode 100644 myapp/app/workers/hard_worker.rb delete mode 100644 myapp/app/workers/lazy_worker.rb delete mode 100755 myapp/bin/bundle delete mode 100755 myapp/bin/rails delete mode 100755 myapp/bin/rake delete mode 100755 myapp/bin/setup delete mode 100644 myapp/config.ru delete mode 100644 myapp/config/application.rb delete mode 100644 myapp/config/boot.rb delete mode 100644 myapp/config/database.yml delete mode 100644 myapp/config/environment.rb delete mode 100644 myapp/config/environments/development.rb delete mode 100644 myapp/config/environments/production.rb delete mode 100644 myapp/config/environments/test.rb delete mode 100644 myapp/config/initializers/assets.rb delete mode 100644 myapp/config/initializers/backtrace_silencers.rb delete mode 100644 myapp/config/initializers/cookies_serializer.rb delete mode 100644 myapp/config/initializers/filter_parameter_logging.rb delete mode 100644 myapp/config/initializers/inflections.rb delete mode 100644 myapp/config/initializers/mime_types.rb delete mode 100644 myapp/config/initializers/secret_token.rb delete mode 100644 myapp/config/initializers/session_store.rb delete mode 100644 myapp/config/initializers/sidekiq.rb delete mode 100644 myapp/config/initializers/wrap_parameters.rb delete mode 100644 myapp/config/locales/en.yml delete mode 100644 myapp/config/routes.rb delete mode 100644 myapp/config/secrets.yml delete mode 100644 myapp/config/sidekiq.yml delete mode 100644 myapp/db/migrate/20120123214055_create_posts.rb delete mode 100644 myapp/db/schema.rb delete mode 100644 myapp/db/seeds.rb delete mode 100644 myapp/lib/assets/.gitkeep delete mode 100644 myapp/lib/tasks/.gitkeep delete mode 100644 myapp/log/.gitkeep delete mode 100755 myapp/script/rails delete mode 100644 myapp/simple.ru delete mode 100644 sidekiq.gemspec delete mode 100644 test/config.yml delete mode 100644 test/config__FILE__and__dir__.yml delete mode 100644 test/config_empty.yml delete mode 100644 test/config_environment.yml delete mode 100644 test/config_queues_without_weights.yml delete mode 100644 test/config_string.yml delete mode 100644 test/config_with_alias.yml delete mode 100644 test/config_with_internal_options.yml delete mode 100644 test/dummy/config/application.rb delete mode 100644 test/dummy/config/database.yml delete mode 100644 test/dummy/config/environment.rb delete mode 100644 test/dummy/config/sidekiq.yml delete mode 100644 test/dummy/tmp/.keep delete mode 100644 test/fake_env.rb delete mode 100644 test/fixtures/en.yml delete mode 100644 test/helper.rb delete mode 100644 test/test_actors.rb delete mode 100644 test/test_api.rb delete mode 100644 test/test_cli.rb delete mode 100644 test/test_client.rb delete mode 100644 test/test_csrf.rb delete mode 100644 test/test_current_attributes.rb delete mode 100644 test/test_dead_set.rb delete mode 100644 test/test_exception_handler.rb delete mode 100644 test/test_extensions.rb delete mode 100644 test/test_fetch.rb delete mode 100644 test/test_job.rb delete mode 100644 test/test_job_generator.rb delete mode 100644 test/test_job_logger.rb delete mode 100644 test/test_launcher.rb delete mode 100644 test/test_logger.rb delete mode 100644 test/test_manager.rb delete mode 100644 test/test_middleware.rb delete mode 100644 test/test_processor.rb delete mode 100644 test/test_rails.rb delete mode 100644 test/test_redis_connection.rb delete mode 100644 test/test_retry.rb delete mode 100644 test/test_retry_exhausted.rb delete mode 100644 test/test_scheduled.rb delete mode 100644 test/test_scheduling.rb delete mode 100644 test/test_sidekiq.rb delete mode 100644 test/test_sidekiqmon.rb delete mode 100644 test/test_systemd.rb delete mode 100644 test/test_testing.rb delete mode 100644 test/test_testing_fake.rb delete mode 100644 test/test_testing_inline.rb delete mode 100644 test/test_util.rb delete mode 100644 test/test_web.rb delete mode 100644 test/test_web_helpers.rb delete mode 100644 test/test_worker.rb delete mode 100644 web/assets/images/apple-touch-icon.png delete mode 100644 web/assets/images/favicon.ico delete mode 100755 web/assets/images/logo.png delete mode 100755 web/assets/images/status.png delete mode 100644 web/assets/javascripts/application.js delete mode 100644 web/assets/javascripts/dashboard.js delete mode 100644 web/assets/stylesheets/application-dark.css delete mode 100644 web/assets/stylesheets/application-rtl.css delete mode 100644 web/assets/stylesheets/application.css delete mode 100644 web/assets/stylesheets/bootstrap-rtl.min.css delete mode 100644 web/assets/stylesheets/bootstrap.css delete mode 100644 web/locales/ar.yml delete mode 100644 web/locales/cs.yml delete mode 100644 web/locales/da.yml delete mode 100644 web/locales/de.yml delete mode 100644 web/locales/el.yml delete mode 100644 web/locales/en.yml delete mode 100644 web/locales/es.yml delete mode 100644 web/locales/fa.yml delete mode 100644 web/locales/fr.yml delete mode 100644 web/locales/he.yml delete mode 100644 web/locales/hi.yml delete mode 100644 web/locales/it.yml delete mode 100644 web/locales/ja.yml delete mode 100644 web/locales/ko.yml delete mode 100644 web/locales/lt.yml delete mode 100644 web/locales/nb.yml delete mode 100644 web/locales/nl.yml delete mode 100644 web/locales/pl.yml delete mode 100644 web/locales/pt-br.yml delete mode 100644 web/locales/pt.yml delete mode 100644 web/locales/ru.yml delete mode 100644 web/locales/sv.yml delete mode 100644 web/locales/ta.yml delete mode 100644 web/locales/uk.yml delete mode 100644 web/locales/ur.yml delete mode 100644 web/locales/vi.yml delete mode 100644 web/locales/zh-cn.yml delete mode 100644 web/locales/zh-tw.yml delete mode 100644 web/views/_footer.erb delete mode 100644 web/views/_job_info.erb delete mode 100644 web/views/_nav.erb delete mode 100644 web/views/_paging.erb delete mode 100644 web/views/_poll_link.erb delete mode 100644 web/views/_status.erb delete mode 100644 web/views/_summary.erb delete mode 100644 web/views/busy.erb delete mode 100644 web/views/dashboard.erb delete mode 100644 web/views/dead.erb delete mode 100644 web/views/layout.erb delete mode 100644 web/views/morgue.erb delete mode 100644 web/views/queue.erb delete mode 100644 web/views/queues.erb delete mode 100644 web/views/retries.erb delete mode 100644 web/views/retry.erb delete mode 100644 web/views/scheduled.erb delete mode 100644 web/views/scheduled_job_info.erb diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 95c2601a..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -Ruby version: -Rails version: -Sidekiq / Pro / Enterprise version(s): - -Please include your initializer, sidekiq.yml, and any error message with the full backtrace. - -If you are using an old version, have you checked the changelogs to see if your issue has been fixed in a later version? - -https://github.com/mperham/sidekiq/blob/main/Changes.md -https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md -https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md diff --git a/.github/contributing.md b/.github/contributing.md deleted file mode 100644 index dffd847a..00000000 --- a/.github/contributing.md +++ /dev/null @@ -1,47 +0,0 @@ -# Contributing - -## Issues - -When opening an issue: - -* include the full **backtrace** with your error -* include your Sidekiq initializer -* list versions you are using: Ruby, Rails, Sidekiq, OS, etc. - -It's always better to include more info rather than less. - -## Code - -It's always best to open an issue before investing a lot of time into a -fix or new functionality. Functionality must meet my design goals and -vision for the project to be accepted; I would be happy to discuss how -your idea can best fit into Sidekiq. - -### Local development setup - -You need Redis installed and a Ruby version that fulfills the requirements in -`sidekiq.gemspec`. Then: - -``` -bundle install -``` - -And in order to run the tests and linter checks: - -``` -bundle exec rake -``` - -## Legal - -By submitting a Pull Request, you disavow any rights or claims to any changes -submitted to the Sidekiq project and assign the copyright of -those changes to Contributed Systems LLC. - -If you cannot or do not want to reassign those rights (your employment -contract for your employer may not allow this), you should not submit a PR. -Open an issue and someone else can do the work. - -This is a legal way of saying "If you submit a PR to us, that code becomes ours". -99.9% of the time that's what you intend anyways; we hope it doesn't scare you -away from contributing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0a462adc..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: CI - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - test: - - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - ruby: ["2.6", "2.7", "3.0", "3.1"] - services: - redis: - image: redis - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - steps: - - uses: actions/checkout@v2 - - name: Set up Ruby - # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, - # change this to (see https://github.com/ruby/setup-ruby#versioning): - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true # 'bundle install' and cache gems - ruby-version: ${{ matrix.ruby }} - - name: Run tests - run: bundle exec rake diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6f286de7..00000000 --- a/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -.rvmrc -.ruby-version -tags -*.swp -dump.rdb -.rbx -coverage/ -vendor/ -.bundle/ -.sass-cache/ -tmp/ -pkg/*.gem -.byebug_history diff --git a/.standard.yml b/.standard.yml deleted file mode 100644 index 22990b4f..00000000 --- a/.standard.yml +++ /dev/null @@ -1,15 +0,0 @@ -ruby_version: 2.5.0 -fix: true -parallel: true -ignore: - - test/**/* - - examples/**/* - - myapp/**/* - - 'lib/sidekiq.rb': - - Lint/InheritException - - 'lib/sidekiq/extensions/**/*': - - Style/MissingRespondToMissing - - 'lib/**/*': - - Lint/RescueException - - Security/YAMLLoad - - Style/GlobalVars diff --git a/3.0-Upgrade.md b/3.0-Upgrade.md deleted file mode 100644 index 8be66fa3..00000000 --- a/3.0-Upgrade.md +++ /dev/null @@ -1,70 +0,0 @@ -# Upgrading to Sidekiq 3.0 - -Sidekiq 3.0 brings several new features but also removes old APIs and -changes a few data elements in Redis. To upgrade cleanly: - -* Upgrade to the latest Sidekiq 2.x and run it for a few weeks. - `gem 'sidekiq', '< 3'` - This is only needed if you have retries pending. -* 3rd party gems which use **client-side middleware** will need to update - due to an API change. The Redis connection for a particular job is - passed thru the middleware to handle sharding where jobs can - be pushed to different redis server instances. - - `def call(worker_class, msg, queue, redis_pool)` - - Client-side middleware should use `redis_pool.with { |conn| ... }` to - perform Redis operations and **not** `Sidekiq.redis`. -* If you used the capistrano integration, you'll need to pull in the - new [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) - gem and use it in your deploy.rb. -* API changes: - - `Sidekiq::Client.registered_workers` replaced by `Sidekiq::Workers.new` - - `Sidekiq::Client.registered_queues` replaced by `Sidekiq::Queue.all` - - `Sidekiq::Worker#retries_exhausted` replaced by `Sidekiq::Worker.sidekiq_retries_exhausted` - - `Sidekiq::Workers#each` has changed significantly with a reworking - of Sidekiq's internal process/thread data model. -* `sidekiq/api` is no longer automatically required. If your code uses - the API, you will need to require it. -* Redis-to-Go is no longer transparently activated on Heroku so as to not play - favorites with any particular Redis service. You need to set a config option - for your app: - `heroku config:set REDIS_PROVIDER=REDISTOGO_URL`. You may also use - the generic `REDIS_URL`. See - [Advanced Options: Setting the Location of your Redis server][1] - for details. -* Anyone using Airbrake, Honeybadger, Exceptional or ExceptionNotifier - will need to update their error gem version to the latest to pull in - Sidekiq support. Sidekiq will not provide explicit support for these - services so as to not play favorites with any particular error service. -* MRI 1.9 is no longer officially supported. Sidekiq's official - support policy is to support the current and previous major releases - of MRI and Rails. As of February 2014, that's MRI 2.1, MRI 2.0, JRuby 1.7, Rails 4.0 - and Rails 3.2. I will consider PRs to fix issues found by users for - other platforms/versions. - -## Error Service Providers - -If you previously provided a middleware to capture job errors, you -should instead provide a global error handler with Sidekiq 3.0. This -ensures **any** error within Sidekiq will be logged appropriately, not -just during job execution. - -```ruby -if Sidekiq::VERSION < '3' - # old behavior - Sidekiq.configure_server do |config| - config.server_middleware do |chain| - chain.add MyErrorService::Middleware - end - end -else - Sidekiq.configure_server do |config| - config.error_handlers << proc {|ex,context| MyErrorService.notify(ex, context) } - end -end -``` - -Your error handler must respond to `call(exception, context_hash)`. - -[1]: https://github.com/mperham/sidekiq/wiki/Advanced-Options#via-env-variable diff --git a/4.0-Upgrade.md b/4.0-Upgrade.md deleted file mode 100644 index b5c2e1bd..00000000 --- a/4.0-Upgrade.md +++ /dev/null @@ -1,53 +0,0 @@ -# Welcome to Sidekiq 4.0! - -Sidekiq 4.0 contains a redesigned, more efficient core with less overhead per job. -See my blog for [an overview of Sidekiq 4's higher performance](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). - -## What's New - -* Sidekiq no longer uses Celluloid. If your application code uses Celluloid, - you will need to pull it in yourself. - -* `redis-namespace` has been removed from Sidekiq's gem dependencies. If - you want to use namespacing ([and I strongly urge you not to](http://www.mikeperham.com/2015/09/24/storing-data-with-redis/)), you'll need to add the gem to your Gemfile: -```ruby -gem 'redis-namespace' -``` - -* **Redis 2.8.0 or greater is required.** Redis 2.8 was released two years - ago and contains **many** useful features which Sidekiq couldn't - leverage until now. **Redis 3.0.3 or greater is recommended** for large - scale use [#2431](https://github.com/mperham/sidekiq/issues/2431). - -* Jobs are now fetched from Redis in parallel, making Sidekiq more - resilient to high network latency. This means that Sidekiq requires - more Redis connections per process. You must have a minimum of - `concurrency + 2` connections in your pool or Sidekiq will exit. - When in doubt, let Sidekiq size the connection pool for you. - -* Worker data is no longer updated in real-time but rather upon every - heartbeat. Don't expect the `Sidekiq::Workers` API to be millisecond-precise. - -* There's a new testing API based off the `Sidekiq::Queues` namespace. All - assertions made against the Worker class still work as expected. -```ruby -assert_equal 0, Sidekiq::Queues["default"].size -HardWorker.perform_async("log") -assert_equal 1, Sidekiq::Queues["default"].size -assert_equal "log", Sidekiq::Queues["default"].first['args'][0] -Sidekiq::Queues.clear_all -``` - -## Upgrade - -First, make sure you are using Redis 2.8 or greater. Next: - -* Upgrade to the latest Sidekiq 3.x. -```ruby -gem 'sidekiq', '< 4' -``` -* Fix any deprecation warnings you see. -* Upgrade to 4.x. -```ruby -gem 'sidekiq', '< 5' -``` diff --git a/5.0-Upgrade.md b/5.0-Upgrade.md deleted file mode 100644 index de782caa..00000000 --- a/5.0-Upgrade.md +++ /dev/null @@ -1,56 +0,0 @@ -# Welcome to Sidekiq 5.0! - -Sidekiq 5.0 contains a reworked job dispatch and execution core to integrate -better with the new Rails 5.0 Executor. It also drops support for older -versions of Ruby and Rails and adds support for RTL languages in the Web UI. - -## What's New - -* Integrate job logging and retry logic directly in with the job - execution logic in Sidekiq::Processor. Previously this logic was - defined as middleware. In Rails 5.0, ActiveSupport::Executor handles ActiveRecord - connection management, job callbacks, development mode class loading, - etc. Because of its extensive responsibilities, the Executor can't be - integrated as Sidekiq middleware; the logging/retry logic had to be pulled out - too. Sidekiq 4.2 had a hack to make it work but this redesign provides - a cleaner integration. [#3235] -* The Delayed Extensions `delay`, `delay_for` and `delay_until` APIs are - no longer available by default. The extensions allow you to marshal - job arguments as YAML, leading to cases where job payloads could be many - 100s of KB or larger if not careful, leading to Redis networking - timeouts or other problems. As noted in the Best Practices wiki page, - Sidekiq is designed for jobs with small, simple arguments. - - Add this line to your initializer to re-enable them and get the old behavior: - ```ruby - Sidekiq::Extensions.enable_delay! - ``` - The old `Sidekiq.remove_delay!` API has been removed as it is now the default. [#3299] -* Sidekiq's quiet signal is now `TSTP` (think of it as **T**hread - **ST**o**P**) instead of USR1 as USR1 is not available on JRuby. - USR1 will continue to be supported in Sidekiq 5.x for backwards - compatibility and will be removed in Sidekiq 6.x. [#3302] -* The Web UI is now bi-directional - it can render either LTR - (left-to-right) or RTL languages. With this change, **Farsi, Arabic, - Hebrew and Urdu** are officially supported. [#3381] -* Jobs which can't be parsed due to invalid JSON are now pushed - immediately to the Dead set since they require manual intervention and - will never execute successfully as is. The Web UI has been updated to - more gracefully display these jobs. [#3296] -* **Rails 3.2** is no longer supported. -* **Ruby 2.0 and Ruby 2.1** are no longer supported. Ruby 2.2.2+ is required. - -## Upgrade - -As always, please upgrade Sidekiq **one major version at a time**. -If you are already running Sidekiq 4.x, then: - -* Upgrade to the latest Sidekiq 4.x. -```ruby -gem 'sidekiq', '< 5' -``` -* Fix any deprecation warnings you see. -* Upgrade to 5.x. -```ruby -gem 'sidekiq', '< 6' -``` diff --git a/6.0-Upgrade.md b/6.0-Upgrade.md deleted file mode 100644 index e65d9e78..00000000 --- a/6.0-Upgrade.md +++ /dev/null @@ -1,72 +0,0 @@ -# Welcome to Sidekiq 6.0! - -Sidekiq 6.0 contains some breaking changes which streamline proper operation -of Sidekiq. It also drops support for EOL versions of Ruby and Rails. - -## What's New - -This release has major breaking changes. Read and test carefully in production. - -- ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq - features/internals like the retry subsystem. Prefer the native - Sidekiq::Worker APIs as some Sidekiq features (e.g. unique jobs) do not work well with AJ. - (requires Rails 6.0.2) -```ruby -class MyJob < ActiveJob::Base - queue_as :myqueue - sidekiq_options retry: 10, backtrace: 20 - def perform(...) - end -end -``` -- Logging has been redesigned to allow pluggable formatters and several - formats ship with Sidekiq: - * default - your typical output on macOS - * heroku - enabled specifically when running in Heroku - * json - a JSON format for search indexing, one hash per line - -Sidekiq will enable the best formatter for the detected environment but -you can override it by configuring the log formatter explicitly. See -'sidekiq/logger' for implementation details. - -```ruby -Sidekiq.configure_server do |config| - config.log_formatter = AcmeCorp::PlainLogFormatter.new - # config.log_formatter = Sidekiq::Logger::Formatters::JSON.new -end -``` -Please see the [Logging](https://github.com/mperham/sidekiq/wiki/Logging) wiki page for the latest documentation and notes. -- **Remove the daemonization, logfile and pidfile command line arguments and `sidekiqctl` binary**. -I've [noted for years](https://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/) -how modern services should be managed with a proper init system. -Managing services manually is more error-prone, let your operating system do it for you. -systemd, upstart, and foreman are three options. See the Deployment wiki page for the latest details. -- **Validate proper usage of the `REDIS_PROVIDER` variable.** -This variable is meant to hold the name of the environment -variable which contains your Redis URL, so that you can switch Redis -providers quickly and easily with a single variable change. It is not -meant to hold the actual Redis URL itself. If you want to manually set -the Redis URL then you may set `REDIS_URL` directly. [#3969] -- **Increase default shutdown timeout from 8 seconds to 25 seconds.** -Both Heroku and ECS now use 30 second shutdown timeout -by default and we want Sidekiq to take advantage of this time. If you -have deployment scripts which depend on the old default timeout, use `-t 8` to -get the old behavior. [#3968] -* **Rails <5** is no longer supported. Rails 6+ only works in zeitwerk mode. -* **Ruby <2.5** is no longer supported. -* **Redis <4** is no longer supported. - -## Upgrade - -As always, please upgrade Sidekiq **one major version at a time**. -If you are already running Sidekiq 5.x, then: - -* Upgrade to the latest Sidekiq 5.x. -```ruby -gem 'sidekiq', '< 6' -``` -* Fix any deprecation warnings you see. -* Upgrade to 6.x. -```ruby -gem 'sidekiq', '< 7' -``` diff --git a/COMM-LICENSE.txt b/COMM-LICENSE.txt deleted file mode 100644 index 880a1d37..00000000 --- a/COMM-LICENSE.txt +++ /dev/null @@ -1,97 +0,0 @@ -END-USER LICENSE AGREEMENT - ------------------------------------------------------------------------------- - -IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT ("EULA") IS A LEGAL AGREEMENT (“Agreement”) BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND CONTRIBUTED SYSTEMS. READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION PROCESS AND USING SIDEKIQ PRO AND RELATED SOFTWARE COMPONENTS (“SOFTWARE”). IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE CONFIRMING YOUR ACCEPTANCE OF THE SOFTWARE AND AGREEING TO BECOME BOUND BY THE TERMS OF THIS AGREEMENT. - ------------------------------------------------------------------------------- - -In order to use the Software under this Agreement, you must receive a “Source URL” at the time of purchase, in accordance with the scope of use and other terms specified for each type of Software and as set forth in this Section 1 of this Agreement. - -1. License Grant - -1.1 General Use. This Agreement grants you a non-exclusive, non-transferable, limited license to the use rights for the Software, without the right to grant sublicenses, subject to the terms and conditions in this Agreement. The Software is licensed, not sold. - -1.2 Unlimited Organization License. If you purchased an Organization License (included with the Sidekiq Pro Software), you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. You may also run an unlimited number of Workers. “Worker” means a thread within a Sidekiq server process which executes jobs. You may concurrently run the software on an unlimited number of Hosts, with each host running an unlimited number of Workers. - -1.3 Limited Enterprise License. If you purchased a Limited License for the Sidekiq Enterprise Software, you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. The aggregate number of Workers run by the hosts must not exceed the maximum number of Workers authorized at the time of purchase. “Worker” means a thread within a Sidekiq server process which executes jobs. In order to run additional Workers, you must purchase an additional allowance from Contributed Systems. - -1.4 Enterprise Site License. If you purchased a Site License for the Sidekiq Enterprise Software, you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. You may also run an unlimited number of Workers. “Worker” means a thread within a Sidekiq server process which executes jobs. You may concurrently run the software on an unlimited number of Hosts, with each host running an unlimited number of Workers. - -1.5 Appliance License. If you purchased an Appliance License, you may distribute the Software in any applications, frameworks, or elements (collectively referred to as an “Application” or “Applications”) that you develop using the Software in accordance with this EULA, provided that such distribution does not violate the restrictions set forth in section 3 of this EULA. You must not remove, obscure or interfere with any copyright, acknowledgment, attribution, trademark, warning or disclaimer statement affixed to, incorporated in or otherwise applied in connection with the Software. You are required to ensure that the Software is not reused by or with any applications other than those with which you distribute it as permitted herein. For example, if You install the Software on a customer's server, that customer is not permitted to use the Software independently of your Application. You must inform Contributed Systems of your knowledge of any infringing use of the Software by any of your customers. You are liable for compliance by those third parties with the terms and conditions of this EULA. You will not owe Contributed Systems any royalties for your distribution of the Software in accordance with this EULA. - -1.6 Archive Copies. You are entitled to make a reasonable amount of copies of the Software for archival purposes. Each copy must reproduce all copyright and other proprietary rights notices on or in the Software Product. - -1.7 Electronic Delivery. All Software and license documentation shall be delivered by electronic means unless otherwise specified on the applicable invoice or at the time of purchase. Software shall be deemed delivered when it is made available for download by you (“Delivery”). - -2. Modifications. Contributed Systems shall provide you with source code so that you can create Modifications of the original software. “Modification” means: (a) any addition to or deletion from the contents of a file included in the original Software or previous Modifications created by You, or (b) any new file that contains any part of the original Software or previous Modifications. While you retain all rights to any original work authored by you as part of the Modifications, We continue to own all copyright and other intellectual property rights in the Software. - -3. Restricted Uses. - -3.1 You shall not (and shall not allow any third party to): (a) decompile, disassemble, or otherwise reverse engineer the Software or attempt to reconstruct or discover any source code, underlying ideas, algorithms, file formats or programming interfaces of the Software by any means whatsoever (except and only to the extent that applicable law prohibits or restricts reverse engineering restrictions); (b) distribute, sell, sublicense, rent, lease or use the Software for time sharing, hosting, service provider or like purposes, except as expressly permitted under this Agreement; (c) redistribute the Software or Modifications other than by including the Software or a portion thereof within your own product, which must have substantially different functionality than the Software or Modifications and must not allow any third party to use the Software or Modifications, or any portions thereof, for software development or application development purposes; (d) redistribute the Software as part of a product, "appliance" or "virtual server"; (e) redistribute the Software on any server which is not directly under your control; (f) remove any product identification, proprietary, copyright or other notices contained in the Software; (g) modify any part of the Software, create a derivative work of any part of the Software (except as permitted in Section 4), or incorporate the Software, except to the extent expressly authorized in writing by Contributed Systems; (h) publicly disseminate performance information or analysis (including, without limitation, benchmarks) from any source relating to the Software; (i) utilize any equipment, device, software, or other means designed to circumvent or remove any form of Source URL or copy protection used by Contributed Systems in connection with the Software, or use the Software together with any authorization code, Source URL, serial number, or other copy protection device not supplied by Contributed Systems; (j) use the Software to develop a product which is competitive with any Contributed Systems product offerings; or (k) use unauthorized Source URLS or keycode(s) or distribute or publish Source URLs or keycode(s), except as may be expressly permitted by Contributed Systems in writing. If your unique Source URL is ever published, Contributed Systems reserves the right to terminate your access without notice. - -3.2 UNDER NO CIRCUMSTANCES MAY YOU USE THE SOFTWARE AS PART OF A PRODUCT OR SERVICE THAT PROVIDES SIMILAR FUNCTIONALITY TO THE SOFTWARE ITSELF. - -The Open Source version of the Software (“LGPL Version”) is licensed -under the terms of the GNU Lesser General Public License version 3.0 -(“LGPL”) and not under this EULA. - -4. Ownership. Notwithstanding anything to the contrary contained herein, except for the limited license rights expressly provided herein, Contributed Systems and its suppliers have and will retain all rights, title and interest (including, without limitation, all patent, copyright, trademark, trade secret and other intellectual property rights) in and to the Software and all copies, modifications and derivative works thereof (including any changes which incorporate any of your ideas, feedback or suggestions). You acknowledge that you are obtaining only a limited license right to the Software, and that irrespective of any use of the words “purchase”, “sale” or like terms hereunder no ownership rights are being conveyed to you under this Agreement or otherwise. - -5. Fees and Payment. The Software license fees will be due and payable in full as set forth in the applicable invoice or at the time of purchase. If the Software does not function properly within two weeks of purchase, please contact us within those two weeks for a refund. You shall be responsible for all taxes, withholdings, duties and levies arising from the order (excluding taxes based on the net income of Contributed Systems). - -6. Support, Maintenance and Services. Subject to the terms and conditions of this Agreement, as set forth in your invoice, and as set forth on the Sidekiq Pro support page (https://github.com/mperham/sidekiq/wiki/Commercial-Support), support and maintenance services may be included with the purchase of your license subscription. - -7. Term of Agreement. - -7.1 Term. This Agreement is effective as of the Delivery of the Software and expires at such time as all license and service subscriptions hereunder have expired in accordance with their own terms (the “Term”). For clarification, the term of your license under this Agreement may be perpetual, limited for Evaluation Version, or designated as a fixed-term license in the Invoice, and shall be specified at your time of purchase. Either party may terminate this Agreement (including all related Invoices) if the other party: (a) fails to cure any material breach of this Agreement within thirty (30) days after written notice of such breach, provided that Contributed Systems may terminate this Agreement immediately upon any breach of Section 3 or if you exceed any other restrictions contained in Section 1, unless otherwise specified in this agreement; (b) ceases operation without a successor; or (c) seeks protection under any bankruptcy, receivership, trust deed, creditors arrangement, composition or comparable proceeding, or if any such proceeding is instituted against such party (and not dismissed within sixty (60) days)). Termination is not an exclusive remedy and the exercise by either party of any remedy under this Agreement will be without prejudice to any other remedies it may have under this Agreement, by law, or otherwise. - -7.2 Termination. Upon any termination of this Agreement, you shall cease any and all use of any Software and destroy all copies thereof. - -7.3 Expiration of License. Upon the expiration of any term under this Agreement, (a) all Software updates and services pursuant to the license shall cease, (b) you may only continue to run existing installations of the Software, (c) you may not install the Software on any additional Hosts, and (d) any new installation of the Software shall require the purchase of a new license subscription from Contributed Systems. - -8. Disclaimer of Warranties. The Software is provided "as is," with all faults, defects and errors, and without warranty of any kind. Contributed Systems does not warrant that the Software will be free of bugs, errors, viruses or other defects, and Contributed Systems shall have no liability of any kind for the use of or inability to use the Software, the Software content or any associated service, and you acknowledge that it is not technically practicable for Contributed Systems to do so. -To the maximum extent permitted by applicable law, Contributed Systems disclaims all warranties, express, implied, arising by law or otherwise, regarding the Software, the Software content and their respective performance or suitability for your intended use, including without limitation any implied warranty of merchantability, fitness for a particular purpose. - -9. Limitation of Liability. - -In no event will Contributed Systems be liable for any direct, indirect, consequential, incidental, special, exemplary, or punitive damages or liabilities whatsoever arising from or relating to the Software, the Software content or this Agreement, whether based on contract, tort (including negligence), strict liability or other theory, even if Contributed Systems has been advised of the possibility of such damages. - -In no event will Contributed Systems' liability exceed the Software license price as indicated in the invoice. The existence of more than one claim will not enlarge or extend this limit. - -10. Remedies. Your exclusive remedy and Contributed Systems' entire liability for breach of this Agreement shall be limited, at Contributed Systems' sole and exclusive discretion, to (a) replacement of any defective software or documentation; or (b) refund of the license fee paid to Contributed Systems, payable in accordance with Contributed Systems' refund policy. - -11. Acknowledgements. - -11.1 Consent to the Use of Data. You agree that Contributed Systems and its affiliates may collect and use technical information gathered as part of the product support services. Contributed Systems may use this information solely to improve products and services and will not disclose this information in a form that personally identifies you. - -11.2 Verification. We or a certified auditor acting on our behalf, may, upon its reasonable request and at its expense, audit you with respect to the use of the Software. Such audit may be conducted by mail, electronic means or through an in-person visit to your place of business. Any such in-person audit shall be conducted during regular business hours at your facilities and shall not unreasonably interfere with your business activities. We shall not remove, copy, or redistribute any electronic material during the course of an audit. If an audit reveals that you are using the Software in a way that is in material violation of the terms of the EULA, then you shall pay our reasonable costs of conducting the audit. In the case of a material violation, you agree to pay Us any amounts owing that are attributable to the unauthorized use. In the alternative, We reserve the right, at our sole option, to terminate the licenses for the Software. - -11.3 Government End Users. If the Software and related documentation are supplied to or purchased by or on behalf of the United States Government, then the Software is deemed to be "commercial software" as that term is used in the Federal Acquisition Regulation system. Rights of the United States shall not exceed the minimum rights set forth in FAR 52.227-19 for "restricted computer software". All other terms and conditions of this Agreement apply. - -12. Third Party Software. Examples included in Software may provide links to third party libraries or code (collectively “Third Party Software”) to implement various functions. Third Party Software does not comprise part of the Software. In some cases, access to Third Party Software may be included along with the Software delivery as a convenience for demonstration purposes. Such source code and libraries may be included in the “…/examples” source tree delivered with the Software and do not comprise the Software. Licensee acknowledges (1) that some part of Third Party Software may require additional licensing of copyright and patents from the owners of such, and (2) that distribution of any of the Software referencing or including any portion of a Third Party Software may require appropriate licensing from such third parties. - - -13. Miscellaneous - -13.1 Entire Agreement. This Agreement sets forth our entire agreement with respect to the Software and the subject matter hereof and supersedes all prior and contemporaneous understandings and agreements whether written or oral. - -13.2 Amendment. Contributed Systems reserves the right, in its sole discretion, to amend this Agreement from time. Amendments to this Agreement can be located at: https://github.com/mperham/sidekiq/blob/main/COMM-LICENSE. - -13.3 Assignment. You may not assign this Agreement or any of its rights under this Agreement without the prior written consent of Contributed Systems and any attempted assignment without such consent shall be void. - -13.4 Export Compliance. You agree to comply with all applicable laws and regulations, including laws, regulations, orders or other restrictions on export, re-export or redistribution of software. - -13.5 Indemnification. You agree to defend, indemnify, and hold harmless Contributed Systems from and against any lawsuits, claims, losses, damages, fines and expenses (including attorneys' fees and costs) arising out of your use of the Software or breach of this Agreement. - -13.6 Governing Law. This Agreement is governed by the laws of the State of Oregon and the United States without regard to conflicts of laws provisions thereof, and without regard to the United Nations Convention on the International Sale of Goods or the Uniform Computer Information Transactions Act, as currently enacted by any jurisdiction or as may be codified or amended from time to time by any jurisdiction. The jurisdiction and venue for actions related to the subject matter hereof shall be the state of Oregon and United States federal courts located in Portland, Oregon, and both parties hereby submit to the personal jurisdiction of such courts. - -13.7 Attorneys' Fees and Costs. The prevailing party in any action to enforce this Agreement will be entitled to recover its attorneys' fees and costs in connection with such action. - -13.8 Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the remainder of this Agreement will remain in full force and effect. - -13.9 Waiver. Failure or neglect by either party to enforce at any time any of the provisions of this licence Agreement shall not be construed or deemed to be a waiver of that party's rights under this Agreement. - -13.10 Headings. The headings of sections and paragraphs of this Agreement are for convenience of reference only and are not intended to restrict, affect or be of any weight in the interpretation or construction of the provisions of such sections or paragraphs. - -14. Contact Information. If you have any questions about this EULA, or if you want to contact Contributed Systems for any reason, please direct correspondence to info@contribsys.com. diff --git a/Changes.md b/Changes.md deleted file mode 100644 index b20cf38f..00000000 --- a/Changes.md +++ /dev/null @@ -1,1864 +0,0 @@ -# Sidekiq Changes - -[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md) - -HEAD ---------- - -- Fix sidekiq.yml YAML load errors on Ruby 3.1 [#5141] -- Sharding support for `perform_bulk` [#5129] -- Refactor job logger for SPEEEEEEED - -6.4.0 ---------- - -- **SECURITY**: Validate input to avoid possible DoS in Web UI. -- Add **strict argument checking** [#5071] - Sidekiq will now log a warning if JSON-unsafe arguments are passed to `perform_async`. - Add `Sidekiq.strict_args!(false)` to your initializer to disable this warning. - This warning will switch to an exception in Sidekiq 7.0. -- Note that Delayed Extensions will be removed in Sidekiq 7.0 [#5076] -- Add `perform_{inline,sync}` in Sidekiq::Job to run a job synchronously [#5061, hasan-ally] -```ruby -SomeJob.perform_async(args...) -SomeJob.perform_sync(args...) -SomeJob.perform_inline(args...) -``` - You can also dynamically redirect a job to run synchronously: -```ruby -SomeJob.set("sync": true).perform_async(args...) # will run via perform_inline -``` -- Replace Sidekiq::Worker `app/workers` generator with Sidekiq::Job `app/sidekiq` generator [#5055] -``` -bin/rails generate sidekiq:job ProcessOrderJob -``` -- Fix job retries losing CurrentAttributes [#5090] -- Tweak shutdown to give long-running threads time to cleanup [#5095] - -6.3.1 ---------- - -- Fix keyword arguments error with CurrentAttributes on Ruby 3.0 [#5048] - -6.3.0 ---------- - -- **BREAK**: The Web UI has been refactored to remove jQuery. Any UI extensions - which use jQuery will break. -- **FEATURE**: Sidekiq.logger has been enhanced so any `Rails.logger` - output in jobs now shows up in the Sidekiq console. Remove any logger - hacks in your initializer and see if it Just Works™ now. [#5021] -- **FEATURE**: Add `Sidekiq::Job` alias for `Sidekiq::Worker`, to better - reflect industry standard terminology. You can now do this: -```ruby -class MyJob - include Sidekiq::Job - sidekiq_options ... - def perform(args) - end -end -``` -- **FEATURE**: Support for serializing ActiveSupport::CurrentAttributes into each job. [#4982] -```ruby -# config/initializers/sidekiq.rb -require "sidekiq/middleware/current_attributes" -Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton -``` -- **FEATURE**: Add `Sidekiq::Worker.perform_bulk` for enqueuing jobs in bulk, - similar to `Sidekiq::Client.push_bulk` [#5042] -```ruby -MyJob.perform_bulk([[1], [2], [3]]) -``` -- Implement `queue_as`, `wait` and `wait_until` for ActiveJob compatibility [#5003] -- Scheduler now uses Lua to reduce Redis load and network roundtrips [#5044] -- Retry Redis operation if we get an `UNBLOCKED` Redis error [#4985] -- Run existing signal traps, if any, before running Sidekiq's trap [#4991] -- Fix fetch bug when using weighted queues which caused Sidekiq to stop - processing queues randomly [#5031] - -6.2.2 ---------- - -- Reduce retry jitter, add jitter to `sidekiq_retry_in` values [#4957] -- Minimize scheduler load on Redis at scale [#4882] -- Improve logging of delay jobs [#4904, BuonOno] -- Minor CSS improvements for buttons and tables, design PRs always welcome! -- Tweak Web UI `Cache-Control` header [#4966] -- Rename internal API class `Sidekiq::Job` to `Sidekiq::JobRecord` [#4955] - -6.2.1 ---------- - -- Update RTT warning logic to handle transient RTT spikes [#4851] -- Fix very low priority CVE on unescaped queue name [#4852] -- Add note about sessions and Rails apps in API mode - -6.2.0 ---------- - -- Store Redis RTT and log if poor [#4824] -- Add process/thread stats to Busy page [#4806] -- Improve Web UI on mobile devices [#4840] -- **Refactor Web UI session usage** [#4804] - Numerous people have hit "Forbidden" errors and struggled with Sidekiq's - Web UI session requirement. If you have code in your initializer for - Web sessions, it's quite possible it will need to be removed. Here's - an overview: -``` -Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app, -make sure you mount Sidekiq::Web *inside* your routes in `config/routes.rb` so -Sidekiq can reuse the Rails session: - - Rails.application.routes.draw do - mount Sidekiq::Web => "/sidekiq" - .... - end - -If this is a bare Rack app, use a session middleware before Sidekiq::Web: - - # first, use IRB to create a shared secret key for sessions and commit it - require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) } - - # now, update your Rack app to include the secret with a session cookie middleware - use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400 - run Sidekiq::Web - -If this is a Rails app in API mode, you need to enable sessions. - - https://guides.rubyonrails.org/api_app.html#using-session-middlewares -``` - -6.1.3 ---------- - -- Warn if Redis is configured to evict data under memory pressure [#4752] -- Add process RSS on the Busy page [#4717] - -6.1.2 ---------- - -- Improve readability in dark mode Web UI [#4674] -- Fix Web UI crash with corrupt session [#4672] -- Allow middleware to yield arguments [#4673, @eugeneius] -- Migrate CI from CircleCI to GitHub Actions [#4677] - -6.1.1 ---------- - -- Jobs are now sorted by age in the Busy Workers table. [#4641] -- Fix "check all" JS logic in Web UI [#4619] - -6.1.0 ---------- - -- Web UI - Dark Mode fixes [#4543, natematykiewicz] -- Ensure `Rack::ContentLength` is loaded as middleware for correct Web UI responses [#4541] -- Avoid exception dumping SSL store in Redis connection logging [#4532] -- Better error messages in Sidekiq::Client [#4549] -- Remove rack-protection, reimplement CSRF protection [#4588] -- Require redis-rb 4.2 [#4591] -- Update to jquery 1.12.4 [#4593] -- Refactor internal fetch logic and API [#4602] - -6.0.7 ---------- - -- Refactor systemd integration to work better with custom binaries [#4511] -- Don't connect to Redis at process exit if not needed [#4502] -- Remove Redis connection naming [#4479] -- Fix Redis Sentinel password redaction [#4499] -- Add Vietnamese locale (vi) [#4528] - -6.0.6 ---------- - -- **Integrate with systemd's watchdog and notification features** [#4488] - Set `Type=notify` in [sidekiq.service](https://github.com/mperham/sidekiq/blob/4b8a8bd3ae42f6e48ae1fdaf95ed7d7af18ed8bb/examples/systemd/sidekiq.service#L30-L39). The integration works automatically. -- Use `setTimeout` rather than `setInterval` to avoid thundering herd [#4480] -- Fix edge case where a job can be pushed without a queue. -- Flush job stats at exit [#4498] -- Check RAILS_ENV before RACK_ENV [#4493] -- Add Lithuanian locale [#4476] - -6.0.5 ---------- - -- Fix broken Web UI response when using NewRelic and Rack 2.1.2+. [#4440] -- Update APIs to use `UNLINK`, not `DEL`. [#4449] -- Fix Ruby 2.7 warnings [#4412] -- Add support for `APP_ENV` [[95fa5d9]](https://github.com/mperham/sidekiq/commit/95fa5d90192148026e52ca2902f1b83c70858ce8) - -6.0.4 ---------- - -- Fix ActiveJob's `sidekiq_options` integration [#4404] -- Sidekiq Pro users will now see a Pause button next to each queue in - the Web UI, allowing them to pause queues manually [#4374, shayonj] -- Fix Sidekiq::Workers API unintentional change in 6.0.2 [#4387] - - -6.0.3 ---------- - -- Fix `Sidekiq::Client.push_bulk` API which was erroneously putting - invalid `at` values in the job payloads [#4321] - -6.0.2 ---------- - -- Fix Sidekiq Enterprise's rolling restart functionality, broken by refactoring in 6.0.0. [#4334] -- More internal refactoring and performance tuning [fatkodima] - -6.0.1 ---------- - -- **Performance tuning**, Sidekiq should be 10-15% faster now [#4303, 4299, - 4269, fatkodima] -- **Dark Mode support in Web UI** (further design polish welcome!) [#4227, mperham, - fatkodima, silent-e] -- **Job-specific log levels**, allowing you to turn on debugging for - problematic workers. [fatkodima, #4287] -```ruby -MyWorker.set(log_level: :debug).perform_async(...) -``` -- **Ad-hoc job tags**. You can tag your jobs with, e.g, subdomain, tenant, country, - locale, application, version, user/client, "alpha/beta/pro/ent", types of jobs, - teams/people responsible for jobs, additional metadata, etc. - Tags are shown on different pages with job listings. Sidekiq Pro users - can filter based on them [fatkodima, #4280] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_options tags: ['bank-ops', 'alpha'] - ... -end -``` -- Fetch scheduled jobs in batches before pushing into specific queues. - This will decrease enqueueing time of scheduled jobs by a third. [fatkodima, #4273] -``` -ScheduledSet with 10,000 jobs -Before: 56.6 seconds -After: 39.2 seconds -``` -- Compress error backtraces before pushing into Redis, if you are - storing error backtraces, this will halve the size of your RetrySet - in Redis [fatkodima, #4272] -``` -RetrySet with 100,000 jobs -Before: 261 MB -After: 129 MB -``` -- Support display of ActiveJob 6.0 payloads in the Web UI [#4263] -- Add `SortedSet#scan` for pattern based scanning. For large sets this API will be **MUCH** faster - than standard iteration using each. [fatkodima, #4262] -```ruby - Sidekiq::DeadSet.new.scan("UnreliableApi") do |job| - job.retry - end -``` -- Dramatically speed up SortedSet#find\_job(jid) by using Redis's ZSCAN - support, approx 10x faster. [fatkodima, #4259] -``` -zscan 0.179366 0.047727 0.227093 ( 1.161376) -enum 8.522311 0.419826 8.942137 ( 9.785079) -``` -- Respect rails' generators `test_framework` option and gracefully handle extra `worker` suffix on generator [fatkodima, #4256] -- Add ability to sort 'Enqueued' page on Web UI by position in the queue [fatkodima, #4248] -- Support `Client.push_bulk` with different delays [fatkodima, #4243] -```ruby -Sidekiq::Client.push_bulk("class" => FooJob, "args" => [[1], [2]], "at" => [1.minute.from_now.to_f, 5.minutes.from_now.to_f]) -``` -- Easier way to test enqueuing specific ActionMailer and ActiveRecord delayed jobs. Instead of manually - parsing embedded class, you can now test by fetching jobs for specific classes. [fatkodima, #4292] -```ruby -assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(FooMailer).size -``` -- Add `sidekiqmon` to gemspec executables [#4242] -- Gracefully handle `Sidekiq.logger = nil` [#4240] -- Inject Sidekiq::LogContext module if user-supplied logger does not include it [#4239] - -6.0 ---------- - -This release has major breaking changes. Read and test carefully in production. - -- With Rails 6.0.2+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq - features/internals like the retry subsystem. [#4213, pirj] -```ruby -class MyJob < ActiveJob::Base - queue_as :myqueue - sidekiq_options retry: 10, backtrace: 20 - def perform(...) - end -end -``` -- Logging has been redesigned to allow for pluggable log formatters: -```ruby -Sidekiq.configure_server do |config| - config.log_formatter = Sidekiq::Logger::Formatters::JSON.new -end -``` -See the [Logging wiki page](https://github.com/mperham/sidekiq/wiki/Logging) for more details. -- **BREAKING CHANGE** Validate proper usage of the `REDIS_PROVIDER` - variable. This variable is meant to hold the name of the environment - variable which contains your Redis URL, so that you can switch Redis - providers quickly and easily with a single variable change. It is not - meant to hold the actual Redis URL itself. If you want to manually set - the Redis URL (not recommended as it implies you have no failover), - then you may set `REDIS_URL` directly. [#3969] -- **BREAKING CHANGE** Increase default shutdown timeout from 8 seconds - to 25 seconds. Both Heroku and ECS now use 30 second shutdown timeout - by default and we want Sidekiq to take advantage of this time. If you - have deployment scripts which depend on the old default timeout, use `-t 8` to - get the old behavior. [#3968] -- **BREAKING CHANGE** Remove the daemonization, logfile and pidfile - arguments to Sidekiq. Use a proper process supervisor (e.g. systemd or - foreman) to manage Sidekiq. See the Deployment wiki page for links to - more resources. -- Integrate the StandardRB code formatter to ensure consistent code - styling. [#4114, gearnode] - -5.2.9 ---------- - -- Release Rack lock due to a cascade of CVEs. [#4566] - Pro-tip: don't lock Rack. - -5.2.8 ---------- - -- Lock to Rack 2.0.x to prevent future incompatibilities -- Fix invalid reference in `sidekiqctl` - -5.2.7 ---------- - -- Fix stale `enqueued_at` when retrying [#4149] -- Move build to [Circle CI](https://circleci.com/gh/mperham/sidekiq) [#4120] - -5.2.6 ---------- - -- Fix edge case where a job failure during Redis outage could result in a lost job [#4141] -- Better handling of malformed job arguments in payload [#4095] -- Restore bootstap's dropdown css component [#4099, urkle] -- Display human-friendly time diff for longer queue latencies [#4111, interlinked] -- Allow `Sidekiq::Worker#set` to be chained - -5.2.5 ---------- - -- Fix default usage of `config/sidekiq.yml` [#4077, Tensho] - -5.2.4 ---------- - -- Add warnings for various deprecations and changes coming in Sidekiq 6.0. - See the 6-0 branch. [#4056] -- Various improvements to the Sidekiq test suite and coverage [#4026, #4039, Tensho] - -5.2.3 ---------- - -- Warning message on invalid REDIS\_PROVIDER [#3970] -- Add `sidekiqctl status` command [#4003, dzunk] -- Update elapsed time calculatons to use monotonic clock [#3999] -- Fix a few issues with mobile Web UI styling [#3973, navied] -- Jobs with `retry: false` now go through the global `death_handlers`, - meaning you can take action on failed ephemeral jobs. [#3980, Benjamin-Dobell] -- Fix race condition in defining Workers. [#3997, mattbooks] - -5.2.2 ---------- - -- Raise error for duplicate queue names in config to avoid unexpected fetch algorithm change [#3911] -- Fix concurrency bug on JRuby [#3958, mattbooks] -- Add "Kill All" button to the retries page [#3938] - -5.2.1 ------------ - -- Fix concurrent modification error during heartbeat [#3921] - -5.2.0 ------------ - -- **Decrease default concurrency from 25 to 10** [#3892] -- Verify connection pool sizing upon startup [#3917] -- Smoother scheduling for large Sidekiq clusters [#3889] -- Switch Sidekiq::Testing impl from alias\_method to Module#prepend, for resiliency [#3852] -- Update Sidekiq APIs to use SCAN for scalability [#3848, ffiller] -- Remove concurrent-ruby gem dependency [#3830] -- Optimize Web UI's bootstrap.css [#3914] - -5.1.3 ------------ - -- Fix version comparison so Ruby 2.2.10 works. [#3808, nateberkopec] - -5.1.2 ------------ - -- Add link to docs in Web UI footer -- Fix crash on Ctrl-C in Windows [#3775, Bernica] -- Remove `freeze` calls on String constants. This is superfluous with Ruby - 2.3+ and `frozen_string_literal: true`. [#3759] -- Fix use of AR middleware outside of Rails [#3787] -- Sidekiq::Worker `sidekiq_retry_in` block can now return nil or 0 to use - the default backoff delay [#3796, dsalahutdinov] - -5.1.1 ------------ - -- Fix Web UI incompatibility with Redis 3.x gem [#3749] - -5.1.0 ------------ - -- **NEW** Global death handlers - called when your job exhausts all - retries and dies. Now you can take action when a job fails permanently. [#3721] -- **NEW** Enable ActiveRecord query cache within jobs by default [#3718, sobrinho] - This will prevent duplicate SELECTS; cache is cleared upon any UPDATE/INSERT/DELETE. - See the issue for how to bypass the cache or disable it completely. -- Scheduler timing is now more accurate, 15 -> 5 seconds [#3734] -- Exceptions during the :startup event will now kill the process [#3717] -- Make `Sidekiq::Client.via` reentrant [#3715] -- Fix use of Sidekiq logger outside of the server process [#3714] -- Tweak `constantize` to better match Rails class lookup. [#3701, caffeinated-tech] - -5.0.5 ------------ - -- Update gemspec to allow newer versions of the Redis gem [#3617] -- Refactor Worker.set so it can be memoized [#3602] -- Fix display of Redis URL in web footer, broken in 5.0.3 [#3560] -- Update `Sidekiq::Job#display_args` to avoid mutation [#3621] - -5.0.4 ------------ - -- Fix "slow startup" performance regression from 5.0.2. [#3525] -- Allow users to disable ID generation since some redis providers disable the CLIENT command. [#3521] - -5.0.3 ------------ - -- Fix overriding `class_attribute` core extension from ActiveSupport with Sidekiq one [PikachuEXE, #3499] -- Allow job logger to be overridden [AlfonsoUceda, #3502] -- Set a default Redis client identifier for debugging [#3516] -- Fix "Uninitialized constant" errors on startup with the delayed extensions [#3509] - -5.0.2 ------------ - -- fix broken release, thanks @nateberkopec - -5.0.1 ------------ - -- Fix incorrect server identity when daemonizing [jwilm, #3496] -- Work around error running Web UI against Redis Cluster [#3492] -- Remove core extensions, Sidekiq is now monkeypatch-free! [#3474] -- Reimplement Web UI's HTTP\_ACCEPT\_LANGUAGE parsing because the spec is utterly - incomprehensible for various edge cases. [johanlunds, natematykiewicz, #3449] -- Update `class_attribute` core extension to avoid warnings -- Expose `job_hash_context` from `Sidekiq::Logging` to support log customization - -5.0.0 ------------ - -- **BREAKING CHANGE** Job dispatch was refactored for safer integration with - Rails 5. The **Logging** and **RetryJobs** server middleware were removed and - functionality integrated directly into Sidekiq::Processor. These aren't - commonly used public APIs so this shouldn't impact most users. -``` -Sidekiq::Middleware::Server::RetryJobs -> Sidekiq::JobRetry -Sidekiq::Middleware::Server::Logging -> Sidekiq::JobLogger -``` -- Quieting Sidekiq is now done via the TSTP signal, the USR1 signal is deprecated. -- The `delay` extension APIs are no longer available by default, you - must opt into them. -- The Web UI is now BiDi and can render RTL languages like Arabic, Farsi and Hebrew. -- Rails 3.2 and Ruby 2.0 and 2.1 are no longer supported. -- The `SomeWorker.set(options)` API was re-written to avoid thread-local state. [#2152] -- Sidekiq Enterprise's encrypted jobs now display "[encrypted data]" in the Web UI instead - of random hex bytes. -- Please see the [5.0 Upgrade notes](5.0-Upgrade.md) for more detail. - -4.2.10 ------------ - -- Scheduled jobs can now be moved directly to the Dead queue via API [#3390] -- Fix edge case leading to job duplication when using Sidekiq Pro's - reliability feature [#3388] -- Fix error class name display on retry page [#3348] -- More robust latency calculation [#3340] - -4.2.9 ------------ - -- Rollback [#3303] which broke Heroku Redis users [#3311] -- Add support for TSTP signal, for Sidekiq 5.0 forward compatibility. [#3302] - -4.2.8 ------------ - -- Fix rare edge case with Redis driver that can create duplicate jobs [#3303] -- Fix Rails 5 loading issue [#3275] -- Restore missing tooltips to timestamps in Web UI [#3310] -- Work on **Sidekiq 5.0** is now active! [#3301] - -4.2.7 ------------ - -- Add new integration testing to verify code loading and job execution - in development and production modes with Rails 4 and 5 [#3241] -- Fix delayed extensions in development mode [#3227, DarthSim] -- Use Worker's `retry` default if job payload does not have a retry - attribute [#3234, mlarraz] - -4.2.6 ------------ - -- Run Rails Executor when in production [#3221, eugeneius] - -4.2.5 ------------ - -- Re-enable eager loading of all code when running non-development Rails 5. [#3203] -- Better root URL handling for zany web servers [#3207] - -4.2.4 ------------ - -- Log errors coming from the Rails 5 reloader. [#3212, eugeneius] -- Clone job data so middleware changes don't appear in Busy tab - -4.2.3 ------------ - -- Disable use of Rails 5's Reloader API in non-development modes, it has proven - to be unstable under load [#3154] -- Allow disabling of Sidekiq::Web's cookie session to handle the - case where the app provides a session already [#3180, inkstak] -```ruby -Sidekiq::Web.set :sessions, false -``` -- Fix Web UI sharding support broken in 4.2.2. [#3169] -- Fix timestamps not updating during UI polling [#3193, shaneog] -- Relax rack-protection version to >= 1.5.0 -- Provide consistent interface to exception handlers, changing the structure of the context hash. [#3161] - -4.2.2 ------------ - -- Fix ever-increasing cookie size with nginx [#3146, cconstantine] -- Fix so Web UI works without trailing slash [#3158, timdorr] - -4.2.1 ------------ - -- Ensure browser does not cache JSON/AJAX responses. [#3136] -- Support old Sinatra syntax for setting config [#3139] - -4.2.0 ------------ - -- Enable development-mode code reloading. **With Rails 5.0+, you don't need - to restart Sidekiq to pick up your Sidekiq::Worker changes anymore!** [#2457] -- **Remove Sinatra dependency**. Sidekiq's Web UI now uses Rack directly. - Thank you to Sidekiq's newest committer, **badosu**, for writing the code - and doing a lot of testing to ensure compatibility with many different - 3rd party plugins. If your Web UI works with 4.1.4 but fails with - 4.2.0, please open an issue. [#3075] -- Allow tuning of concurrency with the `RAILS_MAX_THREADS` env var. [#2985] - This is the same var used by Puma so you can tune all of your systems - the same way: -```sh -web: RAILS_MAX_THREADS=5 bundle exec puma ... -worker: RAILS_MAX_THREADS=10 bundle exec sidekiq ... -``` -Using `-c` or `config/sidekiq.yml` overrides this setting. I recommend -adjusting your `config/database.yml` to use it too so connections are -auto-scaled: -```yaml - pool: <%= ENV['RAILS_MAX_THREADS'] || 5 %> -``` - -4.1.4 ------------ - -- Unlock Sinatra so a Rails 5.0 compatible version may be used [#3048] -- Fix race condition on startup with JRuby [#3043] - - -4.1.3 ------------ - -- Please note the Redis 3.3.0 gem has a [memory leak](https://github.com/redis/redis-rb/issues/612), - Redis 3.2.2 is recommended until that issue is fixed. -- Sinatra 1.4.x is now a required dependency, avoiding cryptic errors - and old bugs due to people not upgrading Sinatra for years. [#3042] -- Fixed race condition in heartbeat which could rarely lead to lingering - processes on the Busy tab. [#2982] -```ruby -# To clean up lingering processes, modify this as necessary to connect to your Redis. -# After 60 seconds, lingering processes should disappear from the Busy page. - -require 'redis' -r = Redis.new(url: "redis://localhost:6379/0") -# uncomment if you need a namespace -#require 'redis-namespace' -#r = Redis::Namespace.new("foo", r) -r.smembers("processes").each do |pro| - r.expire(pro, 60) - r.expire("#{pro}:workers", 60) -end -``` - - -4.1.2 ------------ - -- Fix Redis data leak with worker data when a busy Sidekiq process - crashes. You can find and expire leaked data in Redis with this -script: -```bash -$ redis-cli keys "*:workers" | while read LINE ; do TTL=`redis-cli expire "$LINE" 60`; echo "$LINE"; done; -``` - Please note that `keys` can be dangerous to run on a large, busy Redis. Caveat runner. -- Freeze all string literals with Ruby 2.3. [#2741] -- Client middleware can now stop bulk job push. [#2887] - -4.1.1 ------------ - -- Much better behavior when Redis disappears and comes back. [#2866] -- Update FR locale [dbachet] -- Don't fill logfile in case of Redis downtime [#2860] -- Allow definition of a global retries_exhausted handler. [#2807] -```ruby -Sidekiq.configure_server do |config| - config.default_retries_exhausted = -> (job, ex) do - Sidekiq.logger.info "#{job['class']} job is now dead" - end -end -``` - -4.1.0 ------------ - -- Tag quiet processes in the Web UI [#2757, jcarlson] -- Pass last exception to sidekiq\_retries\_exhausted block [#2787, Nowaker] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retries_exhausted do |job, exception| - end -end -``` -- Add native support for ActiveJob's `set(options)` method allowing -you to override worker options dynamically. This should make it -even easier to switch between ActiveJob and Sidekiq's native APIs [#2780] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_options queue: 'default', retry: true - - def perform(*args) - # do something - end -end - -MyWorker.set(queue: 'high', retry: false).perform_async(1) -``` - -4.0.2 ------------ - -- Better Japanese translations -- Remove `json` gem dependency from gemspec. [#2743] -- There's a new testing API based off the `Sidekiq::Queues` namespace. All - assertions made against the Worker class still work as expected. - [#2676, brandonhilkert] -```ruby -assert_equal 0, Sidekiq::Queues["default"].size -HardWorker.perform_async("log") -assert_equal 1, Sidekiq::Queues["default"].size -assert_equal "log", Sidekiq::Queues["default"].first['args'][0] -Sidekiq::Queues.clear_all -``` - -4.0.1 ------------ - -- Yank new queue-based testing API [#2663] -- Fix invalid constant reference in heartbeat - -4.0.0 ------------ - -- Sidekiq's internals have been completely overhauled for performance - and to remove dependencies. This has resulted in major speedups, as - [detailed on my blog](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). -- See the [4.0 upgrade notes](4.0-Upgrade.md) for more detail. - -3.5.4 ------------ - -- Ensure exception message is a string [#2707] -- Revert racy Process.kill usage in sidekiqctl - -3.5.3 ------------ - -- Adjust shutdown event to run in parallel with the rest of system shutdown. [#2635] - -3.5.2 ------------ - -- **Sidekiq 3 is now in maintenance mode**, only major bugs will be fixed. -- The exception triggering a retry is now passed into `sidekiq_retry_in`, - allowing you to retry more frequently for certain types of errors. - [#2619, kreynolds] -```ruby - sidekiq_retry_in do |count, ex| - case ex - when RuntimeError - 5 * count - else - 10 * count - end - end -``` - -3.5.1 ------------ - -- **FIX MEMORY LEAK** Under rare conditions, threads may leak [#2598, gazay] -- Add Ukrainian locale [#2561, elrakita] -- Disconnect and retry Redis operations if we see a READONLY error [#2550] -- Add server middleware testing harness; see [wiki](https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middleware) [#2534, ryansch] - -3.5.0 ------------ - -- Polished new banner! [#2522, firedev] -- Upgrade to Celluloid 0.17. [#2420, digitalextremist] -- Activate sessions in Sinatra for CSRF protection, requires Rails - monkeypatch due to rails/rails#15843. [#2460, jc00ke] - -3.4.2 ------------ - -- Don't allow `Sidekiq::Worker` in ActiveJob::Base classes. [#2424] -- Safer display of job data in Web UI [#2405] -- Fix CSRF vulnerability in Web UI, thanks to Egor Homakov for - reporting. [#2422] If you are running the Web UI as a standalone Rack app, - ensure you have a [session middleware -configured](https://github.com/mperham/sidekiq/wiki/Monitoring#standalone): -```ruby -use Rack::Session::Cookie, :secret => "some unique secret string here" -``` - -3.4.1 ------------ - -- Lock to Celluloid 0.16 - - -3.4.0 ------------ - -- Set a `created_at` attribute when jobs are created, set `enqueued_at` only - when they go into a queue. Fixes invalid latency calculations with scheduled jobs. - [#2373, mrsimo] -- Don't log timestamp on Heroku [#2343] -- Run `shutdown` event handlers in reverse order of definition [#2374] -- Rename and rework `poll_interval` to be simpler, more predictable [#2317, cainlevy] - The new setting is `average_scheduled_poll_interval`. To configure - Sidekiq to look for scheduled jobs every 5 seconds, just set it to 5. -```ruby -Sidekiq.configure_server do |config| - config.average_scheduled_poll_interval = 5 -end -``` - -3.3.4 ------------ - -- **Improved ActiveJob integration** - Web UI now shows ActiveJobs in a - nicer format and job logging shows the actual class name, requires - Rails 4.2.2+ [#2248, #2259] -- Add Sidekiq::Process#dump\_threads API to trigger TTIN output [#2247] -- Web UI polling now uses Ajax to avoid page reload [#2266, davydovanton] -- Several Web UI styling improvements [davydovanton] -- Add Tamil, Hindi translations for Web UI [ferdinandrosario, tejasbubane] -- Fix Web UI to work with country-specific locales [#2243] -- Handle circular error causes [#2285, eugenk] - -3.3.3 ------------ - -- Fix crash on exit when Redis is down [#2235] -- Fix duplicate logging on startup -- Undeprecate delay extension for ActionMailer 4.2+ . [#2186] - -3.3.2 ------------ - -- Add Sidekiq::Stats#queues back -- Allows configuration of dead job set size and timeout [#2173, jonhyman] -- Refactor scheduler enqueuing so Sidekiq Pro can override it. [#2159] - -3.3.1 ------------ - -- Dumb down ActionMailer integration so it tries to deliver if possible [#2149] -- Stringify Sidekiq.default\_worker\_options's keys [#2126] -- Add random integer to process identity [#2113, michaeldiscala] -- Log Sidekiq Pro's Batch ID if available [#2076] -- Refactor Processor Redis usage to avoid redis/redis-rb#490 [#2094] -- Move /dashboard/stats to /stats. Add /stats/queues. [moserke, #2099] -- Add processes count to /stats [ismaelga, #2141] -- Greatly improve speed of Sidekiq::Stats [ismaelga, #2142] -- Add better usage text for `sidekiqctl`. -- `Sidekiq::Logging.with_context` is now a stack so you can set your - own job context for logging purposes [grosser, #2110] -- Remove usage of Google Fonts in Web UI so it loads in China [#2144] - -3.3.0 ------------ - -- Upgrade to Celluloid 0.16 [#2056] -- Fix typo for generator test file name [dlackty, #2016] -- Add Sidekiq::Middleware::Chain#prepend [seuros, #2029] - -3.2.6 ------------ - -- Deprecate delay extension for ActionMailer 4.2+ . [seuros, #1933] -- Poll interval tuning now accounts for dead processes [epchris, #1984] -- Add non-production environment to Web UI page titles [JacobEvelyn, #2004] - -3.2.5 ------------ - -- Lock Celluloid to 0.15.2 due to bugs in 0.16.0. This prevents the - "hang on shutdown" problem with Celluloid 0.16.0. - -3.2.4 ------------ - -- Fix issue preventing ActionMailer sends working in some cases with - Rails 4. [pbhogan, #1923] - -3.2.3 ------------ - -- Clean invalid bytes from error message before converting to JSON (requires Ruby 2.1+) [#1705] -- Add queues list for each process to the Busy page. [davetoxa, #1897] -- Fix for crash caused by empty config file. [jordan0day, #1901] -- Add Rails Worker generator, `rails g sidekiq:worker User` will create `app/workers/user_worker.rb`. [seuros, #1909] -- Fix Web UI rendering with huge job arguments [jhass, #1918] -- Minor refactoring of Sidekiq::Client internals, for Sidekiq Pro. [#1919] - -3.2.2 ------------ - -- **This version of Sidekiq will no longer start on Ruby 1.9.** Sidekiq - 3 does not support MRI 1.9 but we've allowed it to run before now. -- Fix issue which could cause Sidekiq workers to disappear from the Busy - tab while still being active [#1884] -- Add "Back to App" button in Web UI. You can set the button link via - `Sidekiq::Web.app_url = 'http://www.mysite.com'` [#1875, seuros] -- Add process tag (`-g tag`) to the Busy page so you can differentiate processes at a glance. [seuros, #1878] -- Add "Kill" button to move retries directly to the DJQ so they don't retry. [seuros, #1867] - -3.2.1 ------------ - -- Revert eager loading change for Rails 3.x apps, as it broke a few edge - cases. - -3.2.0 ------------ - -- **Fix issue which caused duplicate job execution in Rails 3.x** - This issue is caused by [improper exception handling in ActiveRecord](https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L281) which changes Sidekiq's Shutdown exception into a database - error, making Sidekiq think the job needs to be retried. **The fix requires Ruby 2.1**. [#1805] -- Update how Sidekiq eager loads Rails application code [#1791, jonleighton] -- Change logging timestamp to show milliseconds. -- Reverse sorting of Dead tab so newer jobs are listed first [#1802] - -3.1.4 ------------ - -- Happy π release! -- Self-tuning Scheduler polling, we use heartbeat info to better tune poll\_interval [#1630] -- Remove all table column width rules, hopefully get better column formatting [#1747] -- Handle edge case where YAML can't be decoded in dev mode [#1761] -- Fix lingering jobs in Busy page on Heroku [#1764] - -3.1.3 ------------ - -- Use ENV['DYNO'] on Heroku for hostname display, rather than an ugly UUID. [#1742] -- Show per-process labels on the Busy page, for feature tagging [#1673] - - -3.1.2 ------------ - -- Suitably chastised, @mperham reverts the Bundler change. - - -3.1.1 ------------ - -- Sidekiq::CLI now runs `Bundler.require(:default, environment)` to boot all gems - before loading any app code. -- Sort queues by name in Web UI [#1734] - - -3.1.0 ------------ - -- New **remote control** feature: you can remotely trigger Sidekiq to quiet - or terminate via API, without signals. This is most useful on JRuby - or Heroku which does not support the USR1 'quiet' signal. Now you can - run a rake task like this at the start of your deploy to quiet your - set of Sidekiq processes. [#1703] -```ruby -namespace :sidekiq do - task :quiet => :environment do - Sidekiq::ProcessSet.new.each(&:quiet!) - end -end -``` -- The Web UI can use the API to quiet or stop all processes via the Busy page. -- The Web UI understands and hides the `Sidekiq::Extensions::Delay*` - classes, instead showing `Class.method` as the Job. [#1718] -- Polish the Dashboard graphs a bit, update Rickshaw [brandonhilkert, #1725] -- The poll interval is now configurable in the Web UI [madebydna, #1713] -- Delay extensions can be removed so they don't conflict with - DelayedJob: put `Sidekiq.remove_delay!` in your initializer. [devaroop, #1674] - - -3.0.2 ------------ - -- Revert gemfile requirement of Ruby 2.0. JRuby 1.7 calls itself Ruby - 1.9.3 and broke with this requirement. - -3.0.1 ------------ - -- Revert pidfile behavior from 2.17.5: Sidekiq will no longer remove its own pidfile - as this is a race condition when restarting. [#1470, #1677] -- Show warning on the Queues page if a queue is paused [#1672] -- Only activate the ActiveRecord middleware if ActiveRecord::Base is defined on boot. [#1666] -- Add ability to disable jobs going to the DJQ with the `dead` option. -```ruby -sidekiq_options :dead => false, :retry => 5 -``` -- Minor fixes - - -3.0.0 ------------ - -Please see [3.0-Upgrade.md](3.0-Upgrade.md) for more comprehensive upgrade notes. - -- **Dead Job Queue** - jobs which run out of retries are now moved to a dead - job queue. These jobs must be retried manually or they will expire - after 6 months or 10,000 jobs. The Web UI contains a "Dead" tab - exposing these jobs. Use `sidekiq_options :retry => false` if you -don't wish jobs to be retried or put in the DJQ. Use -`sidekiq_options :retry => 0` if you don't want jobs to retry but go -straight to the DJQ. -- **Process Lifecycle Events** - you can now register blocks to run at - certain points during the Sidekiq process lifecycle: startup, quiet and - shutdown. -```ruby -Sidekiq.configure_server do |config| - config.on(:startup) do - # do something - end -end -``` -- **Global Error Handlers** - blocks of code which handle errors that - occur anywhere within Sidekiq, not just within middleware. -```ruby -Sidekiq.configure_server do |config| - config.error_handlers << proc {|ex,ctx| ... } -end -``` -- **Process Heartbeat** - each Sidekiq process will ping Redis every 5 - seconds to give a summary of the Sidekiq population at work. -- The Workers tab is now renamed to Busy and contains a list of live - Sidekiq processes and jobs in progress based on the heartbeat. -- **Shardable Client** - Sidekiq::Client instances can use a custom - Redis connection pool, allowing very large Sidekiq installations to scale by - sharding: sending different jobs to different Redis instances. -```ruby -client = Sidekiq::Client.new(ConnectionPool.new { Redis.new }) -client.push(...) -``` -```ruby -Sidekiq::Client.via(ConnectionPool.new { Redis.new }) do - FooWorker.perform_async - BarWorker.perform_async -end -``` - **Sharding support does require a breaking change to client-side -middleware, see 3.0-Upgrade.md.** -- New Chinese, Greek, Swedish and Czech translations for the Web UI. -- Updated most languages translations for the new UI features. -- **Remove official Capistrano integration** - this integration has been - moved into the [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem. -- **Remove official support for MRI 1.9** - Things still might work but - I no longer actively test on it. -- **Remove built-in support for Redis-to-Go**. - Heroku users: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL` -- **Remove built-in error integration for Airbrake, Honeybadger, ExceptionNotifier and Exceptional**. - Each error gem should provide its own Sidekiq integration. Update your error gem to the latest - version to pick up Sidekiq support. -- Upgrade to connection\_pool 2.0 which now creates connections lazily. -- Remove deprecated Sidekiq::Client.registered\_\* APIs -- Remove deprecated support for the old Sidekiq::Worker#retries\_exhausted method. -- Removed 'sidekiq/yaml\_patch', this was never documented or recommended. -- Removed --profile option, #1592 -- Remove usage of the term 'Worker' in the UI for clarity. Users would call both threads and - processes 'workers'. Instead, use "Thread", "Process" or "Job". - -2.17.7 ------------ - -- Auto-prune jobs older than one hour from the Workers page [#1508] -- Add Sidekiq::Workers#prune which can perform the auto-pruning. -- Fix issue where a job could be lost when an exception occurs updating - Redis stats before the job executes [#1511] - -2.17.6 ------------ - -- Fix capistrano integration due to missing pidfile. [#1490] - -2.17.5 ------------ - -- Automatically use the config file found at `config/sidekiq.yml`, if not passed `-C`. [#1481] -- Store 'retried\_at' and 'failed\_at' timestamps as Floats, not Strings. [#1473] -- A `USR2` signal will now reopen _all_ logs, using IO#reopen. Thus, instead of creating a new Logger object, - Sidekiq will now just update the existing Logger's file descriptor [#1163]. -- Remove pidfile when shutting down if started with `-P` [#1470] - -2.17.4 ------------ - -- Fix JID support in inline testing, #1454 -- Polish worker arguments display in UI, #1453 -- Marshal arguments fully to avoid worker mutation, #1452 -- Support reverse paging sorted sets, #1098 - - -2.17.3 ------------ - -- Synchronously terminates the poller and fetcher to fix a race condition in bulk requeue during shutdown [#1406] - -2.17.2 ------------ - -- Fix bug where strictly prioritized queues might be processed out of - order [#1408]. A side effect of this change is that it breaks a queue - declaration syntax that worked, although only because of a bug—it was - never intended to work and never supported. If you were declaring your - queues as a comma-separated list, e.g. `sidekiq -q critical,default,low`, - you must now use the `-q` flag before each queue, e.g. - `sidekiq -q critical -q default -q low`. - -2.17.1 ------------ - -- Expose `delay` extension as `sidekiq_delay` also. This allows you to - run Delayed::Job and Sidekiq in the same process, selectively porting - `delay` calls to `sidekiq_delay`. You just need to ensure that - Sidekiq is required **before** Delayed::Job in your Gemfile. [#1393] -- Bump redis client required version to 3.0.6 -- Minor CSS fixes for Web UI - -2.17.0 ------------ - -- Change `Sidekiq::Client#push_bulk` to return an array of pushed `jid`s. [#1315, barelyknown] -- Web UI refactoring to use more API internally (yummy dogfood!) -- Much faster Sidekiq::Job#delete performance for larger queue sizes -- Further capistrano 3 fixes -- Many misc minor fixes - -2.16.1 ------------ - -- Revert usage of `resolv-replace`. MRI's native DNS lookup releases the GIL. -- Fix several Capistrano 3 issues -- Escaping dynamic data like job args and error messages in Sidekiq Web UI. [#1299, lian] - -2.16.0 ------------ - -- Deprecate `Sidekiq::Client.registered_workers` and `Sidekiq::Client.registered_queues` -- Refactor Sidekiq::Client to be instance-based [#1279] -- Pass all Redis options to the Redis driver so Unix sockets - can be fully configured. [#1270, salimane] -- Allow sidekiq-web extensions to add locale paths so extensions - can be localized. [#1261, ondrejbartas] -- Capistrano 3 support [#1254, phallstrom] -- Use Ruby's `resolv-replace` to enable pure Ruby DNS lookups. - This ensures that any DNS resolution that takes place in worker - threads won't lock up the entire VM on MRI. [#1258] - -2.15.2 ------------ - -- Iterating over Sidekiq::Queue and Sidekiq::SortedSet will now work as - intended when jobs are deleted [#866, aackerman] -- A few more minor Web UI fixes [#1247] - -2.15.1 ------------ - -- Fix several Web UI issues with the Bootstrap 3 upgrade. - -2.15.0 ------------ - -- The Core Sidekiq actors are now monitored. If any crash, the - Sidekiq process logs the error and exits immediately. This is to - help prevent "stuck" Sidekiq processes which are running but don't - appear to be doing any work. [#1194] -- Sidekiq's testing behavior is now dynamic. You can choose between - `inline` and `fake` behavior in your tests. See -[Testing](https://github.com/mperham/sidekiq/wiki/Testing) for detail. [#1193] -- The Retries table has a new column for the error message. -- The Web UI topbar now contains the status and live poll button. -- Orphaned worker records are now auto-vacuumed when you visit the - Workers page in the Web UI. -- Sidekiq.default\_worker\_options allows you to configure default - options for all Sidekiq worker types. - -```ruby -Sidekiq.default_worker_options = { 'queue' => 'default', 'backtrace' => true } -``` -- Added two Sidekiq::Client class methods for compatibility with resque-scheduler: - `enqueue_to_in` and `enqueue_in` [#1212] -- Upgrade Web UI to Bootstrap 3.0. [#1211, jeffboek] - -2.14.1 ------------ - -- Fix misc Web UI issues due to ERB conversion. -- Bump redis-namespace version due to security issue. - -2.14.0 ------------ - -- Removed slim gem dependency, Web UI now uses ERB [Locke23rus, #1120] -- Fix more race conditions in Web UI actions -- Don't reset Job enqueued\_at when retrying -- Timestamp tooltips in the Web UI should use UTC -- Fix invalid usage of handle\_exception causing issues in Airbrake - [#1134] - - -2.13.1 ------------ - -- Make Sidekiq::Middleware::Chain Enumerable -- Make summary bar and graphs responsive [manishval, #1025] -- Adds a job status page for scheduled jobs [jonhyman] -- Handle race condition in retrying and deleting jobs in the Web UI -- The Web UI relative times are now i18n. [MadRabbit, #1088] -- Allow for default number of retry attempts to be set for - `Sidekiq::Middleware::Server::RetryJobs` middleware. [czarneckid] [#1091] - -```ruby -Sidekiq.configure_server do |config| - config.server_middleware do |chain| - chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 10 - end -end -``` - - -2.13.0 ------------ - -- Adding button to move scheduled job to main queue [guiceolin, #1020] -- fix i18n support resetting saved locale when job is retried [#1011] -- log rotation via USR2 now closes the old logger [#1008] -- Add ability to customize retry schedule, like so [jmazzi, #1027] - -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retry_in { |count| count * 2 } -end -``` -- Redesign Worker#retries\_exhausted callback to use same form as above [jmazzi, #1030] - -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retries_exhausted do |msg| - Rails.logger.error "Failed to process #{msg['class']} with args: #{msg['args']}" - end -end -``` - -2.12.4 ------------ - -- Fix error in previous release which crashed the Manager when a - Processor died. - -2.12.3 ------------ - -- Revert back to Celluloid's TaskFiber for job processing which has proven to be more - stable than TaskThread. [#985] -- Avoid possible lockup during hard shutdown [#997] - -At this point, if you are experiencing stability issues with Sidekiq in -Ruby 1.9, please try Ruby 2.0. It seems to be more stable. - -2.12.2 ------------ - -- Relax slim version requirement to >= 1.1.0 -- Refactor historical stats to use TTL, not explicit cleanup. [grosser, #971] - -2.12.1 ------------ - -- Force Celluloid 0.14.1 as 0.14.0 has a serious bug. [#954] -- Scheduled and Retry jobs now use Sidekiq::Client to push - jobs onto the queue, so they use client middleware. [dimko, #948] -- Record the timestamp when jobs are enqueued. Add - Sidekiq::Job#enqueued\_at to query the time. [mariovisic, #944] -- Add Sidekiq::Queue#latency - calculates diff between now and - enqueued\_at for the oldest job in the queue. -- Add testing method `perform_one` that dequeues and performs a single job. - This is mainly to aid testing jobs that spawn other jobs. [fumin, #963] - -2.12.0 ------------ - -- Upgrade to Celluloid 0.14, remove the use of Celluloid's thread - pool. This should halve the number of threads in each Sidekiq - process, thus requiring less resources. [#919] -- Abstract Celluloid usage to Sidekiq::Actor for testing purposes. -- Better handling for Redis downtime when fetching jobs and shutting - down, don't print exceptions every second and print success message - when Redis is back. -- Fix unclean shutdown leading to duplicate jobs [#897] -- Add Korean locale [#890] -- Upgrade test suite to Minitest 5 -- Remove usage of `multi_json` as `json` is now robust on all platforms. - -2.11.2 ------------ - -- Fix Web UI when used without Rails [#886] -- Add Sidekiq::Stats#reset [#349] -- Add Norwegian locale. -- Updates for the JA locale. - -2.11.1 ------------ - -- Fix timeout warning. -- Add Dutch web UI locale. - -2.11.0 ------------ - -- Upgrade to Celluloid 0.13. [#834] -- Remove **timeout** support from `sidekiq_options`. Ruby's timeout - is inherently unsafe in a multi-threaded application and was causing - stability problems for many. See http://bit.ly/OtYpK -- Add Japanese locale for Web UI [#868] -- Fix a few issues with Web UI i18n. - -2.10.1 ------------ - -- Remove need for the i18n gem. (brandonhilkert) -- Improve redis connection info logging on startup for debugging -purposes [#858] -- Revert sinatra/slim as runtime dependencies -- Add `find_job` method to sidekiq/api - - -2.10.0 ------------ - -- Refactor algorithm for putting scheduled jobs onto the queue [#843] -- Fix scheduler thread dying due to incorrect error handling [#839] -- Fix issue which left stale workers if Sidekiq wasn't shutdown while -quiet. [#840] -- I18n for web UI. Please submit translations of `web/locales/en.yml` for -your own language. [#811] -- 'sinatra', 'slim' and 'i18n' are now gem dependencies for Sidekiq. - - -2.9.0 ------------ - -- Update 'sidekiq/testing' to work with any Sidekiq::Client call. It - also serializes the arguments as using Redis would. [#713] -- Raise a Sidekiq::Shutdown error within workers which don't finish within the hard - timeout. This is to prevent unwanted database transaction commits. [#377] -- Lazy load Redis connection pool, you no longer need to specify - anything in Passenger or Unicorn's after_fork callback [#794] -- Add optional Worker#retries_exhausted hook after max retries failed. [jkassemi, #780] -- Fix bug in pagination link to last page [pitr, #774] -- Upstart scripts for multiple Sidekiq instances [dariocravero, #763] -- Use select via pipes instead of poll to catch signals [mrnugget, #761] - -2.8.0 ------------ - -- I18n support! Sidekiq can optionally save and restore the Rails locale - so it will be properly set when your jobs execute. Just include - `require 'sidekiq/middleware/i18n'` in your sidekiq initializer. [#750] -- Fix bug which could lose messages when using namespaces and the message -needs to be requeued in Redis. [#744] -- Refactor Redis namespace support [#747]. The redis namespace can no longer be - passed via the config file, the only supported way is via Ruby in your - initializer: - -```ruby -sidekiq_redis = { :url => 'redis://localhost:3679', :namespace => 'foo' } -Sidekiq.configure_server { |config| config.redis = sidekiq_redis } -Sidekiq.configure_client { |config| config.redis = sidekiq_redis } -``` - -A warning is printed out to the log if a namespace is found in your sidekiq.yml. - - -2.7.5 ------------ - -- Capistrano no longer uses daemonization in order to work with JRuby [#719] -- Refactor signal handling to work on Ruby 2.0 [#728, #730] -- Fix dashboard refresh URL [#732] - -2.7.4 ------------ - -- Fixed daemonization, was broken by some internal refactoring in 2.7.3 [#727] - -2.7.3 ------------ - -- Real-time dashboard is now the default web page -- Make config file optional for capistrano -- Fix Retry All button in the Web UI - -2.7.2 ------------ - -- Remove gem signing infrastructure. It was causing Sidekiq to break -when used via git in Bundler. This is why we can't have nice things. [#688] - - -2.7.1 ------------ - -- Fix issue with hard shutdown [#680] - - -2.7.0 ------------ - -- Add -d daemonize flag, capistrano recipe has been updated to use it [#662] -- Support profiling via `ruby-prof` with -p. When Sidekiq is stopped - via Ctrl-C, it will output `profile.html`. You must add `gem 'ruby-prof'` to your Gemfile for it to work. -- Dynamically update Redis stats on dashboard [brandonhilkert] -- Add Sidekiq::Workers API giving programmatic access to the current - set of active workers. - -``` -workers = Sidekiq::Workers.new -workers.size => 2 -workers.each do |name, work| - # name is a unique identifier per Processor instance - # work is a Hash which looks like: - # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg } -end -``` - -- Allow environment-specific sections within the config file which -override the global values [dtaniwaki, #630] - -``` ---- -:concurrency: 50 -:verbose: false -staging: - :verbose: true - :concurrency: 5 -``` - - -2.6.5 ------------ - -- Several reliability fixes for job requeueing upon termination [apinstein, #622, #624] -- Fix typo in capistrano recipe -- Add `retry_queue` option so retries can be given lower priority [ryanlower, #620] - -```ruby -sidekiq_options queue: 'high', retry_queue: 'low' -``` - -2.6.4 ------------ - -- Fix crash upon empty queue [#612] - -2.6.3 ------------ - -- sidekiqctl exits with non-zero exit code upon error [jmazzi] -- better argument validation in Sidekiq::Client [karlfreeman] - -2.6.2 ------------ - -- Add Dashboard beacon indicating when stats are updated. [brandonhilkert, #606] -- Revert issue with capistrano restart. [#598] - -2.6.1 ------------ - -- Dashboard now live updates summary stats also. [brandonhilkert, #605] -- Add middleware chain APIs `insert_before` and `insert_after` for fine - tuning the order of middleware. [jackrg, #595] - -2.6.0 ------------ - -- Web UI much more mobile friendly now [brandonhilkert, #573] -- Enable live polling for every section in Web UI [brandonhilkert, #567] -- Add Stats API [brandonhilkert, #565] -- Add Stats::History API [brandonhilkert, #570] -- Add Dashboard to Web UI with live and historical stat graphs [brandonhilkert, #580] -- Add option to log output to a file, reopen log file on USR2 signal [mrnugget, #581] - -2.5.4 ------------ - -- `Sidekiq::Client.push` now accepts the worker class as a string so the - Sidekiq client does not have to load your worker classes at all. [#524] -- `Sidekiq::Client.push_bulk` now works with inline testing. -- **Really** fix status icon in Web UI this time. -- Add "Delete All" and "Retry All" buttons to Retries in Web UI - - -2.5.3 ------------ - -- Small Web UI fixes -- Add `delay_until` so you can delay jobs until a specific timestamp: - -```ruby -Auction.delay_until(@auction.ends_at).close(@auction.id) -``` - -This is identical to the existing Sidekiq::Worker method, `perform_at`. - -2.5.2 ------------ - -- Remove asset pipeline from Web UI for much faster, simpler runtime. [#499, #490, #481] -- Add -g option so the procline better identifies a Sidekiq process, defaults to File.basename(Rails.root). [#486] - - sidekiq 2.5.1 myapp [0 of 25 busy] - -- Add splay to retry time so groups of failed jobs don't fire all at once. [#483] - -2.5.1 ------------ - -- Fix issues with core\_ext - -2.5.0 ------------ - -- REDESIGNED WEB UI! [unity, cavneb] -- Support Honeybadger for error delivery -- Inline testing runs the client middleware before executing jobs [#465] -- Web UI can now remove jobs from queue. [#466, dleung] -- Web UI can now show the full message, not just 100 chars [#464, dleung] -- Add APIs for manipulating the retry and job queues. See sidekiq/api. [#457] - - -2.4.0 ------------ - -- ActionMailer.delay.method now only tries to deliver if method returns a valid message. -- Logging now uses "MSG-#{Job ID}", not a random msg ID -- Allow generic Redis provider as environment variable. [#443] -- Add ability to customize sidekiq\_options with delay calls [#450] - -```ruby -Foo.delay(:retry => false).bar -Foo.delay(:retry => 10).bar -Foo.delay(:timeout => 10.seconds).bar -Foo.delay_for(5.minutes, :timeout => 10.seconds).bar -``` - -2.3.3 ------------ - -- Remove option to disable Rails hooks. [#401] -- Allow delay of any module class method - -2.3.2 ------------ - -- Fix retry. 2.3.1 accidentally disabled it. - -2.3.1 ------------ - -- Add Sidekiq::Client.push\_bulk for bulk adding of jobs to Redis. - My own simple test case shows pushing 10,000 jobs goes from 5 sec to 1.5 sec. -- Add support for multiple processes per host to Capistrano recipe -- Re-enable Celluloid::Actor#defer to fix stack overflow issues [#398] - -2.3.0 ------------ - -- Upgrade Celluloid to 0.12 -- Upgrade Twitter Bootstrap to 2.1.0 -- Rescue more Exceptions -- Change Job ID to be Hex, rather than Base64, for HTTP safety -- Use `Airbrake#notify_or_ignore` - -2.2.1 ------------ - -- Add support for custom tabs to Sidekiq::Web [#346] -- Change capistrano recipe to run 'quiet' before deploy:update\_code so - it is run upon both 'deploy' and 'deploy:migrations'. [#352] -- Rescue Exception rather than StandardError to catch and log any sort - of Processor death. - -2.2.0 ------------ - -- Roll back Celluloid optimizations in 2.1.0 which caused instability. -- Add extension to delay any arbitrary class method to Sidekiq. - Previously this was limited to ActiveRecord classes. - -```ruby -SomeClass.delay.class_method(1, 'mike', Date.today) -``` - -- Sidekiq::Client now generates and returns a random, 128-bit Job ID 'jid' which - can be used to track the processing of a Job, e.g. for calling back to a webhook - when a job is finished. - -2.1.1 ------------ - -- Handle networking errors causing the scheduler thread to die [#309] -- Rework exception handling to log all Processor and actor death (#325, subelsky) -- Clone arguments when calling worker so modifications are discarded. (#265, hakanensari) - -2.1.0 ------------ - -- Tune Celluloid to no longer run message processing within a Fiber. - This gives us a full Thread stack and also lowers Sidekiq's memory - usage. -- Add pagination within the Web UI [#253] -- Specify which Redis driver to use: *hiredis* or *ruby* (default) -- Remove FailureJobs and UniqueJobs, which were optional middleware - that I don't want to support in core. [#302] - -2.0.3 ------------ -- Fix sidekiq-web's navbar on mobile devices and windows under 980px (ezkl) -- Fix Capistrano task for first deploys [#259] -- Worker subclasses now properly inherit sidekiq\_options set in - their superclass [#221] -- Add random jitter to scheduler to spread polls across POLL\_INTERVAL - window. [#247] -- Sidekiq has a new mailing list: sidekiq@librelist.org See README. - -2.0.2 ------------ - -- Fix "Retry Now" button on individual retry page. (ezkl) - -2.0.1 ------------ - -- Add "Clear Workers" button to UI. If you kill -9 Sidekiq, the workers - set can fill up with stale entries. -- Update sidekiq/testing to support new scheduled jobs API: - - ```ruby - require 'sidekiq/testing' - DirectWorker.perform_in(10.seconds, 1, 2) - assert_equal 1, DirectWorker.jobs.size - assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01 - ``` - -2.0.0 ------------ - -- **SCHEDULED JOBS**! - -You can now use `perform_at` and `perform_in` to schedule jobs -to run at arbitrary points in the future, like so: - -```ruby - SomeWorker.perform_in(5.days, 'bob', 13) - SomeWorker.perform_at(5.days.from_now, 'bob', 13) -``` - -It also works with the delay extensions: - -```ruby - UserMailer.delay_for(5.days).send_welcome_email(user.id) -``` - -The time is approximately when the job will be placed on the queue; -it is not guaranteed to run at precisely at that moment in time. - -This functionality is meant for one-off, arbitrary jobs. I still -recommend `whenever` or `clockwork` if you want cron-like, -recurring jobs. See `examples/scheduling.rb` - -I want to specially thank @yabawock for his work on sidekiq-scheduler. -His extension for Sidekiq 1.x filled an obvious functional gap that I now think is -useful enough to implement in Sidekiq proper. - -- Fixed issues due to Redis 3.x API changes. Sidekiq now requires - the Redis 3.x client. -- Inline testing now round trips arguments through JSON to catch - serialization issues (betelgeuse) - -1.2.1 ------------ - -- Sidekiq::Worker now has access to Sidekiq's standard logger -- Fix issue with non-StandardErrors leading to Processor exhaustion -- Fix issue with Fetcher slowing Sidekiq shutdown -- Print backtraces for all threads upon TTIN signal [#183] -- Overhaul retries Web UI with new index page and bulk operations [#184] - -1.2.0 ------------ - -- Full or partial error backtraces can optionally be stored as part of the retry - for display in the web UI if you aren't using an error service. [#155] - -```ruby -class Worker - include Sidekiq::Worker - sidekiq_options :backtrace => [true || 10] -end -``` -- Add timeout option to kill a worker after N seconds (blackgold9) - -```ruby -class HangingWorker - include Sidekiq::Worker - sidekiq_options :timeout => 600 - def perform - # will be killed if it takes longer than 10 minutes - end -end -``` - -- Fix delayed extensions not available in workers [#152] -- In test environments add the `#drain` class method to workers. This method - executes all previously queued jobs. (panthomakos) -- Sidekiq workers can be run inline during tests, just `require 'sidekiq/testing/inline'` (panthomakos) -- Queues can now be deleted from the Sidekiq web UI [#154] -- Fix unnecessary shutdown delay due to Retry Poller [#174] - -1.1.4 ------------ - -- Add 24 hr expiry for basic keys set in Redis, to avoid any possible leaking. -- Only register workers in Redis while working, to avoid lingering - workers [#156] -- Speed up shutdown significantly. - -1.1.3 ------------ - -- Better network error handling when fetching jobs from Redis. - Sidekiq will retry once per second until it can re-establish - a connection. (ryanlecompte) -- capistrano recipe now uses `bundle_cmd` if set [#147] -- handle multi\_json API changes (sferik) - -1.1.2 ------------ - -- Fix double restart with cap deploy [#137] - -1.1.1 ------------ - -- Set procline for easy monitoring of Sidekiq status via "ps aux" -- Fix race condition on shutdown [#134] -- Fix hang with cap sidekiq:start [#131] - -1.1.0 ------------ - -- The Sidekiq license has switched from GPLv3 to LGPLv3! -- Sidekiq::Client.push now returns whether the actual Redis - operation succeeded or not. [#123] -- Remove UniqueJobs from the default middleware chain. Its - functionality, while useful, is unexpected for new Sidekiq - users. You can re-enable it with the following config. - Read #119 for more discussion. - -```ruby -Sidekiq.configure_client do |config| - require 'sidekiq/middleware/client/unique_jobs' - config.client_middleware do |chain| - chain.add Sidekiq::Middleware::Client::UniqueJobs - end -end -Sidekiq.configure_server do |config| - require 'sidekiq/middleware/server/unique_jobs' - config.server_middleware do |chain| - chain.add Sidekiq::Middleware::Server::UniqueJobs - end -end -``` - -1.0.0 ------------ - -Thanks to all Sidekiq users and contributors for helping me -get to this big milestone! - -- Default concurrency on client-side to 5, not 25 so we don't - create as many unused Redis connections, same as ActiveRecord's - default pool size. -- Ensure redis= is given a Hash or ConnectionPool. - -0.11.2 ------------ - -- Implement "safe shutdown". The messages for any workers that - are still busy when we hit the TERM timeout will be requeued in - Redis so the messages are not lost when the Sidekiq process exits. - [#110] -- Work around Celluloid's small 4kb stack limit [#115] -- Add support for a custom Capistrano role to limit Sidekiq to - a set of machines. [#113] - -0.11.1 ------------ - -- Fix fetch breaking retry when used with Redis namespaces. [#109] -- Redis connection now just a plain ConnectionPool, not CP::Wrapper. -- Capistrano initial deploy fix [#106] -- Re-implemented weighted queues support (ryanlecompte) - -0.11.0 ------------ - -- Client-side API changes, added sidekiq\_options for Sidekiq::Worker. - As a side effect of this change, the client API works on Ruby 1.8. - It's not officially supported but should work [#103] -- NO POLL! Sidekiq no longer polls Redis, leading to lower network - utilization and lower latency for message processing. -- Add --version CLI option - -0.10.1 ------------ - -- Add details page for jobs in retry queue (jcoene) -- Display relative timestamps in web interface (jcoene) -- Capistrano fixes (hinrik, bensie) - -0.10.0 ------------ - -- Reworked capistrano recipe to make it more fault-tolerant [#94]. -- Automatic failure retry! Sidekiq will now save failed messages - and retry them, with an exponential backoff, over about 20 days. - Did a message fail to process? Just deploy a bug fix in the next - few days and Sidekiq will retry the message eventually. - -0.9.1 ------------ - -- Fix missed deprecations, poor method name in web UI - -0.9.0 ------------ - -- Add -t option to configure the TERM shutdown timeout -- TERM shutdown timeout is now configurable, defaults to 5 seconds. -- USR1 signal now stops Sidekiq from accepting new work, - capistrano sends USR1 at start of deploy and TERM at end of deploy - giving workers the maximum amount of time to finish. -- New Sidekiq::Web rack application available -- Updated Sidekiq.redis API - -0.8.0 ------------ - -- Remove :namespace and :server CLI options (mperham) -- Add ExceptionNotifier support (masterkain) -- Add capistrano support (mperham) -- Workers now log upon start and finish (mperham) -- Messages for terminated workers are now automatically requeued (mperham) -- Add support for Exceptional error reporting (bensie) - -0.7.0 ------------ - -- Example chef recipe and monitrc script (jc00ke) -- Refactor global configuration into Sidekiq.configure\_server and - Sidekiq.configure\_client blocks. (mperham) -- Add optional middleware FailureJobs which saves failed jobs to a - 'failed' queue (fbjork) -- Upon shutdown, workers are now terminated after 5 seconds. This is to - meet Heroku's hard limit of 10 seconds for a process to shutdown. (mperham) -- Refactor middleware API for simplicity, see sidekiq/middleware/chain. (mperham) -- Add `delay` extensions for ActionMailer and ActiveRecord. (mperham) -- Added config file support. See test/config.yml for an example file. (jc00ke) -- Added pidfile for tools like monit (jc00ke) - -0.6.0 ------------ - -- Resque-compatible processing stats in redis (mperham) -- Simple client testing support in sidekiq/testing (mperham) -- Plain old Ruby support via the -r cli flag (mperham) -- Refactored middleware support, introducing ability to add client-side middleware (ryanlecompte) -- Added middleware for ignoring duplicate jobs (ryanlecompte) -- Added middleware for displaying jobs in resque-web dashboard (maxjustus) -- Added redis namespacing support (maxjustus) - -0.5.1 ------------ - -- Initial release! diff --git a/Ent-2.0-Upgrade.md b/Ent-2.0-Upgrade.md deleted file mode 100644 index 79b44a8c..00000000 --- a/Ent-2.0-Upgrade.md +++ /dev/null @@ -1,37 +0,0 @@ -# Welcome to Sidekiq Enterprise 2.0! - -Sidekiq Enterprise 2.0 adds a few new features and adds the requirement that license -credentials be available at runtime. Note that Sidekiq 6.0 does have major breaking changes. - -## What's New - -* Sidekiq Enterprise now requires license credentials at runtime. If you - configured Bundler as described in the access email you need do -nothing, everything should just work. If you are vendoring Sidekiq -Enterprise you will need to configure Bundler also or set -`SIDEKIQ_ENT_USERNAME=abcdef12 bundle exec sidekiq...` when starting the -process. [#4232] -* Dead jobs now release any unique locks they were holding when they died [#4162] -* Backoff can now be customized per rate limiter by passing in a Proc [#4219] -```ruby -limiter = Sidekiq::Limiter.bucket(:stripe, 10, :second, backoff: ->(limiter, job) { - return job['overrated'] || 5 # wait for N seconds, where N is the number of - # times we've failed the rate limit -}) -``` -* Removed deprecated APIs and warnings. -* Various changes for Sidekiq 6.0 -* Requires Ruby 2.5+ and Redis 4.0+ -* Requires Sidekiq 6.0+ and Sidekiq Pro 5.0+ - -## Upgrade - -* Upgrade to the latest Sidekiq Enterprise 1.x. -```ruby -gem 'sidekiq-ent', '< 2' -``` -* Fix any deprecation warnings you see. -* Upgrade to 2.x. -```ruby -gem 'sidekiq-ent', '< 3' -``` diff --git a/Ent-Changes.md b/Ent-Changes.md deleted file mode 100644 index 9c9b2faf..00000000 --- a/Ent-Changes.md +++ /dev/null @@ -1,333 +0,0 @@ -# Sidekiq Enterprise Changelog - -[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md) - -Please see [sidekiq.org](https://sidekiq.org) for more details and how to buy. - -HEAD -------------- - -- Fix periodic jobs missing the "fallback" hour during DST changeover [#5049] - -2.3.0 -------------- - -- Remove jQuery usage in UI tabs -- Pass exception to rate limiter backoff proc [#5024] - -2.2.3 -------------- - -- Fixes for leaky and unlimited limiters [#4809, #4869] -- Invalid leaders now immediately step down [#4950] -- Web UI now displays "next run time" in the specified timezone [#4833] -- Fix swarm memory monitoring on BSDs - -2.2.2 -------------- - -- Periodic job timezone fix [#4796] - -2.2.1 -------------- - -- Support configurable timezones for periodic jobs [#4749] -- Handle edge case leading to negative expiry in uniqueness [#4763] - -2.2.0 -------------- - -- Add new **leaky bucket** rate limiter [#4414] - This allows clients to burst up to X calls before throttling - back to X calls per Y seconds. To limit the user to 60 calls - per minute: -```ruby -leaker = Sidekiq::Limiter.leaky("shopify", 60, :minute) -leaker.within_limit do - ... -end -``` - See the Rate Limiting wiki page for more detail. -- Rate limiters may now customize their reschedule count [#4725] - To disable rate limit reschedules, use `reschedule: 0`. -```ruby -Sidekiq::Limiter.concurrent("somename", 5, reschedule: 0) -``` -- Allow filtering by name in the Rate Limiter UI [#4695] -- Add IT locale - -2.1.2 -------------- - -- The Sidekiq Pro and Enterprise gem servers now `bundle install` much faster with **Bundler 2.2+** [#4158] -- Now that ActiveJobs support `sidekiq_options`, add support for uniqueness in AJs [#4667] - -2.1.1 -------------- - -- Add optional **app preload** in swarm, saves even more memory [#4646] -- Fix incorrect queue tags in historical metrics [#4377] - -2.1.0 -------------- - -- Move historical metrics to use tags rather than interpolating name [#4377] -``` -sidekiq.enqueued.#{name} -> sidekiq.queue.size with tag queue:#{name} -sidekiq.latency.#{name} -> sidekiq.queue.latency with tag queue:#{name} -``` -- Remove `concurrent-ruby` gem dependency [#4586] -- Add systemd `Type=notify` support for swarm [#4511] -- Length swarm's boot timeout to 60 sec [#4544] -- Add NL locale - -2.0.1 -------------- - -- Periodic job registration API adjusted to avoid loading classes in initializer [#4271] -- Remove support for deprecated ENV variables (COUNT, MAXMEM\_MB, INDEX) in swarm code - -2.0.0 -------------- - -- Except for the [newly required credentials](https://github.com/mperham/sidekiq/issues/4232), Sidekiq Enterprise 2.0 does - not have any significant migration steps. -- Sidekiq Enterprise must now be started with valid license credentials. [#4232] -- Call `GC.compact` if possible in sidekiqswarm before forking [#4181] -- Changes for forward-compatibility with Sidekiq 6.0. -- Add death handler to remove any lingering unique locks [#4162] -- Backoff can now be customized per rate limiter [#4219] -- Code formatting changes for StandardRB - -1.8.1 -------------- - -- Fix excessive lock reclaims with concurrent limiter [#4105] -- Add ES translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. - -1.8.0 -------------- - -- Require Sidekiq Pro 4.0 and Sidekiq 5.2. -- Refactor historical metrics API to use revamped Statsd support in Sidekiq Pro -- Add a gauge to historical metrics for `default` queue latency [#4079] - -1.7.2 -------------- - -- Add PT and JA translations -- Fix elapsed time calculations to use monotonic clock [#4000, sj26] -- Fix edge case where flapping leadership would cause old periodic - jobs to be fired once [#3974] -- Add support for sidekiqswarm memory monitoring on FreeBSD [#3884] - -1.7.1 -------------- - -- Fix Lua error in concurrent rate limiter under heavy contention -- Remove superfluous `freeze` calls on Strings [#3759] - -1.7.0 -------------- - -- **NEW FEATURE** [Rolling restarts](https://github.com/mperham/sidekiq/wiki/Ent-Rolling-Restarts) - great for long running jobs! -- Adjust middleware so unique jobs that don't push aren't registered in a Batch [#3662] -- Add new unlimited rate limiter, useful for testing [#3743] -```ruby -limiter = Sidekiq::Limiter.unlimited(...any args...) -``` - -1.6.1 -------------- - -- Fix crash in rate limiter middleware when used with custom exceptions [#3604] - -1.6.0 -------------- - -- Show process "leader" tag on Busy page, requires Sidekiq 5.0.2 [#2867] -- Capture custom metrics with the `save_history` API. [#2815] -- Implement new `unique_until: 'start'` policy option. [#3471] - -1.5.4 -------------- - -- Fix broken Cron page in Web UI [#3458] - -1.5.3 -------------- - -- Remove dependency on the algorithms gem [#3446] -- Allow user to specify max memory in megabytes with SIDEKIQ\_MAXMEM\_MB [#3451] -- Implement logic to detect app startup failure, sidekiqswarm will exit - rather than try to restart the app forever [#3450] -- Another fix for doubly-encrypted arguments [#3368] - -1.5.2 -------------- - -- Fix encrypted arguments double-encrypted by retry or rate limiting [#3368] -- Fix leak in concurrent rate limiter, run this in Rails console to clean up existing data [#3323] -```ruby -expiry = 1.month.to_i; Sidekiq::Limiter.redis { |c| c.scan_each(match: "lmtr-cfree-*") { |key| c.expire(key, expiry) } } -``` - -1.5.1 -------------- - -- Fix issue with census startup when not using Bundler configuration for - source credentials. - -1.5.0 -------------- - -- Add new web authorization API [#3251] -- Update all sidekiqswarm env vars to use SIDEKIQ\_ prefix [#3218] -- Add census reporting, the leader will ping contribsys nightly with aggregate usage metrics - -1.4.0 -------------- - -- No functional changes, require latest Sidekiq and Sidekiq Pro versions - -1.3.2 -------------- - -- Upgrade encryption to use OpenSSL's more secure GCM mode. [#3060] - -1.3.1 -------------- - -- Fix multi-process memory monitoring on CentOS 6.x [#3063] -- Polish the new encryption feature a bit. - -1.3.0 -------------- - -- **BETA** [New encryption feature](https://github.com/mperham/sidekiq/wiki/Ent-Encryption) - which automatically encrypts the last argument of a Worker, aka the secret bag. - -1.2.4 -------------- - -- Fix issue causing some minutely jobs to execute every other minute. -- Log a warning if slow periodic processing causes us to miss a clock tick. - -1.2.3 -------------- - -- Periodic jobs could stop executing until process restart if Redis goes down [#3047] - -1.2.2 -------------- - -- Add API to check if a unique lock is present. See [#2932] for details. -- Tune concurrent limiters to minimize thread thrashing under heavy contention. [#2944] -- Add option for tuning which Bundler groups get preloaded with `sidekiqswarm` [#3025] -``` -SIDEKIQ_PRELOAD=default,production bin/sidekiqswarm ... -# Use an empty value for maximum application compatibility -SIDEKIQ_PRELOAD= bin/sidekiqswarm ... -``` - -1.2.1 -------------- - -- Multi-Process mode can now monitor the RSS memory of children and - restart any that grow too large. To limit children to 1GB each: -``` -MAXMEM_KB=1048576 COUNT=2 bundle exec sidekiqswarm ... -``` - -1.2.0 -------------- - -- **NEW FEATURE** Multi-process mode! Sidekiq Enterprise can now fork multiple worker - processes, enabling significant memory savings. See the [wiki -documentation](https://github.com/mperham/sidekiq/wiki/Ent-Multi-Process) for details. - - -0.7.10 -------------- - -- More precise gemspec dependency versioning - -1.1.0 -------------- - -- **NEW FEATURE** Historical queue metrics, [documented in the wiki](https://github.com/mperham/sidekiq/wiki/Ent-Historical-Metrics) [#2719] - -0.7.9, 1.0.2 -------------- - -- Window limiters can now accept arbitrary window sizes [#2686] -- Fix race condition in window limiters leading to non-stop OverLimit [#2704] -- Fix invalid overage counts when nesting concurrent limiters - -1.0.1 ----------- - -- Fix crash in periodic subsystem when a follower shuts down, thanks - to @justinko for reporting. - -1.0.0 ----------- - -- Enterprise 1.x targets Sidekiq 4.x. -- Rewrite several features to remove Celluloid dependency. No - functional changes. - -0.7.8 ----------- - -- Fix `unique_for: false` [#2658] - - -0.7.7 ----------- - -- Enterprise 0.x targets Sidekiq 3.x. -- Fix racy shutdown event which could lead to disappearing periodic - jobs, requires Sidekiq >= 3.5.3. -- Add new :leader event which is fired when a process gains leadership. - -0.7.6 ----------- - -- Redesign how overrated jobs are rescheduled to avoid creating new - jobs. [#2619] - -0.7.5 ----------- - -- Fix dynamic creation of concurrent limiters [#2617] - -0.7.4 ----------- -- Add additional check to prevent duplicate periodic job creation -- Allow user-specified TTLs for rate limiters [#2607] -- Paginate rate limiter index page [#2606] - -0.7.3 ----------- - -- Rework `Sidekiq::Limiter` redis handling to match global redis handling. -- Allow user to customize rate limit backoff logic and handle custom - rate limit errors. -- Fix scalability issue with Limiter index page. - -0.7.2 ----------- - -- Fix typo which prevented limiters with '0' in their names. - -0.7.1 ----------- - -- Fix issue where unique scheduled jobs can't be enqueued upon schedule - due to the existing unique lock. [#2499] - -0.7.0 ----------- - -Initial release. diff --git a/Gemfile b/Gemfile deleted file mode 100644 index cf298b27..00000000 --- a/Gemfile +++ /dev/null @@ -1,27 +0,0 @@ -source "https://rubygems.org" - -gemspec - -gem "rake" -gem "redis-namespace" -gem "rails", ">= 6.0.2" -gem "sqlite3", platforms: :ruby -gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby - -# mail dependencies -gem "net-smtp", platforms: :mri, require: false - -group :test do - gem "minitest" - gem "simplecov" - gem "codecov", require: false -end - -group :development, :test do - gem "standard", require: false -end - -group :load_test do - gem "hiredis" - gem "toxiproxy" -end diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b0c17da0..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,217 +0,0 @@ -PATH - remote: . - specs: - sidekiq (6.4.1) - connection_pool (>= 2.2.2) - rack (~> 2.0) - redis (>= 4.2.0) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) - mail (>= 2.7.1) - actionmailer (6.1.4.4) - actionpack (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activesupport (= 6.1.4.4) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.1.4.4) - actionview (= 6.1.4.4) - activesupport (= 6.1.4.4) - rack (~> 2.0, >= 2.0.9) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4.4) - actionpack (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) - nokogiri (>= 1.8.5) - actionview (6.1.4.4) - activesupport (= 6.1.4.4) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.4.4) - activesupport (= 6.1.4.4) - globalid (>= 0.3.6) - activemodel (6.1.4.4) - activesupport (= 6.1.4.4) - activerecord (6.1.4.4) - activemodel (= 6.1.4.4) - activesupport (= 6.1.4.4) - activestorage (6.1.4.4) - actionpack (= 6.1.4.4) - activejob (= 6.1.4.4) - activerecord (= 6.1.4.4) - activesupport (= 6.1.4.4) - marcel (~> 1.0.0) - mini_mime (>= 1.1.0) - activesupport (6.1.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - ast (2.4.2) - builder (3.2.4) - codecov (0.2.8) - json - simplecov - concurrent-ruby (1.1.9) - connection_pool (2.2.5) - crass (1.0.6) - digest (3.1.0) - docile (1.3.2) - erubi (1.10.0) - globalid (1.0.0) - activesupport (>= 5.0) - hiredis (0.6.3) - hiredis (0.6.3-java) - i18n (1.8.11) - concurrent-ruby (~> 1.0) - io-wait (0.2.1) - json (2.3.1) - json (2.3.1-java) - loofah (2.13.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - marcel (1.0.2) - method_source (1.0.0) - mini_mime (1.1.2) - mini_portile2 (2.6.1) - minitest (5.15.0) - net-protocol (0.1.2) - io-wait - timeout - net-smtp (0.3.1) - digest - net-protocol - timeout - nio4r (2.5.8) - nio4r (2.5.8-java) - nokogiri (1.12.5) - mini_portile2 (~> 2.6.1) - racc (~> 1.4) - nokogiri (1.12.5-java) - racc (~> 1.4) - parallel (1.21.0) - parser (3.1.0.0) - ast (~> 2.4.1) - racc (1.6.0) - racc (1.6.0-java) - rack (2.2.3) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.1.4.4) - actioncable (= 6.1.4.4) - actionmailbox (= 6.1.4.4) - actionmailer (= 6.1.4.4) - actionpack (= 6.1.4.4) - actiontext (= 6.1.4.4) - actionview (= 6.1.4.4) - activejob (= 6.1.4.4) - activemodel (= 6.1.4.4) - activerecord (= 6.1.4.4) - activestorage (= 6.1.4.4) - activesupport (= 6.1.4.4) - bundler (>= 1.15.0) - railties (= 6.1.4.4) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.4.2) - loofah (~> 2.3) - railties (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) - method_source - rake (>= 0.13) - thor (~> 1.0) - rainbow (3.0.0) - rake (13.0.6) - redis (4.5.1) - redis-namespace (1.8.1) - redis (>= 3.0.4) - regexp_parser (2.2.0) - rexml (3.2.5) - rubocop (1.24.1) - parallel (~> 1.10) - parser (>= 3.0.0.0) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.15.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.15.1) - parser (>= 3.0.1.1) - rubocop-performance (1.13.1) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - ruby-progressbar (1.11.0) - simplecov (0.19.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov-html (0.12.3) - sprockets (4.0.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) - sqlite3 (1.4.2) - standard (1.6.0) - rubocop (= 1.24.1) - rubocop-performance (= 1.13.1) - thor (1.2.1) - timeout (0.2.0) - toxiproxy (1.0.3) - tzinfo (2.0.4) - concurrent-ruby (~> 1.0) - unicode-display_width (2.1.0) - websocket-driver (0.7.5) - websocket-extensions (>= 0.1.0) - websocket-driver (0.7.5-java) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - zeitwerk (2.5.3) - -PLATFORMS - java - ruby - universal-java-1.8 - -DEPENDENCIES - activerecord-jdbcsqlite3-adapter - codecov - hiredis - minitest - net-smtp - rails (>= 6.0.2) - rake - redis-namespace - sidekiq! - simplecov - sqlite3 - standard - toxiproxy diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d92e06b7..00000000 --- a/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) Contributed Systems LLC - -Sidekiq is an Open Source project licensed under the terms of -the LGPLv3 license. Please see -for license text. - -Sidekiq Pro and Sidekiq Enterprise have a commercial-friendly license. -You can find the commercial license in COMM-LICENSE.txt. -Please see https://sidekiq.org for purchasing options. diff --git a/Pro-2.0-Upgrade.md b/Pro-2.0-Upgrade.md deleted file mode 100644 index e6fe75fc..00000000 --- a/Pro-2.0-Upgrade.md +++ /dev/null @@ -1,138 +0,0 @@ -# Upgrading to Sidekiq Pro 2.0 - -Sidekiq Pro 2.0 allows nested batches for more complex job workflows -and provides a new reliable scheduler which uses Lua to guarantee -atomicity and much higher performance. - -It also removes deprecated APIs, changes the batch data format and -how features are activated. Read carefully to ensure your upgrade goes -smoothly. - -Sidekiq Pro 2.0 requires Sidekiq 3.3.2 or greater. Redis 2.8 is -recommended; Redis 2.4 or 2.6 will work but some functionality will not be -available. - -**Note that you CANNOT go back to Pro 1.x once you've created batches -with 2.x. The new batches will not process correctly with 1.x.** - -**If you are on a version of Sidekiq Pro <1.5, you should upgrade to the -latest 1.x version and run it for a week before upgrading to 2.0.** - -## Nested Batches - -Batches can now be nested within the `jobs` method. -This feature enables Sidekiq Pro to handle workflow processing of any size -and complexity! - -```ruby -a = Sidekiq::Batch.new -a.on(:success, SomeCallback) -a.jobs do - SomeWork.perform_async - - b = Sidekiq::Batch.new - b.on(:success, MyCallback) - b.jobs do - OtherWork.perform_async - end -end -``` - -Parent batch callbacks are not processed until all child batch callbacks have -run successfully. In the example above, `MyCallback` will always fire -before `SomeCallback` because `b` is considered a child of `a`. - -Of course you can dynamically add child batches while a batch job is executing. - -```ruby -def perform(*args) - do_something(args) - - if more_work? - # Sidekiq::Worker#batch returns the Batch this job is part of. - batch.jobs do - b = Sidekiq::Batch.new - b.on(:success, MyCallback) - b.jobs do - OtherWork.perform_async - end - end - end -end -``` - -More context: [#1485] - -## Batch Data - -The batch data model was overhauled. Batch data should take -significantly less space in Redis now. A simple benchmark shows 25% -savings but real world savings should be even greater. - -* Batch 2.x BIDs are 14 character URL-safe Base64-encoded strings, e.g. - "vTF1-9QvLPnREQ". Batch 1.x BIDs were 16 character hex-encoded - strings, e.g. "4a3fc67d30370edf". -* In 1.x, batch data was not removed until it naturally expired in Redis. - In 2.x, all data for a batch is removed from Redis once the batch has - run any success callbacks. -* Because of the former point, batch expiry is no longer a concern. - Batch expiry is hardcoded to 30 days and is no longer user-tunable. -* Failed batch jobs no longer automatically store any associated - backtrace in Redis. - -**There's no data migration required. Sidekiq Pro 2.0 transparently handles -both old and new format.** - -More context: [#2130] - -## Reliability - -2.0 brings a new reliable scheduler which uses Lua inside Redis so enqueuing -scheduled jobs is atomic. Benchmarks show it 50x faster when enqueuing -lots of jobs. - -**Two caveats**: -- Client-side middleware is not executed - for each job when enqueued with the reliable scheduler. No Sidekiq or - Sidekiq Pro functionality is affected by this change but some 3rd party - plugins might be. -- The Lua script used inside the reliable scheduler is not safe for use - with Redis Cluster or other multi-master Redis solutions. - It is safe to use with Redis Sentinel or a typical master/slave replication setup. - -**You no longer require anything to use the Reliability features.** - -* Activate reliable fetch and/or the new reliable scheduler: -```ruby -Sidekiq.configure_server do |config| - config.reliable_fetch! - config.reliable_scheduler! -end -``` -* Activate reliable push: -```ruby -Sidekiq::Client.reliable_push! -``` - -More context: [#2130] - -## Other Changes - -* You must require `sidekiq/pro/notifications` if you want to use the - existing notification schemes. I don't recommend using them as the - newer-style `Sidekiq::Batch#on` method is simpler and more flexible. -* Several classes have been renamed. Generally these classes are ones - you should not need to require/use in your own code, e.g. the Batch - middleware. -* You can add `attr_accessor :jid` to a Batch callback class and Sidekiq - Pro will set it to the jid of the callback job. [#2178] -* There's now an official API to iterate all known Batches [#2191] -```ruby -Sidekiq::BatchSet.new.each {|status| p status.bid } -``` -* The Web UI now shows the Sidekiq Pro version in the footer. [#1991] - -## Thanks - -Adam Prescott, Luke van der Hoeven and Jon Hyman all provided valuable -feedback during the release process. Thank you guys! diff --git a/Pro-3.0-Upgrade.md b/Pro-3.0-Upgrade.md deleted file mode 100644 index 5e5559ff..00000000 --- a/Pro-3.0-Upgrade.md +++ /dev/null @@ -1,44 +0,0 @@ -# Welcome to Sidekiq Pro 3.0! - -Sidekiq Pro 3.0 is designed to work with Sidekiq 4.0. - -## What's New - -* **Redis 2.8.0 or greater is required.** Redis 2.8 was released two years - ago and contains **many** useful features which Sidekiq couldn't - leverage until now. **Redis 3.0.3 or greater is recommended** for large - scale use. - -* Sidekiq Pro no longer uses Celluloid. If your application code uses Celluloid, - you will need to pull it in yourself. - -* Pausing and unpausing queues is now instantaneous, no more polling! - -* Reliable fetch has been re-implemented due to the fetch changes in - Sidekiq 4.0. - -* Support for platforms without persistent hostnames. Since the reliable\_fetch - algorithm requires a persistent hostname, an alternative reliability -algorithm is now available for platforms like Heroku and Docker: -```ruby -Sidekiq.configure_server do |config| - config.timed_fetch! -end -``` - The wiki contains [much more detail about each reliability option](https://github.com/mperham/sidekiq/wiki/Pro-Reliability-Server). - -* The old 'sidekiq/notifications' features have been removed. - -## Upgrade - -First, make sure you are using Redis 2.8 or greater. Next: - -* Upgrade to the latest Sidekiq Pro 2.x. -```ruby -gem 'sidekiq-pro', '< 3' -``` -* Fix any deprecation warnings you see. -* Upgrade to 3.x. -```ruby -gem 'sidekiq-pro', '< 4' -``` diff --git a/Pro-4.0-Upgrade.md b/Pro-4.0-Upgrade.md deleted file mode 100644 index 82a4994e..00000000 --- a/Pro-4.0-Upgrade.md +++ /dev/null @@ -1,35 +0,0 @@ -# Welcome to Sidekiq Pro 4.0! - -Sidekiq Pro 4.0 is designed to work with Sidekiq 5.0. - -## What's New - -* Batches now "die" if any of their jobs die. You can enumerate the set - of dead batches and their associated dead jobs. The success callback - for a dead batch will never fire unless these jobs are fixed. -```ruby -Sidekiq::Batch::DeadSet.new.each do |status| - status.dead? # => true - status.dead_jobs # => [...] -end -``` -This API allows you to enumerate the batches which need help. -If you fix the issue and the dead jobs succeed, the batch will succeed. -* The older `reliable_fetch` and `timed_fetch` algorithms have been - removed. Only super\_fetch is available in 4.0. -* The statsd middleware has been tweaked to remove support for legacy, - pre-3.6.0 configuration and add relevant tags. -* Requires Sidekiq 5.0.5+. - -## Upgrade - -* Upgrade to the latest Sidekiq Pro 3.x. -```ruby -gem 'sidekiq-pro', '< 4' -``` -* Fix any deprecation warnings you see. -* Upgrade to 4.x. -```ruby -gem 'sidekiq-pro', '< 5' -``` - diff --git a/Pro-5.0-Upgrade.md b/Pro-5.0-Upgrade.md deleted file mode 100644 index bab7c7a3..00000000 --- a/Pro-5.0-Upgrade.md +++ /dev/null @@ -1,25 +0,0 @@ -# Welcome to Sidekiq Pro 5.0! - -Sidekiq Pro 5.0 is mainly a cleanup release for Sidekiq 6.0. The -migration should be as close to trivial as a major version bump can be. -Note that Sidekiq 6.0 does have major breaking changes. - -## What's New - -* New localizations for the Sidekiq Pro Web UI: ES, ZH, PT, JA, RU -* Removed deprecated APIs and warnings. -* Various changes for Sidekiq 6.0 -* Requires Ruby 2.5+ and Redis 4.0+ -* Requires Sidekiq 6.0+. - -## Upgrade - -* Upgrade to the latest Sidekiq Pro 4.x. -```ruby -gem 'sidekiq-pro', '< 5' -``` -* Fix any deprecation warnings you see. -* Upgrade to 5.x. -```ruby -gem 'sidekiq-pro', '< 6' -``` diff --git a/Pro-Changes.md b/Pro-Changes.md deleted file mode 100644 index 803f36f2..00000000 --- a/Pro-Changes.md +++ /dev/null @@ -1,851 +0,0 @@ -# Sidekiq Pro Changelog - -[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md) - -Please see [sidekiq.org](https://sidekiq.org/) for more details and how to buy. - -HEAD ---------- - -- Fix namespace issue with dogstatsd-ruby in Ruby 3+ [#5094] - -5.3.0 ---------- - -- Fix thread-safety issue with Sidekiq::Pro::Config -- Allow job-specific options in Statsd metrics [#5037] -```ruby -# add to your initializer -Sidekiq::Middleware::Server::Statsd.options = ->(klass, job, q) do - {tags: ["worker:#{klass}", "queue:#{q}"]}.tap do |h| - h[:tags] << "tenant:#{job['tenant_id']}" if job["tenant_id"] - end -end -``` - -5.2.4 ---------- - -- Initialize paused queue set before allowing jobs to be fetched [#4975] - -5.2.3 ---------- - -- Reduce superfluous logging of Redis errors [#4969] -- Display dead JIDs on Batch details page [#4926] - -5.2.2 ---------- - -- Include poison pill info in super_fetch's orphan handler [#4859] -- Use Sidekiq::Batch::Immutable error so race conditions can easily be caught [#4845] -- Fix sharded UI not using middleware in Sidekiq 6.2 [#4843] -- Compatibility with dogstatsd-ruby 4.x and 5.x [#4863] - -5.2.1 ---------- - -- Propagate death callbacks to parent batches [#4774] -- Allow customization of Batch linger to quickly reclaim memory in Redis [#4772] -- Fix disappearing processes in Busy due to super_fetch initialization when used in - tandem with `SIDEKIQ_PRELOAD_APP=1` in `sidekiqswarm`. [#4733] - -5.2.0 ---------- - -- The Sidekiq Pro and Enterprise gem servers now `bundle install` much faster with **Bundler 2.2+** [#4158] -- Fix issue with reliable push and multiple shards [#4669] -- Fix Pro memory leak due to fetch refactoring in Sidekiq 6.1 [#4652] -- Gracefully handle poison pill jobs [#4633] -- Remove support for multi-shard batches [#4642] -- Rename `Sidekiq::Rack::BatchStatus` to `Sidekiq::Pro::BatchStatus` [#4655] - -5.1.1 ---------- - -- Fix broken basic fetcher [#4616] - -5.1.0 ---------- - -- Remove old Statsd metrics with `WorkerName` in the name [#4377] -``` -job.WorkerName.count -> job.count with tag worker:WorkerName -job.WorkerName.perform -> job.perform with tag worker:WorkerName -job.WorkerName.failure -> job.failure with tag worker:WorkerName -``` -- Remove `concurrent-ruby` gem dependency [#4586] -- Update `constantize` for batch callbacks. [#4469] -- Add queue tag to `jobs.recovered.fetch` metric [#4594] -- Refactor Pro's fetch infrastructure [#4602] - -5.0.1 ---------- - -- Rejigger batch failures UI to add direct links to retries and scheduled jobs [#4209] -- Delete batch data with `UNLINK` [#4155] -- Fix bug where a scheduled job can lose its scheduled time when using reliable push [#4267] -- Sidekiq::JobSet#scan and #find_job APIs have been promoted to Sidekiq OSS. [#4259] - -5.0.0 ---------- - -- There is no significant migration from Sidekiq Pro 4.0 to 5.0 - but make sure you read the [update notes for Sidekiq -6.0](https://github.com/mperham/sidekiq/blob/master/6.0-Upgrade.md). -- Removed various deprecated APIs and associated warnings. -- **BREAKING CHANGE** Remove the `Sidekiq::Batch::Status#dead_jobs` API in favor of - `Sidekiq::Batch::Status#dead_jids`. [#4217] -- Update Sidekiq Pro codebase to use StandardRB formatting -- Fix lingering "b-XXX-died" elements in Redis which could cause - excessive memory usage. [#4217] -- Add ES translations, see issues [#3949](https://github.com/mperham/sidekiq/issues/3949) and [#3951](https://github.com/mperham/sidekiq/issues/3951) to add your own language. - -4.0.5 ---------- - -- Increase super\_fetch retriever thread count from 1 to 2 to make it - less sensitive to Redis latency. -- Better handling of invalid job JSON by reliable scheduler [#4053] -- Added ZH, PT, JA and RU translations. - -4.0.4 ---------- - -- Update Sidekiq::Client patches to work with new Module#prepend - mechanism in Sidekiq 5.2.0. [#3930] - -4.0.3 ---------- - -- Add at\_exit handler to push any saved jobs in `reliable_push` when exiting. [#3823] -- Implement batch death callback. This is fired the first time a job within a batch dies. [#3841] -```ruby -batch = Sidekiq::Batch.new -batch.on(:death, ...) -``` - -4.0.2 ---------- - -- Remove super\_fetch edge case leading to an unnecessary `sleep(1)` - call and resulting latency [#3790] -- Fix possible bad statsd metric call on super\_fetch startup -- Remove superfluous `freeze` calls on Strings [#3759] - -4.0.1 ---------- - -- Fix incompatibility with the statsd-ruby gem [#3740] -- Add tags to Statsd metrics when using Datadog [#3744] - -4.0.0 ---------- - -- See the [Sidekiq Pro 4.0](Pro-4.0-Upgrade.md) release notes. - - -3.7.1 ---------- - -- Deprecate timed\_fetch. Switch to super\_fetch: -```ruby -config.super_fetch! -``` - - -3.7.0 ---------- - -- Refactor batch job success/failure to gracefully handle several edge - cases with regard to Sidekiq::Shutdown. This should greatly reduce - the chances of seeing the long-standing "negative pending count" problem. [#3710] - - -3.6.1 ---------- - -- Add support for Datadog::Statsd, it is the recommended Statsd client. [#3699] -```ruby -Sidekiq::Pro.dogstatsd = ->{ Datadog::Statsd.new("metrics.example.com", 8125) } -``` -- Size the statsd connection pool based on Sidekiq's concurrency [#3700] - - -3.6.0 ---------- - -This release overhauls the Statsd metrics support and adds more -metrics for tracking Pro feature usage. In your initializer: -```ruby -Sidekiq::Pro.statsd = ->{ ::Statsd.new("127.0.0.1", 8125) } -``` -Sidekiq Pro will emit more metrics to Statsd: -``` -jobs.expired - when a job is expired -jobs.recovered.push - when a job is recovered by reliable_push after network outage -jobs.recovered.fetch - when a job is recovered by super_fetch after process crash -batch.created - when a batch is created -batch.complete - when a batch is completed -batch.success - when a batch is successful -``` -Sidekiq Pro's existing Statsd middleware has been rewritten to leverage the new API. -Everything should be backwards compatible with one deprecation notice. - - -3.5.4 ---------- - -- Fix case in SuperFetch where Redis downtime can lead to processor thread death [#3684] -- Fix case where TimedFetch might not recover some pending jobs -- Fix edge case in Batch::Status#poll leading to premature completion [#3640] -- Adjust scan API to check 100 elements at a time, to minimize network round trips - when scanning large sets. - -3.5.3 ---------- - -- Restore error check for super\_fetch's job ack [#3601] -- Trim error messages saved in Batch's failure hash, preventing huge - messages from bloating Redis. [#3570] - -3.5.2 ---------- - -- Fix `Status#completed?` when run against a Batch that had succeeded - and was deleted. [#3519] - -3.5.1 ---------- - -- Work with Sidekiq 5.0.2+ -- Improve performance of super\_fetch with weighted queues [#3489] - -3.5.0 ---------- - -- Add queue pause/unpause endpoints for scripting via curl [#3445] -- Change how super\_fetch names private queues to avoid hostname/queue clashes. [#3443] -- Re-implement `Sidekiq::Queue#delete_job` to avoid O(n) runtime [#3408] -- Batch page displays Pending JIDs if less than 10 [#3130] -- Batch page has a Search button to find associated Retries [#3130] -- Make Batch UI progress bar more friendly to the colorblind [#3387] - -3.4.5 ---------- - -- Fix potential job loss with reliable scheduler when lots of jobs are scheduled - at precisely the same time. Thanks to raivil for his hard work in - reproducing the bug. [#3371] - -3.4.4 ---------- - -- Optimize super\_fetch shutdown to restart jobs quicker [#3249] - -3.4.3 ---------- - -- Limit reliable scheduler to enqueue up to 100 jobs per call, minimizing Redis latency [#3332] -- Fix bug in super\_fetch logic for queues with `_` in the name [#3339] - -3.4.2 ---------- - -- Add `Batch::Status#invalidated?` API which returns true if any/all - JIDs were invalidated within the batch. [#3326] - -3.4.1 ---------- - -- Allow super\_fetch's orphan job check to happen as often as every hour [#3273] -- Officially deprecate reliable\_fetch algorithm, I now recommend you use `super_fetch` instead: -```ruby -Sidekiq.configure_server do |config| - config.super_fetch! -end -``` -Also note that Sidekiq's `-i/--index` option is no longer used/relevant with super\_fetch. -- Don't display "Delete/Retry All" buttons when filtering in Web UI [#3243] -- Reimplement Sidekiq::JobSet#find\_job with ZSCAN [#3197] - -3.4.0 ---------- - -- Introducing the newest reliable fetching algorithm: `super_fetch`! This - algorithm will replace reliable\_fetch in Pro 4.0. super\_fetch is - bullet-proof across all environments, no longer requiring stable - hostnames or an index to be set per-process. [#3077] -```ruby -Sidekiq.configure_server do |config| - config.super_fetch! -end -``` - Thank you to @jonhyman for code review and the Sidekiq Pro customers that - beta tested super\_fetch. - -3.3.3 ---------- - -- Update Web UI extension to work with Sidekiq 4.2.0's new Web UI. [#3075] - -3.3.2 ---------- - -- Minimize batch memory usage after success [#3083] -- Extract batch's 24 hr linger expiry to a LINGER constant so it can be tuned. [#3011] - -3.3.1 ---------- - -- If environment is unset, treat it as development so reliable\_fetch works as before 3.2.2. - -3.3.0 ---------- - -- Don't delete batches immediately upon success but set a 24 hr expiry, this allows - Sidekiq::Batch::Status#poll to work, even after batch success. [#3011] -- New `Sidekiq::PendingSet#destroy(jid)` API to remove poison pill jobs [#3015] - -3.2.2 ---------- - -- A default value for -i is only set in development now, staging or - other environments must set an index if you wish to use reliable\_fetch. [#2971] -- Fix nil dereference when checking for jobs over timeout in timed\_fetch - - -3.2.1 ---------- - -- timed\_fetch now works with namespaces. [ryansch] - - -3.2.0 ---------- - -- Fixed detection of missing batches, `NoSuchBatch` should be raised - properly now if `Sidekiq::Batch.new(bid)` is called on a batch no - longer in Redis. -- Remove support for Pro 1.x format batches. This version will no - longer seamlessly process batches created with Sidekiq Pro 1.x. - As always, upgrade one major version at a time to ensure a smooth - transition. -- Fix edge case where a parent batch could expire before a child batch - was finished processing, leading to missing batches [#2889] - -2.1.5 ---------- - -- Fix edge case where a parent batch could expire before a child batch - was finished processing, leading to missing batches [#2889] - -3.1.0 ---------- - -- New container-friendly fetch algorithm: `timed_fetch`. See the - [wiki documentation](https://github.com/mperham/sidekiq/wiki/Pro-Reliability-Server) - for trade offs between the two reliability options. You should - use this if you are on Heroku, Docker, Amazon ECS or EBS or - another container-based system. - - -3.0.6 ---------- - -- Fix race condition on reliable fetch shutdown - -3.0.5 ---------- - -- Statsd metrics now account for ActiveJob class names -- Allow reliable fetch internals to be overridden [jonhyman] - -3.0.4 ---------- - -- Queue pausing no longer requires reliable fetch. [#2786] - -3.0.3, 2.1.4 ------------- - -- Convert Lua-based `Sidekiq::Queue#delete_by_class` to Ruby-based, to - avoid O(N^2) performance and possible Redis failure. [#2806] - -3.0.2 ------------ - -- Make job registration with batch part of the atomic push so batch - metadata can't get out of sync with the job data. [#2714] - -3.0.1 ------------ - -- Remove a number of Redis version checks since we can assume 2.8+ now. -- Fix expiring jobs client middleware not loaded on server - -3.0.0 ------------ - -- See the [Pro 3.0 release notes](Pro-3.0-Upgrade.md). - -2.1.3 ------------ - -- Don't enable strict priority if using weighted queueing like `-q a,1 -q b,1` -- Safer JSON mangling in Lua [#2639] - -2.1.2 ------------ - -- Lock Sidekiq Pro 2.x to Sidekiq 3.x. - -2.1.1 ------------ - -- Make ShardSet lazier so Redis can first be initialized at startup. [#2603] - - -2.1.0 ------------ - -- Explicit support for sharding batches. You list your Redis shards and - Sidekiq Pro will randomly spread batches across the shards. The BID - will indicate which shard contains the batch data. Jobs within a - batch may be spread across all shards too. [#2548, jonhyman] -- Officially deprecate Sidekiq::Notifications code. Notifications have - been undocumented for months now. [#2575] - - -2.0.8 ------------ - -- Fix reliable scheduler mangling large numeric arguments. Lua's CJSON - library cannot accurately encode numbers larger than 14 digits! [#2478] - -2.0.7 ------------ - -- Optimize delete of enormous batches (100,000s of jobs) [#2458] - -2.0.6, 1.9.3 --------------- - -- CSRF protection in Sidekiq 3.4.2 broke job filtering in the Web UI [#2442] -- Sidekiq Pro 1.x is now limited to Sidekiq < 3.5.0. - -2.0.5 ------------ - -- Atomic scheduler now sets `enqueued_at` [#2414] -- Batches now account for jobs which are stopped by client middleware [#2406] -- Ignore redundant calls to `Sidekiq::Client.reliable_push!` [#2408] - -2.0.4 ------------ - -- Reliable push now supports sharding [#2409] -- Reliable push now only catches Redis exceptions [#2307] - -2.0.3 ------------ - -- Display Batch callback data on the Batch details page. [#2347] -- Fix incompatibility with Pro Web and Rack middleware. [#2344] Thank - you to Jason Clark for the tip on how to fix it. - -2.0.2 ------------ - -- Multiple Web UIs can now run in the same process. [#2267] If you have - multiple Redis shards, you can mount UIs for all in the same process: -```ruby -POOL1 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6379/0") } -POOL2 = ConnectionPool.new { Redis.new(:url => "redis://localhost:6378/0") } - -mount Sidekiq::Pro::Web => '/sidekiq' # default -mount Sidekiq::Pro::Web.with(redis_pool: POOL1), at: '/sidekiq1', as: 'sidekiq1' # shard1 -mount Sidekiq::Pro::Web.with(redis_pool: POOL2), at: '/sidekiq2', as: 'sidekiq2' # shard2 -``` -- **SECURITY** Fix batch XSS in error data. Thanks to moneybird.com for - reporting the issue. - -2.0.1 ------------ - -- Add `batch.callback_queue` so batch callbacks can use a higher - priority queue than jobs. [#2200] -- Gracefully recover if someone runs `SCRIPT FLUSH` on Redis. [#2240] -- Ignore errors when attempting `bulk_requeue`, allowing clean shutdown - -2.0.0 ------------ - -- See [the Upgrade Notes](Pro-2.0-Upgrade.md) for detailed notes. - -1.9.2 ------------ - -- As of 1/1/2015, Sidekiq Pro is hosted on a new dedicated server. - Happy new year and let's hope for 100% uptime! -- Fix bug in reliable\_fetch where jobs could be duplicated if a Sidekiq - process crashed and you were using weighted queues. [#2120] - -1.9.1 ------------ - -- **SECURITY** Fix XSS in batch description, thanks to intercom.io for reporting the - issue. If you don't use batch descriptions, you don't need the fix. - -1.9.0 ------------ - -- Add new expiring jobs feature [#1982] -- Show batch expiration on Batch details page [#1981] -- Add '$' batch success token to the pubsub support. [#1953] - - -1.8.0 ------------ - -- Fix race condition where Batches can complete - before they have been fully defined or only half-defined. Requires - Sidekiq 3.2.3. [#1919] - - -1.7.6 ------------ - -- Quick release to verify #1919 - - -1.7.5 ------------ - -- Fix job filtering within the Dead tab. -- Add APIs and wiki documentation for invalidating jobs within a batch. - - -1.7.4 ------------ - -- Awesome ANSI art startup banner! - - -1.7.3 ------------ - -- Batch callbacks should use the same queue as the associated jobs. - -1.7.2 ------------ - -- **DEPRECATION** Use `Batch#on(:complete)` instead of `Batch#notify`. - The specific Campfire, HipChat, email and other notification schemes - will be removed in 2.0.0. -- Remove batch from UI when successful. [#1745] -- Convert batch callbacks to be asynchronous jobs for error handling [#1744] - -1.7.1 ------------ - -- Fix for paused queues being processed for a few seconds when starting - a new Sidekiq process. -- Add a 5 sec delay when starting reliable fetch on Heroku to minimize - any duplicate job processing with another process shutting down. - -1.7.0 ------------ - -- Add ability to pause reliable queues via API. -```ruby -q = Sidekiq::Queue.new("critical") -q.pause! -q.paused? # => true -q.unpause! -``` - -Sidekiq polls Redis every 10 seconds for paused queues so pausing will take -a few seconds to take effect. - -1.6.0 ------------ - -- Compatible with Sidekiq 3. - -1.5.1 ------------ - -- Due to a breaking API change in Sidekiq 3.0, this version is limited - to Sidekiq 2.x. - -1.5.0 ------------ - -- Fix issue on Heroku where reliable fetch could orphan jobs [#1573] - - -1.4.3 ------------ - -- Reverse sorting of Batches in Web UI [#1098] -- Refactoring for Sidekiq 3.0, Pro now requires Sidekiq 2.17.5 - -1.4.2 ------------ - -- Tolerate expired Batches in the web UI. -- Fix 100% CPU usage when using weighted queues and reliable fetch. - -1.4.1 ------------ - -- Add batch progress bar to batch detail page. [#1398] -- Fix race condition in initializing Lua scripts - - -1.4.0 ------------ - -- Default batch expiration has been extended to 3 days, from 1 day previously. -- Batches now sort in the Web UI according to expiry time, not creation time. -- Add user-configurable batch expiry. If your batches might take longer - than 72 hours to process, you can extend the expiration date. - -```ruby -b = Sidekiq::Batch.new -b.expires_in 5.days -... -``` - -1.3.2 ------------ - -- Lazy load Lua scripts so a Redis connection is not required on bootup. - -1.3.1 ------------ - -- Fix a gemspec packaging issue which broke the Batch UI. - -1.3.0 ------------ - -Thanks to @jonhyman for his contributions to this Sidekiq Pro release. - -This release includes new functionality based on the SCAN command newly -added to Redis 2.8. Pro still works with Redis 2.4 but some -functionality will be unavailable. - -- Job Filtering in the Web UI! - You can now filter retries and scheduled jobs in the Web UI so you - only see the jobs relevant to your needs. Queues cannot be filtered; - Redis does not provide the same SCAN operation on the LIST type. - **Redis 2.8** - ![Filtering](https://f.cloud.github.com/assets/2911/1619465/f47529f2-5657-11e3-8cd1-33899eb72aad.png) -- SCAN support in the Sidekiq::SortedSet API. Here's an example that - finds all jobs which contain the substring "Warehouse::OrderShip" - and deletes all matching retries. If the set is large, this API - will be **MUCH** faster than standard iteration using each. - **Redis 2.8** -```ruby - Sidekiq::RetrySet.new.scan("Warehouse::OrderShip") do |job| - job.delete - end -``` - -- Sidekiq::Batch#jobs now returns the set of JIDs added to the batch. -- Sidekiq::Batch#jids returns the complete set of JIDs associated with the batch. -- Sidekiq::Batch#remove\_jobs(jid, jid, ...) removes JIDs from the set, allowing early termination of jobs if they become irrelevant according to application logic. -- Sidekiq::Batch#include?(jid) allows jobs to check if they are still - relevant to a Batch and exit early if not. -- Sidekiq::SortedSet#find\_job(jid) now uses server-side Lua if possible **Redis 2.6** [jonhyman] -- The statsd integration now sets global job counts: -```ruby - jobs.count - jobs.success - jobs.failure -``` - -- Change shutdown logic to push leftover jobs in the private queue back - into the public queue when shutting down with Reliable Fetch. This - allows the safe decommission of a Sidekiq Pro process when autoscaling. [jonhyman] -- Add support for weighted random fetching with Reliable Fetch [jonhyman] -- Pro now requires Sidekiq 2.17.0 - -1.2.5 ------------ - -- Convert Batch UI to use Sidekiq 2.16's support for extension localization. -- Update reliable\_push to work with Sidekiq::Client refactoring in 2.16 -- Pro now requires Sidekiq 2.16.0 - -1.2.4 ------------ - -- Convert Batch UI to Bootstrap 3 -- Pro now requires Sidekiq 2.15.0 -- Add Sidekiq::Batch::Status#delete [#1205] - -1.2.3 ------------ - -- Pro now requires Sidekiq 2.14.0 -- Fix bad exception handling in batch callbacks [#1134] -- Convert Batch UI to ERB - -1.2.2 ------------ - -- Problem with reliable fetch which could lead to lost jobs when Sidekiq - is shut down normally. Thanks to MikaelAmborn for the report. [#1109] - -1.2.1 ------------ - -- Forgot to push paging code necessary for `delete_job` performance. - -1.2.0 ------------ - -- **LEAK** Fix batch key which didn't expire in Redis. Keys match - /b-[a-f0-9]{16}-pending/, e.g. "b-4f55163ddba10aa0-pending" [#1057] -- **Reliable fetch now supports multiple queues**, using the algorithm spec'd - by @jackrg [#1102] -- Fix issue with reliable\_push where it didn't return the JID for a pushed - job when sending previously cached jobs to Redis. -- Add fast Sidekiq::Queue#delete\_job(jid) API which leverages Lua so job lookup is - 100% server-side. Benchmark vs Sidekiq's Job#delete API. **Redis 2.6** - -``` -Sidekiq Pro API - 0.030000 0.020000 0.050000 ( 1.640659) -Sidekiq API - 17.250000 2.220000 19.470000 ( 22.193300) -``` - -- Add fast Sidekiq::Queue#delete\_by\_class(klass) API to remove all - jobs of a given type. Uses server-side Lua for performance. **Redis 2.6** - -1.1.0 ------------ - -- New `sidekiq/pro/reliable_push` which makes Sidekiq::Client resiliant - to Redis network failures. [#793] -- Move `sidekiq/reliable_fetch` to `sidekiq/pro/reliable_fetch` - - -1.0.0 ------------ - -- Sidekiq Pro changelog moved to mperham/sidekiq for public visibility. -- Add new Rack endpoint for easy polling of batch status via JavaScript. See `sidekiq/rack/batch_status` - -0.9.3 ------------ - -- Fix bad /batches path in Web UI -- Fix Sinatra conflict with sidekiq-failures - -0.9.2 ------------ - -- Fix issue with lifecycle notifications not firing. - -0.9.1 ------------ - -- Update due to Sidekiq API changes. - -0.9.0 ------------ - -- Rearchitect Sidekiq's Fetch code to support different fetch -strategies. Add a ReliableFetch strategy which works with Redis' -RPOPLPUSH to ensure we don't lose messages, even when the Sidekiq -process crashes unexpectedly. [mperham/sidekiq#607] - -0.8.2 ------------ - -- Reimplement existing notifications using batch on_complete events. - -0.8.1 ------------ - -- Rejigger batch callback notifications. - - -0.8.0 ------------ - -- Add new Batch 'callback' notification support, for in-process - notification. -- Symbolize option keys passed to Pony [mperham/sidekiq#603] -- Batch no longer requires the Web UI since Web UI usage is optional. - You must require is manually in your Web process: - -```ruby -require 'sidekiq/web' -require 'sidekiq/batch/web' -mount Sidekiq::Web => '/sidekiq' -``` - - -0.7.1 ------------ - -- Worker instances can access the associated jid and bid via simple - accessors. -- Batches can now be modified while being processed so, e.g. a batch - job can add additional jobs to its own batch. - -```ruby -def perform(...) - batch = Sidekiq::Batch.new(bid) # instantiate batch associated with this job - batch.jobs do - SomeWorker.perform_async # add another job - end -end -``` - -- Save error backtraces in batch's failure info for display in Web UI. -- Clean up email notification a bit. - - -0.7.0 ------------ - -- Add optional batch description -- Mutable batches. Batches can now be modified to add additional jobs - at runtime. Example would be a batch job which needs to create more - jobs based on the data it is processing. - -```ruby -batch = Sidekiq::Batch.new(bid) -batch.jobs do - # define more jobs here -end -``` -- Fix issues with symbols vs strings in option hashes - - -0.6.1 ------------ - -- Webhook notification support - - -0.6 ------------ - -- Redis pubsub -- Email polish - - -0.5 ------------ - -- Batches -- Notifications -- Statsd middleware diff --git a/README.md b/README.md deleted file mode 100644 index 0acf8f59..00000000 --- a/README.md +++ /dev/null @@ -1,98 +0,0 @@ -Sidekiq -============== - -[![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq) -![Build](https://github.com/mperham/sidekiq/workflows/CI/badge.svg) - -Simple, efficient background processing for Ruby. - -Sidekiq uses threads to handle many jobs at the same time in the -same process. It does not require Rails but will integrate tightly with -Rails to make background processing dead simple. - -Performance ---------------- - -Version | Latency | Garbage created for 10k jobs | Time to process 100k jobs | Throughput | Ruby ------------------|------|---------|---------|------------------------|----- -Sidekiq 6.0.2 | 3 ms | 156 MB | 14.0 sec| **7100 jobs/sec** | MRI 2.6.3 -Sidekiq 6.0.0 | 3 ms | 156 MB | 19 sec | 5200 jobs/sec | MRI 2.6.3 -Sidekiq 4.0.0 | 10 ms | 151 MB | 22 sec | 4500 jobs/sec | -Sidekiq 3.5.1 | 22 ms | 1257 MB | 125 sec | 800 jobs/sec | -Resque 1.25.2 | - | - | 420 sec | 240 jobs/sec | -DelayedJob 4.1.1 | - | - | 465 sec | 215 jobs/sec | - -This benchmark can be found in `bin/sidekiqload` and assumes a Redis network latency of 1ms. - -Requirements ------------------ - -- Redis: 4.0+ -- Ruby: MRI 2.5+ or JRuby 9.2+. - -Sidekiq 6.0 supports Rails 5.0+ but does not require it. - - -Installation ------------------ - - gem install sidekiq - - -Getting Started ------------------ - -See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Getting-Started) and follow the simple setup process. -You can watch [this YouTube playlist](https://www.youtube.com/playlist?list=PLjeHh2LSCFrWGT5uVjUuFKAcrcj5kSai1) to learn all about -Sidekiq and see its features in action. Here's the Web UI: - -![Web UI](https://github.com/mperham/sidekiq/raw/main/examples/web-ui.png) - - -Want to Upgrade? -------------------- - -I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more -features, a commercial-friendly license and allow you to support high -quality open source development all at the same time. Please see the -[Sidekiq](https://sidekiq.org/) homepage for more detail. - -Subscribe to the **[quarterly newsletter](https://tinyletter.com/sidekiq)** to stay informed about the latest -features and changes to Sidekiq and its bigger siblings. - - -Problems? ------------------ - -**Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public. - -If you have a problem, please review the [FAQ](https://github.com/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages. -Searching the [issues](https://github.com/mperham/sidekiq/issues) for your problem is also a good idea. - -Sidekiq Pro and Sidekiq Enterprise customers get private email support. You can purchase at https://sidekiq.org; email support@contribsys.com for help. - -Useful resources: - -* Product documentation is in the [wiki](https://github.com/mperham/sidekiq/wiki). -* Occasional announcements are made to the [@sidekiq](https://twitter.com/sidekiq) Twitter account. -* The [Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow has lots of useful Q & A. - -Every Friday morning is Sidekiq happy hour: I video chat and answer questions. -See the [Sidekiq support page](https://sidekiq.org/support.html) for details. - -Contributing ------------------ - -Please see [the contributing guidelines](https://github.com/mperham/sidekiq/blob/main/.github/contributing.md). - - -License ------------------ - -Please see [LICENSE](https://github.com/mperham/sidekiq/blob/main/LICENSE) for licensing details. - - -Author ------------------ - -Mike Perham, [@getajobmike](https://twitter.com/getajobmike) / [@sidekiq](https://twitter.com/sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com) diff --git a/Rakefile b/Rakefile deleted file mode 100644 index a5dd6f6d..00000000 --- a/Rakefile +++ /dev/null @@ -1,10 +0,0 @@ -require "bundler/gem_tasks" -require "rake/testtask" -require "standard/rake" - -Rake::TestTask.new(:test) do |test| - test.warning = true - test.pattern = "test/**/test_*.rb" -end - -task default: [:standard, :test] diff --git a/bin/sidekiq b/bin/sidekiq deleted file mode 100755 index 57fd2284..00000000 --- a/bin/sidekiq +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env ruby - -# Quiet some warnings we see when running in warning mode: -# RUBYOPT=-w bundle exec sidekiq -$TESTING = false - -require_relative "../lib/sidekiq/cli" - -def integrate_with_systemd - return unless ENV["NOTIFY_SOCKET"] - - Sidekiq.configure_server do |config| - Sidekiq.logger.info "Enabling systemd notification integration" - require "sidekiq/sd_notify" - config.on(:startup) do - Sidekiq::SdNotify.ready - end - config.on(:shutdown) do - Sidekiq::SdNotify.stopping - end - Sidekiq.start_watchdog if Sidekiq::SdNotify.watchdog? - end -end - -begin - cli = Sidekiq::CLI.instance - cli.parse - - integrate_with_systemd - - cli.run -rescue => e - raise e if $DEBUG - if Sidekiq.error_handlers.length == 0 - warn e.message - warn e.backtrace.join("\n") - else - cli.handle_exception e - end - - exit 1 -end diff --git a/bin/sidekiqload b/bin/sidekiqload deleted file mode 100755 index 026c2903..00000000 --- a/bin/sidekiqload +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env ruby - -# Quiet some warnings we see when running in warning mode: -# RUBYOPT=-w bundle exec sidekiq -$TESTING = false - -# require "ruby-prof" -require "bundler/setup" -Bundler.require(:default, :load_test) - -require_relative "../lib/sidekiq/cli" -require_relative "../lib/sidekiq/launcher" - -Sidekiq.configure_server do |config| - config.options[:concurrency] = 10 - config.redis = {db: 13, port: 6380} - # config.redis = { db: 13, port: 6380, driver: :hiredis} - config.options[:queues] << "default" - config.logger.level = Logger::ERROR - config.average_scheduled_poll_interval = 2 - config.reliable! if defined?(Sidekiq::Pro) -end - -class LoadWorker - include Sidekiq::Worker - sidekiq_options retry: 1 - sidekiq_retry_in do |x| - 1 - end - - def perform(idx, ts = nil) - puts(Time.now.to_f - ts) if !ts.nil? - # raise idx.to_s if idx % 100 == 1 - end -end - -# brew tap shopify/shopify -# brew install toxiproxy -# gem install toxiproxy -# run `toxiproxy-server` in a separate terminal window. -require "toxiproxy" -# simulate a non-localhost network for realer-world conditions. -# adding 1ms of network latency has an ENORMOUS impact on benchmarks -Toxiproxy.populate([{ - name: "redis", - listen: "127.0.0.1:6380", - upstream: "127.0.0.1:6379" -}]) - -self_read, self_write = IO.pipe -%w[INT TERM TSTP TTIN].each do |sig| - trap sig do - self_write.puts(sig) - end -rescue ArgumentError - puts "Signal #{sig} not supported" -end - -Sidekiq.redis { |c| c.flushdb } -def handle_signal(launcher, sig) - Sidekiq.logger.debug "Got #{sig} signal" - case sig - when "INT" - # Handle Ctrl-C in JRuby like MRI - # http://jira.codehaus.org/browse/JRUBY-4637 - raise Interrupt - when "TERM" - # Heroku sends TERM and then waits 30 seconds for process to exit. - raise Interrupt - when "TSTP" - Sidekiq.logger.info "Received TSTP, no longer accepting new work" - launcher.quiet - when "TTIN" - Thread.list.each do |thread| - Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}" - if thread.backtrace - Sidekiq.logger.warn thread.backtrace.join("\n") - else - Sidekiq.logger.warn "" - end - end - end -end - -def Process.rss - `ps -o rss= -p #{Process.pid}`.chomp.to_i -end - -iter = 50 -count = 10_000 - -iter.times do - arr = Array.new(count) do - [] - end - count.times do |idx| - arr[idx][0] = idx - end - Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr) -end -Sidekiq.logger.error "Created #{count * iter} jobs" - -start = Time.now - -Monitoring = Thread.new do - while true - sleep 0.2 - qsize = Sidekiq.redis do |conn| - conn.llen "queue:default" - end - total = qsize - # Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}") - if total == 0 - Sidekiq.logger.error("Done, #{iter * count} jobs in #{Time.now - start} sec") - Sidekiq.logger.error("Now here's the latency for three jobs") - - LoadWorker.perform_async(1, Time.now.to_f) - LoadWorker.perform_async(2, Time.now.to_f) - LoadWorker.perform_async(3, Time.now.to_f) - - sleep 0.2 - exit(0) - end - end -end - -begin - # RubyProf::exclude_threads = [ Monitoring ] - # RubyProf.start - events = Sidekiq.options[:lifecycle_events][:startup] - events.each(&:call) - events.clear - - Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis" - Toxiproxy[:redis].downstream(:latency, latency: 1).apply do - launcher = Sidekiq::Launcher.new(Sidekiq.options) - launcher.run - - while readable_io = IO.select([self_read]) - signal = readable_io.first[0].gets.strip - handle_signal(launcher, signal) - end - end -rescue SystemExit => e - # Sidekiq.logger.error("Profiling...") - # result = RubyProf.stop - # printer = RubyProf::GraphHtmlPrinter.new(result) - # printer.print(File.new("output.html", "w"), :min_percent => 1) - # normal -rescue => e - raise e if $DEBUG - warn e.message - warn e.backtrace.join("\n") - exit 1 -end diff --git a/bin/sidekiqmon b/bin/sidekiqmon deleted file mode 100755 index ca931948..00000000 --- a/bin/sidekiqmon +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby - -require "sidekiq/monitor" - -section = "all" -section = ARGV[0] if ARGV.size == 1 - -Sidekiq::Monitor::Status.new.display(section) diff --git a/code_of_conduct.md b/code_of_conduct.md deleted file mode 100644 index e3c8d3dd..00000000 --- a/code_of_conduct.md +++ /dev/null @@ -1,50 +0,0 @@ -# Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of -fostering an open and welcoming community, we pledge to respect all people who -contribute through reporting issues, posting feature requests, updating -documentation, submitting pull requests or patches, and other activities. - -We are committed to making participation in this project a harassment-free -experience for everyone, regardless of level of experience, gender, gender -identity and expression, sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing other's private information, such as physical or electronic - addresses, without explicit permission -* Other unethical or unprofessional conduct - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -By adopting this Code of Conduct, project maintainers commit themselves to -fairly and consistently applying these principles to every aspect of managing -this project. Project maintainers who do not follow or enforce the Code of -Conduct may be permanently removed from the project team. - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project maintainer at mperham AT gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. Maintainers are -obligated to maintain confidentiality with regard to the reporter of an -incident. - - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 1.3.0, available at -[http://contributor-covenant.org/version/1/3/0/][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/examples/busy-ui.png b/examples/busy-ui.png deleted file mode 100644 index 9ffe03dd754cebb785b3b438e5681636df36477d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70667 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 diff --git a/examples/complex_batch_workflow.png b/examples/complex_batch_workflow.png deleted file mode 100644 index f2a234d583ce37f9c6d045d02f4379c040fa112f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29012 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 OQ&-i#l5y$Ylm7vS$G^q^ diff --git a/examples/complex_batch_workflow.svg b/examples/complex_batch_workflow.svg deleted file mode 100644 index b62d849e..00000000 --- a/examples/complex_batch_workflow.svg +++ /dev/null @@ -1 +0,0 @@ -
A
A
B
B
C
C
D
D
E
E
F
F
G
G
H
H
I
I
J
J
K
K
L
L
M
M
diff --git a/examples/config.yml b/examples/config.yml deleted file mode 100644 index d5924ea2..00000000 --- a/examples/config.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Sample configuration file for Sidekiq. -# Options here can still be overridden by cmd line args. -# Place this file at config/sidekiq.yml and Sidekiq will -# pick it up automatically. ---- -:verbose: false -:concurrency: 10 -:timeout: 25 - -# Sidekiq will run this file through ERB when reading it so you can -# even put in dynamic logic, like a host-specific queue. -# http://www.mikeperham.com/2013/11/13/advanced-sidekiq-host-specific-queues/ -:queues: - - critical - - default - - <%= `hostname`.strip %> - - low - -# you can override concurrency based on environment -production: - :concurrency: 25 -staging: - :concurrency: 15 diff --git a/examples/ent-bucket.png b/examples/ent-bucket.png deleted file mode 100644 index 7cc6e8725295abe09e36e6d934c3ca4e77dab709..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 125347 zcmZ^}1zej=^DZ2qcyTT6E=7vFTXEOoF2$|5yA*e~;_gtaIK|!F5(o~3FMXc(FW>K+ zb8{!`&dyvjbIt4~fkY}RN~0hVAOQdX6j>PwRRG}K6#xL;fB*xL1m|@^1b}yzqMt

L=l9i2@mzVV;J1aXoGem>g#mm9f*pu18 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 diff --git a/examples/ent-concurrent.png b/examples/ent-concurrent.png deleted file mode 100644 index 6ec80174310f5161779d9da84ad4638d8e51f2d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148292 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%0A`sNyQe7cpabio#+;G&N9o%aQQo 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

{TWsJ+z8$576SO0k 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 OssG{CMg|X^U_3ep&WGh>>nD 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

G+f}D0 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Ð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 -class <%= class_name %>Job - include Sidekiq::Job - - def perform(*args) - # Do something - end -end -<% end -%> \ No newline at end of file diff --git a/lib/generators/sidekiq/templates/job_spec.rb.erb b/lib/generators/sidekiq/templates/job_spec.rb.erb deleted file mode 100644 index 161f5d87..00000000 --- a/lib/generators/sidekiq/templates/job_spec.rb.erb +++ /dev/null @@ -1,6 +0,0 @@ -require 'rails_helper' -<% module_namespacing do -%> -RSpec.describe <%= class_name %>Job, type: :job do - pending "add some examples to (or delete) #{__FILE__}" -end -<% end -%> diff --git a/lib/generators/sidekiq/templates/job_test.rb.erb b/lib/generators/sidekiq/templates/job_test.rb.erb deleted file mode 100644 index 4cd8bc83..00000000 --- a/lib/generators/sidekiq/templates/job_test.rb.erb +++ /dev/null @@ -1,8 +0,0 @@ -require 'test_helper' -<% module_namespacing do -%> -class <%= class_name %>JobTest < Minitest::Test - def test_example - skip "add some examples to (or delete) #{__FILE__}" - end -end -<% end -%> diff --git a/lib/sidekiq.rb b/lib/sidekiq.rb deleted file mode 100644 index 76113a66..00000000 --- a/lib/sidekiq.rb +++ /dev/null @@ -1,270 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/version" -fail "Sidekiq #{Sidekiq::VERSION} does not support Ruby versions below 2.5.0." if RUBY_PLATFORM != "java" && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0") - -require "sidekiq/logger" -require "sidekiq/client" -require "sidekiq/worker" -require "sidekiq/job" -require "sidekiq/redis_connection" -require "sidekiq/delay" - -require "json" - -module Sidekiq - NAME = "Sidekiq" - LICENSE = "See LICENSE and the LGPL-3.0 for licensing details." - - DEFAULTS = { - queues: [], - labels: [], - concurrency: 10, - require: ".", - strict: true, - environment: nil, - timeout: 25, - poll_interval_average: nil, - average_scheduled_poll_interval: 5, - on_complex_arguments: :warn, - error_handlers: [], - death_handlers: [], - lifecycle_events: { - startup: [], - quiet: [], - shutdown: [], - heartbeat: [] - }, - dead_max_jobs: 10_000, - dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months - reloader: proc { |&block| block.call } - } - - DEFAULT_WORKER_OPTIONS = { - "retry" => true, - "queue" => "default" - } - - FAKE_INFO = { - "redis_version" => "9.9.9", - "uptime_in_days" => "9999", - "connected_clients" => "9999", - "used_memory_human" => "9P", - "used_memory_peak_human" => "9P" - } - - def self.❨╯°□°❩╯︵┻━┻ - puts "Calm down, yo." - end - - def self.options - @options ||= DEFAULTS.dup - end - - def self.options=(opts) - @options = opts - end - - ## - # Configuration for Sidekiq server, use like: - # - # Sidekiq.configure_server do |config| - # config.redis = { :namespace => 'myapp', :size => 25, :url => 'redis://myhost:8877/0' } - # config.server_middleware do |chain| - # chain.add MyServerHook - # end - # end - def self.configure_server - yield self if server? - end - - ## - # Configuration for Sidekiq client, use like: - # - # Sidekiq.configure_client do |config| - # config.redis = { :namespace => 'myapp', :size => 1, :url => 'redis://myhost:8877/0' } - # end - def self.configure_client - yield self unless server? - end - - def self.server? - defined?(Sidekiq::CLI) - end - - def self.redis - raise ArgumentError, "requires a block" unless block_given? - redis_pool.with do |conn| - retryable = true - begin - yield conn - rescue Redis::BaseError => ex - # 2550 Failover can cause the server to become a replica, need - # to disconnect and reopen the socket to get back to the primary. - # 4495 Use the same logic if we have a "Not enough replicas" error from the primary - # 4985 Use the same logic when a blocking command is force-unblocked - # The same retry logic is also used in client.rb - if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/ - conn.disconnect! - retryable = false - retry - end - raise - end - end - end - - def self.redis_info - redis do |conn| - # admin commands can't go through redis-namespace starting - # in redis-namespace 2.0 - if conn.respond_to?(:namespace) - conn.redis.info - else - conn.info - end - rescue Redis::CommandError => ex - # 2850 return fake version when INFO command has (probably) been renamed - raise unless /unknown command/.match?(ex.message) - FAKE_INFO - end - end - - def self.redis_pool - @redis ||= Sidekiq::RedisConnection.create - end - - def self.redis=(hash) - @redis = if hash.is_a?(ConnectionPool) - hash - else - Sidekiq::RedisConnection.create(hash) - end - end - - def self.client_middleware - @client_chain ||= Middleware::Chain.new - yield @client_chain if block_given? - @client_chain - end - - def self.server_middleware - @server_chain ||= default_server_middleware - yield @server_chain if block_given? - @server_chain - end - - def self.default_server_middleware - Middleware::Chain.new - end - - def self.default_worker_options=(hash) - # stringify - @default_worker_options = default_worker_options.merge(hash.transform_keys(&:to_s)) - end - - def self.default_worker_options - defined?(@default_worker_options) ? @default_worker_options : DEFAULT_WORKER_OPTIONS - end - - ## - # Death handlers are called when all retries for a job have been exhausted and - # the job dies. It's the notification to your application - # that this job will not succeed without manual intervention. - # - # Sidekiq.configure_server do |config| - # config.death_handlers << ->(job, ex) do - # end - # end - def self.death_handlers - options[:death_handlers] - end - - def self.load_json(string) - JSON.parse(string) - end - - def self.dump_json(object) - JSON.generate(object) - end - - def self.log_formatter - @log_formatter ||= if ENV["DYNO"] - Sidekiq::Logger::Formatters::WithoutTimestamp.new - else - Sidekiq::Logger::Formatters::Pretty.new - end - end - - def self.log_formatter=(log_formatter) - @log_formatter = log_formatter - logger.formatter = log_formatter - end - - def self.logger - @logger ||= Sidekiq::Logger.new($stdout, level: Logger::INFO) - end - - def self.logger=(logger) - if logger.nil? - self.logger.level = Logger::FATAL - return self.logger - end - - logger.extend(Sidekiq::LoggingUtils) - - @logger = logger - end - - def self.pro? - defined?(Sidekiq::Pro) - end - - # How frequently Redis should be checked by a random Sidekiq process for - # scheduled and retriable jobs. Each individual process will take turns by - # waiting some multiple of this value. - # - # See sidekiq/scheduled.rb for an in-depth explanation of this value - def self.average_scheduled_poll_interval=(interval) - options[:average_scheduled_poll_interval] = interval - end - - # Register a proc to handle any error which occurs within the Sidekiq process. - # - # Sidekiq.configure_server do |config| - # config.error_handlers << proc {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) } - # end - # - # The default error handler logs errors to Sidekiq.logger. - def self.error_handlers - options[:error_handlers] - end - - # Register a block to run at a point in the Sidekiq lifecycle. - # :startup, :quiet or :shutdown are valid events. - # - # Sidekiq.configure_server do |config| - # config.on(:shutdown) do - # puts "Goodbye cruel world!" - # end - # end - def self.on(event, &block) - raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol) - raise ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event) - options[:lifecycle_events][event] << block - end - - def self.strict_args!(mode = :raise) - options[:on_complex_arguments] = mode - end - - # We are shutting down Sidekiq but what about workers that - # are working on some long job? This error is - # raised in workers that have not finished within the hard - # timeout limit. This is needed to rollback db transactions, - # otherwise Ruby's Thread#kill will commit. See #377. - # DO NOT RESCUE THIS ERROR IN YOUR WORKERS - class Shutdown < Interrupt; end -end - -require "sidekiq/rails" if defined?(::Rails::Engine) diff --git a/lib/sidekiq/api.rb b/lib/sidekiq/api.rb deleted file mode 100644 index 2076f899..00000000 --- a/lib/sidekiq/api.rb +++ /dev/null @@ -1,1008 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq" - -require "zlib" -require "base64" - -module Sidekiq - class Stats - def initialize - fetch_stats_fast! - end - - def processed - stat :processed - end - - def failed - stat :failed - end - - def scheduled_size - stat :scheduled_size - end - - def retry_size - stat :retry_size - end - - def dead_size - stat :dead_size - end - - def enqueued - stat :enqueued - end - - def processes_size - stat :processes_size - end - - def workers_size - stat :workers_size - end - - def default_queue_latency - stat :default_queue_latency - end - - def queues - Sidekiq::Stats::Queues.new.lengths - end - - # O(1) redis calls - def fetch_stats_fast! - pipe1_res = Sidekiq.redis { |conn| - conn.pipelined do |pipeline| - pipeline.get("stat:processed") - pipeline.get("stat:failed") - pipeline.zcard("schedule") - pipeline.zcard("retry") - pipeline.zcard("dead") - pipeline.scard("processes") - pipeline.lrange("queue:default", -1, -1) - end - } - - default_queue_latency = if (entry = pipe1_res[6].first) - job = begin - Sidekiq.load_json(entry) - rescue - {} - end - now = Time.now.to_f - thence = job["enqueued_at"] || now - now - thence - else - 0 - end - - @stats = { - processed: pipe1_res[0].to_i, - failed: pipe1_res[1].to_i, - scheduled_size: pipe1_res[2], - retry_size: pipe1_res[3], - dead_size: pipe1_res[4], - processes_size: pipe1_res[5], - - default_queue_latency: default_queue_latency - } - end - - # O(number of processes + number of queues) redis calls - def fetch_stats_slow! - processes = Sidekiq.redis { |conn| - conn.sscan_each("processes").to_a - } - - queues = Sidekiq.redis { |conn| - conn.sscan_each("queues").to_a - } - - pipe2_res = Sidekiq.redis { |conn| - conn.pipelined do |pipeline| - processes.each { |key| pipeline.hget(key, "busy") } - queues.each { |queue| pipeline.llen("queue:#{queue}") } - end - } - - s = processes.size - workers_size = pipe2_res[0...s].sum(&:to_i) - enqueued = pipe2_res[s..-1].sum(&:to_i) - - @stats[:workers_size] = workers_size - @stats[:enqueued] = enqueued - @stats - end - - def fetch_stats! - fetch_stats_fast! - fetch_stats_slow! - end - - def reset(*stats) - all = %w[failed processed] - stats = stats.empty? ? all : all & stats.flatten.compact.map(&:to_s) - - mset_args = [] - stats.each do |stat| - mset_args << "stat:#{stat}" - mset_args << 0 - end - Sidekiq.redis do |conn| - conn.mset(*mset_args) - end - end - - private - - def stat(s) - fetch_stats_slow! if @stats[s].nil? - @stats[s] || raise(ArgumentError, "Unknown stat #{s}") - end - - class Queues - def lengths - Sidekiq.redis do |conn| - queues = conn.sscan_each("queues").to_a - - lengths = conn.pipelined { |pipeline| - queues.each do |queue| - pipeline.llen("queue:#{queue}") - end - } - - array_of_arrays = queues.zip(lengths).sort_by { |_, size| -size } - array_of_arrays.to_h - end - end - end - - class History - def initialize(days_previous, start_date = nil) - # we only store five years of data in Redis - raise ArgumentError if days_previous < 1 || days_previous > (5 * 365) - @days_previous = days_previous - @start_date = start_date || Time.now.utc.to_date - end - - def processed - @processed ||= date_stat_hash("processed") - end - - def failed - @failed ||= date_stat_hash("failed") - end - - private - - def date_stat_hash(stat) - stat_hash = {} - dates = @start_date.downto(@start_date - @days_previous + 1).map { |date| - date.strftime("%Y-%m-%d") - } - - keys = dates.map { |datestr| "stat:#{stat}:#{datestr}" } - - begin - Sidekiq.redis do |conn| - conn.mget(keys).each_with_index do |value, idx| - stat_hash[dates[idx]] = value ? value.to_i : 0 - end - end - rescue Redis::CommandError - # mget will trigger a CROSSSLOT error when run against a Cluster - # TODO Someone want to add Cluster support? - end - - stat_hash - end - end - end - - ## - # Encapsulates a queue within Sidekiq. - # Allows enumeration of all jobs within the queue - # and deletion of jobs. - # - # queue = Sidekiq::Queue.new("mailer") - # queue.each do |job| - # job.klass # => 'MyWorker' - # job.args # => [1, 2, 3] - # job.delete if job.jid == 'abcdef1234567890' - # end - # - class Queue - include Enumerable - - ## - # Return all known queues within Redis. - # - def self.all - Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) } - end - - attr_reader :name - - def initialize(name = "default") - @name = name.to_s - @rname = "queue:#{name}" - end - - def size - Sidekiq.redis { |con| con.llen(@rname) } - end - - # Sidekiq Pro overrides this - def paused? - false - end - - ## - # Calculates this queue's latency, the difference in seconds since the oldest - # job in the queue was enqueued. - # - # @return Float - def latency - entry = Sidekiq.redis { |conn| - conn.lrange(@rname, -1, -1) - }.first - return 0 unless entry - job = Sidekiq.load_json(entry) - now = Time.now.to_f - thence = job["enqueued_at"] || now - now - thence - end - - def each - initial_size = size - deleted_size = 0 - page = 0 - page_size = 50 - - loop do - range_start = page * page_size - deleted_size - range_end = range_start + page_size - 1 - entries = Sidekiq.redis { |conn| - conn.lrange @rname, range_start, range_end - } - break if entries.empty? - page += 1 - entries.each do |entry| - yield JobRecord.new(entry, @name) - end - deleted_size = initial_size - size - end - end - - ## - # Find the job with the given JID within this queue. - # - # This is a slow, inefficient operation. Do not use under - # normal conditions. - def find_job(jid) - detect { |j| j.jid == jid } - end - - def clear - Sidekiq.redis do |conn| - conn.multi do |transaction| - transaction.unlink(@rname) - transaction.srem("queues", name) - end - end - end - alias_method :💣, :clear - end - - ## - # Encapsulates a pending job within a Sidekiq queue or - # sorted set. - # - # The job should be considered immutable but may be - # removed from the queue via JobRecord#delete. - # - class JobRecord - attr_reader :item - attr_reader :value - - def initialize(item, queue_name = nil) - @args = nil - @value = item - @item = item.is_a?(Hash) ? item : parse(item) - @queue = queue_name || @item["queue"] - end - - def parse(item) - Sidekiq.load_json(item) - rescue JSON::ParserError - # If the job payload in Redis is invalid JSON, we'll load - # the item as an empty hash and store the invalid JSON as - # the job 'args' for display in the Web UI. - @invalid = true - @args = [item] - {} - end - - def klass - self["class"] - end - - def display_class - # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI - @klass ||= self["display_class"] || begin - case klass - when /\ASidekiq::Extensions::Delayed/ - safe_load(args[0], klass) do |target, method, _| - "#{target}.#{method}" - end - when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" - job_class = @item["wrapped"] || args[0] - if job_class == "ActionMailer::DeliveryJob" || job_class == "ActionMailer::MailDeliveryJob" - # MailerClass#mailer_method - args[0]["arguments"][0..1].join("#") - else - job_class - end - else - klass - end - end - end - - def display_args - # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI - @display_args ||= case klass - when /\ASidekiq::Extensions::Delayed/ - safe_load(args[0], args) do |_, _, arg, kwarg| - if !kwarg || kwarg.empty? - arg - else - [arg, kwarg] - end - end - when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper" - job_args = self["wrapped"] ? args[0]["arguments"] : [] - if (self["wrapped"] || args[0]) == "ActionMailer::DeliveryJob" - # remove MailerClass, mailer_method and 'deliver_now' - job_args.drop(3) - elsif (self["wrapped"] || args[0]) == "ActionMailer::MailDeliveryJob" - # remove MailerClass, mailer_method and 'deliver_now' - job_args.drop(3).first["args"] - else - job_args - end - else - if self["encrypt"] - # no point in showing 150+ bytes of random garbage - args[-1] = "[encrypted data]" - end - args - end - end - - def args - @args || @item["args"] - end - - def jid - self["jid"] - end - - def enqueued_at - self["enqueued_at"] ? Time.at(self["enqueued_at"]).utc : nil - end - - def created_at - Time.at(self["created_at"] || self["enqueued_at"] || 0).utc - end - - def tags - self["tags"] || [] - end - - def error_backtrace - # Cache nil values - if defined?(@error_backtrace) - @error_backtrace - else - value = self["error_backtrace"] - @error_backtrace = value && uncompress_backtrace(value) - end - end - - attr_reader :queue - - def latency - now = Time.now.to_f - now - (@item["enqueued_at"] || @item["created_at"] || now) - end - - ## - # Remove this job from the queue. - def delete - count = Sidekiq.redis { |conn| - conn.lrem("queue:#{@queue}", 1, @value) - } - count != 0 - end - - def [](name) - # nil will happen if the JSON fails to parse. - # We don't guarantee Sidekiq will work with bad job JSON but we should - # make a best effort to minimize the damage. - @item ? @item[name] : nil - end - - private - - def safe_load(content, default) - yield(*YAML.load(content)) - rescue => ex - # #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into - # memory yet so the YAML can't be loaded. - Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == "development" - default - end - - def uncompress_backtrace(backtrace) - if backtrace.is_a?(Array) - # Handle old jobs with raw Array backtrace format - backtrace - else - decoded = Base64.decode64(backtrace) - uncompressed = Zlib::Inflate.inflate(decoded) - begin - Sidekiq.load_json(uncompressed) - rescue - # Handle old jobs with marshalled backtrace format - # TODO Remove in 7.x - Marshal.load(uncompressed) - end - end - end - end - - class SortedEntry < JobRecord - attr_reader :score - attr_reader :parent - - def initialize(parent, score, item) - super(item) - @score = score - @parent = parent - end - - def at - Time.at(score).utc - end - - def delete - if @value - @parent.delete_by_value(@parent.name, @value) - else - @parent.delete_by_jid(score, jid) - end - end - - def reschedule(at) - Sidekiq.redis do |conn| - conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item)) - end - end - - def add_to_queue - remove_job do |message| - msg = Sidekiq.load_json(message) - Sidekiq::Client.push(msg) - end - end - - def retry - remove_job do |message| - msg = Sidekiq.load_json(message) - msg["retry_count"] -= 1 if msg["retry_count"] - Sidekiq::Client.push(msg) - end - end - - ## - # Place job in the dead set - def kill - remove_job do |message| - DeadSet.new.kill(message) - end - end - - def error? - !!item["error_class"] - end - - private - - def remove_job - Sidekiq.redis do |conn| - results = conn.multi { |transaction| - transaction.zrangebyscore(parent.name, score, score) - transaction.zremrangebyscore(parent.name, score, score) - }.first - - if results.size == 1 - yield results.first - else - # multiple jobs with the same score - # find the one with the right JID and push it - matched, nonmatched = results.partition { |message| - if message.index(jid) - msg = Sidekiq.load_json(message) - msg["jid"] == jid - else - false - end - } - - msg = matched.first - yield msg if msg - - # push the rest back onto the sorted set - conn.multi do |transaction| - nonmatched.each do |message| - transaction.zadd(parent.name, score.to_f.to_s, message) - end - end - end - end - end - end - - class SortedSet - include Enumerable - - attr_reader :name - - def initialize(name) - @name = name - @_size = size - end - - def size - Sidekiq.redis { |c| c.zcard(name) } - end - - def scan(match, count = 100) - return to_enum(:scan, match, count) unless block_given? - - match = "*#{match}*" unless match.include?("*") - Sidekiq.redis do |conn| - conn.zscan_each(name, match: match, count: count) do |entry, score| - yield SortedEntry.new(self, score, entry) - end - end - end - - def clear - Sidekiq.redis do |conn| - conn.unlink(name) - end - end - alias_method :💣, :clear - end - - class JobSet < SortedSet - def schedule(timestamp, message) - Sidekiq.redis do |conn| - conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(message)) - end - end - - def each - initial_size = @_size - offset_size = 0 - page = -1 - page_size = 50 - - loop do - range_start = page * page_size + offset_size - range_end = range_start + page_size - 1 - elements = Sidekiq.redis { |conn| - conn.zrange name, range_start, range_end, with_scores: true - } - break if elements.empty? - page -= 1 - elements.reverse_each do |element, score| - yield SortedEntry.new(self, score, element) - end - offset_size = initial_size - @_size - end - end - - ## - # Fetch jobs that match a given time or Range. Job ID is an - # optional second argument. - def fetch(score, jid = nil) - begin_score, end_score = - if score.is_a?(Range) - [score.first, score.last] - else - [score, score] - end - - elements = Sidekiq.redis { |conn| - conn.zrangebyscore(name, begin_score, end_score, with_scores: true) - } - - elements.each_with_object([]) do |element, result| - data, job_score = element - entry = SortedEntry.new(self, job_score, data) - result << entry if jid.nil? || entry.jid == jid - end - end - - ## - # Find the job with the given JID within this sorted set. - # This is a slower O(n) operation. Do not use for app logic. - def find_job(jid) - Sidekiq.redis do |conn| - conn.zscan_each(name, match: "*#{jid}*", count: 100) do |entry, score| - job = JSON.parse(entry) - matched = job["jid"] == jid - return SortedEntry.new(self, score, entry) if matched - end - end - nil - end - - def delete_by_value(name, value) - Sidekiq.redis do |conn| - ret = conn.zrem(name, value) - @_size -= 1 if ret - ret - end - end - - def delete_by_jid(score, jid) - Sidekiq.redis do |conn| - elements = conn.zrangebyscore(name, score, score) - elements.each do |element| - if element.index(jid) - message = Sidekiq.load_json(element) - if message["jid"] == jid - ret = conn.zrem(name, element) - @_size -= 1 if ret - break ret - end - end - end - end - end - - alias_method :delete, :delete_by_jid - end - - ## - # Allows enumeration of scheduled jobs within Sidekiq. - # Based on this, you can search/filter for jobs. Here's an - # example where I'm selecting all jobs of a certain type - # and deleting them from the schedule queue. - # - # r = Sidekiq::ScheduledSet.new - # r.select do |scheduled| - # scheduled.klass == 'Sidekiq::Extensions::DelayedClass' && - # scheduled.args[0] == 'User' && - # scheduled.args[1] == 'setup_new_subscriber' - # end.map(&:delete) - class ScheduledSet < JobSet - def initialize - super "schedule" - end - end - - ## - # Allows enumeration of retries within Sidekiq. - # Based on this, you can search/filter for jobs. Here's an - # example where I'm selecting all jobs of a certain type - # and deleting them from the retry queue. - # - # r = Sidekiq::RetrySet.new - # r.select do |retri| - # retri.klass == 'Sidekiq::Extensions::DelayedClass' && - # retri.args[0] == 'User' && - # retri.args[1] == 'setup_new_subscriber' - # end.map(&:delete) - class RetrySet < JobSet - def initialize - super "retry" - end - - def retry_all - each(&:retry) while size > 0 - end - - def kill_all - each(&:kill) while size > 0 - end - end - - ## - # Allows enumeration of dead jobs within Sidekiq. - # - class DeadSet < JobSet - def initialize - super "dead" - end - - def kill(message, opts = {}) - now = Time.now.to_f - Sidekiq.redis do |conn| - conn.multi do |transaction| - transaction.zadd(name, now.to_s, message) - transaction.zremrangebyscore(name, "-inf", now - self.class.timeout) - transaction.zremrangebyrank(name, 0, - self.class.max_jobs) - end - end - - if opts[:notify_failure] != false - job = Sidekiq.load_json(message) - r = RuntimeError.new("Job killed by API") - r.set_backtrace(caller) - Sidekiq.death_handlers.each do |handle| - handle.call(job, r) - end - end - true - end - - def retry_all - each(&:retry) while size > 0 - end - - def self.max_jobs - Sidekiq.options[:dead_max_jobs] - end - - def self.timeout - Sidekiq.options[:dead_timeout_in_seconds] - end - end - - ## - # Enumerates the set of Sidekiq processes which are actively working - # right now. Each process sends a heartbeat to Redis every 5 seconds - # so this set should be relatively accurate, barring network partitions. - # - # Yields a Sidekiq::Process. - # - class ProcessSet - include Enumerable - - def initialize(clean_plz = true) - cleanup if clean_plz - end - - # Cleans up dead processes recorded in Redis. - # Returns the number of processes cleaned. - def cleanup - count = 0 - Sidekiq.redis do |conn| - procs = conn.sscan_each("processes").to_a.sort - heartbeats = conn.pipelined { |pipeline| - procs.each do |key| - pipeline.hget(key, "info") - end - } - - # the hash named key has an expiry of 60 seconds. - # if it's not found, that means the process has not reported - # in to Redis and probably died. - to_prune = procs.select.with_index { |proc, i| - heartbeats[i].nil? - } - count = conn.srem("processes", to_prune) unless to_prune.empty? - end - count - end - - def each - result = Sidekiq.redis { |conn| - procs = conn.sscan_each("processes").to_a.sort - - # We're making a tradeoff here between consuming more memory instead of - # making more roundtrips to Redis, but if you have hundreds or thousands of workers, - # you'll be happier this way - conn.pipelined do |pipeline| - procs.each do |key| - pipeline.hmget(key, "info", "busy", "beat", "quiet", "rss", "rtt_us") - end - end - } - - result.each do |info, busy, at_s, quiet, rss, rtt| - # If a process is stopped between when we query Redis for `procs` and - # when we query for `result`, we will have an item in `result` that is - # composed of `nil` values. - next if info.nil? - - hash = Sidekiq.load_json(info) - yield Process.new(hash.merge("busy" => busy.to_i, - "beat" => at_s.to_f, - "quiet" => quiet, - "rss" => rss.to_i, - "rtt_us" => rtt.to_i)) - end - end - - # This method is not guaranteed accurate since it does not prune the set - # based on current heartbeat. #each does that and ensures the set only - # contains Sidekiq processes which have sent a heartbeat within the last - # 60 seconds. - def size - Sidekiq.redis { |conn| conn.scard("processes") } - end - - # Total number of threads available to execute jobs. - # For Sidekiq Enterprise customers this number (in production) must be - # less than or equal to your licensed concurrency. - def total_concurrency - sum { |x| x["concurrency"].to_i } - end - - def total_rss_in_kb - sum { |x| x["rss"].to_i } - end - alias_method :total_rss, :total_rss_in_kb - - # Returns the identity of the current cluster leader or "" if no leader. - # This is a Sidekiq Enterprise feature, will always return "" in Sidekiq - # or Sidekiq Pro. - def leader - @leader ||= begin - x = Sidekiq.redis { |c| c.get("dear-leader") } - # need a non-falsy value so we can memoize - x ||= "" - x - end - end - end - - # - # Sidekiq::Process represents an active Sidekiq process talking with Redis. - # Each process has a set of attributes which look like this: - # - # { - # 'hostname' => 'app-1.example.com', - # 'started_at' => , - # 'pid' => 12345, - # 'tag' => 'myapp' - # 'concurrency' => 25, - # 'queues' => ['default', 'low'], - # 'busy' => 10, - # 'beat' => , - # 'identity' => , - # } - class Process - def initialize(hash) - @attribs = hash - end - - def tag - self["tag"] - end - - def labels - Array(self["labels"]) - end - - def [](key) - @attribs[key] - end - - def identity - self["identity"] - end - - def queues - self["queues"] - end - - def quiet! - signal("TSTP") - end - - def stop! - signal("TERM") - end - - def dump_threads - signal("TTIN") - end - - def stopping? - self["quiet"] == "true" - end - - private - - def signal(sig) - key = "#{identity}-signals" - Sidekiq.redis do |c| - c.multi do |transaction| - transaction.lpush(key, sig) - transaction.expire(key, 60) - end - end - end - end - - ## - # The WorkSet stores the work being done by this Sidekiq cluster. - # It tracks the process and thread working on each job. - # - # WARNING WARNING WARNING - # - # This is live data that can change every millisecond. - # If you call #size => 5 and then expect #each to be - # called 5 times, you're going to have a bad time. - # - # works = Sidekiq::WorkSet.new - # works.size => 2 - # works.each do |process_id, thread_id, work| - # # process_id is a unique identifier per Sidekiq process - # # thread_id is a unique identifier per thread - # # work is a Hash which looks like: - # # { 'queue' => name, 'run_at' => timestamp, 'payload' => job_hash } - # # run_at is an epoch Integer. - # end - # - class WorkSet - include Enumerable - - def each(&block) - results = [] - Sidekiq.redis do |conn| - procs = conn.sscan_each("processes").to_a - procs.sort.each do |key| - valid, workers = conn.pipelined { |pipeline| - pipeline.exists?(key) - pipeline.hgetall("#{key}:workers") - } - next unless valid - workers.each_pair do |tid, json| - hsh = Sidekiq.load_json(json) - p = hsh["payload"] - # avoid breaking API, this is a side effect of the JSON optimization in #4316 - hsh["payload"] = Sidekiq.load_json(p) if p.is_a?(String) - results << [key, tid, hsh] - end - end - end - - results.sort_by { |(_, _, hsh)| hsh["run_at"] }.each(&block) - end - - # Note that #size is only as accurate as Sidekiq's heartbeat, - # which happens every 5 seconds. It is NOT real-time. - # - # Not very efficient if you have lots of Sidekiq - # processes but the alternative is a global counter - # which can easily get out of sync with crashy processes. - def size - Sidekiq.redis do |conn| - procs = conn.sscan_each("processes").to_a - if procs.empty? - 0 - else - conn.pipelined { |pipeline| - procs.each do |key| - pipeline.hget(key, "busy") - end - }.sum(&:to_i) - end - end - end - end - # Since "worker" is a nebulous term, we've deprecated the use of this class name. - # Is "worker" a process, a type of job, a thread? Undefined! - # WorkSet better describes the data. - Workers = WorkSet -end diff --git a/lib/sidekiq/cli.rb b/lib/sidekiq/cli.rb deleted file mode 100644 index eeccb926..00000000 --- a/lib/sidekiq/cli.rb +++ /dev/null @@ -1,427 +0,0 @@ -# frozen_string_literal: true - -$stdout.sync = true - -require "yaml" -require "singleton" -require "optparse" -require "erb" -require "fileutils" - -require "sidekiq" -require "sidekiq/launcher" -require "sidekiq/util" - -module Sidekiq - class CLI - include Util - include Singleton unless $TESTING - - attr_accessor :launcher - attr_accessor :environment - - def parse(args = ARGV) - setup_options(args) - initialize_logger - validate! - end - - def jruby? - defined?(::JRUBY_VERSION) - end - - # Code within this method is not tested because it alters - # global process state irreversibly. PRs which improve the - # test coverage of Sidekiq::CLI are welcomed. - def run(boot_app: true) - boot_application if boot_app - - if environment == "development" && $stdout.tty? && Sidekiq.log_formatter.is_a?(Sidekiq::Logger::Formatters::Pretty) - print_banner - end - logger.info "Booted Rails #{::Rails.version} application in #{environment} environment" if rails_app? - - self_read, self_write = IO.pipe - sigs = %w[INT TERM TTIN TSTP] - # USR1 and USR2 don't work on the JVM - sigs << "USR2" if Sidekiq.pro? && !jruby? - sigs.each do |sig| - old_handler = Signal.trap(sig) do - if old_handler.respond_to?(:call) - begin - old_handler.call - rescue Exception => exc - # signal handlers can't use Logger so puts only - puts ["Error in #{sig} handler", exc].inspect - end - end - self_write.puts(sig) - end - rescue ArgumentError - puts "Signal #{sig} not supported" - end - - logger.info "Running in #{RUBY_DESCRIPTION}" - logger.info Sidekiq::LICENSE - logger.info "Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org" unless defined?(::Sidekiq::Pro) - - # touch the connection pool so it is created before we - # fire startup and start multithreading. - info = Sidekiq.redis_info - ver = info["redis_version"] - raise "You are connecting to Redis v#{ver}, Sidekiq requires Redis v4.0.0 or greater" if ver < "4" - - maxmemory_policy = info["maxmemory_policy"] - if maxmemory_policy != "noeviction" - logger.warn <<~EOM - - - WARNING: Your Redis instance will evict Sidekiq data under heavy load. - The 'noeviction' maxmemory policy is recommended (current policy: '#{maxmemory_policy}'). - See: https://github.com/mperham/sidekiq/wiki/Using-Redis#memory - - EOM - end - - # Since the user can pass us a connection pool explicitly in the initializer, we - # need to verify the size is large enough or else Sidekiq's performance is dramatically slowed. - cursize = Sidekiq.redis_pool.size - needed = Sidekiq.options[:concurrency] + 2 - raise "Your pool of #{cursize} Redis connections is too small, please increase the size to at least #{needed}" if cursize < needed - - # cache process identity - Sidekiq.options[:identity] = identity - - # Touch middleware so it isn't lazy loaded by multiple threads, #3043 - Sidekiq.server_middleware - - # Before this point, the process is initializing with just the main thread. - # Starting here the process will now have multiple threads running. - fire_event(:startup, reverse: false, reraise: true) - - logger.debug { "Client Middleware: #{Sidekiq.client_middleware.map(&:klass).join(", ")}" } - logger.debug { "Server Middleware: #{Sidekiq.server_middleware.map(&:klass).join(", ")}" } - - launch(self_read) - end - - def launch(self_read) - if environment == "development" && $stdout.tty? - logger.info "Starting processing, hit Ctrl-C to stop" - end - - @launcher = Sidekiq::Launcher.new(options) - - begin - launcher.run - - while (readable_io = IO.select([self_read])) - signal = readable_io.first[0].gets.strip - handle_signal(signal) - end - rescue Interrupt - logger.info "Shutting down" - launcher.stop - logger.info "Bye!" - - # Explicitly exit so busy Processor threads won't block process shutdown. - # - # NB: slow at_exit handlers will prevent a timely exit if they take - # a while to run. If Sidekiq is getting here but the process isn't exiting, - # use the TTIN signal to determine where things are stuck. - exit(0) - end - end - - def self.w - "\e[37m" - end - - def self.r - "\e[31m" - end - - def self.b - "\e[30m" - end - - def self.reset - "\e[0m" - end - - def self.banner - %{ - #{w} m, - #{w} `$b - #{w} .ss, $$: .,d$ - #{w} `$$P,d$P' .,md$P"' - #{w} ,$$$$$b#{b}/#{w}md$$$P^' - #{w} .d$$$$$$#{b}/#{w}$$$P' - #{w} $$^' `"#{b}/#{w}$$$' #{r}____ _ _ _ _ - #{w} $: ,$$: #{r} / ___|(_) __| | ___| | _(_) __ _ - #{w} `b :$$ #{r} \\___ \\| |/ _` |/ _ \\ |/ / |/ _` | - #{w} $$: #{r} ___) | | (_| | __/ <| | (_| | - #{w} $$ #{r}|____/|_|\\__,_|\\___|_|\\_\\_|\\__, | - #{w} .d$$ #{r} |_| - #{reset}} - end - - SIGNAL_HANDLERS = { - # Ctrl-C in terminal - "INT" => ->(cli) { raise Interrupt }, - # TERM is the signal that Sidekiq must exit. - # Heroku sends TERM and then waits 30 seconds for process to exit. - "TERM" => ->(cli) { raise Interrupt }, - "TSTP" => ->(cli) { - Sidekiq.logger.info "Received TSTP, no longer accepting new work" - cli.launcher.quiet - }, - "TTIN" => ->(cli) { - Thread.list.each do |thread| - Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}" - if thread.backtrace - Sidekiq.logger.warn thread.backtrace.join("\n") - else - Sidekiq.logger.warn "" - end - end - } - } - UNHANDLED_SIGNAL_HANDLER = ->(cli) { Sidekiq.logger.info "No signal handler registered, ignoring" } - SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER - - def handle_signal(sig) - Sidekiq.logger.debug "Got #{sig} signal" - SIGNAL_HANDLERS[sig].call(self) - end - - private - - def print_banner - puts "\e[31m" - puts Sidekiq::CLI.banner - puts "\e[0m" - end - - def set_environment(cli_env) - # See #984 for discussion. - # APP_ENV is now the preferred ENV term since it is not tech-specific. - # Both Sinatra 2.0+ and Sidekiq support this term. - # RAILS_ENV and RACK_ENV are there for legacy support. - @environment = cli_env || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" - end - - def symbolize_keys_deep!(hash) - hash.keys.each do |k| - symkey = k.respond_to?(:to_sym) ? k.to_sym : k - hash[symkey] = hash.delete k - symbolize_keys_deep! hash[symkey] if hash[symkey].is_a? Hash - end - end - - alias_method :die, :exit - alias_method :☠, :exit - - def setup_options(args) - # parse CLI options - opts = parse_options(args) - - set_environment opts[:environment] - - # check config file presence - if opts[:config_file] - unless File.exist?(opts[:config_file]) - raise ArgumentError, "No such file #{opts[:config_file]}" - end - else - config_dir = if File.directory?(opts[:require].to_s) - File.join(opts[:require], "config") - else - File.join(options[:require], "config") - end - - %w[sidekiq.yml sidekiq.yml.erb].each do |config_file| - path = File.join(config_dir, config_file) - opts[:config_file] ||= path if File.exist?(path) - end - end - - # parse config file options - opts = parse_config(opts[:config_file]).merge(opts) if opts[:config_file] - - # set defaults - opts[:queues] = ["default"] if opts[:queues].nil? - opts[:concurrency] = Integer(ENV["RAILS_MAX_THREADS"]) if opts[:concurrency].nil? && ENV["RAILS_MAX_THREADS"] - - # merge with defaults - options.merge!(opts) - end - - def options - Sidekiq.options - end - - def boot_application - ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment - - if File.directory?(options[:require]) - require "rails" - if ::Rails::VERSION::MAJOR < 5 - raise "Sidekiq no longer supports this version of Rails" - else - require "sidekiq/rails" - require File.expand_path("#{options[:require]}/config/environment.rb") - end - options[:tag] ||= default_tag - else - require options[:require] - end - end - - def default_tag - dir = ::Rails.root - name = File.basename(dir) - prevdir = File.dirname(dir) # Capistrano release directory? - if name.to_i != 0 && prevdir - if File.basename(prevdir) == "releases" - return File.basename(File.dirname(prevdir)) - end - end - name - end - - def validate! - if !File.exist?(options[:require]) || - (File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb")) - logger.info "==================================================================" - logger.info " Please point Sidekiq to a Rails application or a Ruby file " - logger.info " to load your worker classes with -r [DIR|FILE]." - logger.info "==================================================================" - logger.info @parser - die(1) - end - - [:concurrency, :timeout].each do |opt| - raise ArgumentError, "#{opt}: #{options[opt]} is not a valid value" if options.key?(opt) && options[opt].to_i <= 0 - end - end - - def parse_options(argv) - opts = {} - @parser = option_parser(opts) - @parser.parse!(argv) - opts - end - - def option_parser(opts) - parser = OptionParser.new { |o| - o.on "-c", "--concurrency INT", "processor threads to use" do |arg| - opts[:concurrency] = Integer(arg) - end - - o.on "-d", "--daemon", "Daemonize process" do |arg| - puts "ERROR: Daemonization mode was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services" - end - - o.on "-e", "--environment ENV", "Application environment" do |arg| - opts[:environment] = arg - end - - o.on "-g", "--tag TAG", "Process tag for procline" do |arg| - opts[:tag] = arg - end - - o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg| - queue, weight = arg.split(",") - parse_queue opts, queue, weight - end - - o.on "-r", "--require [PATH|DIR]", "Location of Rails application with workers or file to require" do |arg| - opts[:require] = arg - end - - o.on "-t", "--timeout NUM", "Shutdown timeout" do |arg| - opts[:timeout] = Integer(arg) - end - - o.on "-v", "--verbose", "Print more verbose output" do |arg| - opts[:verbose] = arg - end - - o.on "-C", "--config PATH", "path to YAML config file" do |arg| - opts[:config_file] = arg - end - - o.on "-L", "--logfile PATH", "path to writable logfile" do |arg| - puts "ERROR: Logfile redirection was removed in Sidekiq 6.0, Sidekiq will only log to STDOUT" - end - - o.on "-P", "--pidfile PATH", "path to pidfile" do |arg| - puts "ERROR: PID file creation was removed in Sidekiq 6.0, please use a proper process supervisor to start and manage your services" - end - - o.on "-V", "--version", "Print version and exit" do |arg| - puts "Sidekiq #{Sidekiq::VERSION}" - die(0) - end - } - - parser.banner = "sidekiq [options]" - parser.on_tail "-h", "--help", "Show help" do - logger.info parser - die 1 - end - - parser - end - - def initialize_logger - Sidekiq.logger.level = ::Logger::DEBUG if options[:verbose] - end - - def parse_config(path) - erb = ERB.new(File.read(path)) - erb.filename = File.expand_path(path) - opts = load_yaml(erb.result) || {} - - if opts.respond_to? :deep_symbolize_keys! - opts.deep_symbolize_keys! - else - symbolize_keys_deep!(opts) - end - - opts = opts.merge(opts.delete(environment.to_sym) || {}) - opts.delete(:strict) - - parse_queues(opts, opts.delete(:queues) || []) - - opts - end - - def load_yaml(src) - if Psych::VERSION > "4.0" - YAML.safe_load(src, permitted_classes: [Symbol], aliases: true) - else - YAML.load(src) - end - end - - def parse_queues(opts, queues_and_weights) - queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) } - end - - def parse_queue(opts, queue, weight = nil) - opts[:queues] ||= [] - opts[:strict] = true if opts[:strict].nil? - raise ArgumentError, "queues: #{queue} cannot be defined twice" if opts[:queues].include?(queue) - [weight.to_i, 1].max.times { opts[:queues] << queue.to_s } - opts[:strict] = false if weight.to_i > 0 - end - - def rails_app? - defined?(::Rails) && ::Rails.respond_to?(:application) - end - end -end - -require "sidekiq/systemd" diff --git a/lib/sidekiq/client.rb b/lib/sidekiq/client.rb deleted file mode 100644 index 74feb56b..00000000 --- a/lib/sidekiq/client.rb +++ /dev/null @@ -1,240 +0,0 @@ -# frozen_string_literal: true - -require "securerandom" -require "sidekiq/middleware/chain" -require "sidekiq/job_util" - -module Sidekiq - class Client - include Sidekiq::JobUtil - - ## - # Define client-side middleware: - # - # client = Sidekiq::Client.new - # client.middleware do |chain| - # chain.use MyClientMiddleware - # end - # client.push('class' => 'SomeWorker', 'args' => [1,2,3]) - # - # All client instances default to the globally-defined - # Sidekiq.client_middleware but you can change as necessary. - # - def middleware(&block) - @chain ||= Sidekiq.client_middleware - if block - @chain = @chain.dup - yield @chain - end - @chain - end - - attr_accessor :redis_pool - - # Sidekiq::Client normally uses the default Redis pool but you may - # pass a custom ConnectionPool if you want to shard your - # Sidekiq jobs across several Redis instances (for scalability - # reasons, e.g.) - # - # Sidekiq::Client.new(ConnectionPool.new { Redis.new }) - # - # Generally this is only needed for very large Sidekiq installs processing - # thousands of jobs per second. I don't recommend sharding unless you - # cannot scale any other way (e.g. splitting your app into smaller apps). - def initialize(redis_pool = nil) - @redis_pool = redis_pool || Thread.current[:sidekiq_via_pool] || Sidekiq.redis_pool - end - - ## - # The main method used to push a job to Redis. Accepts a number of options: - # - # queue - the named queue to use, default 'default' - # class - the worker class to call, required - # args - an array of simple arguments to the perform method, must be JSON-serializable - # at - timestamp to schedule the job (optional), must be Numeric (e.g. Time.now.to_f) - # retry - whether to retry this job if it fails, default true or an integer number of retries - # backtrace - whether to save any error backtrace, default false - # - # If class is set to the class name, the jobs' options will be based on Sidekiq's default - # worker options. Otherwise, they will be based on the job class's options. - # - # Any options valid for a worker class's sidekiq_options are also available here. - # - # All options must be strings, not symbols. NB: because we are serializing to JSON, all - # symbols in 'args' will be converted to strings. Note that +backtrace: true+ can take quite a bit of - # space in Redis; a large volume of failing jobs can start Redis swapping if you aren't careful. - # - # Returns a unique Job ID. If middleware stops the job, nil will be returned instead. - # - # Example: - # push('queue' => 'my_queue', 'class' => MyWorker, 'args' => ['foo', 1, :bat => 'bar']) - # - def push(item) - normed = normalize_item(item) - payload = process_single(item["class"], normed) - - if payload - raw_push([payload]) - payload["jid"] - end - end - - ## - # Push a large number of jobs to Redis. This method cuts out the redis - # network round trip latency. I wouldn't recommend pushing more than - # 1000 per call but YMMV based on network quality, size of job args, etc. - # A large number of jobs can cause a bit of Redis command processing latency. - # - # Takes the same arguments as #push except that args is expected to be - # an Array of Arrays. All other keys are duplicated for each job. Each job - # is run through the client middleware pipeline and each job gets its own Job ID - # as normal. - # - # Returns an array of the of pushed jobs' jids. The number of jobs pushed can be less - # than the number given if the middleware stopped processing for one or more jobs. - def push_bulk(items) - args = items["args"] - raise ArgumentError, "Bulk arguments must be an Array of Arrays: [[1], [2]]" unless args.is_a?(Array) && args.all?(Array) - return [] if args.empty? # no jobs to push - - at = items.delete("at") - raise ArgumentError, "Job 'at' must be a Numeric or an Array of Numeric timestamps" if at && (Array(at).empty? || !Array(at).all? { |entry| entry.is_a?(Numeric) }) - raise ArgumentError, "Job 'at' Array must have same size as 'args' Array" if at.is_a?(Array) && at.size != args.size - - normed = normalize_item(items) - payloads = args.map.with_index { |job_args, index| - copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12), "enqueued_at" => Time.now.to_f) - copy["at"] = (at.is_a?(Array) ? at[index] : at) if at - - result = process_single(items["class"], copy) - result || nil - }.compact - - raw_push(payloads) unless payloads.empty? - payloads.collect { |payload| payload["jid"] } - end - - # Allows sharding of jobs across any number of Redis instances. All jobs - # defined within the block will use the given Redis connection pool. - # - # pool = ConnectionPool.new { Redis.new } - # Sidekiq::Client.via(pool) do - # SomeWorker.perform_async(1,2,3) - # SomeOtherWorker.perform_async(1,2,3) - # end - # - # Generally this is only needed for very large Sidekiq installs processing - # thousands of jobs per second. I do not recommend sharding unless - # you cannot scale any other way (e.g. splitting your app into smaller apps). - def self.via(pool) - raise ArgumentError, "No pool given" if pool.nil? - current_sidekiq_pool = Thread.current[:sidekiq_via_pool] - Thread.current[:sidekiq_via_pool] = pool - yield - ensure - Thread.current[:sidekiq_via_pool] = current_sidekiq_pool - end - - class << self - def push(item) - new.push(item) - end - - def push_bulk(items) - new.push_bulk(items) - end - - # Resque compatibility helpers. Note all helpers - # should go through Worker#client_push. - # - # Example usage: - # Sidekiq::Client.enqueue(MyWorker, 'foo', 1, :bat => 'bar') - # - # Messages are enqueued to the 'default' queue. - # - def enqueue(klass, *args) - klass.client_push("class" => klass, "args" => args) - end - - # Example usage: - # Sidekiq::Client.enqueue_to(:queue_name, MyWorker, 'foo', 1, :bat => 'bar') - # - def enqueue_to(queue, klass, *args) - klass.client_push("queue" => queue, "class" => klass, "args" => args) - end - - # Example usage: - # Sidekiq::Client.enqueue_to_in(:queue_name, 3.minutes, MyWorker, 'foo', 1, :bat => 'bar') - # - def enqueue_to_in(queue, interval, klass, *args) - int = interval.to_f - now = Time.now.to_f - ts = (int < 1_000_000_000 ? now + int : int) - - item = {"class" => klass, "args" => args, "at" => ts, "queue" => queue} - item.delete("at") if ts <= now - - klass.client_push(item) - end - - # Example usage: - # Sidekiq::Client.enqueue_in(3.minutes, MyWorker, 'foo', 1, :bat => 'bar') - # - def enqueue_in(interval, klass, *args) - klass.perform_in(interval, *args) - end - end - - private - - def raw_push(payloads) - @redis_pool.with do |conn| - retryable = true - begin - conn.pipelined do |pipeline| - atomic_push(pipeline, payloads) - end - rescue Redis::BaseError => ex - # 2550 Failover can cause the server to become a replica, need - # to disconnect and reopen the socket to get back to the primary. - # 4495 Use the same logic if we have a "Not enough replicas" error from the primary - # 4985 Use the same logic when a blocking command is force-unblocked - # The retry logic is copied from sidekiq.rb - if retryable && ex.message =~ /READONLY|NOREPLICAS|UNBLOCKED/ - conn.disconnect! - retryable = false - retry - end - raise - end - end - true - end - - def atomic_push(conn, payloads) - if payloads.first.key?("at") - conn.zadd("schedule", payloads.map { |hash| - at = hash.delete("at").to_s - [at, Sidekiq.dump_json(hash)] - }) - else - queue = payloads.first["queue"] - now = Time.now.to_f - to_push = payloads.map { |entry| - entry["enqueued_at"] = now - Sidekiq.dump_json(entry) - } - conn.sadd("queues", queue) - conn.lpush("queue:#{queue}", to_push) - end - end - - def process_single(worker_class, item) - queue = item["queue"] - - middleware.invoke(worker_class, item, queue, @redis_pool) do - item - end - end - end -end diff --git a/lib/sidekiq/delay.rb b/lib/sidekiq/delay.rb deleted file mode 100644 index 026307bf..00000000 --- a/lib/sidekiq/delay.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - module Extensions - def self.enable_delay! - Sidekiq.logger.error "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0. #{caller(1..1).first}" - - if defined?(::ActiveSupport) - require "sidekiq/extensions/active_record" - require "sidekiq/extensions/action_mailer" - - # Need to patch Psych so it can autoload classes whose names are serialized - # in the delayed YAML. - Psych::Visitors::ToRuby.prepend(Sidekiq::Extensions::PsychAutoload) - - ActiveSupport.on_load(:active_record) do - include Sidekiq::Extensions::ActiveRecord - end - ActiveSupport.on_load(:action_mailer) do - extend Sidekiq::Extensions::ActionMailer - end - end - - require "sidekiq/extensions/class_methods" - Module.__send__(:include, Sidekiq::Extensions::Klass) - end - - module PsychAutoload - def resolve_class(klass_name) - return nil if !klass_name || klass_name.empty? - # constantize - names = klass_name.split("::") - names.shift if names.empty? || names.first.empty? - - names.inject(Object) do |constant, name| - constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) - end - rescue NameError - super - end - end - end -end diff --git a/lib/sidekiq/exception_handler.rb b/lib/sidekiq/exception_handler.rb deleted file mode 100644 index 77382177..00000000 --- a/lib/sidekiq/exception_handler.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq" - -module Sidekiq - module ExceptionHandler - class Logger - def call(ex, ctx) - Sidekiq.logger.warn(Sidekiq.dump_json(ctx)) unless ctx.empty? - Sidekiq.logger.warn("#{ex.class.name}: #{ex.message}") - Sidekiq.logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil? - end - - Sidekiq.error_handlers << Sidekiq::ExceptionHandler::Logger.new - end - - def handle_exception(ex, ctx = {}) - Sidekiq.error_handlers.each do |handler| - handler.call(ex, ctx) - rescue => ex - Sidekiq.logger.error "!!! ERROR HANDLER THREW AN ERROR !!!" - Sidekiq.logger.error ex - Sidekiq.logger.error ex.backtrace.join("\n") unless ex.backtrace.nil? - end - end - end -end diff --git a/lib/sidekiq/extensions/action_mailer.rb b/lib/sidekiq/extensions/action_mailer.rb deleted file mode 100644 index 444b82aa..00000000 --- a/lib/sidekiq/extensions/action_mailer.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/extensions/generic_proxy" - -module Sidekiq - module Extensions - ## - # Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email - # delivery to Sidekiq. - # - # @example - # UserMailer.delay.send_welcome_email(new_user) - # UserMailer.delay_for(5.days).send_welcome_email(new_user) - # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user) - class DelayedMailer - include Sidekiq::Worker - - def perform(yml) - (target, method_name, args) = YAML.load(yml) - msg = target.public_send(method_name, *args) - # The email method can return nil, which causes ActionMailer to return - # an undeliverable empty message. - if msg - msg.deliver_now - else - raise "#{target.name}##{method_name} returned an undeliverable mail object" - end - end - end - - module ActionMailer - def sidekiq_delay(options = {}) - Proxy.new(DelayedMailer, self, options) - end - - def sidekiq_delay_for(interval, options = {}) - Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f)) - end - - def sidekiq_delay_until(timestamp, options = {}) - Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f)) - end - alias_method :delay, :sidekiq_delay - alias_method :delay_for, :sidekiq_delay_for - alias_method :delay_until, :sidekiq_delay_until - end - end -end diff --git a/lib/sidekiq/extensions/active_record.rb b/lib/sidekiq/extensions/active_record.rb deleted file mode 100644 index 5342ed85..00000000 --- a/lib/sidekiq/extensions/active_record.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/extensions/generic_proxy" - -module Sidekiq - module Extensions - ## - # Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method - # execution to Sidekiq. - # - # @example - # User.recent_signups.each { |user| user.delay.mark_as_awesome } - # - # Please note, this is not recommended as this will serialize the entire - # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances. - # This is here for backwards compatibility with Delayed::Job only. - class DelayedModel - include Sidekiq::Worker - - def perform(yml) - (target, method_name, args) = YAML.load(yml) - target.__send__(method_name, *args) - end - end - - module ActiveRecord - def sidekiq_delay(options = {}) - Proxy.new(DelayedModel, self, options) - end - - def sidekiq_delay_for(interval, options = {}) - Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f)) - end - - def sidekiq_delay_until(timestamp, options = {}) - Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f)) - end - alias_method :delay, :sidekiq_delay - alias_method :delay_for, :sidekiq_delay_for - alias_method :delay_until, :sidekiq_delay_until - end - end -end diff --git a/lib/sidekiq/extensions/class_methods.rb b/lib/sidekiq/extensions/class_methods.rb deleted file mode 100644 index 1723b6f8..00000000 --- a/lib/sidekiq/extensions/class_methods.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/extensions/generic_proxy" - -module Sidekiq - module Extensions - ## - # Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method - # execution to Sidekiq. - # - # @example - # User.delay.delete_inactive - # Wikipedia.delay.download_changes_for(Date.today) - # - class DelayedClass - include Sidekiq::Worker - - def perform(yml) - (target, method_name, args) = YAML.load(yml) - target.__send__(method_name, *args) - end - end - - module Klass - def sidekiq_delay(options = {}) - Proxy.new(DelayedClass, self, options) - end - - def sidekiq_delay_for(interval, options = {}) - Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f)) - end - - def sidekiq_delay_until(timestamp, options = {}) - Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f)) - end - alias_method :delay, :sidekiq_delay - alias_method :delay_for, :sidekiq_delay_for - alias_method :delay_until, :sidekiq_delay_until - end - end -end - -Module.__send__(:include, Sidekiq::Extensions::Klass) unless defined?(::Rails) diff --git a/lib/sidekiq/extensions/generic_proxy.rb b/lib/sidekiq/extensions/generic_proxy.rb deleted file mode 100644 index 313dd48c..00000000 --- a/lib/sidekiq/extensions/generic_proxy.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -require "yaml" - -module Sidekiq - module Extensions - SIZE_LIMIT = 8_192 - - class Proxy < BasicObject - def initialize(performable, target, options = {}) - @performable = performable - @target = target - @opts = options - end - - def method_missing(name, *args) - # Sidekiq has a limitation in that its message must be JSON. - # JSON can't round trip real Ruby objects so we use YAML to - # serialize the objects to a String. The YAML will be converted - # to JSON and then deserialized on the other side back into a - # Ruby object. - obj = [@target, name, args] - marshalled = ::YAML.dump(obj) - if marshalled.size > SIZE_LIMIT - ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" } - end - @performable.client_push({"class" => @performable, - "args" => [marshalled], - "display_class" => "#{@target}.#{name}"}.merge(@opts)) - end - end - end -end diff --git a/lib/sidekiq/fetch.rb b/lib/sidekiq/fetch.rb deleted file mode 100644 index f3d1fbb8..00000000 --- a/lib/sidekiq/fetch.rb +++ /dev/null @@ -1,89 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq" - -module Sidekiq - class BasicFetch - # We want the fetch operation to timeout every few seconds so the thread - # can check if the process is shutting down. - TIMEOUT = 2 - - UnitOfWork = Struct.new(:queue, :job) { - def acknowledge - # nothing to do - end - - def queue_name - queue.delete_prefix("queue:") - end - - def requeue - Sidekiq.redis do |conn| - conn.rpush(queue, job) - end - end - } - - def initialize(options) - raise ArgumentError, "missing queue list" unless options[:queues] - @options = options - @strictly_ordered_queues = !!@options[:strict] - @queues = @options[:queues].map { |q| "queue:#{q}" } - if @strictly_ordered_queues - @queues.uniq! - @queues << TIMEOUT - end - end - - def retrieve_work - qs = queues_cmd - # 4825 Sidekiq Pro with all queues paused will return an - # empty set of queues with a trailing TIMEOUT value. - if qs.size <= 1 - sleep(TIMEOUT) - return nil - end - - work = Sidekiq.redis { |conn| conn.brpop(*qs) } - UnitOfWork.new(*work) if work - end - - def bulk_requeue(inprogress, options) - return if inprogress.empty? - - Sidekiq.logger.debug { "Re-queueing terminated jobs" } - jobs_to_requeue = {} - inprogress.each do |unit_of_work| - jobs_to_requeue[unit_of_work.queue] ||= [] - jobs_to_requeue[unit_of_work.queue] << unit_of_work.job - end - - Sidekiq.redis do |conn| - conn.pipelined do |pipeline| - jobs_to_requeue.each do |queue, jobs| - pipeline.rpush(queue, jobs) - end - end - end - Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis") - rescue => ex - Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{ex.message}") - end - - # Creating the Redis#brpop command takes into account any - # configured queue weights. By default Redis#brpop returns - # data from the first queue that has pending elements. We - # recreate the queue command each time we invoke Redis#brpop - # to honor weights and avoid queue starvation. - def queues_cmd - if @strictly_ordered_queues - @queues - else - permute = @queues.shuffle - permute.uniq! - permute << TIMEOUT - permute - end - end - end -end diff --git a/lib/sidekiq/job.rb b/lib/sidekiq/job.rb deleted file mode 100644 index c8ccc794..00000000 --- a/lib/sidekiq/job.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "sidekiq/worker" - -module Sidekiq - # Sidekiq::Job is a new alias for Sidekiq::Worker as of Sidekiq 6.3.0. - # Use `include Sidekiq::Job` rather than `include Sidekiq::Worker`. - # - # The term "worker" is too generic and overly confusing, used in several - # different contexts meaning different things. Many people call a Sidekiq - # process a "worker". Some people call the thread that executes jobs a - # "worker". This change brings Sidekiq closer to ActiveJob where your job - # classes extend ApplicationJob. - Job = Worker -end diff --git a/lib/sidekiq/job_logger.rb b/lib/sidekiq/job_logger.rb deleted file mode 100644 index 91be17b1..00000000 --- a/lib/sidekiq/job_logger.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - class JobLogger - def initialize(logger = Sidekiq.logger) - @logger = logger - end - - def call(item, queue) - start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @logger.info("start") - - yield - - Sidekiq::Context.add(:elapsed, elapsed(start)) - @logger.info("done") - rescue Exception - Sidekiq::Context.add(:elapsed, elapsed(start)) - @logger.info("fail") - - raise - end - - def prepare(job_hash, &block) - # If we're using a wrapper class, like ActiveJob, use the "wrapped" - # attribute to expose the underlying thing. - h = { - class: job_hash["display_class"] || job_hash["wrapped"] || job_hash["class"], - jid: job_hash["jid"] - } - h[:bid] = job_hash["bid"] if job_hash.has_key?("bid") - h[:tags] = job_hash["tags"] if job_hash.has_key?("tags") - - Thread.current[:sidekiq_context] = h - level = job_hash["log_level"] - if level - @logger.log_at(level, &block) - else - yield - end - ensure - Thread.current[:sidekiq_context] = nil - end - - private - - def elapsed(start) - (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - start).round(3) - end - end -end diff --git a/lib/sidekiq/job_retry.rb b/lib/sidekiq/job_retry.rb deleted file mode 100644 index 1d96f6d0..00000000 --- a/lib/sidekiq/job_retry.rb +++ /dev/null @@ -1,261 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/scheduled" -require "sidekiq/api" - -require "zlib" -require "base64" - -module Sidekiq - ## - # Automatically retry jobs that fail in Sidekiq. - # Sidekiq's retry support assumes a typical development lifecycle: - # - # 0. Push some code changes with a bug in it. - # 1. Bug causes job processing to fail, Sidekiq's middleware captures - # the job and pushes it onto a retry queue. - # 2. Sidekiq retries jobs in the retry queue multiple times with - # an exponential delay, the job continues to fail. - # 3. After a few days, a developer deploys a fix. The job is - # reprocessed successfully. - # 4. Once retries are exhausted, Sidekiq will give up and move the - # job to the Dead Job Queue (aka morgue) where it must be dealt with - # manually in the Web UI. - # 5. After 6 months on the DJQ, Sidekiq will discard the job. - # - # A job looks like: - # - # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => true } - # - # The 'retry' option also accepts a number (in place of 'true'): - # - # { 'class' => 'HardWorker', 'args' => [1, 2, 'foo'], 'retry' => 5 } - # - # The job will be retried this number of times before giving up. (If simply - # 'true', Sidekiq retries 25 times) - # - # Relevant options for job retries: - # - # * 'queue' - the queue for the initial job - # * 'retry_queue' - if job retries should be pushed to a different (e.g. lower priority) queue - # * 'retry_count' - number of times we've retried so far. - # * 'error_message' - the message from the exception - # * 'error_class' - the exception class - # * 'failed_at' - the first time it failed - # * 'retried_at' - the last time it was retried - # * 'backtrace' - the number of lines of error backtrace to store - # - # We don't store the backtrace by default as that can add a lot of overhead - # to the job and everyone is using an error service, right? - # - # The default number of retries is 25 which works out to about 3 weeks - # You can change the default maximum number of retries in your initializer: - # - # Sidekiq.options[:max_retries] = 7 - # - # or limit the number of retries for a particular worker and send retries to - # a low priority queue with: - # - # class MyWorker - # include Sidekiq::Worker - # sidekiq_options retry: 10, retry_queue: 'low' - # end - # - class JobRetry - class Handled < ::RuntimeError; end - - class Skip < Handled; end - - include Sidekiq::Util - - DEFAULT_MAX_RETRY_ATTEMPTS = 25 - - def initialize(options = {}) - @max_retries = Sidekiq.options.merge(options).fetch(:max_retries, DEFAULT_MAX_RETRY_ATTEMPTS) - end - - # The global retry handler requires only the barest of data. - # We want to be able to retry as much as possible so we don't - # require the worker to be instantiated. - def global(jobstr, queue) - yield - rescue Handled => ex - raise ex - rescue Sidekiq::Shutdown => ey - # ignore, will be pushed back onto queue during hard_shutdown - raise ey - rescue Exception => e - # ignore, will be pushed back onto queue during hard_shutdown - raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e) - - msg = Sidekiq.load_json(jobstr) - if msg["retry"] - attempt_retry(nil, msg, queue, e) - else - Sidekiq.death_handlers.each do |handler| - handler.call(msg, e) - rescue => handler_ex - handle_exception(handler_ex, {context: "Error calling death handler", job: msg}) - end - end - - raise Handled - end - - # The local retry support means that any errors that occur within - # this block can be associated with the given worker instance. - # This is required to support the `sidekiq_retries_exhausted` block. - # - # Note that any exception from the block is wrapped in the Skip - # exception so the global block does not reprocess the error. The - # Skip exception is unwrapped within Sidekiq::Processor#process before - # calling the handle_exception handlers. - def local(worker, jobstr, queue) - yield - rescue Handled => ex - raise ex - rescue Sidekiq::Shutdown => ey - # ignore, will be pushed back onto queue during hard_shutdown - raise ey - rescue Exception => e - # ignore, will be pushed back onto queue during hard_shutdown - raise Sidekiq::Shutdown if exception_caused_by_shutdown?(e) - - msg = Sidekiq.load_json(jobstr) - if msg["retry"].nil? - msg["retry"] = worker.class.get_sidekiq_options["retry"] - end - - raise e unless msg["retry"] - attempt_retry(worker, msg, queue, e) - # We've handled this error associated with this job, don't - # need to handle it at the global level - raise Skip - end - - private - - # Note that +worker+ can be nil here if an error is raised before we can - # instantiate the worker instance. All access must be guarded and - # best effort. - def attempt_retry(worker, msg, queue, exception) - max_retry_attempts = retry_attempts_from(msg["retry"], @max_retries) - - msg["queue"] = (msg["retry_queue"] || queue) - - m = exception_message(exception) - if m.respond_to?(:scrub!) - m.force_encoding("utf-8") - m.scrub! - end - - msg["error_message"] = m - msg["error_class"] = exception.class.name - count = if msg["retry_count"] - msg["retried_at"] = Time.now.to_f - msg["retry_count"] += 1 - else - msg["failed_at"] = Time.now.to_f - msg["retry_count"] = 0 - end - - if msg["backtrace"] - lines = if msg["backtrace"] == true - exception.backtrace - else - exception.backtrace[0...msg["backtrace"].to_i] - end - - msg["error_backtrace"] = compress_backtrace(lines) - end - - if count < max_retry_attempts - delay = delay_for(worker, count, exception) - # Logging here can break retries if the logging device raises ENOSPC #3979 - # logger.debug { "Failure! Retry #{count} in #{delay} seconds" } - retry_at = Time.now.to_f + delay - payload = Sidekiq.dump_json(msg) - Sidekiq.redis do |conn| - conn.zadd("retry", retry_at.to_s, payload) - end - else - # Goodbye dear message, you (re)tried your best I'm sure. - retries_exhausted(worker, msg, exception) - end - end - - def retries_exhausted(worker, msg, exception) - begin - block = worker&.sidekiq_retries_exhausted_block - block&.call(msg, exception) - rescue => e - handle_exception(e, {context: "Error calling retries_exhausted", job: msg}) - end - - send_to_morgue(msg) unless msg["dead"] == false - - Sidekiq.death_handlers.each do |handler| - handler.call(msg, exception) - rescue => e - handle_exception(e, {context: "Error calling death handler", job: msg}) - end - end - - def send_to_morgue(msg) - logger.info { "Adding dead #{msg["class"]} job #{msg["jid"]}" } - payload = Sidekiq.dump_json(msg) - DeadSet.new.kill(payload, notify_failure: false) - end - - def retry_attempts_from(msg_retry, default) - if msg_retry.is_a?(Integer) - msg_retry - else - default - end - end - - def delay_for(worker, count, exception) - jitter = rand(10) * (count + 1) - if worker&.sidekiq_retry_in_block - custom_retry_in = retry_in(worker, count, exception).to_i - return custom_retry_in + jitter if custom_retry_in > 0 - end - (count**4) + 15 + jitter - end - - def retry_in(worker, count, exception) - worker.sidekiq_retry_in_block.call(count, exception) - rescue Exception => e - handle_exception(e, {context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{worker.class.name}, falling back to default"}) - nil - end - - def exception_caused_by_shutdown?(e, checked_causes = []) - return false unless e.cause - - # Handle circular causes - checked_causes << e.object_id - return false if checked_causes.include?(e.cause.object_id) - - e.cause.instance_of?(Sidekiq::Shutdown) || - exception_caused_by_shutdown?(e.cause, checked_causes) - end - - # Extract message from exception. - # Set a default if the message raises an error - def exception_message(exception) - # App code can stuff all sorts of crazy binary data into the error message - # that won't convert to JSON. - exception.message.to_s[0, 10_000] - rescue - +"!!! ERROR MESSAGE THREW AN ERROR !!!" - end - - def compress_backtrace(backtrace) - serialized = Sidekiq.dump_json(backtrace) - compressed = Zlib::Deflate.deflate(serialized) - Base64.encode64(compressed) - end - end -end diff --git a/lib/sidekiq/job_util.rb b/lib/sidekiq/job_util.rb deleted file mode 100644 index 65e354b2..00000000 --- a/lib/sidekiq/job_util.rb +++ /dev/null @@ -1,65 +0,0 @@ -require "securerandom" -require "time" - -module Sidekiq - module JobUtil - # These functions encapsulate various job utilities. - # They must be simple and free from side effects. - - def validate(item) - raise(ArgumentError, "Job must be a Hash with 'class' and 'args' keys: `#{item}`") unless item.is_a?(Hash) && item.key?("class") && item.key?("args") - raise(ArgumentError, "Job args must be an Array: `#{item}`") unless item["args"].is_a?(Array) - raise(ArgumentError, "Job class must be either a Class or String representation of the class name: `#{item}`") unless item["class"].is_a?(Class) || item["class"].is_a?(String) - raise(ArgumentError, "Job 'at' must be a Numeric timestamp: `#{item}`") if item.key?("at") && !item["at"].is_a?(Numeric) - raise(ArgumentError, "Job tags must be an Array: `#{item}`") if item["tags"] && !item["tags"].is_a?(Array) - - if Sidekiq.options[:on_complex_arguments] == :raise - msg = <<~EOM - Job arguments to #{item["class"]} must be native JSON types, see https://github.com/mperham/sidekiq/wiki/Best-Practices. - To disable this error, remove `Sidekiq.strict_args!` from your initializer. - EOM - raise(ArgumentError, msg) unless json_safe?(item) - elsif Sidekiq.options[:on_complex_arguments] == :warn - Sidekiq.logger.warn <<~EOM unless json_safe?(item) - Job arguments to #{item["class"]} do not serialize to JSON safely. This will raise an error in - Sidekiq 7.0. See https://github.com/mperham/sidekiq/wiki/Best-Practices or raise an error today - by calling `Sidekiq.strict_args!` during Sidekiq initialization. - EOM - end - end - - def normalize_item(item) - validate(item) - - # merge in the default sidekiq_options for the item's class and/or wrapped element - # this allows ActiveJobs to control sidekiq_options too. - defaults = normalized_hash(item["class"]) - defaults = defaults.merge(item["wrapped"].get_sidekiq_options) if item["wrapped"].respond_to?(:get_sidekiq_options) - item = defaults.merge(item) - - raise(ArgumentError, "Job must include a valid queue name") if item["queue"].nil? || item["queue"] == "" - - item["class"] = item["class"].to_s - item["queue"] = item["queue"].to_s - item["jid"] ||= SecureRandom.hex(12) - item["created_at"] ||= Time.now.to_f - - item - end - - def normalized_hash(item_class) - if item_class.is_a?(Class) - raise(ArgumentError, "Message must include a Sidekiq::Worker class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options) - item_class.get_sidekiq_options - else - Sidekiq.default_worker_options - end - end - - private - - def json_safe?(item) - JSON.parse(JSON.dump(item["args"])) == item["args"] - end - end -end diff --git a/lib/sidekiq/launcher.rb b/lib/sidekiq/launcher.rb deleted file mode 100644 index 73a62468..00000000 --- a/lib/sidekiq/launcher.rb +++ /dev/null @@ -1,263 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/manager" -require "sidekiq/fetch" -require "sidekiq/scheduled" - -module Sidekiq - # The Launcher starts the Manager and Poller threads and provides the process heartbeat. - class Launcher - include Util - - STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years - - PROCTITLES = [ - proc { "sidekiq" }, - proc { Sidekiq::VERSION }, - proc { |me, data| data["tag"] }, - proc { |me, data| "[#{Processor::WORKER_STATE.size} of #{data["concurrency"]} busy]" }, - proc { |me, data| "stopping" if me.stopping? } - ] - - attr_accessor :manager, :poller, :fetcher - - def initialize(options) - options[:fetch] ||= BasicFetch.new(options) - @manager = Sidekiq::Manager.new(options) - @poller = Sidekiq::Scheduled::Poller.new - @done = false - @options = options - end - - def run - @thread = safe_thread("heartbeat", &method(:start_heartbeat)) - @poller.start - @manager.start - end - - # Stops this instance from processing any more jobs, - # - def quiet - @done = true - @manager.quiet - @poller.terminate - end - - # Shuts down the process. This method does not - # return until all work is complete and cleaned up. - # It can take up to the timeout to complete. - def stop - deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @options[:timeout] - - @done = true - @manager.quiet - @poller.terminate - - @manager.stop(deadline) - - # Requeue everything in case there was a worker who grabbed work while stopped - # This call is a no-op in Sidekiq but necessary for Sidekiq Pro. - strategy = @options[:fetch] - strategy.bulk_requeue([], @options) - - clear_heartbeat - end - - def stopping? - @done - end - - private unless $TESTING - - BEAT_PAUSE = 5 - - def start_heartbeat - loop do - heartbeat - sleep BEAT_PAUSE - end - Sidekiq.logger.info("Heartbeat stopping...") - end - - def clear_heartbeat - # Remove record from Redis since we are shutting down. - # Note we don't stop the heartbeat thread; if the process - # doesn't actually exit, it'll reappear in the Web UI. - Sidekiq.redis do |conn| - conn.pipelined do |pipeline| - pipeline.srem("processes", identity) - pipeline.unlink("#{identity}:workers") - end - end - rescue - # best effort, ignore network errors - end - - def heartbeat - $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") - - ❤ - end - - def self.flush_stats - fails = Processor::FAILURE.reset - procd = Processor::PROCESSED.reset - return if fails + procd == 0 - - nowdate = Time.now.utc.strftime("%Y-%m-%d") - begin - Sidekiq.redis do |conn| - conn.pipelined do |pipeline| - pipeline.incrby("stat:processed", procd) - pipeline.incrby("stat:processed:#{nowdate}", procd) - pipeline.expire("stat:processed:#{nowdate}", STATS_TTL) - - pipeline.incrby("stat:failed", fails) - pipeline.incrby("stat:failed:#{nowdate}", fails) - pipeline.expire("stat:failed:#{nowdate}", STATS_TTL) - end - end - rescue => ex - # we're exiting the process, things might be shut down so don't - # try to handle the exception - Sidekiq.logger.warn("Unable to flush stats: #{ex}") - end - end - at_exit(&method(:flush_stats)) - - def ❤ - key = identity - fails = procd = 0 - - begin - fails = Processor::FAILURE.reset - procd = Processor::PROCESSED.reset - curstate = Processor::WORKER_STATE.dup - - workers_key = "#{key}:workers" - nowdate = Time.now.utc.strftime("%Y-%m-%d") - - Sidekiq.redis do |conn| - conn.multi do |transaction| - transaction.incrby("stat:processed", procd) - transaction.incrby("stat:processed:#{nowdate}", procd) - transaction.expire("stat:processed:#{nowdate}", STATS_TTL) - - transaction.incrby("stat:failed", fails) - transaction.incrby("stat:failed:#{nowdate}", fails) - transaction.expire("stat:failed:#{nowdate}", STATS_TTL) - - transaction.unlink(workers_key) - curstate.each_pair do |tid, hash| - conn.hset(workers_key, tid, Sidekiq.dump_json(hash)) - end - conn.expire(workers_key, 60) - end - end - - rtt = check_rtt - - fails = procd = 0 - kb = memory_usage(::Process.pid) - - _, exists, _, _, msg = Sidekiq.redis { |conn| - conn.multi { |transaction| - transaction.sadd("processes", key) - transaction.exists?(key) - transaction.hmset(key, "info", to_json, - "busy", curstate.size, - "beat", Time.now.to_f, - "rtt_us", rtt, - "quiet", @done, - "rss", kb) - transaction.expire(key, 60) - transaction.rpop("#{key}-signals") - } - } - - # first heartbeat or recovering from an outage and need to reestablish our heartbeat - fire_event(:heartbeat) unless exists - - return unless msg - - ::Process.kill(msg, ::Process.pid) - rescue => e - # ignore all redis/network issues - logger.error("heartbeat: #{e}") - # don't lose the counts if there was a network issue - Processor::PROCESSED.incr(procd) - Processor::FAILURE.incr(fails) - end - end - - # We run the heartbeat every five seconds. - # Capture five samples of RTT, log a warning if each sample - # is above our warning threshold. - RTT_READINGS = RingBuffer.new(5) - RTT_WARNING_LEVEL = 50_000 - - def check_rtt - a = b = 0 - Sidekiq.redis do |x| - a = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond) - x.ping - b = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :microsecond) - end - rtt = b - a - RTT_READINGS << rtt - # Ideal RTT for Redis is < 1000µs - # Workable is < 10,000µs - # Log a warning if it's a disaster. - if RTT_READINGS.all? { |x| x > RTT_WARNING_LEVEL } - Sidekiq.logger.warn <<~EOM - Your Redis network connection is performing extremely poorly. - Last RTT readings were #{RTT_READINGS.buffer.inspect}, ideally these should be < 1000. - Ensure Redis is running in the same AZ or datacenter as Sidekiq. - If these values are close to 100,000, that means your Sidekiq process may be - CPU overloaded; see https://github.com/mperham/sidekiq/discussions/5039 - EOM - RTT_READINGS.reset - end - rtt - end - - MEMORY_GRABBER = case RUBY_PLATFORM - when /linux/ - ->(pid) { - IO.readlines("/proc/#{$$}/status").each do |line| - next unless line.start_with?("VmRSS:") - break line.split[1].to_i - end - } - when /darwin|bsd/ - ->(pid) { - `ps -o pid,rss -p #{pid}`.lines.last.split.last.to_i - } - else - ->(pid) { 0 } - end - - def memory_usage(pid) - MEMORY_GRABBER.call(pid) - end - - def to_data - @data ||= { - "hostname" => hostname, - "started_at" => Time.now.to_f, - "pid" => ::Process.pid, - "tag" => @options[:tag] || "", - "concurrency" => @options[:concurrency], - "queues" => @options[:queues].uniq, - "labels" => @options[:labels], - "identity" => identity - } - end - - def to_json - # this data changes infrequently so dump it to a string - # now so we don't need to dump it every heartbeat. - @json ||= Sidekiq.dump_json(to_data) - end - end -end diff --git a/lib/sidekiq/logger.rb b/lib/sidekiq/logger.rb deleted file mode 100644 index 49d435a1..00000000 --- a/lib/sidekiq/logger.rb +++ /dev/null @@ -1,170 +0,0 @@ -# frozen_string_literal: true - -require "logger" -require "time" - -module Sidekiq - module Context - def self.with(hash) - orig_context = current.dup - current.merge!(hash) - yield - ensure - Thread.current[:sidekiq_context] = orig_context - end - - def self.current - Thread.current[:sidekiq_context] ||= {} - end - - def self.add(k, v) - Thread.current[:sidekiq_context][k] = v - end - end - - module LoggingUtils - LEVELS = { - "debug" => 0, - "info" => 1, - "warn" => 2, - "error" => 3, - "fatal" => 4 - } - LEVELS.default_proc = proc do |_, level| - Sidekiq.logger.warn("Invalid log level: #{level.inspect}") - nil - end - - def debug? - level <= 0 - end - - def info? - level <= 1 - end - - def warn? - level <= 2 - end - - def error? - level <= 3 - end - - def fatal? - level <= 4 - end - - def local_level - Thread.current[:sidekiq_log_level] - end - - def local_level=(level) - case level - when Integer - Thread.current[:sidekiq_log_level] = level - when Symbol, String - Thread.current[:sidekiq_log_level] = LEVELS[level.to_s] - when nil - Thread.current[:sidekiq_log_level] = nil - else - raise ArgumentError, "Invalid log level: #{level.inspect}" - end - end - - def level - local_level || super - end - - # Change the thread-local level for the duration of the given block. - def log_at(level) - old_local_level = local_level - self.local_level = level - yield - ensure - self.local_level = old_local_level - end - - # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+. - # FIXME: Remove when the minimum Ruby version supports overriding Logger#level. - def add(severity, message = nil, progname = nil, &block) - severity ||= ::Logger::UNKNOWN - progname ||= @progname - - return true if @logdev.nil? || severity < level - - if message.nil? - if block - message = yield - else - message = progname - progname = @progname - end - end - - @logdev.write format_message(format_severity(severity), Time.now, progname, message) - end - end - - class Logger < ::Logger - include LoggingUtils - - def initialize(*args, **kwargs) - super - self.formatter = Sidekiq.log_formatter - end - - module Formatters - class Base < ::Logger::Formatter - def tid - Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36) - end - - def ctx - Sidekiq::Context.current - end - - def format_context - if ctx.any? - " " + ctx.compact.map { |k, v| - case v - when Array - "#{k}=#{v.join(",")}" - else - "#{k}=#{v}" - end - }.join(" ") - end - end - end - - class Pretty < Base - def call(severity, time, program_name, message) - "#{time.utc.iso8601(3)} pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n" - end - end - - class WithoutTimestamp < Pretty - def call(severity, time, program_name, message) - "pid=#{::Process.pid} tid=#{tid}#{format_context} #{severity}: #{message}\n" - end - end - - class JSON < Base - def call(severity, time, program_name, message) - hash = { - ts: time.utc.iso8601(3), - pid: ::Process.pid, - tid: tid, - lvl: severity, - msg: message - } - c = ctx - hash["ctx"] = c unless c.empty? - - Sidekiq.dump_json(hash) << "\n" - end - end - end - end -end diff --git a/lib/sidekiq/manager.rb b/lib/sidekiq/manager.rb deleted file mode 100644 index 4038f373..00000000 --- a/lib/sidekiq/manager.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/util" -require "sidekiq/processor" -require "sidekiq/fetch" -require "set" - -module Sidekiq - ## - # The Manager is the central coordination point in Sidekiq, controlling - # the lifecycle of the Processors. - # - # Tasks: - # - # 1. start: Spin up Processors. - # 3. processor_died: Handle job failure, throw away Processor, create new one. - # 4. quiet: shutdown idle Processors. - # 5. stop: hard stop the Processors by deadline. - # - # Note that only the last task requires its own Thread since it has to monitor - # the shutdown process. The other tasks are performed by other threads. - # - class Manager - include Util - - attr_reader :workers - attr_reader :options - - def initialize(options = {}) - logger.debug { options.inspect } - @options = options - @count = options[:concurrency] || 10 - raise ArgumentError, "Concurrency of #{@count} is not supported" if @count < 1 - - @done = false - @workers = Set.new - @count.times do - @workers << Processor.new(self, options) - end - @plock = Mutex.new - end - - def start - @workers.each do |x| - x.start - end - end - - def quiet - return if @done - @done = true - - logger.info { "Terminating quiet workers" } - @workers.each { |x| x.terminate } - fire_event(:quiet, reverse: true) - end - - def stop(deadline) - quiet - fire_event(:shutdown, reverse: true) - - # some of the shutdown events can be async, - # we don't have any way to know when they're done but - # give them a little time to take effect - sleep PAUSE_TIME - return if @workers.empty? - - logger.info { "Pausing to allow workers to finish..." } - wait_for(deadline) { @workers.empty? } - return if @workers.empty? - - hard_shutdown - end - - def processor_stopped(processor) - @plock.synchronize do - @workers.delete(processor) - end - end - - def processor_died(processor, reason) - @plock.synchronize do - @workers.delete(processor) - unless @done - p = Processor.new(self, options) - @workers << p - p.start - end - end - end - - def stopped? - @done - end - - private - - def hard_shutdown - # We've reached the timeout and we still have busy workers. - # They must die but their jobs shall live on. - cleanup = nil - @plock.synchronize do - cleanup = @workers.dup - end - - if cleanup.size > 0 - jobs = cleanup.map { |p| p.job }.compact - - logger.warn { "Terminating #{cleanup.size} busy worker threads" } - logger.warn { "Work still in progress #{jobs.inspect}" } - - # Re-enqueue unfinished jobs - # NOTE: You may notice that we may push a job back to redis before - # the worker thread is terminated. This is ok because Sidekiq's - # contract says that jobs are run AT LEAST once. Process termination - # is delayed until we're certain the jobs are back in Redis because - # it is worse to lose a job than to run it twice. - strategy = @options[:fetch] - strategy.bulk_requeue(jobs, @options) - end - - cleanup.each do |processor| - processor.kill - end - - # when this method returns, we immediately call `exit` which may not give - # the remaining threads time to run `ensure` blocks, etc. We pause here up - # to 3 seconds to give threads a minimal amount of time to run `ensure` blocks. - deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + 3 - wait_for(deadline) { @workers.empty? } - end - end -end diff --git a/lib/sidekiq/middleware/chain.rb b/lib/sidekiq/middleware/chain.rb deleted file mode 100644 index c039cb3f..00000000 --- a/lib/sidekiq/middleware/chain.rb +++ /dev/null @@ -1,162 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - # Middleware is code configured to run before/after - # a message is processed. It is patterned after Rack - # middleware. Middleware exists for the client side - # (pushing jobs onto the queue) as well as the server - # side (when jobs are actually processed). - # - # To add middleware for the client: - # - # Sidekiq.configure_client do |config| - # config.client_middleware do |chain| - # chain.add MyClientHook - # end - # end - # - # To modify middleware for the server, just call - # with another block: - # - # Sidekiq.configure_server do |config| - # config.server_middleware do |chain| - # chain.add MyServerHook - # chain.remove ActiveRecord - # end - # end - # - # To insert immediately preceding another entry: - # - # Sidekiq.configure_client do |config| - # config.client_middleware do |chain| - # chain.insert_before ActiveRecord, MyClientHook - # end - # end - # - # To insert immediately after another entry: - # - # Sidekiq.configure_client do |config| - # config.client_middleware do |chain| - # chain.insert_after ActiveRecord, MyClientHook - # end - # end - # - # This is an example of a minimal server middleware: - # - # class MyServerHook - # def call(worker_instance, msg, queue) - # puts "Before work" - # yield - # puts "After work" - # end - # end - # - # This is an example of a minimal client middleware, note - # the method must return the result or the job will not push - # to Redis: - # - # class MyClientHook - # def call(worker_class, msg, queue, redis_pool) - # puts "Before push" - # result = yield - # puts "After push" - # result - # end - # end - # - module Middleware - class Chain - include Enumerable - - def initialize_copy(copy) - copy.instance_variable_set(:@entries, entries.dup) - end - - def each(&block) - entries.each(&block) - end - - def initialize - @entries = nil - yield self if block_given? - end - - def entries - @entries ||= [] - end - - def remove(klass) - entries.delete_if { |entry| entry.klass == klass } - end - - def add(klass, *args) - remove(klass) - entries << Entry.new(klass, *args) - end - - def prepend(klass, *args) - remove(klass) - entries.insert(0, Entry.new(klass, *args)) - end - - def insert_before(oldklass, newklass, *args) - i = entries.index { |entry| entry.klass == newklass } - new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i) - i = entries.index { |entry| entry.klass == oldklass } || 0 - entries.insert(i, new_entry) - end - - def insert_after(oldklass, newklass, *args) - i = entries.index { |entry| entry.klass == newklass } - new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i) - i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1 - entries.insert(i + 1, new_entry) - end - - def exists?(klass) - any? { |entry| entry.klass == klass } - end - - def empty? - @entries.nil? || @entries.empty? - end - - def retrieve - map(&:make_new) - end - - def clear - entries.clear - end - - def invoke(*args) - return yield if empty? - - chain = retrieve - traverse_chain = proc do - if chain.empty? - yield - else - chain.shift.call(*args, &traverse_chain) - end - end - traverse_chain.call - end - end - - private - - class Entry - attr_reader :klass - - def initialize(klass, *args) - @klass = klass - @args = args - end - - def make_new - @klass.new(*@args) - end - end - end -end diff --git a/lib/sidekiq/middleware/current_attributes.rb b/lib/sidekiq/middleware/current_attributes.rb deleted file mode 100644 index f62b587b..00000000 --- a/lib/sidekiq/middleware/current_attributes.rb +++ /dev/null @@ -1,57 +0,0 @@ -require "active_support/current_attributes" - -module Sidekiq - ## - # Automatically save and load any current attributes in the execution context - # so context attributes "flow" from Rails actions into any associated jobs. - # This can be useful for multi-tenancy, i18n locale, timezone, any implicit - # per-request attribute. See +ActiveSupport::CurrentAttributes+. - # - # @example - # - # # in your initializer - # require "sidekiq/middleware/current_attributes" - # Sidekiq::CurrentAttributes.persist(Myapp::Current) - # - module CurrentAttributes - class Save - def initialize(cattr) - @klass = cattr - end - - def call(_, job, _, _) - attrs = @klass.attributes - if job.has_key?("cattr") - job["cattr"].merge!(attrs) - else - job["cattr"] = attrs - end - yield - end - end - - class Load - def initialize(cattr) - @klass = cattr - end - - def call(_, job, _, &block) - if job.has_key?("cattr") - @klass.set(job["cattr"], &block) - else - yield - end - end - end - - def self.persist(klass) - Sidekiq.configure_client do |config| - config.client_middleware.add Save, klass - end - Sidekiq.configure_server do |config| - config.client_middleware.add Save, klass - config.server_middleware.add Load, klass - end - end - end -end diff --git a/lib/sidekiq/middleware/i18n.rb b/lib/sidekiq/middleware/i18n.rb deleted file mode 100644 index 93c60025..00000000 --- a/lib/sidekiq/middleware/i18n.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -# -# Simple middleware to save the current locale and restore it when the job executes. -# Use it by requiring it in your initializer: -# -# require 'sidekiq/middleware/i18n' -# -module Sidekiq::Middleware::I18n - # Get the current locale and store it in the message - # to be sent to Sidekiq. - class Client - def call(_worker, msg, _queue, _redis) - msg["locale"] ||= I18n.locale - yield - end - end - - # Pull the msg locale out and set the current thread to use it. - class Server - def call(_worker, msg, _queue, &block) - I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block) - end - end -end - -Sidekiq.configure_client do |config| - config.client_middleware do |chain| - chain.add Sidekiq::Middleware::I18n::Client - end -end - -Sidekiq.configure_server do |config| - config.client_middleware do |chain| - chain.add Sidekiq::Middleware::I18n::Client - end - config.server_middleware do |chain| - chain.add Sidekiq::Middleware::I18n::Server - end -end diff --git a/lib/sidekiq/monitor.rb b/lib/sidekiq/monitor.rb deleted file mode 100644 index 4d69d045..00000000 --- a/lib/sidekiq/monitor.rb +++ /dev/null @@ -1,133 +0,0 @@ -#!/usr/bin/env ruby - -require "fileutils" -require "sidekiq/api" - -class Sidekiq::Monitor - class Status - VALID_SECTIONS = %w[all version overview processes queues] - COL_PAD = 2 - - def display(section = nil) - section ||= "all" - unless VALID_SECTIONS.include? section - puts "I don't know how to check the status of '#{section}'!" - puts "Try one of these: #{VALID_SECTIONS.join(", ")}" - return - end - send(section) - rescue => e - puts "Couldn't get status: #{e}" - end - - def all - version - puts - overview - puts - processes - puts - queues - end - - def version - puts "Sidekiq #{Sidekiq::VERSION}" - puts Time.now.utc - end - - def overview - puts "---- Overview ----" - puts " Processed: #{delimit stats.processed}" - puts " Failed: #{delimit stats.failed}" - puts " Busy: #{delimit stats.workers_size}" - puts " Enqueued: #{delimit stats.enqueued}" - puts " Retries: #{delimit stats.retry_size}" - puts " Scheduled: #{delimit stats.scheduled_size}" - puts " Dead: #{delimit stats.dead_size}" - end - - def processes - puts "---- Processes (#{process_set.size}) ----" - process_set.each_with_index do |process, index| - puts "#{process["identity"]} #{tags_for(process)}" - puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})" - puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)" - puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}" - puts "" unless (index + 1) == process_set.size - end - end - - def queues - puts "---- Queues (#{queue_data.size}) ----" - columns = { - name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD], - size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD], - latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD] - } - columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) } - puts - queue_data.each do |q| - columns.each do |col, (dir, width)| - print q.send(col).public_send(dir, width) - end - puts - end - end - - private - - def delimit(number) - number.to_s.reverse.scan(/.{1,3}/).join(",").reverse - end - - def split_multiline(values, opts = {}) - return "none" unless values - pad = opts[:pad] || 0 - max_length = opts[:max_length] || (80 - pad) - out = [] - line = "" - values.each do |value| - if (line.length + value.length) > max_length - out << line - line = " " * pad - end - line << value + ", " - end - out << line[0..-3] - out.join("\n") - end - - def tags_for(process) - tags = [ - process["tag"], - process["labels"], - (process["quiet"] == "true" ? "quiet" : nil) - ].flatten.compact - tags.any? ? "[#{tags.join("] [")}]" : nil - end - - def time_ago(timestamp) - seconds = Time.now - Time.at(timestamp) - return "just now" if seconds < 60 - return "a minute ago" if seconds < 120 - return "#{seconds.floor / 60} minutes ago" if seconds < 3600 - return "an hour ago" if seconds < 7200 - "#{seconds.floor / 60 / 60} hours ago" - end - - QUEUE_STRUCT = Struct.new(:name, :size, :latency) - def queue_data - @queue_data ||= Sidekiq::Queue.all.map { |q| - QUEUE_STRUCT.new(q.name, q.size.to_s, sprintf("%#.2f", q.latency)) - } - end - - def process_set - @process_set ||= Sidekiq::ProcessSet.new - end - - def stats - @stats ||= Sidekiq::Stats.new - end - end -end diff --git a/lib/sidekiq/paginator.rb b/lib/sidekiq/paginator.rb deleted file mode 100644 index e9417420..00000000 --- a/lib/sidekiq/paginator.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - module Paginator - def page(key, pageidx = 1, page_size = 25, opts = nil) - current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i - pageidx = current_page - 1 - total_size = 0 - items = [] - starting = pageidx * page_size - ending = starting + page_size - 1 - - Sidekiq.redis do |conn| - type = conn.type(key) - rev = opts && opts[:reverse] - - case type - when "zset" - total_size, items = conn.multi { |transaction| - transaction.zcard(key) - if rev - transaction.zrevrange(key, starting, ending, with_scores: true) - else - transaction.zrange(key, starting, ending, with_scores: true) - end - } - [current_page, total_size, items] - when "list" - total_size, items = conn.multi { |transaction| - conn.llen(key) - if rev - transaction.lrange(key, -ending - 1, -starting - 1) - else - transaction.lrange(key, starting, ending) - end - } - items.reverse! if rev - [current_page, total_size, items] - when "none" - [1, 0, []] - else - raise "can't page a #{type}" - end - end - end - end -end diff --git a/lib/sidekiq/processor.rb b/lib/sidekiq/processor.rb deleted file mode 100644 index 39d3752c..00000000 --- a/lib/sidekiq/processor.rb +++ /dev/null @@ -1,280 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/util" -require "sidekiq/fetch" -require "sidekiq/job_logger" -require "sidekiq/job_retry" - -module Sidekiq - ## - # The Processor is a standalone thread which: - # - # 1. fetches a job from Redis - # 2. executes the job - # a. instantiate the Worker - # b. run the middleware chain - # c. call #perform - # - # A Processor can exit due to shutdown (processor_stopped) - # or due to an error during job execution (processor_died) - # - # If an error occurs in the job execution, the - # Processor calls the Manager to create a new one - # to replace itself and exits. - # - class Processor - include Util - - attr_reader :thread - attr_reader :job - - def initialize(mgr, options) - @mgr = mgr - @down = false - @done = false - @job = nil - @thread = nil - @strategy = options[:fetch] - @reloader = options[:reloader] || proc { |&block| block.call } - @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new - @retrier = Sidekiq::JobRetry.new - end - - def terminate(wait = false) - @done = true - return unless @thread - @thread.value if wait - end - - def kill(wait = false) - @done = true - return unless @thread - # unlike the other actors, terminate does not wait - # for the thread to finish because we don't know how - # long the job will take to finish. Instead we - # provide a `kill` method to call after the shutdown - # timeout passes. - @thread.raise ::Sidekiq::Shutdown - @thread.value if wait - end - - def start - @thread ||= safe_thread("processor", &method(:run)) - end - - private unless $TESTING - - def run - process_one until @done - @mgr.processor_stopped(self) - rescue Sidekiq::Shutdown - @mgr.processor_stopped(self) - rescue Exception => ex - @mgr.processor_died(self, ex) - end - - def process_one - @job = fetch - process(@job) if @job - @job = nil - end - - def get_one - work = @strategy.retrieve_work - if @down - logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" } - @down = nil - end - work - rescue Sidekiq::Shutdown - rescue => ex - handle_fetch_exception(ex) - end - - def fetch - j = get_one - if j && @done - j.requeue - nil - else - j - end - end - - def handle_fetch_exception(ex) - unless @down - @down = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - logger.error("Error fetching job: #{ex}") - handle_exception(ex) - end - sleep(1) - nil - end - - def dispatch(job_hash, queue, jobstr) - # since middleware can mutate the job hash - # we need to clone it to report the original - # job structure to the Web UI - # or to push back to redis when retrying. - # To avoid costly and, most of the time, useless cloning here, - # we pass original String of JSON to respected methods - # to re-parse it there if we need access to the original, untouched job - - @job_logger.prepare(job_hash) do - @retrier.global(jobstr, queue) do - @job_logger.call(job_hash, queue) do - stats(jobstr, queue) do - # Rails 5 requires a Reloader to wrap code execution. In order to - # constantize the worker and instantiate an instance, we have to call - # the Reloader. It handles code loading, db connection management, etc. - # Effectively this block denotes a "unit of work" to Rails. - @reloader.call do - klass = constantize(job_hash["class"]) - worker = klass.new - worker.jid = job_hash["jid"] - @retrier.local(worker, jobstr, queue) do - yield worker - end - end - end - end - end - end - end - - def process(work) - jobstr = work.job - queue = work.queue_name - - # Treat malformed JSON as a special case: job goes straight to the morgue. - job_hash = nil - begin - job_hash = Sidekiq.load_json(jobstr) - rescue => ex - handle_exception(ex, {context: "Invalid JSON for job", jobstr: jobstr}) - # we can't notify because the job isn't a valid hash payload. - DeadSet.new.kill(jobstr, notify_failure: false) - return work.acknowledge - end - - ack = false - begin - dispatch(job_hash, queue, jobstr) do |worker| - Sidekiq.server_middleware.invoke(worker, job_hash, queue) do - execute_job(worker, job_hash["args"]) - end - end - ack = true - rescue Sidekiq::Shutdown - # Had to force kill this job because it didn't finish - # within the timeout. Don't acknowledge the work since - # we didn't properly finish it. - rescue Sidekiq::JobRetry::Handled => h - # this is the common case: job raised error and Sidekiq::JobRetry::Handled - # signals that we created a retry successfully. We can acknowlege the job. - ack = true - e = h.cause || h - handle_exception(e, {context: "Job raised exception", job: job_hash, jobstr: jobstr}) - raise e - rescue Exception => ex - # Unexpected error! This is very bad and indicates an exception that got past - # the retry subsystem (e.g. network partition). We won't acknowledge the job - # so it can be rescued when using Sidekiq Pro. - handle_exception(ex, {context: "Internal exception!", job: job_hash, jobstr: jobstr}) - raise ex - ensure - if ack - # We don't want a shutdown signal to interrupt job acknowledgment. - Thread.handle_interrupt(Sidekiq::Shutdown => :never) do - work.acknowledge - end - end - end - end - - def execute_job(worker, cloned_args) - worker.perform(*cloned_args) - end - - # Ruby doesn't provide atomic counters out of the box so we'll - # implement something simple ourselves. - # https://bugs.ruby-lang.org/issues/14706 - class Counter - def initialize - @value = 0 - @lock = Mutex.new - end - - def incr(amount = 1) - @lock.synchronize { @value += amount } - end - - def reset - @lock.synchronize { - val = @value - @value = 0 - val - } - end - end - - # jruby's Hash implementation is not threadsafe, so we wrap it in a mutex here - class SharedWorkerState - def initialize - @worker_state = {} - @lock = Mutex.new - end - - def set(tid, hash) - @lock.synchronize { @worker_state[tid] = hash } - end - - def delete(tid) - @lock.synchronize { @worker_state.delete(tid) } - end - - def dup - @lock.synchronize { @worker_state.dup } - end - - def size - @lock.synchronize { @worker_state.size } - end - - def clear - @lock.synchronize { @worker_state.clear } - end - end - - PROCESSED = Counter.new - FAILURE = Counter.new - WORKER_STATE = SharedWorkerState.new - - def stats(jobstr, queue) - WORKER_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i}) - - begin - yield - rescue Exception - FAILURE.incr - raise - ensure - WORKER_STATE.delete(tid) - PROCESSED.incr - end - end - - def constantize(str) - return Object.const_get(str) unless str.include?("::") - - names = str.split("::") - names.shift if names.empty? || names.first.empty? - - names.inject(Object) do |constant, name| - # the false flag limits search for name to under the constant namespace - # which mimics Rails' behaviour - constant.const_get(name, false) - end - end - end -end diff --git a/lib/sidekiq/rails.rb b/lib/sidekiq/rails.rb deleted file mode 100644 index cd467426..00000000 --- a/lib/sidekiq/rails.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/worker" - -module Sidekiq - class Rails < ::Rails::Engine - class Reloader - def initialize(app = ::Rails.application) - @app = app - end - - def call - @app.reloader.wrap do - yield - end - end - - def inspect - "#" - end - end - - # By including the Options module, we allow AJs to directly control sidekiq features - # via the *sidekiq_options* class method and, for instance, not use AJ's retry system. - # AJ retries don't show up in the Sidekiq UI Retries tab, save any error data, can't be - # manually retried, don't automatically die, etc. - # - # class SomeJob < ActiveJob::Base - # queue_as :default - # sidekiq_options retry: 3, backtrace: 10 - # def perform - # end - # end - initializer "sidekiq.active_job_integration" do - ActiveSupport.on_load(:active_job) do - include ::Sidekiq::Worker::Options unless respond_to?(:sidekiq_options) - end - end - - initializer "sidekiq.rails_logger" do - Sidekiq.configure_server do |_| - # This is the integration code necessary so that if code uses `Rails.logger.info "Hello"`, - # it will appear in the Sidekiq console with all of the job context. See #5021 and - # https://github.com/rails/rails/blob/b5f2b550f69a99336482739000c58e4e04e033aa/railties/lib/rails/commands/server/server_command.rb#L82-L84 - unless ::Rails.logger == ::Sidekiq.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout) - ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(::Sidekiq.logger)) - end - end - end - - # This hook happens after all initializers are run, just before returning - # from config/environment.rb back to sidekiq/cli.rb. - # - # None of this matters on the client-side, only within the Sidekiq process itself. - config.after_initialize do - Sidekiq.configure_server do |_| - Sidekiq.options[:reloader] = Sidekiq::Rails::Reloader.new - end - end - end -end diff --git a/lib/sidekiq/redis_connection.rb b/lib/sidekiq/redis_connection.rb deleted file mode 100644 index d44f0b87..00000000 --- a/lib/sidekiq/redis_connection.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -require "connection_pool" -require "redis" -require "uri" - -module Sidekiq - class RedisConnection - class << self - def create(options = {}) - symbolized_options = options.transform_keys(&:to_sym) - - if !symbolized_options[:url] && (u = determine_redis_provider) - symbolized_options[:url] = u - end - - size = if symbolized_options[:size] - symbolized_options[:size] - elsif Sidekiq.server? - # Give ourselves plenty of connections. pool is lazy - # so we won't create them until we need them. - Sidekiq.options[:concurrency] + 5 - elsif ENV["RAILS_MAX_THREADS"] - Integer(ENV["RAILS_MAX_THREADS"]) - else - 5 - end - - verify_sizing(size, Sidekiq.options[:concurrency]) if Sidekiq.server? - - pool_timeout = symbolized_options[:pool_timeout] || 1 - log_info(symbolized_options) - - ConnectionPool.new(timeout: pool_timeout, size: size) do - build_client(symbolized_options) - end - end - - private - - # Sidekiq needs a lot of concurrent Redis connections. - # - # We need a connection for each Processor. - # We need a connection for Pro's real-time change listener - # We need a connection to various features to call Redis every few seconds: - # - the process heartbeat. - # - enterprise's leader election - # - enterprise's cron support - def verify_sizing(size, concurrency) - raise ArgumentError, "Your Redis connection pool is too small for Sidekiq to work. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2) - end - - def build_client(options) - namespace = options[:namespace] - - client = Redis.new client_opts(options) - if namespace - begin - require "redis/namespace" - Redis::Namespace.new(namespace, redis: client) - rescue LoadError - Sidekiq.logger.error("Your Redis configuration uses the namespace '#{namespace}' but the redis-namespace gem is not included in the Gemfile." \ - "Add the gem to your Gemfile to continue using a namespace. Otherwise, remove the namespace parameter.") - exit(-127) - end - else - client - end - end - - def client_opts(options) - opts = options.dup - if opts[:namespace] - opts.delete(:namespace) - end - - if opts[:network_timeout] - opts[:timeout] = opts[:network_timeout] - opts.delete(:network_timeout) - end - - opts[:driver] ||= Redis::Connection.drivers.last || "ruby" - - # Issue #3303, redis-rb will silently retry an operation. - # This can lead to duplicate jobs if Sidekiq::Client's LPUSH - # is performed twice but I believe this is much, much rarer - # than the reconnect silently fixing a problem; we keep it - # on by default. - opts[:reconnect_attempts] ||= 1 - - opts - end - - def log_info(options) - redacted = "REDACTED" - - # Deep clone so we can muck with these options all we want and exclude - # params from dump-and-load that may contain objects that Marshal is - # unable to safely dump. - keys = options.keys - [:logger, :ssl_params] - scrubbed_options = Marshal.load(Marshal.dump(options.slice(*keys))) - if scrubbed_options[:url] && (uri = URI.parse(scrubbed_options[:url])) && uri.password - uri.password = redacted - scrubbed_options[:url] = uri.to_s - end - if scrubbed_options[:password] - scrubbed_options[:password] = redacted - end - scrubbed_options[:sentinels]&.each do |sentinel| - sentinel[:password] = redacted if sentinel[:password] - end - if Sidekiq.server? - Sidekiq.logger.info("Booting Sidekiq #{Sidekiq::VERSION} with redis options #{scrubbed_options}") - else - Sidekiq.logger.debug("#{Sidekiq::NAME} client with redis options #{scrubbed_options}") - end - end - - def determine_redis_provider - # If you have this in your environment: - # MY_REDIS_URL=redis://hostname.example.com:1238/4 - # then set: - # REDIS_PROVIDER=MY_REDIS_URL - # and Sidekiq will find your custom URL variable with no custom - # initialization code at all. - # - p = ENV["REDIS_PROVIDER"] - if p && p =~ /:/ - raise <<~EOM - REDIS_PROVIDER should be set to the name of the variable which contains the Redis URL, not a URL itself. - Platforms like Heroku will sell addons that publish a *_URL variable. You need to tell Sidekiq with REDIS_PROVIDER, e.g.: - - REDISTOGO_URL=redis://somehost.example.com:6379/4 - REDIS_PROVIDER=REDISTOGO_URL - EOM - end - - ENV[ - p || "REDIS_URL" - ] - end - end - end -end diff --git a/lib/sidekiq/scheduled.rb b/lib/sidekiq/scheduled.rb deleted file mode 100644 index 761dab5d..00000000 --- a/lib/sidekiq/scheduled.rb +++ /dev/null @@ -1,208 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq" -require "sidekiq/util" -require "sidekiq/api" - -module Sidekiq - module Scheduled - SETS = %w[retry schedule] - - class Enq - LUA_ZPOPBYSCORE = <<~LUA - local key, now = KEYS[1], ARGV[1] - local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1) - if jobs[1] then - redis.call("zrem", key, jobs[1]) - return jobs[1] - end - LUA - - def initialize - @done = false - @lua_zpopbyscore_sha = nil - end - - def enqueue_jobs(sorted_sets = SETS) - # A job's "score" in Redis is the time at which it should be processed. - # Just check Redis for the set of jobs with a timestamp before now. - Sidekiq.redis do |conn| - sorted_sets.each do |sorted_set| - # Get next item in the queue with score (time to execute) <= now. - # We need to go through the list one at a time to reduce the risk of something - # going wrong between the time jobs are popped from the scheduled queue and when - # they are pushed onto a work queue and losing the jobs. - while !@done && (job = zpopbyscore(conn, keys: [sorted_set], argv: [Time.now.to_f.to_s])) - Sidekiq::Client.push(Sidekiq.load_json(job)) - Sidekiq.logger.debug { "enqueued #{sorted_set}: #{job}" } - end - end - end - end - - def terminate - @done = true - end - - private - - def zpopbyscore(conn, keys: nil, argv: nil) - if @lua_zpopbyscore_sha.nil? - raw_conn = conn.respond_to?(:redis) ? conn.redis : conn - @lua_zpopbyscore_sha = raw_conn.script(:load, LUA_ZPOPBYSCORE) - end - - conn.evalsha(@lua_zpopbyscore_sha, keys: keys, argv: argv) - rescue Redis::CommandError => e - raise unless e.message.start_with?("NOSCRIPT") - - @lua_zpopbyscore_sha = nil - retry - end - end - - ## - # The Poller checks Redis every N seconds for jobs in the retry or scheduled - # set have passed their timestamp and should be enqueued. If so, it - # just pops the job back onto its original queue so the - # workers can pick it up like any other job. - class Poller - include Util - - INITIAL_WAIT = 10 - - def initialize - @enq = (Sidekiq.options[:scheduled_enq] || Sidekiq::Scheduled::Enq).new - @sleeper = ConnectionPool::TimedStack.new - @done = false - @thread = nil - @count_calls = 0 - end - - # Shut down this instance, will pause until the thread is dead. - def terminate - @done = true - @enq.terminate if @enq.respond_to?(:terminate) - - if @thread - t = @thread - @thread = nil - @sleeper << 0 - t.value - end - end - - def start - @thread ||= safe_thread("scheduler") { - initial_wait - - until @done - enqueue - wait - end - Sidekiq.logger.info("Scheduler exiting...") - } - end - - def enqueue - @enq.enqueue_jobs - rescue => ex - # Most likely a problem with redis networking. - # Punt and try again at the next interval - logger.error ex.message - handle_exception(ex) - end - - private - - def wait - @sleeper.pop(random_poll_interval) - rescue Timeout::Error - # expected - rescue => ex - # if poll_interval_average hasn't been calculated yet, we can - # raise an error trying to reach Redis. - logger.error ex.message - handle_exception(ex) - sleep 5 - end - - def random_poll_interval - # We want one Sidekiq process to schedule jobs every N seconds. We have M processes - # and **don't** want to coordinate. - # - # So in N*M second timespan, we want each process to schedule once. The basic loop is: - # - # * sleep a random amount within that N*M timespan - # * wake up and schedule - # - # We want to avoid one edge case: imagine a set of 2 processes, scheduling every 5 seconds, - # so N*M = 10. Each process decides to randomly sleep 8 seconds, now we've failed to meet - # that 5 second average. Thankfully each schedule cycle will sleep randomly so the next - # iteration could see each process sleep for 1 second, undercutting our average. - # - # So below 10 processes, we special case and ensure the processes sleep closer to the average. - # In the example above, each process should schedule every 10 seconds on average. We special - # case smaller clusters to add 50% so they would sleep somewhere between 5 and 15 seconds. - # As we run more processes, the scheduling interval average will approach an even spread - # between 0 and poll interval so we don't need this artifical boost. - # - if process_count < 10 - # For small clusters, calculate a random interval that is ±50% the desired average. - poll_interval_average * rand + poll_interval_average.to_f / 2 - else - # With 10+ processes, we should have enough randomness to get decent polling - # across the entire timespan - poll_interval_average * rand - end - end - - # We do our best to tune the poll interval to the size of the active Sidekiq - # cluster. If you have 30 processes and poll every 15 seconds, that means one - # Sidekiq is checking Redis every 0.5 seconds - way too often for most people - # and really bad if the retry or scheduled sets are large. - # - # Instead try to avoid polling more than once every 15 seconds. If you have - # 30 Sidekiq processes, we'll poll every 30 * 15 or 450 seconds. - # To keep things statistically random, we'll sleep a random amount between - # 225 and 675 seconds for each poll or 450 seconds on average. Otherwise restarting - # all your Sidekiq processes at the same time will lead to them all polling at - # the same time: the thundering herd problem. - # - # We only do this if poll_interval_average is unset (the default). - def poll_interval_average - Sidekiq.options[:poll_interval_average] ||= scaled_poll_interval - end - - # Calculates an average poll interval based on the number of known Sidekiq processes. - # This minimizes a single point of failure by dispersing check-ins but without taxing - # Redis if you run many Sidekiq processes. - def scaled_poll_interval - process_count * Sidekiq.options[:average_scheduled_poll_interval] - end - - def process_count - # The work buried within Sidekiq::ProcessSet#cleanup can be - # expensive at scale. Cut it down by 90% with this counter. - # NB: This method is only called by the scheduler thread so we - # don't need to worry about the thread safety of +=. - pcount = Sidekiq::ProcessSet.new(@count_calls % 10 == 0).size - pcount = 1 if pcount == 0 - @count_calls += 1 - pcount - end - - def initial_wait - # Have all processes sleep between 5-15 seconds. 10 seconds - # to give time for the heartbeat to register (if the poll interval is going to be calculated by the number - # of workers), and 5 random seconds to ensure they don't all hit Redis at the same time. - total = 0 - total += INITIAL_WAIT unless Sidekiq.options[:poll_interval_average] - total += (5 * rand) - - @sleeper.pop(total) - rescue Timeout::Error - end - end - end -end diff --git a/lib/sidekiq/sd_notify.rb b/lib/sidekiq/sd_notify.rb deleted file mode 100644 index 26f6863c..00000000 --- a/lib/sidekiq/sd_notify.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true - -# The MIT License -# -# Copyright (c) 2017, 2018, 2019, 2020 Agis Anastasopoulos -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -# the Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# This is a copy of https://github.com/agis/ruby-sdnotify as of commit a7d52ee -# The only changes made was "rehoming" it within the Sidekiq module to avoid -# namespace collisions and applying standard's code formatting style. - -require "socket" - -# SdNotify is a pure-Ruby implementation of sd_notify(3). It can be used to -# notify systemd about state changes. Methods of this package are no-op on -# non-systemd systems (eg. Darwin). -# -# The API maps closely to the original implementation of sd_notify(3), -# therefore be sure to check the official man pages prior to using SdNotify. -# -# @see https://www.freedesktop.org/software/systemd/man/sd_notify.html -module Sidekiq - module SdNotify - # Exception raised when there's an error writing to the notification socket - class NotifyError < RuntimeError; end - - READY = "READY=1" - RELOADING = "RELOADING=1" - STOPPING = "STOPPING=1" - STATUS = "STATUS=" - ERRNO = "ERRNO=" - MAINPID = "MAINPID=" - WATCHDOG = "WATCHDOG=1" - FDSTORE = "FDSTORE=1" - - def self.ready(unset_env = false) - notify(READY, unset_env) - end - - def self.reloading(unset_env = false) - notify(RELOADING, unset_env) - end - - def self.stopping(unset_env = false) - notify(STOPPING, unset_env) - end - - # @param status [String] a custom status string that describes the current - # state of the service - def self.status(status, unset_env = false) - notify("#{STATUS}#{status}", unset_env) - end - - # @param errno [Integer] - def self.errno(errno, unset_env = false) - notify("#{ERRNO}#{errno}", unset_env) - end - - # @param pid [Integer] - def self.mainpid(pid, unset_env = false) - notify("#{MAINPID}#{pid}", unset_env) - end - - def self.watchdog(unset_env = false) - notify(WATCHDOG, unset_env) - end - - def self.fdstore(unset_env = false) - notify(FDSTORE, unset_env) - end - - # @return [Boolean] true if the service manager expects watchdog keep-alive - # notification messages to be sent from this process. - # - # If the $WATCHDOG_USEC environment variable is set, - # and the $WATCHDOG_PID variable is unset or set to the PID of the current - # process - # - # @note Unlike sd_watchdog_enabled(3), this method does not mutate the - # environment. - def self.watchdog? - wd_usec = ENV["WATCHDOG_USEC"] - wd_pid = ENV["WATCHDOG_PID"] - - return false unless wd_usec - - begin - wd_usec = Integer(wd_usec) - rescue - return false - end - - return false if wd_usec <= 0 - return true if !wd_pid || wd_pid == $$.to_s - - false - end - - # Notify systemd with the provided state, via the notification socket, if - # any. - # - # Generally this method will be used indirectly through the other methods - # of the library. - # - # @param state [String] - # @param unset_env [Boolean] - # - # @return [Fixnum, nil] the number of bytes written to the notification - # socket or nil if there was no socket to report to (eg. the program wasn't - # started by systemd) - # - # @raise [NotifyError] if there was an error communicating with the systemd - # socket - # - # @see https://www.freedesktop.org/software/systemd/man/sd_notify.html - def self.notify(state, unset_env = false) - sock = ENV["NOTIFY_SOCKET"] - - return nil unless sock - - ENV.delete("NOTIFY_SOCKET") if unset_env - - begin - Addrinfo.unix(sock, :DGRAM).connect do |s| - s.close_on_exec = true - s.write(state) - end - rescue => e - raise NotifyError, "#{e.class}: #{e.message}", e.backtrace - end - end - end -end diff --git a/lib/sidekiq/systemd.rb b/lib/sidekiq/systemd.rb deleted file mode 100644 index 7e6472f5..00000000 --- a/lib/sidekiq/systemd.rb +++ /dev/null @@ -1,24 +0,0 @@ -# -# Sidekiq's systemd integration allows Sidekiq to inform systemd: -# 1. when it has successfully started -# 2. when it is starting shutdown -# 3. periodically for a liveness check with a watchdog thread -# -module Sidekiq - def self.start_watchdog - usec = Integer(ENV["WATCHDOG_USEC"]) - return Sidekiq.logger.error("systemd Watchdog too fast: " + usec) if usec < 1_000_000 - - sec_f = usec / 1_000_000.0 - # "It is recommended that a daemon sends a keep-alive notification message - # to the service manager every half of the time returned here." - ping_f = sec_f / 2 - Sidekiq.logger.info "Pinging systemd watchdog every #{ping_f.round(1)} sec" - Thread.new do - loop do - sleep ping_f - Sidekiq::SdNotify.watchdog - end - end - end -end diff --git a/lib/sidekiq/testing.rb b/lib/sidekiq/testing.rb deleted file mode 100644 index 25c0ee28..00000000 --- a/lib/sidekiq/testing.rb +++ /dev/null @@ -1,342 +0,0 @@ -# frozen_string_literal: true - -require "securerandom" -require "sidekiq" - -module Sidekiq - class Testing - class << self - attr_accessor :__test_mode - - def __set_test_mode(mode) - if block_given? - current_mode = __test_mode - begin - self.__test_mode = mode - yield - ensure - self.__test_mode = current_mode - end - else - self.__test_mode = mode - end - end - - def disable!(&block) - __set_test_mode(:disable, &block) - end - - def fake!(&block) - __set_test_mode(:fake, &block) - end - - def inline!(&block) - __set_test_mode(:inline, &block) - end - - def enabled? - __test_mode != :disable - end - - def disabled? - __test_mode == :disable - end - - def fake? - __test_mode == :fake - end - - def inline? - __test_mode == :inline - end - - def server_middleware - @server_chain ||= Middleware::Chain.new - yield @server_chain if block_given? - @server_chain - end - - def constantize(str) - names = str.split("::") - names.shift if names.empty? || names.first.empty? - - names.inject(Object) do |constant, name| - constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) - end - end - end - end - - # Default to fake testing to keep old behavior - Sidekiq::Testing.fake! - - class EmptyQueueError < RuntimeError; end - - module TestingClient - def raw_push(payloads) - if Sidekiq::Testing.fake? - payloads.each do |job| - job = Sidekiq.load_json(Sidekiq.dump_json(job)) - job["enqueued_at"] = Time.now.to_f unless job["at"] - Queues.push(job["queue"], job["class"], job) - end - true - elsif Sidekiq::Testing.inline? - payloads.each do |job| - klass = Sidekiq::Testing.constantize(job["class"]) - job["id"] ||= SecureRandom.hex(12) - job_hash = Sidekiq.load_json(Sidekiq.dump_json(job)) - klass.process_job(job_hash) - end - true - else - super - end - end - end - - Sidekiq::Client.prepend TestingClient - - module Queues - ## - # The Queues class is only for testing the fake queue implementation. - # There are 2 data structures involved in tandem. This is due to the - # Rspec syntax of change(QueueWorker.jobs, :size). It keeps a reference - # to the array. Because the array was dervied from a filter of the total - # jobs enqueued, it appeared as though the array didn't change. - # - # To solve this, we'll keep 2 hashes containing the jobs. One with keys based - # on the queue, and another with keys of the worker names, so the array for - # QueueWorker.jobs is a straight reference to a real array. - # - # Queue-based hash: - # - # { - # "default"=>[ - # { - # "class"=>"TestTesting::QueueWorker", - # "args"=>[1, 2], - # "retry"=>true, - # "queue"=>"default", - # "jid"=>"abc5b065c5c4b27fc1102833", - # "created_at"=>1447445554.419934 - # } - # ] - # } - # - # Worker-based hash: - # - # { - # "TestTesting::QueueWorker"=>[ - # { - # "class"=>"TestTesting::QueueWorker", - # "args"=>[1, 2], - # "retry"=>true, - # "queue"=>"default", - # "jid"=>"abc5b065c5c4b27fc1102833", - # "created_at"=>1447445554.419934 - # } - # ] - # } - # - # Example: - # - # require 'sidekiq/testing' - # - # assert_equal 0, Sidekiq::Queues["default"].size - # HardWorker.perform_async(:something) - # assert_equal 1, Sidekiq::Queues["default"].size - # assert_equal :something, Sidekiq::Queues["default"].first['args'][0] - # - # You can also clear all workers' jobs: - # - # assert_equal 0, Sidekiq::Queues["default"].size - # HardWorker.perform_async(:something) - # Sidekiq::Queues.clear_all - # assert_equal 0, Sidekiq::Queues["default"].size - # - # This can be useful to make sure jobs don't linger between tests: - # - # RSpec.configure do |config| - # config.before(:each) do - # Sidekiq::Queues.clear_all - # end - # end - # - class << self - def [](queue) - jobs_by_queue[queue] - end - - def push(queue, klass, job) - jobs_by_queue[queue] << job - jobs_by_worker[klass] << job - end - - def jobs_by_queue - @jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] } - end - - def jobs_by_worker - @jobs_by_worker ||= Hash.new { |hash, key| hash[key] = [] } - end - - def delete_for(jid, queue, klass) - jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid } - jobs_by_worker[klass].delete_if { |job| job["jid"] == jid } - end - - def clear_for(queue, klass) - jobs_by_queue[queue].clear - jobs_by_worker[klass].clear - end - - def clear_all - jobs_by_queue.clear - jobs_by_worker.clear - end - end - end - - module Worker - ## - # The Sidekiq testing infrastructure overrides perform_async - # so that it does not actually touch the network. Instead it - # stores the asynchronous jobs in a per-class array so that - # their presence/absence can be asserted by your tests. - # - # This is similar to ActionMailer's :test delivery_method and its - # ActionMailer::Base.deliveries array. - # - # Example: - # - # require 'sidekiq/testing' - # - # assert_equal 0, HardWorker.jobs.size - # HardWorker.perform_async(:something) - # assert_equal 1, HardWorker.jobs.size - # assert_equal :something, HardWorker.jobs[0]['args'][0] - # - # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size - # MyMailer.delay.send_welcome_email('foo@example.com') - # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size - # - # You can also clear and drain all workers' jobs: - # - # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size - # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size - # - # MyMailer.delay.send_welcome_email('foo@example.com') - # MyModel.delay.do_something_hard - # - # assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size - # assert_equal 1, Sidekiq::Extensions::DelayedModel.jobs.size - # - # Sidekiq::Worker.clear_all # or .drain_all - # - # assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size - # assert_equal 0, Sidekiq::Extensions::DelayedModel.jobs.size - # - # This can be useful to make sure jobs don't linger between tests: - # - # RSpec.configure do |config| - # config.before(:each) do - # Sidekiq::Worker.clear_all - # end - # end - # - # or for acceptance testing, i.e. with cucumber: - # - # AfterStep do - # Sidekiq::Worker.drain_all - # end - # - # When I sign up as "foo@example.com" - # Then I should receive a welcome email to "foo@example.com" - # - module ClassMethods - # Queue for this worker - def queue - get_sidekiq_options["queue"] - end - - # Jobs queued for this worker - def jobs - Queues.jobs_by_worker[to_s] - end - - # Clear all jobs for this worker - def clear - Queues.clear_for(queue, to_s) - end - - # Drain and run all jobs for this worker - def drain - while jobs.any? - next_job = jobs.first - Queues.delete_for(next_job["jid"], next_job["queue"], to_s) - process_job(next_job) - end - end - - # Pop out a single job and perform it - def perform_one - raise(EmptyQueueError, "perform_one called with empty job queue") if jobs.empty? - next_job = jobs.first - Queues.delete_for(next_job["jid"], queue, to_s) - process_job(next_job) - end - - def process_job(job) - worker = new - worker.jid = job["jid"] - worker.bid = job["bid"] if worker.respond_to?(:bid=) - Sidekiq::Testing.server_middleware.invoke(worker, job, job["queue"]) do - execute_job(worker, job["args"]) - end - end - - def execute_job(worker, args) - worker.perform(*args) - end - end - - class << self - def jobs # :nodoc: - Queues.jobs_by_queue.values.flatten - end - - # Clear all queued jobs across all workers - def clear_all - Queues.clear_all - end - - # Drain all queued jobs across all workers - def drain_all - while jobs.any? - worker_classes = jobs.map { |job| job["class"] }.uniq - - worker_classes.each do |worker_class| - Sidekiq::Testing.constantize(worker_class).drain - end - end - end - end - end - - module TestingExtensions - def jobs_for(klass) - jobs.select do |job| - marshalled = job["args"][0] - marshalled.index(klass.to_s) && YAML.load(marshalled)[0] == klass - end - end - end - - Sidekiq::Extensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedMailer) - Sidekiq::Extensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::Extensions::DelayedModel) -end - -if defined?(::Rails) && Rails.respond_to?(:env) && !Rails.env.test? && !$TESTING - warn("⛔️ WARNING: Sidekiq testing API enabled, but this is not the test environment. Your jobs will not go to Redis.", uplevel: 1) -end diff --git a/lib/sidekiq/testing/inline.rb b/lib/sidekiq/testing/inline.rb deleted file mode 100644 index d83e929c..00000000 --- a/lib/sidekiq/testing/inline.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/testing" - -## -# The Sidekiq inline infrastructure overrides perform_async so that it -# actually calls perform instead. This allows workers to be run inline in a -# testing environment. -# -# This is similar to `Resque.inline = true` functionality. -# -# Example: -# -# require 'sidekiq/testing/inline' -# -# $external_variable = 0 -# -# class ExternalWorker -# include Sidekiq::Worker -# -# def perform -# $external_variable = 1 -# end -# end -# -# assert_equal 0, $external_variable -# ExternalWorker.perform_async -# assert_equal 1, $external_variable -# -Sidekiq::Testing.inline! diff --git a/lib/sidekiq/util.rb b/lib/sidekiq/util.rb deleted file mode 100644 index 42d4055a..00000000 --- a/lib/sidekiq/util.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -require "forwardable" -require "socket" -require "securerandom" -require "sidekiq/exception_handler" - -module Sidekiq - ## - # This module is part of Sidekiq core and not intended for extensions. - # - - class RingBuffer - include Enumerable - extend Forwardable - def_delegators :@buf, :[], :each, :size - - def initialize(size, default = 0) - @size = size - @buf = Array.new(size, default) - @index = 0 - end - - def <<(element) - @buf[@index % @size] = element - @index += 1 - element - end - - def buffer - @buf - end - - def reset(default = 0) - @buf.fill(default) - end - end - - module Util - include ExceptionHandler - - # hack for quicker development / testing environment #2774 - PAUSE_TIME = $stdout.tty? ? 0.1 : 0.5 - - # Wait for the orblock to be true or the deadline passed. - def wait_for(deadline, &condblock) - remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - while remaining > PAUSE_TIME - return if condblock.call - sleep PAUSE_TIME - remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - end - end - - def watchdog(last_words) - yield - rescue Exception => ex - handle_exception(ex, {context: last_words}) - raise ex - end - - def safe_thread(name, &block) - Thread.new do - Thread.current.name = name - watchdog(name, &block) - end - end - - def logger - Sidekiq.logger - end - - def redis(&block) - Sidekiq.redis(&block) - end - - def tid - Thread.current["sidekiq_tid"] ||= (Thread.current.object_id ^ ::Process.pid).to_s(36) - end - - def hostname - ENV["DYNO"] || Socket.gethostname - end - - def process_nonce - @@process_nonce ||= SecureRandom.hex(6) - end - - def identity - @@identity ||= "#{hostname}:#{::Process.pid}:#{process_nonce}" - end - - def fire_event(event, options = {}) - reverse = options[:reverse] - reraise = options[:reraise] - - arr = Sidekiq.options[:lifecycle_events][event] - arr.reverse! if reverse - arr.each do |block| - block.call - rescue => ex - handle_exception(ex, {context: "Exception during Sidekiq lifecycle event.", event: event}) - raise ex if reraise - end - arr.clear - end - end -end diff --git a/lib/sidekiq/version.rb b/lib/sidekiq/version.rb deleted file mode 100644 index a3b28e61..00000000 --- a/lib/sidekiq/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - VERSION = "6.4.1" -end diff --git a/lib/sidekiq/web.rb b/lib/sidekiq/web.rb deleted file mode 100644 index 04e7ef45..00000000 --- a/lib/sidekiq/web.rb +++ /dev/null @@ -1,169 +0,0 @@ -# frozen_string_literal: true - -require "erb" - -require "sidekiq" -require "sidekiq/api" -require "sidekiq/paginator" -require "sidekiq/web/helpers" - -require "sidekiq/web/router" -require "sidekiq/web/action" -require "sidekiq/web/application" -require "sidekiq/web/csrf_protection" - -require "rack/content_length" -require "rack/builder" -require "rack/static" - -module Sidekiq - class Web - ROOT = File.expand_path("#{File.dirname(__FILE__)}/../../web") - VIEWS = "#{ROOT}/views" - LOCALES = ["#{ROOT}/locales"] - LAYOUT = "#{VIEWS}/layout.erb" - ASSETS = "#{ROOT}/assets" - - DEFAULT_TABS = { - "Dashboard" => "", - "Busy" => "busy", - "Queues" => "queues", - "Retries" => "retries", - "Scheduled" => "scheduled", - "Dead" => "morgue" - } - - class << self - def settings - self - end - - def default_tabs - DEFAULT_TABS - end - - def custom_tabs - @custom_tabs ||= {} - end - alias_method :tabs, :custom_tabs - - def locales - @locales ||= LOCALES - end - - def views - @views ||= VIEWS - end - - def enable(*opts) - opts.each { |key| set(key, true) } - end - - def disable(*opts) - opts.each { |key| set(key, false) } - end - - def middlewares - @middlewares ||= [] - end - - def use(*args, &block) - middlewares << [args, block] - end - - def set(attribute, value) - send(:"#{attribute}=", value) - end - - def sessions=(val) - puts "WARNING: Sidekiq::Web.sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}" - end - - def session_secret=(val) - puts "WARNING: Sidekiq::Web.session_secret= is no longer relevant and will be removed in Sidekiq 7.0. #{caller(1..1).first}" - end - - attr_accessor :app_url, :redis_pool - attr_writer :locales, :views - end - - def self.inherited(child) - child.app_url = app_url - child.redis_pool = redis_pool - end - - def settings - self.class.settings - end - - def middlewares - @middlewares ||= self.class.middlewares - end - - def use(*args, &block) - middlewares << [args, block] - end - - def call(env) - app.call(env) - end - - def self.call(env) - @app ||= new - @app.call(env) - end - - def app - @app ||= build - end - - def enable(*opts) - opts.each { |key| set(key, true) } - end - - def disable(*opts) - opts.each { |key| set(key, false) } - end - - def set(attribute, value) - send(:"#{attribute}=", value) - end - - def sessions=(val) - puts "Sidekiq::Web#sessions= is no longer relevant and will be removed in Sidekiq 7.0. #{caller[2..2].first}" - end - - def self.register(extension) - extension.registered(WebApplication) - end - - private - - def build - klass = self.class - m = middlewares - - rules = [] - rules = [[:all, {"Cache-Control" => "public, max-age=86400"}]] unless ENV["SIDEKIQ_WEB_TESTING"] - - ::Rack::Builder.new do - use Rack::Static, urls: ["/stylesheets", "/images", "/javascripts"], - root: ASSETS, - cascade: true, - header_rules: rules - m.each { |middleware, block| use(*middleware, &block) } - use Sidekiq::Web::CsrfProtection unless $TESTING - run WebApplication.new(klass) - end - end - end - - Sidekiq::WebApplication.helpers WebHelpers - Sidekiq::WebApplication.helpers Sidekiq::Paginator - - Sidekiq::WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _render - #{ERB.new(File.read(Web::LAYOUT)).src} - end - RUBY -end diff --git a/lib/sidekiq/web/action.rb b/lib/sidekiq/web/action.rb deleted file mode 100644 index 48133685..00000000 --- a/lib/sidekiq/web/action.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - class WebAction - RACK_SESSION = "rack.session" - - attr_accessor :env, :block, :type - - def settings - Web.settings - end - - def request - @request ||= ::Rack::Request.new(env) - end - - def halt(res) - throw :halt, [res, {"Content-Type" => "text/plain"}, [res.to_s]] - end - - def redirect(location) - throw :halt, [302, {"Location" => "#{request.base_url}#{location}"}, []] - end - - def params - indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key } - - indifferent_hash.merge! request.params - route_params.each { |k, v| indifferent_hash[k.to_s] = v } - - indifferent_hash - end - - def route_params - env[WebRouter::ROUTE_PARAMS] - end - - def session - env[RACK_SESSION] - end - - def erb(content, options = {}) - if content.is_a? Symbol - unless respond_to?(:"_erb_#{content}") - src = ERB.new(File.read("#{Web.settings.views}/#{content}.erb")).src - WebAction.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _erb_#{content} - #{src} - end - RUBY - end - end - - if @_erb - _erb(content, options[:locals]) - else - @_erb = true - content = _erb(content, options[:locals]) - - _render { content } - end - end - - def render(engine, content, options = {}) - raise "Only erb templates are supported" if engine != :erb - - erb(content, options) - end - - def json(payload) - [200, {"Content-Type" => "application/json", "Cache-Control" => "private, no-store"}, [Sidekiq.dump_json(payload)]] - end - - def initialize(env, block) - @_erb = false - @env = env - @block = block - @files ||= {} - end - - private - - def _erb(file, locals) - locals&.each { |k, v| define_singleton_method(k) { v } unless singleton_methods.include? k } - - if file.is_a?(String) - ERB.new(file).result(binding) - else - send(:"_erb_#{file}") - end - end - end -end diff --git a/lib/sidekiq/web/application.rb b/lib/sidekiq/web/application.rb deleted file mode 100644 index 8debac91..00000000 --- a/lib/sidekiq/web/application.rb +++ /dev/null @@ -1,366 +0,0 @@ -# frozen_string_literal: true - -module Sidekiq - class WebApplication - extend WebRouter - - REDIS_KEYS = %w[redis_version uptime_in_days connected_clients used_memory_human used_memory_peak_human] - CSP_HEADER = [ - "default-src 'self' https: http:", - "child-src 'self'", - "connect-src 'self' https: http: wss: ws:", - "font-src 'self' https: http:", - "frame-src 'self'", - "img-src 'self' https: http: data:", - "manifest-src 'self'", - "media-src 'self'", - "object-src 'none'", - "script-src 'self' https: http: 'unsafe-inline'", - "style-src 'self' https: http: 'unsafe-inline'", - "worker-src 'self'", - "base-uri 'self'" - ].join("; ").freeze - - def initialize(klass) - @klass = klass - end - - def settings - @klass.settings - end - - def self.settings - Sidekiq::Web.settings - end - - def self.tabs - Sidekiq::Web.tabs - end - - def self.set(key, val) - # nothing, backwards compatibility - end - - head "/" do - # HEAD / is the cheapest heartbeat possible, - # it hits Redis to ensure connectivity - Sidekiq.redis { |c| c.llen("queue:default") } - "" - end - - get "/" do - @redis_info = redis_info.select { |k, v| REDIS_KEYS.include? k } - days = (params["days"] || 30).to_i - return halt(401) if days < 1 || days > 180 - - stats_history = Sidekiq::Stats::History.new(days) - @processed_history = stats_history.processed - @failed_history = stats_history.failed - - erb(:dashboard) - end - - get "/busy" do - erb(:busy) - end - - post "/busy" do - if params["identity"] - p = Sidekiq::Process.new("identity" => params["identity"]) - p.quiet! if params["quiet"] - p.stop! if params["stop"] - else - processes.each do |pro| - pro.quiet! if params["quiet"] - pro.stop! if params["stop"] - end - end - - redirect "#{root_path}busy" - end - - get "/queues" do - @queues = Sidekiq::Queue.all - - erb(:queues) - end - - QUEUE_NAME = /\A[a-z_:.\-0-9]+\z/i - - get "/queues/:name" do - @name = route_params[:name] - - halt(404) if !@name || @name !~ QUEUE_NAME - - @count = (params["count"] || 25).to_i - @queue = Sidekiq::Queue.new(@name) - (@current_page, @total_size, @jobs) = page("queue:#{@name}", params["page"], @count, reverse: params["direction"] == "asc") - @jobs = @jobs.map { |msg| Sidekiq::JobRecord.new(msg, @name) } - - erb(:queue) - end - - post "/queues/:name" do - queue = Sidekiq::Queue.new(route_params[:name]) - - if Sidekiq.pro? && params["pause"] - queue.pause! - elsif Sidekiq.pro? && params["unpause"] - queue.unpause! - else - queue.clear - end - - redirect "#{root_path}queues" - end - - post "/queues/:name/delete" do - name = route_params[:name] - Sidekiq::JobRecord.new(params["key_val"], name).delete - - redirect_with_query("#{root_path}queues/#{CGI.escape(name)}") - end - - get "/morgue" do - @count = (params["count"] || 25).to_i - (@current_page, @total_size, @dead) = page("dead", params["page"], @count, reverse: true) - @dead = @dead.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - - erb(:morgue) - end - - get "/morgue/:key" do - key = route_params[:key] - halt(404) unless key - - @dead = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first - - if @dead.nil? - redirect "#{root_path}morgue" - else - erb(:dead) - end - end - - post "/morgue" do - redirect(request.path) unless params["key"] - - params["key"].each do |key| - job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first - retry_or_delete_or_kill job, params if job - end - - redirect_with_query("#{root_path}morgue") - end - - post "/morgue/all/delete" do - Sidekiq::DeadSet.new.clear - - redirect "#{root_path}morgue" - end - - post "/morgue/all/retry" do - Sidekiq::DeadSet.new.retry_all - - redirect "#{root_path}morgue" - end - - post "/morgue/:key" do - key = route_params[:key] - halt(404) unless key - - job = Sidekiq::DeadSet.new.fetch(*parse_params(key)).first - retry_or_delete_or_kill job, params if job - - redirect_with_query("#{root_path}morgue") - end - - get "/retries" do - @count = (params["count"] || 25).to_i - (@current_page, @total_size, @retries) = page("retry", params["page"], @count) - @retries = @retries.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - - erb(:retries) - end - - get "/retries/:key" do - @retry = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first - - if @retry.nil? - redirect "#{root_path}retries" - else - erb(:retry) - end - end - - post "/retries" do - redirect(request.path) unless params["key"] - - params["key"].each do |key| - job = Sidekiq::RetrySet.new.fetch(*parse_params(key)).first - retry_or_delete_or_kill job, params if job - end - - redirect_with_query("#{root_path}retries") - end - - post "/retries/all/delete" do - Sidekiq::RetrySet.new.clear - - redirect "#{root_path}retries" - end - - post "/retries/all/retry" do - Sidekiq::RetrySet.new.retry_all - - redirect "#{root_path}retries" - end - - post "/retries/all/kill" do - Sidekiq::RetrySet.new.kill_all - - redirect "#{root_path}retries" - end - - post "/retries/:key" do - job = Sidekiq::RetrySet.new.fetch(*parse_params(route_params[:key])).first - - retry_or_delete_or_kill job, params if job - - redirect_with_query("#{root_path}retries") - end - - get "/scheduled" do - @count = (params["count"] || 25).to_i - (@current_page, @total_size, @scheduled) = page("schedule", params["page"], @count) - @scheduled = @scheduled.map { |msg, score| Sidekiq::SortedEntry.new(nil, score, msg) } - - erb(:scheduled) - end - - get "/scheduled/:key" do - @job = Sidekiq::ScheduledSet.new.fetch(*parse_params(route_params[:key])).first - - if @job.nil? - redirect "#{root_path}scheduled" - else - erb(:scheduled_job_info) - end - end - - post "/scheduled" do - redirect(request.path) unless params["key"] - - params["key"].each do |key| - job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first - delete_or_add_queue job, params if job - end - - redirect_with_query("#{root_path}scheduled") - end - - post "/scheduled/:key" do - key = route_params[:key] - halt(404) unless key - - job = Sidekiq::ScheduledSet.new.fetch(*parse_params(key)).first - delete_or_add_queue job, params if job - - redirect_with_query("#{root_path}scheduled") - end - - get "/dashboard/stats" do - redirect "#{root_path}stats" - end - - get "/stats" do - sidekiq_stats = Sidekiq::Stats.new - redis_stats = redis_info.select { |k, v| REDIS_KEYS.include? k } - json( - sidekiq: { - processed: sidekiq_stats.processed, - failed: sidekiq_stats.failed, - busy: sidekiq_stats.workers_size, - processes: sidekiq_stats.processes_size, - enqueued: sidekiq_stats.enqueued, - scheduled: sidekiq_stats.scheduled_size, - retries: sidekiq_stats.retry_size, - dead: sidekiq_stats.dead_size, - default_latency: sidekiq_stats.default_queue_latency - }, - redis: redis_stats, - server_utc_time: server_utc_time - ) - end - - get "/stats/queues" do - json Sidekiq::Stats::Queues.new.lengths - end - - def call(env) - action = self.class.match(env) - return [404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found"]] unless action - - app = @klass - resp = catch(:halt) do - self.class.run_befores(app, action) - action.instance_exec env, &action.block - ensure - self.class.run_afters(app, action) - end - - case resp - when Array - # redirects go here - resp - else - # rendered content goes here - headers = { - "Content-Type" => "text/html", - "Cache-Control" => "private, no-store", - "Content-Language" => action.locale, - "Content-Security-Policy" => CSP_HEADER - } - # we'll let Rack calculate Content-Length for us. - [200, headers, [resp]] - end - end - - def self.helpers(mod = nil, &block) - if block - WebAction.class_eval(&block) - else - WebAction.send(:include, mod) - end - end - - def self.before(path = nil, &block) - befores << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block] - end - - def self.after(path = nil, &block) - afters << [path && Regexp.new("\\A#{path.gsub("*", ".*")}\\z"), block] - end - - def self.run_befores(app, action) - run_hooks(befores, app, action) - end - - def self.run_afters(app, action) - run_hooks(afters, app, action) - end - - def self.run_hooks(hooks, app, action) - hooks.select { |p, _| !p || p =~ action.env[WebRouter::PATH_INFO] } - .each { |_, b| action.instance_exec(action.env, app, &b) } - end - - def self.befores - @befores ||= [] - end - - def self.afters - @afters ||= [] - end - end -end diff --git a/lib/sidekiq/web/csrf_protection.rb b/lib/sidekiq/web/csrf_protection.rb deleted file mode 100644 index 2a260a59..00000000 --- a/lib/sidekiq/web/csrf_protection.rb +++ /dev/null @@ -1,180 +0,0 @@ -# frozen_string_literal: true - -# this file originally based on authenticity_token.rb from the sinatra/rack-protection project -# -# The MIT License (MIT) -# -# Copyright (c) 2011-2017 Konstantin Haase -# Copyright (c) 2015-2017 Zachary Scott -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# 'Software'), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -require "securerandom" -require "base64" -require "rack/request" - -module Sidekiq - class Web - class CsrfProtection - def initialize(app, options = nil) - @app = app - end - - def call(env) - accept?(env) ? admit(env) : deny(env) - end - - private - - def admit(env) - # On each successful request, we create a fresh masked token - # which will be used in any forms rendered for this request. - s = session(env) - s[:csrf] ||= SecureRandom.base64(TOKEN_LENGTH) - env[:csrf_token] = mask_token(s[:csrf]) - @app.call(env) - end - - def safe?(env) - %w[GET HEAD OPTIONS TRACE].include? env["REQUEST_METHOD"] - end - - def logger(env) - @logger ||= (env["rack.logger"] || ::Logger.new(env["rack.errors"])) - end - - def deny(env) - logger(env).warn "attack prevented by #{self.class}" - [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] - end - - def session(env) - env["rack.session"] || fail(<<~EOM) - Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app, - make sure you mount Sidekiq::Web *inside* your application routes: - - - Rails.application.routes.draw do - mount Sidekiq::Web => "/sidekiq" - .... - end - - - If this is a Rails app in API mode, you need to enable sessions. - - https://guides.rubyonrails.org/api_app.html#using-session-middlewares - - If this is a bare Rack app, use a session middleware before Sidekiq::Web: - - # first, use IRB to create a shared secret key for sessions and commit it - require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) } - - # now use the secret with a session cookie middleware - use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400 - run Sidekiq::Web - - EOM - end - - def accept?(env) - return true if safe?(env) - - giventoken = ::Rack::Request.new(env).params["authenticity_token"] - valid_token?(env, giventoken) - end - - TOKEN_LENGTH = 32 - - # Checks that the token given to us as a parameter matches - # the token stored in the session. - def valid_token?(env, giventoken) - return false if giventoken.nil? || giventoken.empty? - - begin - token = decode_token(giventoken) - rescue ArgumentError # client input is invalid - return false - end - - sess = session(env) - localtoken = sess[:csrf] - - # Checks that Rack::Session::Cookie actualy contains the csrf toekn - return false if localtoken.nil? - - # Rotate the session token after every use - sess[:csrf] = SecureRandom.base64(TOKEN_LENGTH) - - # See if it's actually a masked token or not. We should be able - # to handle any unmasked tokens that we've issued without error. - - if unmasked_token?(token) - compare_with_real_token token, localtoken - elsif masked_token?(token) - unmasked = unmask_token(token) - compare_with_real_token unmasked, localtoken - else - false # Token is malformed - end - end - - # Creates a masked version of the authenticity token that varies - # on each request. The masking is used to mitigate SSL attacks - # like BREACH. - def mask_token(token) - token = decode_token(token) - one_time_pad = SecureRandom.random_bytes(token.length) - encrypted_token = xor_byte_strings(one_time_pad, token) - masked_token = one_time_pad + encrypted_token - Base64.strict_encode64(masked_token) - end - - # Essentially the inverse of +mask_token+. - def unmask_token(masked_token) - # Split the token into the one-time pad and the encrypted - # value and decrypt it - token_length = masked_token.length / 2 - one_time_pad = masked_token[0...token_length] - encrypted_token = masked_token[token_length..-1] - xor_byte_strings(one_time_pad, encrypted_token) - end - - def unmasked_token?(token) - token.length == TOKEN_LENGTH - end - - def masked_token?(token) - token.length == TOKEN_LENGTH * 2 - end - - def compare_with_real_token(token, local) - ::Rack::Utils.secure_compare(token.to_s, decode_token(local).to_s) - end - - def decode_token(token) - Base64.strict_decode64(token) - end - - def xor_byte_strings(s1, s2) - s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack("c*") - end - end - end -end diff --git a/lib/sidekiq/web/helpers.rb b/lib/sidekiq/web/helpers.rb deleted file mode 100644 index efb5f1b3..00000000 --- a/lib/sidekiq/web/helpers.rb +++ /dev/null @@ -1,342 +0,0 @@ -# frozen_string_literal: true - -require "uri" -require "set" -require "yaml" -require "cgi" - -module Sidekiq - # This is not a public API - module WebHelpers - def strings(lang) - @strings ||= {} - - # Allow sidekiq-web extensions to add locale paths - # so extensions can be localized - @strings[lang] ||= settings.locales.each_with_object({}) do |path, global| - find_locale_files(lang).each do |file| - strs = YAML.load(File.open(file)) - global.merge!(strs[lang]) - end - end - end - - def singularize(str, count) - if count == 1 && str.respond_to?(:singularize) # rails - str.singularize - else - str - end - end - - def clear_caches - @strings = nil - @locale_files = nil - @available_locales = nil - end - - def locale_files - @locale_files ||= settings.locales.flat_map { |path| - Dir["#{path}/*.yml"] - } - end - - def available_locales - @available_locales ||= locale_files.map { |path| File.basename(path, ".yml") }.uniq - end - - def find_locale_files(lang) - locale_files.select { |file| file =~ /\/#{lang}\.yml$/ } - end - - # This is a hook for a Sidekiq Pro feature. Please don't touch. - def filtering(*) - end - - # This view helper provide ability display you html code in - # to head of page. Example: - # - # <% add_to_head do %> - # - # - # <% end %> - # - def add_to_head - @head_html ||= [] - @head_html << yield.dup if block_given? - end - - def display_custom_head - @head_html.join if defined?(@head_html) - end - - def text_direction - get_locale["TextDirection"] || "ltr" - end - - def rtl? - text_direction == "rtl" - end - - # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 - def user_preferred_languages - languages = env["HTTP_ACCEPT_LANGUAGE"] - languages.to_s.downcase.gsub(/\s+/, "").split(",").map { |language| - locale, quality = language.split(";q=", 2) - locale = nil if locale == "*" # Ignore wildcards - quality = quality ? quality.to_f : 1.0 - [locale, quality] - }.sort { |(_, left), (_, right)| - right <=> left - }.map(&:first).compact - end - - # Given an Accept-Language header like "fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2" - # this method will try to best match the available locales to the user's preferred languages. - # - # Inspiration taken from https://github.com/iain/http_accept_language/blob/master/lib/http_accept_language/parser.rb - def locale - @locale ||= begin - matched_locale = user_preferred_languages.map { |preferred| - preferred_language = preferred.split("-", 2).first - - lang_group = available_locales.select { |available| - preferred_language == available.split("-", 2).first - } - - lang_group.find { |lang| lang == preferred } || lang_group.min_by(&:length) - }.compact.first - - matched_locale || "en" - end - end - - # within is used by Sidekiq Pro - def display_tags(job, within = nil) - job.tags.map { |tag| - "#{::Rack::Utils.escape_html(tag)}" - }.join(" ") - end - - # mperham/sidekiq#3243 - def unfiltered? - yield unless env["PATH_INFO"].start_with?("/filter/") - end - - def get_locale - strings(locale) - end - - def t(msg, options = {}) - string = get_locale[msg] || strings("en")[msg] || msg - if options.empty? - string - else - string % options - end - end - - def sort_direction_label - params[:direction] == "asc" ? "↑" : "↓" - end - - def workers - @workers ||= Sidekiq::Workers.new - end - - def processes - @processes ||= Sidekiq::ProcessSet.new - end - - def stats - @stats ||= Sidekiq::Stats.new - end - - def redis_connection - Sidekiq.redis do |conn| - conn.connection[:id] - end - end - - def namespace - @ns ||= Sidekiq.redis { |conn| conn.respond_to?(:namespace) ? conn.namespace : nil } - end - - def redis_info - Sidekiq.redis_info - end - - def root_path - "#{env["SCRIPT_NAME"]}/" - end - - def current_path - @current_path ||= request.path_info.gsub(/^\//, "") - end - - def current_status - workers.size == 0 ? "idle" : "active" - end - - def relative_time(time) - stamp = time.getutc.iso8601 - %() - end - - def job_params(job, score) - "#{score}-#{job["jid"]}" - end - - def parse_params(params) - score, jid = params.split("-", 2) - [score.to_f, jid] - end - - SAFE_QPARAMS = %w[page direction] - - # Merge options with current params, filter safe params, and stringify to query string - def qparams(options) - stringified_options = options.transform_keys(&:to_s) - - to_query_string(params.merge(stringified_options)) - end - - def to_query_string(params) - params.map { |key, value| - SAFE_QPARAMS.include?(key) ? "#{key}=#{CGI.escape(value.to_s)}" : next - }.compact.join("&") - end - - def truncate(text, truncate_after_chars = 2000) - truncate_after_chars && text.size > truncate_after_chars ? "#{text[0..truncate_after_chars]}..." : text - end - - def display_args(args, truncate_after_chars = 2000) - return "Invalid job payload, args is nil" if args.nil? - return "Invalid job payload, args must be an Array, not #{args.class.name}" unless args.is_a?(Array) - - begin - args.map { |arg| - h(truncate(to_display(arg), truncate_after_chars)) - }.join(", ") - rescue - "Illegal job arguments: #{h args.inspect}" - end - end - - def csrf_tag - "" - end - - def to_display(arg) - arg.inspect - rescue - begin - arg.to_s - rescue => ex - "Cannot display argument: [#{ex.class.name}] #{ex.message}" - end - end - - RETRY_JOB_KEYS = Set.new(%w[ - queue class args retry_count retried_at failed_at - jid error_message error_class backtrace - error_backtrace enqueued_at retry wrapped - created_at tags display_class - ]) - - def retry_extra_items(retry_job) - @retry_extra_items ||= {}.tap do |extra| - retry_job.item.each do |key, value| - extra[key] = value unless RETRY_JOB_KEYS.include?(key) - end - end - end - - def format_memory(rss_kb) - return "0" if rss_kb.nil? || rss_kb == 0 - - if rss_kb < 100_000 - "#{number_with_delimiter(rss_kb)} KB" - elsif rss_kb < 10_000_000 - "#{number_with_delimiter((rss_kb / 1024.0).to_i)} MB" - else - "#{number_with_delimiter((rss_kb / (1024.0 * 1024.0)).round(1))} GB" - end - end - - def number_with_delimiter(number) - return "" if number.nil? - - begin - Float(number) - rescue ArgumentError, TypeError - return number - end - - options = {delimiter: ",", separator: "."} - parts = number.to_s.to_str.split(".") - parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") - parts.join(options[:separator]) - end - - def h(text) - ::Rack::Utils.escape_html(text) - rescue ArgumentError => e - raise unless e.message.eql?("invalid byte sequence in UTF-8") - text.encode!("UTF-16", "UTF-8", invalid: :replace, replace: "").encode!("UTF-8", "UTF-16") - retry - end - - # Any paginated list that performs an action needs to redirect - # back to the proper page after performing that action. - def redirect_with_query(url) - r = request.referer - if r && r =~ /\?/ - ref = URI(r) - redirect("#{url}?#{ref.query}") - else - redirect url - end - end - - def environment_title_prefix - environment = Sidekiq.options[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development" - - "[#{environment.upcase}] " unless environment == "production" - end - - def product_version - "Sidekiq v#{Sidekiq::VERSION}" - end - - def server_utc_time - Time.now.utc.strftime("%H:%M:%S UTC") - end - - def redis_connection_and_namespace - @redis_connection_and_namespace ||= begin - namespace_suffix = namespace.nil? ? "" : "##{namespace}" - "#{redis_connection}#{namespace_suffix}" - end - end - - def retry_or_delete_or_kill(job, params) - if params["retry"] - job.retry - elsif params["delete"] - job.delete - elsif params["kill"] - job.kill - end - end - - def delete_or_add_queue(job, params) - if params["delete"] - job.delete - elsif params["add_to_queue"] - job.add_to_queue - end - end - end -end diff --git a/lib/sidekiq/web/router.rb b/lib/sidekiq/web/router.rb deleted file mode 100644 index 34d586ea..00000000 --- a/lib/sidekiq/web/router.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: true - -require "rack" - -module Sidekiq - module WebRouter - GET = "GET" - DELETE = "DELETE" - POST = "POST" - PUT = "PUT" - PATCH = "PATCH" - HEAD = "HEAD" - - ROUTE_PARAMS = "rack.route_params" - REQUEST_METHOD = "REQUEST_METHOD" - PATH_INFO = "PATH_INFO" - - def head(path, &block) - route(HEAD, path, &block) - end - - def get(path, &block) - route(GET, path, &block) - end - - def post(path, &block) - route(POST, path, &block) - end - - def put(path, &block) - route(PUT, path, &block) - end - - def patch(path, &block) - route(PATCH, path, &block) - end - - def delete(path, &block) - route(DELETE, path, &block) - end - - def route(method, path, &block) - @routes ||= {GET => [], POST => [], PUT => [], PATCH => [], DELETE => [], HEAD => []} - - @routes[method] << WebRoute.new(method, path, block) - end - - def match(env) - request_method = env[REQUEST_METHOD] - path_info = ::Rack::Utils.unescape env[PATH_INFO] - - # There are servers which send an empty string when requesting the root. - # These servers should be ashamed of themselves. - path_info = "/" if path_info == "" - - @routes[request_method].each do |route| - params = route.match(request_method, path_info) - if params - env[ROUTE_PARAMS] = params - - return WebAction.new(env, route.block) - end - end - - nil - end - end - - class WebRoute - attr_accessor :request_method, :pattern, :block, :name - - NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^.:$\/]+)/ - - def initialize(request_method, pattern, block) - @request_method = request_method - @pattern = pattern - @block = block - end - - def matcher - @matcher ||= compile - end - - def compile - if pattern.match?(NAMED_SEGMENTS_PATTERN) - p = pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)') - - Regexp.new("\\A#{p}\\Z") - else - pattern - end - end - - def match(request_method, path) - case matcher - when String - {} if path == matcher - else - path_match = path.match(matcher) - path_match&.named_captures&.transform_keys(&:to_sym) - end - end - end -end diff --git a/lib/sidekiq/worker.rb b/lib/sidekiq/worker.rb deleted file mode 100644 index 4e0140a8..00000000 --- a/lib/sidekiq/worker.rb +++ /dev/null @@ -1,362 +0,0 @@ -# frozen_string_literal: true - -require "sidekiq/client" - -module Sidekiq - ## - # Include this module in your worker class and you can easily create - # asynchronous jobs: - # - # class HardWorker - # include Sidekiq::Worker - # sidekiq_options queue: 'critical', retry: 5 - # - # def perform(*args) - # # do some work - # end - # end - # - # Then in your Rails app, you can do this: - # - # HardWorker.perform_async(1, 2, 3) - # - # Note that perform_async is a class method, perform is an instance method. - # - # Sidekiq::Worker also includes several APIs to provide compatibility with - # ActiveJob. - # - # class SomeWorker - # include Sidekiq::Worker - # queue_as :critical - # - # def perform(...) - # end - # end - # - # SomeWorker.set(wait_until: 1.hour).perform_async(123) - # - # Note that arguments passed to the job must still obey Sidekiq's - # best practice for simple, JSON-native data types. Sidekiq will not - # implement ActiveJob's more complex argument serialization. For - # this reason, we don't implement `perform_later` as our call semantics - # are very different. - # - module Worker - ## - # The Options module is extracted so we can include it in ActiveJob::Base - # and allow native AJs to configure Sidekiq features/internals. - module Options - def self.included(base) - base.extend(ClassMethods) - base.sidekiq_class_attribute :sidekiq_options_hash - base.sidekiq_class_attribute :sidekiq_retry_in_block - base.sidekiq_class_attribute :sidekiq_retries_exhausted_block - end - - module ClassMethods - ACCESSOR_MUTEX = Mutex.new - - ## - # Allows customization for this type of Worker. - # Legal options: - # - # queue - name of queue to use for this job type, default *default* - # retry - enable retries for this Worker in case of error during execution, - # *true* to use the default or *Integer* count - # backtrace - whether to save any error backtrace in the retry payload to display in web UI, - # can be true, false or an integer number of lines to save, default *false* - # - # In practice, any option is allowed. This is the main mechanism to configure the - # options for a specific job. - def sidekiq_options(opts = {}) - opts = opts.transform_keys(&:to_s) # stringify - self.sidekiq_options_hash = get_sidekiq_options.merge(opts) - end - - def sidekiq_retry_in(&block) - self.sidekiq_retry_in_block = block - end - - def sidekiq_retries_exhausted(&block) - self.sidekiq_retries_exhausted_block = block - end - - def get_sidekiq_options # :nodoc: - self.sidekiq_options_hash ||= Sidekiq.default_worker_options - end - - def sidekiq_class_attribute(*attrs) - instance_reader = true - instance_writer = true - - attrs.each do |name| - synchronized_getter = "__synchronized_#{name}" - - singleton_class.instance_eval do - undef_method(name) if method_defined?(name) || private_method_defined?(name) - end - - define_singleton_method(synchronized_getter) { nil } - singleton_class.class_eval do - private(synchronized_getter) - end - - define_singleton_method(name) { ACCESSOR_MUTEX.synchronize { send synchronized_getter } } - - ivar = "@#{name}" - - singleton_class.instance_eval do - m = "#{name}=" - undef_method(m) if method_defined?(m) || private_method_defined?(m) - end - define_singleton_method("#{name}=") do |val| - singleton_class.class_eval do - ACCESSOR_MUTEX.synchronize do - undef_method(synchronized_getter) if method_defined?(synchronized_getter) || private_method_defined?(synchronized_getter) - define_method(synchronized_getter) { val } - end - end - - if singleton_class? - class_eval do - undef_method(name) if method_defined?(name) || private_method_defined?(name) - define_method(name) do - if instance_variable_defined? ivar - instance_variable_get ivar - else - singleton_class.send name - end - end - end - end - val - end - - if instance_reader - undef_method(name) if method_defined?(name) || private_method_defined?(name) - define_method(name) do - if instance_variable_defined?(ivar) - instance_variable_get ivar - else - self.class.public_send name - end - end - end - - if instance_writer - m = "#{name}=" - undef_method(m) if method_defined?(m) || private_method_defined?(m) - attr_writer name - end - end - end - end - end - - attr_accessor :jid - - def self.included(base) - raise ArgumentError, "Sidekiq::Worker cannot be included in an ActiveJob: #{base.name}" if base.ancestors.any? { |c| c.name == "ActiveJob::Base" } - - base.include(Options) - base.extend(ClassMethods) - end - - def logger - Sidekiq.logger - end - - # This helper class encapsulates the set options for `set`, e.g. - # - # SomeWorker.set(queue: 'foo').perform_async(....) - # - class Setter - include Sidekiq::JobUtil - - def initialize(klass, opts) - @klass = klass - @opts = opts - - # ActiveJob compatibility - interval = @opts.delete(:wait_until) || @opts.delete(:wait) - at(interval) if interval - end - - def set(options) - interval = options.delete(:wait_until) || options.delete(:wait) - @opts.merge!(options) - at(interval) if interval - self - end - - def perform_async(*args) - if @opts["sync"] == true - perform_inline(*args) - else - @klass.client_push(@opts.merge("args" => args, "class" => @klass)) - end - end - - # Explicit inline execution of a job. Returns nil if the job did not - # execute, true otherwise. - def perform_inline(*args) - raw = @opts.merge("args" => args, "class" => @klass).transform_keys(&:to_s) - - # validate and normalize payload - item = normalize_item(raw) - queue = item["queue"] - - # run client-side middleware - result = Sidekiq.client_middleware.invoke(item["class"], item, queue, Sidekiq.redis_pool) do - item - end - return nil unless result - - # round-trip the payload via JSON - msg = Sidekiq.load_json(Sidekiq.dump_json(item)) - - # prepare the job instance - klass = msg["class"].constantize - job = klass.new - job.jid = msg["jid"] - job.bid = msg["bid"] if job.respond_to?(:bid) - - # run the job through server-side middleware - result = Sidekiq.server_middleware.invoke(job, msg, msg["queue"]) do - # perform it - job.perform(*msg["args"]) - true - end - return nil unless result - # jobs do not return a result. they should store any - # modified state. - true - end - alias_method :perform_sync, :perform_inline - - def perform_bulk(args, batch_size: 1_000) - hash = @opts.transform_keys(&:to_s) - pool = Thread.current[:sidekiq_via_pool] || @klass.get_sidekiq_options["pool"] || Sidekiq.redis_pool - client = Sidekiq::Client.new(pool) - result = args.each_slice(batch_size).flat_map do |slice| - client.push_bulk(hash.merge("class" => @klass, "args" => slice)) - end - - result.is_a?(Enumerator::Lazy) ? result.force : result - end - - # +interval+ must be a timestamp, numeric or something that acts - # numeric (like an activesupport time interval). - def perform_in(interval, *args) - at(interval).perform_async(*args) - end - alias_method :perform_at, :perform_in - - private - - def at(interval) - int = interval.to_f - now = Time.now.to_f - ts = (int < 1_000_000_000 ? now + int : int) - # Optimization to enqueue something now that is scheduled to go out now or in the past - @opts["at"] = ts if ts > now - self - end - end - - module ClassMethods - def delay(*args) - raise ArgumentError, "Do not call .delay on a Sidekiq::Worker class, call .perform_async" - end - - def delay_for(*args) - raise ArgumentError, "Do not call .delay_for on a Sidekiq::Worker class, call .perform_in" - end - - def delay_until(*args) - raise ArgumentError, "Do not call .delay_until on a Sidekiq::Worker class, call .perform_at" - end - - def queue_as(q) - sidekiq_options("queue" => q.to_s) - end - - def set(options) - Setter.new(self, options) - end - - def perform_async(*args) - Setter.new(self, {}).perform_async(*args) - end - - # Inline execution of job's perform method after passing through Sidekiq.client_middleware and Sidekiq.server_middleware - def perform_inline(*args) - Setter.new(self, {}).perform_inline(*args) - end - - ## - # Push a large number of jobs to Redis, while limiting the batch of - # each job payload to 1,000. This method helps cut down on the number - # of round trips to Redis, which can increase the performance of enqueueing - # large numbers of jobs. - # - # +items+ must be an Array of Arrays. - # - # For finer-grained control, use `Sidekiq::Client.push_bulk` directly. - # - # Example (3 Redis round trips): - # - # SomeWorker.perform_async(1) - # SomeWorker.perform_async(2) - # SomeWorker.perform_async(3) - # - # Would instead become (1 Redis round trip): - # - # SomeWorker.perform_bulk([[1], [2], [3]]) - # - def perform_bulk(*args, **kwargs) - Setter.new(self, {}).perform_bulk(*args, **kwargs) - end - - # +interval+ must be a timestamp, numeric or something that acts - # numeric (like an activesupport time interval). - def perform_in(interval, *args) - int = interval.to_f - now = Time.now.to_f - ts = (int < 1_000_000_000 ? now + int : int) - - item = {"class" => self, "args" => args} - - # Optimization to enqueue something now that is scheduled to go out now or in the past - item["at"] = ts if ts > now - - client_push(item) - end - alias_method :perform_at, :perform_in - - ## - # Allows customization for this type of Worker. - # Legal options: - # - # queue - use a named queue for this Worker, default 'default' - # retry - enable the RetryJobs middleware for this Worker, *true* to use the default - # or *Integer* count - # backtrace - whether to save any error backtrace in the retry payload to display in web UI, - # can be true, false or an integer number of lines to save, default *false* - # pool - use the given Redis connection pool to push this type of job to a given shard. - # - # In practice, any option is allowed. This is the main mechanism to configure the - # options for a specific job. - def sidekiq_options(opts = {}) - super - end - - def client_push(item) # :nodoc: - pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool - stringified_item = item.transform_keys(&:to_s) - - Sidekiq::Client.new(pool).push(stringified_item) - end - end - end -end diff --git a/myapp/.gitignore b/myapp/.gitignore deleted file mode 100644 index ec0b1960..00000000 --- a/myapp/.gitignore +++ /dev/null @@ -1,30 +0,0 @@ -# See https://help.github.com/articles/ignoring-files for more about ignoring files. -# -# If you find yourself ignoring temporary files generated by your text editor -# or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile '~/.gitignore_global' - -# Ignore bundler config. -/.bundle - -# Ignore the default SQLite database. -/db/*.sqlite3 -/db/*.sqlite3-journal - -# Ignore all logfiles and tempfiles. -/log/* -/tmp/* -!/log/.keep -!/tmp/.keep - -# Ignore uploaded files in development -/storage/* - -/node_modules -/yarn-error.log - -/public/assets -.byebug_history - -# Ignore master key for decrypting credentials and more. -/config/master.key diff --git a/myapp/Gemfile b/myapp/Gemfile deleted file mode 100644 index bbd59cef..00000000 --- a/myapp/Gemfile +++ /dev/null @@ -1,11 +0,0 @@ -source 'https://rubygems.org' - -gem 'sidekiq', :path => '..' -gem 'rails' -gem 'puma' - -# Ubuntu required packages to install rails -# apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev libsqlite3-dev -platforms :ruby do - gem 'sqlite3' -end diff --git a/myapp/Rakefile b/myapp/Rakefile deleted file mode 100644 index e85f9139..00000000 --- a/myapp/Rakefile +++ /dev/null @@ -1,6 +0,0 @@ -# Add your own tasks in files placed in lib/tasks ending in .rake, -# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. - -require_relative 'config/application' - -Rails.application.load_tasks diff --git a/myapp/app/assets/config/manifest.js b/myapp/app/assets/config/manifest.js deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/app/controllers/application_controller.rb b/myapp/app/controllers/application_controller.rb deleted file mode 100644 index 09705d12..00000000 --- a/myapp/app/controllers/application_controller.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ApplicationController < ActionController::Base -end diff --git a/myapp/app/controllers/work_controller.rb b/myapp/app/controllers/work_controller.rb deleted file mode 100644 index fcf1b4b3..00000000 --- a/myapp/app/controllers/work_controller.rb +++ /dev/null @@ -1,44 +0,0 @@ -class WorkController < ApplicationController - def index - @count = rand(100) - puts "Adding #{@count} jobs" - @count.times do |x| - HardWorker.perform_async('bubba', 0.01, x) - end - end - - def email - UserMailer.delay_for(30.seconds).greetings(Time.now) - render :plain => 'enqueued' - end - - def bulk - Sidekiq::Client.push_bulk('class' => HardWorker, - 'args' => [['bob', 1, 1], ['mike', 1, 2]]) - render :plain => 'enbulked' - end - - def long - 50.times do |x| - HardWorker.perform_async('bob', 15, x) - end - render :plain => 'enqueued' - end - - def crash - HardWorker.perform_async('crash', 1, Time.now.to_f) - render :plain => 'enqueued' - end - - def delayed_post - p = Post.first - unless p - p = Post.create!(:title => "Title!", :body => 'Body!') - p2 = Post.create!(:title => "Other!", :body => 'Second Body!') - else - p2 = Post.second - end - p.delay.long_method(p2) - render :plain => 'enqueued' - end -end diff --git a/myapp/app/helpers/application_helper.rb b/myapp/app/helpers/application_helper.rb deleted file mode 100644 index de6be794..00000000 --- a/myapp/app/helpers/application_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module ApplicationHelper -end diff --git a/myapp/app/jobs/application_job.rb b/myapp/app/jobs/application_job.rb deleted file mode 100644 index a009ace5..00000000 --- a/myapp/app/jobs/application_job.rb +++ /dev/null @@ -1,2 +0,0 @@ -class ApplicationJob < ActiveJob::Base -end diff --git a/myapp/app/jobs/exit_job.rb b/myapp/app/jobs/exit_job.rb deleted file mode 100644 index a9d78de1..00000000 --- a/myapp/app/jobs/exit_job.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ExitJob < ApplicationJob - queue_as :default - - def perform(*args) - Sidekiq.logger.warn "Success" - Thread.new do - sleep 0.1 - exit(0) - end - end -end diff --git a/myapp/app/jobs/some_job.rb b/myapp/app/jobs/some_job.rb deleted file mode 100644 index ec2c210c..00000000 --- a/myapp/app/jobs/some_job.rb +++ /dev/null @@ -1,8 +0,0 @@ -class SomeJob < ApplicationJob - queue_as :default - - def perform(*args) - puts "What's up?!?!" - # Do something later - end -end diff --git a/myapp/app/mailers/.gitkeep b/myapp/app/mailers/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/app/mailers/user_mailer.rb b/myapp/app/mailers/user_mailer.rb deleted file mode 100644 index becd58e9..00000000 --- a/myapp/app/mailers/user_mailer.rb +++ /dev/null @@ -1,9 +0,0 @@ -class UserMailer < ActionMailer::Base - default from: "sidekiq@example.com" - - def greetings(now) - @now = now - @hostname = `hostname`.strip - mail(:to => 'mperham@gmail.com', :subject => 'Ahoy Matey!') - end -end diff --git a/myapp/app/models/.gitkeep b/myapp/app/models/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/app/models/exiter.rb b/myapp/app/models/exiter.rb deleted file mode 100644 index 61ef5147..00000000 --- a/myapp/app/models/exiter.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Exiter - def self.run - Sidekiq.logger.warn "Success" - Thread.new do - sleep 0.1 - exit(0) - end - end -end diff --git a/myapp/app/models/post.rb b/myapp/app/models/post.rb deleted file mode 100644 index 490d0dee..00000000 --- a/myapp/app/models/post.rb +++ /dev/null @@ -1,9 +0,0 @@ -class Post < ActiveRecord::Base - def long_method(other_post) - puts "Running long method with #{self.id} and #{other_post.id}" - end - - def self.testing - Sidekiq.logger.info "Test" - end -end diff --git a/myapp/app/views/layouts/application.html.erb b/myapp/app/views/layouts/application.html.erb deleted file mode 100644 index 0f2f3601..00000000 --- a/myapp/app/views/layouts/application.html.erb +++ /dev/null @@ -1,15 +0,0 @@ - - - - Myapp - <%= csrf_meta_tags %> - <%= csp_meta_tag %> - - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> - <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> - - - - <%= yield %> - - diff --git a/myapp/app/views/user_mailer/greetings.html.erb b/myapp/app/views/user_mailer/greetings.html.erb deleted file mode 100644 index bdc17008..00000000 --- a/myapp/app/views/user_mailer/greetings.html.erb +++ /dev/null @@ -1,3 +0,0 @@ -

- Hi Mike, it's <%= @now %> and I'm at <%= @hostname %>. -

diff --git a/myapp/app/views/work/index.html.erb b/myapp/app/views/work/index.html.erb deleted file mode 100644 index bcbe068d..00000000 --- a/myapp/app/views/work/index.html.erb +++ /dev/null @@ -1 +0,0 @@ -Added <%= @count %> jobs! diff --git a/myapp/app/workers/exit_worker.rb b/myapp/app/workers/exit_worker.rb deleted file mode 100644 index 41f95cd1..00000000 --- a/myapp/app/workers/exit_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -class ExitWorker - include Sidekiq::Worker - - def perform - logger.warn "Success" - Thread.new do - sleep 0.1 - exit(0) - end - end -end diff --git a/myapp/app/workers/hard_worker.rb b/myapp/app/workers/hard_worker.rb deleted file mode 100644 index 83409cf9..00000000 --- a/myapp/app/workers/hard_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class HardWorker - include Sidekiq::Worker - sidekiq_options :backtrace => 5 - - def perform(name, count, salt) - raise name if name == 'crash' - logger.info Time.now - sleep count - end -end diff --git a/myapp/app/workers/lazy_worker.rb b/myapp/app/workers/lazy_worker.rb deleted file mode 100644 index 157cf72c..00000000 --- a/myapp/app/workers/lazy_worker.rb +++ /dev/null @@ -1,6 +0,0 @@ -class LazyWorker - include Sidekiq::Worker - - def perform - end -end diff --git a/myapp/bin/bundle b/myapp/bin/bundle deleted file mode 100755 index f19acf5b..00000000 --- a/myapp/bin/bundle +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -load Gem.bin_path('bundler', 'bundle') diff --git a/myapp/bin/rails b/myapp/bin/rails deleted file mode 100755 index 5badb2fd..00000000 --- a/myapp/bin/rails +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end -APP_PATH = File.expand_path('../config/application', __dir__) -require_relative '../config/boot' -require 'rails/commands' diff --git a/myapp/bin/rake b/myapp/bin/rake deleted file mode 100755 index d87d5f57..00000000 --- a/myapp/bin/rake +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end -require_relative '../config/boot' -require 'rake' -Rake.application.run diff --git a/myapp/bin/setup b/myapp/bin/setup deleted file mode 100755 index 94fd4d79..00000000 --- a/myapp/bin/setup +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env ruby -require 'fileutils' -include FileUtils - -# path to your application root. -APP_ROOT = File.expand_path('..', __dir__) - -def system!(*args) - system(*args) || abort("\n== Command #{args} failed ==") -end - -chdir APP_ROOT do - # This script is a starting point to setup your application. - # Add necessary setup steps to this file. - - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') - - # Install JavaScript dependencies if using Yarn - # system('bin/yarn') - - # puts "\n== Copying sample files ==" - # unless File.exist?('config/database.yml') - # cp 'config/database.yml.sample', 'config/database.yml' - # end - - puts "\n== Preparing database ==" - system! 'bin/rails db:setup' - - puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' - - puts "\n== Restarting application server ==" - system! 'bin/rails restart' -end diff --git a/myapp/config.ru b/myapp/config.ru deleted file mode 100644 index f7ba0b52..00000000 --- a/myapp/config.ru +++ /dev/null @@ -1,5 +0,0 @@ -# This file is used by Rack-based servers to start the application. - -require_relative 'config/environment' - -run Rails.application diff --git a/myapp/config/application.rb b/myapp/config/application.rb deleted file mode 100644 index d2e6429b..00000000 --- a/myapp/config/application.rb +++ /dev/null @@ -1,34 +0,0 @@ -require_relative 'boot' - -require 'rails' -%w( - active_record/railtie - action_controller/railtie - action_view/railtie - action_mailer/railtie - active_job/railtie - sprockets/railtie -).each do |railtie| - begin - require railtie - rescue LoadError - end -end - -# Require the gems listed in Gemfile, including any gems -# you've limited to :test, :development, or :production. -Bundler.require(*Rails.groups) - -module Myapp - class Application < Rails::Application - # Load the defaults for whichever Rails version we are using, we need to - # ensure we work with all defaults. - config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" - - # Settings in config/environments/* take precedence over those specified here. - # Application configuration can go into files in config/initializers - # -- all .rb files in that directory are automatically loaded after loading - # the framework and any gems in your application. - config.active_job.queue_adapter = :sidekiq - end -end diff --git a/myapp/config/boot.rb b/myapp/config/boot.rb deleted file mode 100644 index 30f5120d..00000000 --- a/myapp/config/boot.rb +++ /dev/null @@ -1,3 +0,0 @@ -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) - -require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/myapp/config/database.yml b/myapp/config/database.yml deleted file mode 100644 index 0d02f249..00000000 --- a/myapp/config/database.yml +++ /dev/null @@ -1,25 +0,0 @@ -# SQLite version 3.x -# gem install sqlite3 -# -# Ensure the SQLite 3 gem is defined in your Gemfile -# gem 'sqlite3' -# -default: &default - adapter: sqlite3 - pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - timeout: 5000 - -development: - <<: *default - database: db/development.sqlite3 - -# Warning: The database defined as "test" will be erased and -# re-generated from your development database when you run "rake". -# Do not set this db to the same as development or production. -test: - <<: *default - database: db/test.sqlite3 - -production: - <<: *default - database: db/production.sqlite3 diff --git a/myapp/config/environment.rb b/myapp/config/environment.rb deleted file mode 100644 index 426333bb..00000000 --- a/myapp/config/environment.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Load the Rails application. -require_relative 'application' - -# Initialize the Rails application. -Rails.application.initialize! diff --git a/myapp/config/environments/development.rb b/myapp/config/environments/development.rb deleted file mode 100644 index 9a842b56..00000000 --- a/myapp/config/environments/development.rb +++ /dev/null @@ -1,61 +0,0 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # In the development environment your application's code is reloaded on - # every request. This slows down response time but is perfect for development - # since you don't have to restart the web server when you make code changes. - config.cache_classes = false - - # Do not eager load code on boot. - config.eager_load = false - - # Show full error reports. - config.consider_all_requests_local = true - - # Enable/disable caching. By default caching is disabled. - # Run rails dev:cache to toggle caching. - if Rails.root.join('tmp', 'caching-dev.txt').exist? - config.action_controller.perform_caching = true - - config.cache_store = :memory_store - config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.to_i}" - } - else - config.action_controller.perform_caching = false - - config.cache_store = :null_store - end - - # Store uploaded files on the local file system (see config/storage.yml for options) - #config.active_storage.service = :local - - # Don't care if the mailer can't send. - config.action_mailer.raise_delivery_errors = false - - config.action_mailer.perform_caching = false - - # Print deprecation notices to the Rails logger. - config.active_support.deprecation = :log - - # Raise an error on page load if there are pending migrations. - config.active_record.migration_error = :page_load - - # Highlight code that triggered database queries in logs. - config.active_record.verbose_query_logs = true - - # Debug mode disables concatenation and preprocessing of assets. - # This option may cause significant delays in view rendering with a large - # number of complex assets. - # config.assets.debug = true - - # Suppress logger output for asset requests. - # config.assets.quiet = true - - # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true - - # Use an evented file watcher to asynchronously detect changes in source code, - # routes, locales, etc. This feature depends on the listen gem. - #config.file_watcher = ActiveSupport::EventedFileUpdateChecker -end diff --git a/myapp/config/environments/production.rb b/myapp/config/environments/production.rb deleted file mode 100644 index 2a20538b..00000000 --- a/myapp/config/environments/production.rb +++ /dev/null @@ -1,94 +0,0 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # Code is not reloaded between requests. - config.cache_classes = true - - # Eager load code on boot. This eager loads most of Rails and - # your application in memory, allowing both threaded web servers - # and those relying on copy on write to perform better. - # Rake tasks automatically ignore this option for performance. - config.eager_load = true - - # Full error reports are disabled and caching is turned on. - config.consider_all_requests_local = false - config.action_controller.perform_caching = true - - # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] - # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). - # config.require_master_key = true - - # Disable serving static files from the `/public` folder by default since - # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? - - # Compress JavaScripts and CSS. - config.assets.js_compressor = :uglifier - # config.assets.css_compressor = :sass - - # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = false - - # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb - - # Enable serving of images, stylesheets, and JavaScripts from an asset server. - # config.action_controller.asset_host = 'http://assets.example.com' - - # Specifies the header that your server uses for sending files. - # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache - # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX - - # Store uploaded files on the local file system (see config/storage.yml for options) - #config.active_storage.service = :local - - # Mount Action Cable outside main process or domain - # config.action_cable.mount_path = nil - # config.action_cable.url = 'wss://example.com/cable' - # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] - - # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - # config.force_ssl = true - - # Use the lowest log level to ensure availability of diagnostic information - # when problems arise. - config.log_level = :debug - - # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] - - # Use a different cache store in production. - # config.cache_store = :mem_cache_store - - # Use a real queuing backend for Active Job (and separate queues per environment) - # config.active_job.queue_adapter = :resque - # config.active_job.queue_name_prefix = "myapp_#{Rails.env}" - - config.action_mailer.perform_caching = false - - # Ignore bad email addresses and do not raise email delivery errors. - # Set this to true and configure the email server for immediate delivery to raise delivery errors. - # config.action_mailer.raise_delivery_errors = false - - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true - - # Send deprecation notices to registered listeners. - config.active_support.deprecation = :notify - - # Use default logging formatter so that PID and timestamp are not suppressed. - config.log_formatter = ::Logger::Formatter.new - - # Use a different logger for distributed setups. - # require 'syslog/logger' - # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') - - if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) - logger.formatter = config.log_formatter - config.logger = ActiveSupport::TaggedLogging.new(logger) - end - - # Do not dump schema after migrations. - config.active_record.dump_schema_after_migration = false -end diff --git a/myapp/config/environments/test.rb b/myapp/config/environments/test.rb deleted file mode 100644 index ccf4335d..00000000 --- a/myapp/config/environments/test.rb +++ /dev/null @@ -1,46 +0,0 @@ -Rails.application.configure do - # Settings specified here will take precedence over those in config/application.rb. - - # The test environment is used exclusively to run your application's - # test suite. You never need to work with it otherwise. Remember that - # your test database is "scratch space" for the test suite and is wiped - # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true - - # Do not eager load code on boot. This avoids loading your whole application - # just for the purpose of running a single test. If you are using a tool that - # preloads Rails for running tests, you may have to set it to true. - config.eager_load = false - - # Configure public file server for tests with Cache-Control for performance. - config.public_file_server.enabled = true - config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.to_i}" - } - - # Show full error reports and disable caching. - config.consider_all_requests_local = true - config.action_controller.perform_caching = false - - # Raise exceptions instead of rendering exception templates. - config.action_dispatch.show_exceptions = false - - # Disable request forgery protection in test environment. - config.action_controller.allow_forgery_protection = false - - # Store uploaded files on the local file system in a temporary directory - #config.active_storage.service = :test - - config.action_mailer.perform_caching = false - - # Tell Action Mailer not to deliver emails to the real world. - # The :test delivery method accumulates sent emails in the - # ActionMailer::Base.deliveries array. - config.action_mailer.delivery_method = :test - - # Print deprecation notices to the stderr. - config.active_support.deprecation = :stderr - - # Raises error for missing translations - # config.action_view.raise_on_missing_translations = true -end diff --git a/myapp/config/initializers/assets.rb b/myapp/config/initializers/assets.rb deleted file mode 100644 index ff2d2299..00000000 --- a/myapp/config/initializers/assets.rb +++ /dev/null @@ -1,11 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Version of your assets, change this if you want to expire all your assets. -# Rails.application.config.assets.version = '1.0' - -# Add additional assets to the asset load path -# Rails.application.config.assets.paths << Emoji.images_path - -# Precompile additional assets. -# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. -# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/myapp/config/initializers/backtrace_silencers.rb b/myapp/config/initializers/backtrace_silencers.rb deleted file mode 100644 index 59385cdf..00000000 --- a/myapp/config/initializers/backtrace_silencers.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. -# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } - -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. -# Rails.backtrace_cleaner.remove_silencers! diff --git a/myapp/config/initializers/cookies_serializer.rb b/myapp/config/initializers/cookies_serializer.rb deleted file mode 100644 index 5a6a32d3..00000000 --- a/myapp/config/initializers/cookies_serializer.rb +++ /dev/null @@ -1,5 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Specify a serializer for the signed and encrypted cookie jars. -# Valid options are :json, :marshal, and :hybrid. -Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/myapp/config/initializers/filter_parameter_logging.rb b/myapp/config/initializers/filter_parameter_logging.rb deleted file mode 100644 index 4a994e1e..00000000 --- a/myapp/config/initializers/filter_parameter_logging.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [:password] diff --git a/myapp/config/initializers/inflections.rb b/myapp/config/initializers/inflections.rb deleted file mode 100644 index ac033bf9..00000000 --- a/myapp/config/initializers/inflections.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Add new inflection rules using the following format. Inflections -# are locale specific, and you may define rules for as many different -# locales as you wish. All of these examples are active by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end - -# These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections(:en) do |inflect| -# inflect.acronym 'RESTful' -# end diff --git a/myapp/config/initializers/mime_types.rb b/myapp/config/initializers/mime_types.rb deleted file mode 100644 index dc189968..00000000 --- a/myapp/config/initializers/mime_types.rb +++ /dev/null @@ -1,4 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Add new mime types for use in respond_to blocks: -# Mime::Type.register "text/richtext", :rtf diff --git a/myapp/config/initializers/secret_token.rb b/myapp/config/initializers/secret_token.rb deleted file mode 100644 index 007d2f86..00000000 --- a/myapp/config/initializers/secret_token.rb +++ /dev/null @@ -1,7 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -Myapp::Application.config.secret_token = 'bdd335500c81ba1f279f9dd8198d1f334445d0193168ed73c1282502180dca27e3e102ec345e99b2acac6a63f7fe29da69c60cc9e76e8da34fb5d4989db24cd8' diff --git a/myapp/config/initializers/session_store.rb b/myapp/config/initializers/session_store.rb deleted file mode 100644 index bf64a296..00000000 --- a/myapp/config/initializers/session_store.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Be sure to restart your server when you modify this file. - -Rails.application.config.session_store :cookie_store, key: '_myapp_session' diff --git a/myapp/config/initializers/sidekiq.rb b/myapp/config/initializers/sidekiq.rb deleted file mode 100644 index 0fe9f539..00000000 --- a/myapp/config/initializers/sidekiq.rb +++ /dev/null @@ -1,44 +0,0 @@ -Sidekiq.configure_client do |config| - config.redis = { :size => 2 } -end -Sidekiq.configure_server do |config| - config.on(:startup) { } - config.on(:quiet) { } - config.on(:shutdown) do - #result = RubyProf.stop - - ## Write the results to a file - ## Requires railsexpress patched MRI build - # brew install qcachegrind - #File.open("callgrind.profile", "w") do |f| - #RubyProf::CallTreePrinter.new(result).print(f, :min_percent => 1) - #end - end -end - -class EmptyWorker - include Sidekiq::Worker - - def perform - end -end - -class TimedWorker - include Sidekiq::Worker - - def perform(start) - now = Time.now.to_f - puts "Latency: #{now - start} sec" - end -end - -Sidekiq::Extensions.enable_delay! - -module Myapp - class Current < ActiveSupport::CurrentAttributes - attribute :tenant_id - end -end - -require "sidekiq/middleware/current_attributes" -Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton \ No newline at end of file diff --git a/myapp/config/initializers/wrap_parameters.rb b/myapp/config/initializers/wrap_parameters.rb deleted file mode 100644 index bbfc3961..00000000 --- a/myapp/config/initializers/wrap_parameters.rb +++ /dev/null @@ -1,14 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# This file contains settings for ActionController::ParamsWrapper which -# is enabled by default. - -# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] -end - -# To enable root element in JSON for ActiveRecord objects. -# ActiveSupport.on_load(:active_record) do -# self.include_root_in_json = true -# end diff --git a/myapp/config/locales/en.yml b/myapp/config/locales/en.yml deleted file mode 100644 index 06539571..00000000 --- a/myapp/config/locales/en.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t 'hello' -# -# In views, this is aliased to just `t`: -# -# <%= t('hello') %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - -en: - hello: "Hello world" diff --git a/myapp/config/routes.rb b/myapp/config/routes.rb deleted file mode 100644 index c32e3f26..00000000 --- a/myapp/config/routes.rb +++ /dev/null @@ -1,15 +0,0 @@ -# turns off browser asset caching so we can test CSS changes quickly -ENV['SIDEKIQ_WEB_TESTING'] = '1' - -require 'sidekiq/web' -Sidekiq::Web.app_url = '/' - -Rails.application.routes.draw do - mount Sidekiq::Web => '/sidekiq' - get "work" => "work#index" - get "work/email" => "work#email" - get "work/post" => "work#delayed_post" - get "work/long" => "work#long" - get "work/crash" => "work#crash" - get "work/bulk" => "work#bulk" -end diff --git a/myapp/config/secrets.yml b/myapp/config/secrets.yml deleted file mode 100644 index 0093078d..00000000 --- a/myapp/config/secrets.yml +++ /dev/null @@ -1,22 +0,0 @@ -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! - -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rake secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -development: - secret_key_base: c96477a6cbbc1304f41d7b26d5acb28187a8e8a16308a6d0d0310433e86054fe0bbc6d23a5c9269bd21b1a45650dd0ace354db80b9647bffb1c8bb600a00a1ae - -test: - secret_key_base: ff0824c8b17ce583c8402dbe9968fec3a169dd6a294d4768fdef22cb083a048a2b4bababc5f37ca3a5d3c1b034e13fd703490671cb32db2983c4abb65e67fb09 - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/myapp/config/sidekiq.yml b/myapp/config/sidekiq.yml deleted file mode 100644 index b56158ff..00000000 --- a/myapp/config/sidekiq.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -:labels: - - some_label diff --git a/myapp/db/migrate/20120123214055_create_posts.rb b/myapp/db/migrate/20120123214055_create_posts.rb deleted file mode 100644 index ca423815..00000000 --- a/myapp/db/migrate/20120123214055_create_posts.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreatePosts < ActiveRecord::Migration[6.0] - def change - create_table :posts do |t| - t.string :title - t.string :body - - t.timestamps - end - end -end diff --git a/myapp/db/schema.rb b/myapp/db/schema.rb deleted file mode 100644 index 5c5c5ced..00000000 --- a/myapp/db/schema.rb +++ /dev/null @@ -1,22 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead -# of editing this file, please use the migrations feature of Active Record to -# incrementally modify your database, and then regenerate this schema definition. -# -# This file is the source Rails uses to define your schema when running `bin/rails -# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to -# be faster and is potentially less error prone than running all of your -# migrations from scratch. Old migrations may fail to apply correctly if those -# migrations use external dependencies or application code. -# -# It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema.define(version: 2012_01_23_214055) do - - create_table "posts", force: :cascade do |t| - t.string "title" - t.string "body" - t.datetime "created_at", precision: 6, null: false - t.datetime "updated_at", precision: 6, null: false - end - -end diff --git a/myapp/db/seeds.rb b/myapp/db/seeds.rb deleted file mode 100644 index 4edb1e85..00000000 --- a/myapp/db/seeds.rb +++ /dev/null @@ -1,7 +0,0 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) -# Mayor.create(name: 'Emanuel', city: cities.first) diff --git a/myapp/lib/assets/.gitkeep b/myapp/lib/assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/lib/tasks/.gitkeep b/myapp/lib/tasks/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/log/.gitkeep b/myapp/log/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/myapp/script/rails b/myapp/script/rails deleted file mode 100755 index f8da2cff..00000000 --- a/myapp/script/rails +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/myapp/simple.ru b/myapp/simple.ru deleted file mode 100644 index 8e007471..00000000 --- a/myapp/simple.ru +++ /dev/null @@ -1,19 +0,0 @@ -# Easiest way to run Sidekiq::Web. -# Run with "bundle exec rackup simple.ru" - -require 'sidekiq/web' - -# A Web process always runs as client, no need to configure server -Sidekiq.configure_client do |config| - config.redis = { url: 'redis://localhost:6379/0', size: 1 } -end - -Sidekiq::Client.push('class' => "HardWorker", 'args' => []) - -# In a multi-process deployment, all Web UI instances should share -# this secret key so they can all decode the encrypted browser cookies -# and provide a working session. -# Rails does this in /config/initializers/secret_token.rb -secret_key = SecureRandom.hex(32) -use Rack::Session::Cookie, secret: secret_key, same_site: true, max_age: 86400 -run Sidekiq::Web diff --git a/sidekiq.gemspec b/sidekiq.gemspec deleted file mode 100644 index e3d5bf76..00000000 --- a/sidekiq.gemspec +++ /dev/null @@ -1,28 +0,0 @@ -require_relative "lib/sidekiq/version" - -Gem::Specification.new do |gem| - gem.authors = ["Mike Perham"] - gem.email = ["mperham@gmail.com"] - gem.summary = "Simple, efficient background processing for Ruby" - gem.description = "Simple, efficient background processing for Ruby." - gem.homepage = "https://sidekiq.org" - gem.license = "LGPL-3.0" - - gem.executables = ["sidekiq", "sidekiqmon"] - gem.files = ["sidekiq.gemspec", "README.md", "Changes.md", "LICENSE"] + `git ls-files | grep -E '^(bin|lib|web)'`.split("\n") - gem.name = "sidekiq" - gem.version = Sidekiq::VERSION - gem.required_ruby_version = ">= 2.5.0" - - gem.metadata = { - "homepage_uri" => "https://sidekiq.org", - "bug_tracker_uri" => "https://github.com/mperham/sidekiq/issues", - "documentation_uri" => "https://github.com/mperham/sidekiq/wiki", - "changelog_uri" => "https://github.com/mperham/sidekiq/blob/main/Changes.md", - "source_code_uri" => "https://github.com/mperham/sidekiq" - } - - gem.add_dependency "redis", ">= 4.2.0" - gem.add_dependency "connection_pool", ">= 2.2.2" - gem.add_dependency "rack", "~> 2.0" -end diff --git a/test/config.yml b/test/config.yml deleted file mode 100644 index d1270add..00000000 --- a/test/config.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -:verbose: false -:require: ./test/fake_env.rb -:concurrency: 50 -:queues: - - [<%="very_"%>often, 2] - - [seldom, 1] diff --git a/test/config__FILE__and__dir__.yml b/test/config__FILE__and__dir__.yml deleted file mode 100644 index 27bbe354..00000000 --- a/test/config__FILE__and__dir__.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -:require: ./test/fake_env.rb - -:__FILE__: <%= __FILE__ %> -:__dir__: <%= __dir__ %> diff --git a/test/config_empty.yml b/test/config_empty.yml deleted file mode 100644 index ed97d539..00000000 --- a/test/config_empty.yml +++ /dev/null @@ -1 +0,0 @@ ---- diff --git a/test/config_environment.yml b/test/config_environment.yml deleted file mode 100644 index b53ab451..00000000 --- a/test/config_environment.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -:concurrency: 50 - -staging: - :verbose: false - :require: ./test/fake_env.rb - :concurrency: 50 - :queues: - - [<%="very_"%>often, 2] - - [seldom, 1] diff --git a/test/config_queues_without_weights.yml b/test/config_queues_without_weights.yml deleted file mode 100644 index 4d5370dc..00000000 --- a/test/config_queues_without_weights.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -:queues: - - [queue_1] - - [queue_2] diff --git a/test/config_string.yml b/test/config_string.yml deleted file mode 100644 index 9eb50306..00000000 --- a/test/config_string.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -verbose: false -require: ./test/fake_env.rb -concurrency: 50 -queues: - - [<%="very_"%>often, 2] - - [seldom, 1] - diff --git a/test/config_with_alias.yml b/test/config_with_alias.yml deleted file mode 100644 index 7f9f8bb0..00000000 --- a/test/config_with_alias.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -:production: &production - :verbose: true - :require: ./test/fake_env.rb - :concurrency: 50 - :queues: - - [<%="very_"%>often, 2] - - [seldom, 1] - -:staging: - <<: *production - :verbose: false \ No newline at end of file diff --git a/test/config_with_internal_options.yml b/test/config_with_internal_options.yml deleted file mode 100644 index fe5da7b6..00000000 --- a/test/config_with_internal_options.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -:verbose: false -:timeout: 10 -:require: ./test/fake_env.rb -:concurrency: 50 -:tag: tag -:queues: - - [often] - - [seldom] - -:strict: false diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb deleted file mode 100644 index f7fb6414..00000000 --- a/test/dummy/config/application.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require "rails" -require "active_model/railtie" -require "active_job/railtie" -require "active_record/railtie" -require "action_controller/railtie" -require "action_mailer/railtie" -require "action_view/railtie" -require "rails/test_unit/railtie" - -module Dummy - class Application < Rails::Application - config.root = File.expand_path("../..", __FILE__) - config.eager_load = false - config.logger = Logger.new('/dev/null') - config.load_defaults "6.0" - end -end diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml deleted file mode 100644 index 29cc2b2f..00000000 --- a/test/dummy/config/database.yml +++ /dev/null @@ -1,11 +0,0 @@ -development: - adapter: sqlite3 - database: db/development.sqlite3 - pool: 5 - timeout: 5000 - -test: - adapter: sqlite3 - database: db/test.sqlite3 - pool: 5 - timeout: 5000 diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb deleted file mode 100644 index 875ab0f2..00000000 --- a/test/dummy/config/environment.rb +++ /dev/null @@ -1,3 +0,0 @@ -require_relative 'application' - -Rails.application.initialize! diff --git a/test/dummy/config/sidekiq.yml b/test/dummy/config/sidekiq.yml deleted file mode 100644 index b9e5211b..00000000 --- a/test/dummy/config/sidekiq.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -:require: ./test/fake_env.rb -:concurrency: 25 diff --git a/test/dummy/tmp/.keep b/test/dummy/tmp/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/fake_env.rb b/test/fake_env.rb deleted file mode 100644 index 8e9b8f90..00000000 --- a/test/fake_env.rb +++ /dev/null @@ -1 +0,0 @@ -# frozen_string_literal: true diff --git a/test/fixtures/en.yml b/test/fixtures/en.yml deleted file mode 100644 index 26500216..00000000 --- a/test/fixtures/en.yml +++ /dev/null @@ -1,2 +0,0 @@ -en: - translated_text: 'Changed text from add locals' diff --git a/test/helper.rb b/test/helper.rb deleted file mode 100644 index f283c6a9..00000000 --- a/test/helper.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'bundler/setup' -Bundler.require(:default, :test) - -require 'minitest/pride' -require 'minitest/autorun' - -$TESTING = true -# disable minitest/parallel threads -ENV["MT_CPU"] = "0" -ENV["N"] = "0" -# Disable any stupid backtrace cleansers -ENV["BACKTRACE"] = "1" - -if ENV["COVERAGE"] - require 'simplecov' - SimpleCov.start do - enable_coverage :branch - add_filter "/test/" - add_filter "/myapp/" - end - if ENV['CI'] - require 'codecov' - SimpleCov.formatter = SimpleCov::Formatter::Codecov - end -end - -ENV['REDIS_URL'] ||= 'redis://localhost/15' - -Sidekiq.logger = ::Logger.new(STDOUT) -Sidekiq.logger.level = Logger::ERROR - -def capture_logging(lvl=Logger::INFO) - old = Sidekiq.logger - begin - out = StringIO.new - logger = ::Logger.new(out) - logger.level = lvl - Sidekiq.logger = logger - yield - out.string - ensure - Sidekiq.logger = old - end -end diff --git a/test/test_actors.rb b/test/test_actors.rb deleted file mode 100644 index 63cbfac4..00000000 --- a/test/test_actors.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/cli' -require 'sidekiq/fetch' -require 'sidekiq/scheduled' -require 'sidekiq/processor' - -describe 'Actors' do - class JoeWorker - include Sidekiq::Worker - def perform(slp) - raise "boom" if slp == "boom" - sleep(slp) if slp > 0 - $count += 1 - end - end - - before do - Sidekiq.redis {|c| c.flushdb} - end - - describe 'scheduler' do - it 'can start and stop' do - f = Sidekiq::Scheduled::Poller.new - f.start - f.terminate - end - - it 'can schedule' do - ss = Sidekiq::ScheduledSet.new - q = Sidekiq::Queue.new - - JoeWorker.perform_in(0.01, 0) - - assert_equal 0, q.size - assert_equal 1, ss.size - - sleep 0.015 - s = Sidekiq::Scheduled::Poller.new - s.enqueue - assert_equal 1, q.size - assert_equal 0, ss.size - s.terminate - end - end - - describe 'processor' do - before do - $count = 0 - end - - it 'can start and stop' do - m = Mgr.new - f = Sidekiq::Processor.new(m, m.options) - f.terminate - end - - class Mgr - attr_reader :latest_error - attr_reader :mutex - attr_reader :cond - def initialize - @mutex = ::Mutex.new - @cond = ::ConditionVariable.new - end - def processor_died(inst, err) - @latest_error = err - @mutex.synchronize do - @cond.signal - end - end - def processor_stopped(inst) - @mutex.synchronize do - @cond.signal - end - end - def options - opts = { :concurrency => 3, :queues => ['default'] } - opts[:fetch] = Sidekiq::BasicFetch.new(opts) - opts - end - end - - it 'can process' do - mgr = Mgr.new - - p = Sidekiq::Processor.new(mgr, mgr.options) - JoeWorker.perform_async(0) - - a = $count - p.process_one - b = $count - assert_equal a + 1, b - end - - it 'deals with errors' do - mgr = Mgr.new - - p = Sidekiq::Processor.new(mgr, mgr.options) - JoeWorker.perform_async("boom") - q = Sidekiq::Queue.new - assert_equal 1, q.size - - a = $count - mgr.mutex.synchronize do - p.start - mgr.cond.wait(mgr.mutex) - end - b = $count - assert_equal a, b - - sleep 0.001 - assert_equal false, p.thread.status - p.terminate(true) - refute_nil mgr.latest_error - assert_equal RuntimeError, mgr.latest_error.class - end - - it 'gracefully kills' do - mgr = Mgr.new - - p = Sidekiq::Processor.new(mgr, mgr.options) - JoeWorker.perform_async(1) - q = Sidekiq::Queue.new - assert_equal 1, q.size - - a = $count - p.start - sleep(0.05) - p.terminate - p.kill(true) - - b = $count - assert_equal a, b - assert_equal false, p.thread.status - refute mgr.latest_error, mgr.latest_error.to_s - end - end -end diff --git a/test/test_api.rb b/test/test_api.rb deleted file mode 100644 index 5342ebd1..00000000 --- a/test/test_api.rb +++ /dev/null @@ -1,680 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/api' -require 'active_job' -require 'action_mailer' - -describe 'API' do - before do - Sidekiq.redis {|c| c.flushdb } - end - - describe "stats" do - it "is initially zero" do - s = Sidekiq::Stats.new - assert_equal 0, s.processed - assert_equal 0, s.failed - assert_equal 0, s.enqueued - assert_equal 0, s.default_queue_latency - assert_equal 0, s.workers_size - end - - describe "processed" do - it "returns number of processed jobs" do - Sidekiq.redis { |conn| conn.set("stat:processed", 5) } - s = Sidekiq::Stats.new - assert_equal 5, s.processed - end - end - - describe "failed" do - it "returns number of failed jobs" do - Sidekiq.redis { |conn| conn.set("stat:failed", 5) } - s = Sidekiq::Stats.new - assert_equal 5, s.failed - end - end - - describe "reset" do - before do - Sidekiq.redis do |conn| - conn.set('stat:processed', 5) - conn.set('stat:failed', 10) - end - end - - it 'will reset all stats by default' do - Sidekiq::Stats.new.reset - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 0, s.processed - end - - it 'can reset individual stats' do - Sidekiq::Stats.new.reset('failed') - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed - end - - it 'can accept anything that responds to #to_s' do - Sidekiq::Stats.new.reset(:failed) - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed - end - - it 'ignores anything other than "failed" or "processed"' do - Sidekiq::Stats.new.reset((1..10).to_a, ['failed']) - s = Sidekiq::Stats.new - assert_equal 0, s.failed - assert_equal 5, s.processed - end - end - - describe "workers_size" do - it 'retrieves the number of busy workers' do - Sidekiq.redis do |c| - c.sadd("processes", "process_1") - c.sadd("processes", "process_2") - c.hset("process_1", "busy", 1) - c.hset("process_2", "busy", 2) - end - s = Sidekiq::Stats.new - assert_equal 3, s.workers_size - end - end - - describe "queues" do - it "is initially empty" do - s = Sidekiq::Stats::Queues.new - assert_equal 0, s.lengths.size - end - - it "returns a hash of queue and size in order" do - Sidekiq.redis do |conn| - conn.rpush 'queue:foo', '{}' - conn.sadd 'queues', 'foo' - - 3.times { conn.rpush 'queue:bar', '{}' } - conn.sadd 'queues', 'bar' - end - - s = Sidekiq::Stats::Queues.new - assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths - assert_equal "bar", s.lengths.first.first - - assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths - end - end - - describe "enqueued" do - it 'handles latency for good jobs' do - Sidekiq.redis do |conn| - conn.rpush 'queue:default', "{\"enqueued_at\": #{Time.now.to_f}}" - conn.sadd 'queues', 'default' - end - s = Sidekiq::Stats.new - assert s.default_queue_latency > 0 - q = Sidekiq::Queue.new - assert q.latency > 0 - end - - it 'handles latency for incomplete jobs' do - Sidekiq.redis do |conn| - conn.rpush 'queue:default', '{}' - conn.sadd 'queues', 'default' - end - s = Sidekiq::Stats.new - assert_equal 0, s.default_queue_latency - q = Sidekiq::Queue.new - assert_equal 0, q.latency - end - - it "returns total enqueued jobs" do - Sidekiq.redis do |conn| - conn.rpush 'queue:foo', '{}' - conn.sadd 'queues', 'foo' - - 3.times { conn.rpush 'queue:bar', '{}' } - conn.sadd 'queues', 'bar' - end - - s = Sidekiq::Stats.new - assert_equal 4, s.enqueued - end - end - - describe "over time" do - before do - require 'active_support/core_ext/time/conversions' - @before = Time::DATE_FORMATS[:default] - Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S" - end - - after do - Time::DATE_FORMATS[:default] = @before - end - - describe "history" do - it "does not allow invalid input" do - assert_raises(ArgumentError) { Sidekiq::Stats::History.new(-1) } - assert_raises(ArgumentError) { Sidekiq::Stats::History.new(0) } - assert_raises(ArgumentError) { Sidekiq::Stats::History.new(2000) } - assert Sidekiq::Stats::History.new(200) - end - end - - describe "processed" do - it 'retrieves hash of dates' do - Sidekiq.redis do |c| - c.incrby("stat:processed:2012-12-24", 4) - c.incrby("stat:processed:2012-12-25", 1) - c.incrby("stat:processed:2012-12-26", 6) - c.incrby("stat:processed:2012-12-27", 2) - end - Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do - s = Sidekiq::Stats::History.new(2) - assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed) - - s = Sidekiq::Stats::History.new(3) - assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) - - s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) - assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) - end - end - end - - describe "failed" do - it 'retrieves hash of dates' do - Sidekiq.redis do |c| - c.incrby("stat:failed:2012-12-24", 4) - c.incrby("stat:failed:2012-12-25", 1) - c.incrby("stat:failed:2012-12-26", 6) - c.incrby("stat:failed:2012-12-27", 2) - end - Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do - s = Sidekiq::Stats::History.new(2) - assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed - - s = Sidekiq::Stats::History.new(3) - assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed - - s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) - assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed - end - end - end - end - end - - describe 'with an empty database' do - it 'shows queue as empty' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - assert_equal 0, q.latency - end - - before do - ActiveJob::Base.queue_adapter = :sidekiq - ActiveJob::Base.logger = nil - end - - class ApiMailer < ActionMailer::Base - def test_email(*) - end - end - - class ApiJob < ActiveJob::Base - def perform(*) - end - end - - class ApiWorker - include Sidekiq::Worker - end - - class WorkerWithTags - include Sidekiq::Worker - sidekiq_options tags: ['foo'] - end - - it 'can enumerate jobs' do - q = Sidekiq::Queue.new - Time.stub(:now, Time.new(2012, 12, 26)) do - ApiWorker.perform_async(1, 'mike') - assert_equal [ApiWorker.name], q.map(&:klass) - - job = q.first - assert_equal 24, job.jid.size - assert_equal [1, 'mike'], job.args - assert_equal Time.new(2012, 12, 26), job.enqueued_at - end - assert q.latency > 10_000_000 - - q = Sidekiq::Queue.new('other') - assert_equal 0, q.size - end - - it 'enumerates jobs in descending score order' do - # We need to enqueue more than 50 items, which is the page size when retrieving - # from Redis to ensure everything is sorted: the pages and the items withing them. - 51.times { ApiWorker.perform_in(100, 1, 'foo') } - - set = Sidekiq::ScheduledSet.new.to_a - - assert_equal set.sort_by { |job| -job.score }, set - end - - it 'has no enqueued_at time for jobs enqueued in the future' do - job_id = ApiWorker.perform_in(100, 1, 'foo') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - assert_nil job.enqueued_at - end - - it 'unwraps delayed jobs' do - Sidekiq::Extensions.enable_delay! - Sidekiq::Queue.delay.foo(1,2,3) - q = Sidekiq::Queue.new - x = q.first - assert_equal "Sidekiq::Queue.foo", x.display_class - assert_equal [1,2,3], x.display_args - end - - it 'handles previous (raw Array) error_backtrace format' do - add_retry - job = Sidekiq::RetrySet.new.first - assert_equal ['line1', 'line2'], job.error_backtrace - end - - it 'handles previous (marshalled Array) error_backtrace format' do - backtrace = ['line1', 'line2'] - serialized = Marshal.dump(backtrace) - compressed = Zlib::Deflate.deflate(serialized) - encoded = Base64.encode64(compressed) - - payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1], 'queue' => 'default', 'jid' => 'jid', 'error_backtrace' => encoded) - Sidekiq.redis do |conn| - conn.zadd('retry', Time.now.to_f.to_s, payload) - end - - job = Sidekiq::RetrySet.new.first - assert_equal backtrace, job.error_backtrace - end - - describe "Rails unwrapping" do - SERIALIZED_JOBS = { - "5.x" => [ - '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ApiJob","queue":"default","args":[{"job_class":"ApiJob","job_id":"f1bde53f-3852-4ae4-a879-c12eacebbbb0","provider_job_id":null,"queue_name":"default","priority":null,"arguments":[1,2,3],"executions":0,"locale":"en"}],"retry":true,"jid":"099eee72911085a511d0e312","created_at":1568305542.339916,"enqueued_at":1568305542.339947}', - '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::DeliveryJob","queue":"mailers","args":[{"job_class":"ActionMailer::DeliveryJob","job_id":"19cc0115-3d1c-4bbe-a51e-bfa1385895d1","provider_job_id":null,"queue_name":"mailers","priority":null,"arguments":["ApiMailer","test_email","deliver_now",1,2,3],"executions":0,"locale":"en"}],"retry":true,"jid":"37436e5504936400e8cf98db","created_at":1568305542.370133,"enqueued_at":1568305542.370241}', - ], - "6.x" => [ - '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ApiJob","queue":"default","args":[{"job_class":"ApiJob","job_id":"ff2b48d4-bdce-4825-af6b-ef8c11ab651e","provider_job_id":null,"queue_name":"default","priority":null,"arguments":[1,2,3],"executions":0,"exception_executions":{},"locale":"en","timezone":"UTC","enqueued_at":"2019-09-12T16:28:37Z"}],"retry":true,"jid":"ce121bf77b37ae81fe61b6dc","created_at":1568305717.9469702,"enqueued_at":1568305717.947005}', - '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::MailDeliveryJob","queue":"mailers","args":[{"job_class":"ActionMailer::MailDeliveryJob","job_id":"2f967da1-a389-479c-9a4e-5cc059e6d65c","provider_job_id":null,"queue_name":"mailers","priority":null,"arguments":["ApiMailer","test_email","deliver_now",{"args":[1,2,3],"_aj_symbol_keys":["args"]}],"executions":0,"exception_executions":{},"locale":"en","timezone":"UTC","enqueued_at":"2019-09-12T16:28:37Z"}],"retry":true,"jid":"469979df52bb9ef9f48b49e1","created_at":1568305717.9457421,"enqueued_at":1568305717.9457731}', - ], - }.each_pair do |ver,jobs| - it "unwraps ActiveJob #{ver} jobs" do - #ApiJob.perform_later(1,2,3) - #puts Sidekiq::Queue.new.first.value - x = Sidekiq::JobRecord.new(jobs[0], "default") - assert_equal ApiJob.name, x.display_class - assert_equal [1,2,3], x.display_args - end - - it "unwraps ActionMailer #{ver} jobs" do - #ApiMailer.test_email(1,2,3).deliver_later - #puts Sidekiq::Queue.new("mailers").first.value - x = Sidekiq::JobRecord.new(jobs[1], "mailers") - assert_equal "#{ApiMailer.name}#test_email", x.display_class - assert_equal [1,2,3], x.display_args - end - end - end - - it 'has no enqueued_at time for jobs enqueued in the future' do - job_id = ApiWorker.perform_in(100, 1, 'foo') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - assert_nil job.enqueued_at - end - - it 'returns tags field for jobs' do - job_id = ApiWorker.perform_async - assert_equal [], Sidekiq::Queue.new.find_job(job_id).tags - - job_id = WorkerWithTags.perform_async - assert_equal ['foo'], Sidekiq::Queue.new.find_job(job_id).tags - end - - it 'can delete jobs' do - q = Sidekiq::Queue.new - ApiWorker.perform_async(1, 'mike') - assert_equal 1, q.size - - x = q.first - assert_equal ApiWorker.name, x.display_class - assert_equal [1,'mike'], x.display_args - - assert_equal [true], q.map(&:delete) - assert_equal 0, q.size - end - - it "can move scheduled job to queue" do - remain_id = ApiWorker.perform_in(100, 1, 'jason') - job_id = ApiWorker.perform_in(100, 1, 'jason') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - q = Sidekiq::Queue.new - job.add_to_queue - queued_job = q.find_job(job_id) - refute_nil queued_job - assert_equal queued_job.jid, job_id - assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) - refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) - end - - it "handles multiple scheduled jobs when moving to queue" do - jids = Sidekiq::Client.push_bulk('class' => ApiWorker, - 'args' => [[1, 'jason'], [2, 'jason']], - 'at' => Time.now.to_f) - assert_equal 2, jids.size - (remain_id, job_id) = jids - job = Sidekiq::ScheduledSet.new.find_job(job_id) - q = Sidekiq::Queue.new - job.add_to_queue - queued_job = q.find_job(job_id) - refute_nil queued_job - assert_equal queued_job.jid, job_id - assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) - refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) - end - - it 'can kill a scheduled job' do - job_id = ApiWorker.perform_in(100, 1, '{"foo":123}') - job = Sidekiq::ScheduledSet.new.find_job(job_id) - ds = Sidekiq::DeadSet.new - assert_equal 0, ds.size - job.kill - assert_equal 1, ds.size - end - - it 'can find a scheduled job by jid' do - 10.times do |idx| - ApiWorker.perform_in(idx, 1) - end - - job_id = ApiWorker.perform_in(5, 1) - job = Sidekiq::ScheduledSet.new.find_job(job_id) - assert_equal job_id, job.jid - - ApiWorker.perform_in(100, 1, 'jid' => 'jid_in_args') - assert_nil Sidekiq::ScheduledSet.new.find_job('jid_in_args') - end - - it 'can remove jobs when iterating over a sorted set' do - # scheduled jobs must be greater than SortedSet#each underlying page size - 51.times do - ApiWorker.perform_in(100, 'aaron') - end - set = Sidekiq::ScheduledSet.new - set.map(&:delete) - assert_equal set.size, 0 - end - - it 'can remove jobs when iterating over a queue' do - # initial queue size must be greater than Queue#each underlying page size - 51.times do - ApiWorker.perform_async(1, 'aaron') - end - q = Sidekiq::Queue.new - q.map(&:delete) - assert_equal q.size, 0 - end - - it 'can find job by id in queues' do - q = Sidekiq::Queue.new - job_id = ApiWorker.perform_async(1, 'jason') - job = q.find_job(job_id) - refute_nil job - assert_equal job_id, job.jid - end - - it 'can clear a queue' do - q = Sidekiq::Queue.new - 2.times { ApiWorker.perform_async(1, 'mike') } - q.clear - - Sidekiq.redis do |conn| - refute conn.smembers('queues').include?('foo') - refute conn.exists?('queue:foo') - end - end - - it 'can fetch by score' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 2, r.fetch(same_time).size - end - - it 'can fetch by score and jid' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 1, r.fetch(same_time, 'bob1').size - end - - it 'can fetch by score range' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time + 1) - add_retry('bob3', same_time + 2) - r = Sidekiq::RetrySet.new - range = (same_time..(same_time + 1)) - assert_equal 2, r.fetch(range).size - end - - it 'can fetch by score range and jid' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time + 1) - add_retry('bob3', same_time + 2) - r = Sidekiq::RetrySet.new - range = (same_time..(same_time + 1)) - jobs = r.fetch(range, 'bob2') - assert_equal 1, jobs.size - assert_equal jobs[0].jid, 'bob2' - end - - it 'shows empty retries' do - r = Sidekiq::RetrySet.new - assert_equal 0, r.size - end - - it 'can enumerate retries' do - add_retry - - r = Sidekiq::RetrySet.new - assert_equal 1, r.size - array = r.to_a - assert_equal 1, array.size - - retri = array.first - assert_equal 'ApiWorker', retri.klass - assert_equal 'default', retri.queue - assert_equal 'bob', retri.jid - assert_in_delta Time.now.to_f, retri.at.to_f, 0.02 - end - - it 'requires a jid to delete an entry' do - start_time = Time.now.to_f - add_retry('bob2', Time.now.to_f) - assert_raises(ArgumentError) do - Sidekiq::RetrySet.new.delete(start_time) - end - end - - it 'can delete a single retry from score and jid' do - same_time = Time.now.to_f - add_retry('bob1', same_time) - add_retry('bob2', same_time) - r = Sidekiq::RetrySet.new - assert_equal 2, r.size - Sidekiq::RetrySet.new.delete(same_time, 'bob1') - assert_equal 1, r.size - end - - it 'can retry a retry' do - add_retry - r = Sidekiq::RetrySet.new - assert_equal 1, r.size - r.first.retry - assert_equal 0, r.size - assert_equal 1, Sidekiq::Queue.new('default').size - job = Sidekiq::Queue.new('default').first - assert_equal 'bob', job.jid - assert_equal 1, job['retry_count'] - end - - it 'can clear retries' do - add_retry - add_retry('test') - r = Sidekiq::RetrySet.new - assert_equal 2, r.size - r.clear - assert_equal 0, r.size - end - - it 'can scan retries' do - add_retry - add_retry('test') - r = Sidekiq::RetrySet.new - assert_instance_of Enumerator, r.scan('Worker') - assert_equal 2, r.scan('ApiWorker').to_a.size - assert_equal 1, r.scan('*test*').to_a.size - end - - it 'can enumerate processes' do - identity_string = "identity_string" - odata = { - 'pid' => 123, - 'hostname' => Socket.gethostname, - 'key' => identity_string, - 'identity' => identity_string, - 'started_at' => Time.now.to_f - 15, - 'queues' => ['foo', 'bar'] - } - - time = Time.now.to_f - Sidekiq.redis do |conn| - conn.multi do |transaction| - transaction.sadd('processes', odata['key']) - transaction.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time) - transaction.sadd('processes', 'fake:pid') - end - end - - ps = Sidekiq::ProcessSet.new.to_a - assert_equal 1, ps.size - data = ps.first - assert_equal 10, data['busy'] - assert_equal time, data['beat'] - assert_equal 123, data['pid'] - assert_equal ['foo', 'bar'], data.queues - data.quiet! - data.stop! - signals_string = "#{odata['key']}-signals" - assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) } - assert_equal "TSTP", Sidekiq.redis{|c| c.lpop(signals_string) } - end - - it 'can enumerate workers' do - w = Sidekiq::Workers.new - assert_equal 0, w.size - w.each do - assert false - end - - hn = Socket.gethostname - key = "#{hn}:#{$$}" - pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i } - Sidekiq.redis do |conn| - conn.sadd('processes', key) - conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f) - end - - s = "#{key}:workers" - data = Sidekiq.dump_json({ 'payload' => "{}", 'queue' => 'default', 'run_at' => Time.now.to_i }) - Sidekiq.redis do |c| - c.hmset(s, '1234', data) - end - - w.each do |p, x, y| - assert_equal key, p - assert_equal "1234", x - assert_equal 'default', y['queue'] - assert_equal({}, y['payload']) - assert_equal Time.now.year, Time.at(y['run_at']).year - end - - s = "#{key}:workers" - data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) }) - Sidekiq.redis do |c| - c.multi do |transaction| - transaction.hmset(s, '5678', data) - transaction.hmset("b#{s}", '5678', data) - end - end - - assert_equal ['5678', '1234'], w.map { |_, tid, _| tid } - end - - it 'can reschedule jobs' do - add_retry('foo1') - add_retry('foo2') - - retries = Sidekiq::RetrySet.new - assert_equal 2, retries.size - refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) - - retries.each do |retri| - retri.reschedule(Time.now + 15) if retri.jid == 'foo1' - retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2' - end - - assert_equal 2, retries.size - assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) - assert(retries.map { |r| r.score > (Time.now.to_f + 14) }.any?) - end - - it 'prunes processes which have died' do - data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f } - key = "#{data['hostname']}:#{data['pid']}" - Sidekiq.redis do |conn| - conn.sadd('processes', key) - conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f) - end - - ps = Sidekiq::ProcessSet.new - assert_equal 1, ps.size - assert_equal 1, ps.to_a.size - - Sidekiq.redis do |conn| - conn.sadd('processes', "bar:987") - conn.sadd('processes', "bar:986") - end - - ps = Sidekiq::ProcessSet.new - assert_equal 1, ps.size - assert_equal 1, ps.to_a.size - end - - def add_retry(jid = 'bob', at = Time.now.to_f) - payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f, 'error_backtrace' => ['line1', 'line2']) - Sidekiq.redis do |conn| - conn.zadd('retry', at.to_s, payload) - end - end - end -end diff --git a/test/test_cli.rb b/test/test_cli.rb deleted file mode 100644 index 7fce9077..00000000 --- a/test/test_cli.rb +++ /dev/null @@ -1,591 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'sidekiq/cli' - -describe Sidekiq::CLI do - describe '#parse' do - before do - Sidekiq.options = Sidekiq::DEFAULTS.dup - @logger = Sidekiq.logger - @logdev = StringIO.new - Sidekiq.logger = Logger.new(@logdev) - end - - after do - Sidekiq.logger = @logger - end - - subject { Sidekiq::CLI.new } - - def logdev - @logdev ||= StringIO.new - end - - describe '#parse' do - describe 'options' do - describe 'require' do - it 'accepts with -r' do - subject.parse(%w[sidekiq -r ./test/fake_env.rb]) - - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - end - end - - describe 'concurrency' do - it 'accepts with -c' do - subject.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb]) - - assert_equal 60, Sidekiq.options[:concurrency] - end - - describe 'when concurrency is empty and RAILS_MAX_THREADS env var is set' do - before do - ENV['RAILS_MAX_THREADS'] = '9' - end - - after do - ENV.delete('RAILS_MAX_THREADS') - end - - it 'sets concurrency from RAILS_MAX_THREADS env var' do - subject.parse(%w[sidekiq -r ./test/fake_env.rb]) - - assert_equal 9, Sidekiq.options[:concurrency] - end - - it 'option overrides RAILS_MAX_THREADS env var' do - subject.parse(%w[sidekiq -c 60 -r ./test/fake_env.rb]) - - assert_equal 60, Sidekiq.options[:concurrency] - end - end - end - - describe 'setting internal options via the config file' do - describe 'setting the `strict` option via the config file' do - it 'discards the `strict` option specified via the config file' do - subject.parse(%w[sidekiq -C ./test/config_with_internal_options.yml]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - end - - describe 'queues' do - it 'accepts with -q' do - subject.parse(%w[sidekiq -q foo -r ./test/fake_env.rb]) - - assert_equal ['foo'], Sidekiq.options[:queues] - end - - describe 'when weights are not present' do - it 'accepts queues without weights' do - subject.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb]) - - assert_equal ['foo', 'bar'], Sidekiq.options[:queues] - end - - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -q foo -q bar -r ./test/fake_env.rb]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when weights are present' do - it 'accepts queues with weights' do - subject.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb]) - - assert_equal ['foo', 'foo', 'foo', 'bar'], Sidekiq.options[:queues] - end - - it 'does not set strictly ordered queues' do - subject.parse(%w[sidekiq -q foo,3 -q bar -r ./test/fake_env.rb]) - - assert_equal false, !!Sidekiq.options[:strict] - end - end - - it 'accepts queues with multi-word names' do - subject.parse(%w[sidekiq -q queue_one -q queue-two -r ./test/fake_env.rb]) - - assert_equal ['queue_one', 'queue-two'], Sidekiq.options[:queues] - end - - it 'accepts queues with dots in the name' do - subject.parse(%w[sidekiq -q foo.bar -r ./test/fake_env.rb]) - - assert_equal ['foo.bar'], Sidekiq.options[:queues] - end - - describe 'when duplicate queue names' do - it 'raises an argument error' do - assert_raises(ArgumentError) { subject.parse(%w[sidekiq -q foo -q foo -r ./test/fake_env.rb]) } - assert_raises(ArgumentError) { subject.parse(%w[sidekiq -q foo,3 -q foo,1 -r ./test/fake_env.rb]) } - end - end - - describe 'when queues are empty' do - describe 'when no queues are specified via -q' do - it "sets 'default' queue" do - subject.parse(%w[sidekiq -r ./test/fake_env.rb]) - - assert_equal ['default'], Sidekiq.options[:queues] - end - end - - describe 'when no queues are specified via the config file' do - it "sets 'default' queue" do - subject.parse(%w[sidekiq -C ./test/config_empty.yml -r ./test/fake_env.rb]) - - assert_equal ['default'], Sidekiq.options[:queues] - end - end - end - end - - describe 'timeout' do - it 'accepts with -t' do - subject.parse(%w[sidekiq -t 30 -r ./test/fake_env.rb]) - - assert_equal 30, Sidekiq.options[:timeout] - end - end - - describe 'verbose' do - it 'accepts with -v' do - subject.parse(%w[sidekiq -v -r ./test/fake_env.rb]) - - assert_equal Logger::DEBUG, Sidekiq.logger.level - end - end - - describe 'config file' do - it 'accepts with -C' do - subject.parse(%w[sidekiq -C ./test/config.yml]) - - assert_equal './test/config.yml', Sidekiq.options[:config_file] - refute Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_nil Sidekiq.options[:environment] - assert_equal 50, Sidekiq.options[:concurrency] - assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } - assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } - end - - it 'accepts stringy keys' do - subject.parse(%w[sidekiq -C ./test/config_string.yml]) - - assert_equal './test/config_string.yml', Sidekiq.options[:config_file] - refute Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_nil Sidekiq.options[:environment] - assert_equal 50, Sidekiq.options[:concurrency] - assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } - assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } - end - - it 'accepts environment specific config' do - subject.parse(%w[sidekiq -e staging -C ./test/config_environment.yml]) - - assert_equal './test/config_environment.yml', Sidekiq.options[:config_file] - refute Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_equal 'staging', Sidekiq.options[:environment] - assert_equal 50, Sidekiq.options[:concurrency] - assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } - assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } - end - - it 'accepts environment specific config with alias' do - subject.parse(%w[sidekiq -e staging -C ./test/config_with_alias.yml]) - assert_equal './test/config_with_alias.yml', Sidekiq.options[:config_file] - refute Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_equal 'staging', Sidekiq.options[:environment] - assert_equal 50, Sidekiq.options[:concurrency] - assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } - assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } - - subject.parse(%w[sidekiq -e production -C ./test/config_with_alias.yml]) - assert_equal './test/config_with_alias.yml', Sidekiq.options[:config_file] - assert Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_equal 'production', Sidekiq.options[:environment] - assert_equal 50, Sidekiq.options[:concurrency] - assert_equal 2, Sidekiq.options[:queues].count { |q| q == 'very_often' } - assert_equal 1, Sidekiq.options[:queues].count { |q| q == 'seldom' } - end - - it 'exposes ERB expected __FILE__ and __dir__' do - given_path = './test/config__FILE__and__dir__.yml' - expected_file = File.expand_path(given_path) - # As per Ruby's Kernel module docs, __dir__ is equivalent to File.dirname(File.realpath(__FILE__)) - expected_dir = File.dirname(File.realpath(expected_file)) - - subject.parse(%W[sidekiq -C #{given_path}]) - - assert_equal(expected_file, Sidekiq.options.fetch(:__FILE__)) - assert_equal(expected_dir, Sidekiq.options.fetch(:__dir__)) - end - end - - describe 'default config file' do - describe 'when required path is a directory' do - it 'tries config/sidekiq.yml from required diretory' do - subject.parse(%w[sidekiq -r ./test/dummy]) - - assert_equal './test/dummy/config/sidekiq.yml', Sidekiq.options[:config_file] - assert_equal 25, Sidekiq.options[:concurrency] - end - end - - describe 'when required path is a file' do - it 'tries config/sidekiq.yml from current diretory' do - Sidekiq.options[:require] = './test/dummy' # stub current dir – ./ - - subject.parse(%w[sidekiq -r ./test/fake_env.rb]) - - assert_equal './test/dummy/config/sidekiq.yml', Sidekiq.options[:config_file] - assert_equal 25, Sidekiq.options[:concurrency] - end - end - - describe 'without any required path' do - it 'tries config/sidekiq.yml from current diretory' do - Sidekiq.options[:require] = './test/dummy' # stub current dir – ./ - - subject.parse(%w[sidekiq]) - - assert_equal './test/dummy/config/sidekiq.yml', Sidekiq.options[:config_file] - assert_equal 25, Sidekiq.options[:concurrency] - end - end - - describe 'when config file and flags' do - it 'merges options' do - subject.parse(%w[sidekiq -C ./test/config.yml - -e snoop - -c 100 - -r ./test/fake_env.rb - -q often,7 - -q seldom,3]) - - assert_equal './test/config.yml', Sidekiq.options[:config_file] - refute Sidekiq.options[:verbose] - assert_equal './test/fake_env.rb', Sidekiq.options[:require] - assert_equal 'snoop', Sidekiq.options[:environment] - assert_equal 100, Sidekiq.options[:concurrency] - assert_equal 7, Sidekiq.options[:queues].count { |q| q == 'often' } - assert_equal 3, Sidekiq.options[:queues].count { |q| q == 'seldom' } - end - - describe 'when the config file specifies queues with weights' do - describe 'when -q specifies queues without weights' do - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config.yml - -r ./test/fake_env.rb - -q foo -q bar]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies no queues' do - it 'does not set strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config.yml - -r ./test/fake_env.rb]) - - assert_equal false, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies queues with weights' do - it 'does not set strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config.yml - -r ./test/fake_env.rb - -q foo,2 -q bar,3]) - - assert_equal false, !!Sidekiq.options[:strict] - end - end - end - - describe 'when the config file specifies queues without weights' do - describe 'when -q specifies queues without weights' do - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_queues_without_weights.yml - -r ./test/fake_env.rb - -q foo -q bar]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies no queues' do - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_queues_without_weights.yml - -r ./test/fake_env.rb]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies queues with weights' do - it 'does not set strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_queues_without_weights.yml - -r ./test/fake_env.rb - -q foo,2 -q bar,3]) - - assert_equal false, !!Sidekiq.options[:strict] - end - end - end - - describe 'when the config file specifies no queues' do - describe 'when -q specifies queues without weights' do - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_empty.yml - -r ./test/fake_env.rb - -q foo -q bar]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies no queues' do - it 'sets strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_empty.yml - -r ./test/fake_env.rb]) - - assert_equal true, !!Sidekiq.options[:strict] - end - end - - describe 'when -q specifies queues with weights' do - it 'does not set strictly ordered queues' do - subject.parse(%w[sidekiq -C ./test/config_empty.yml - -r ./test/fake_env.rb - -q foo,2 -q bar,3]) - - assert_equal false, !!Sidekiq.options[:strict] - end - end - end - end - - describe 'default config file' do - describe 'when required path is a directory' do - it 'tries config/sidekiq.yml' do - subject.parse(%w[sidekiq -r ./test/dummy]) - - assert_equal 'sidekiq.yml', File.basename(Sidekiq.options[:config_file]) - assert_equal 25, Sidekiq.options[:concurrency] - end - end - end - end - end - - describe 'validation' do - describe 'when required application path does not exist' do - it 'exits with status 1' do - exit = assert_raises(SystemExit) { subject.parse(%w[sidekiq -r /non/existent/path]) } - assert_equal 1, exit.status - end - end - - describe 'when required path is a directory without config/application.rb' do - it 'exits with status 1' do - exit = assert_raises(SystemExit) { subject.parse(%w[sidekiq -r ./test/fixtures]) } - assert_equal 1, exit.status - end - - describe 'when config file path does not exist' do - it 'raises argument error' do - assert_raises(ArgumentError) do - subject.parse(%w[sidekiq -r ./test/fake_env.rb -C /non/existent/path]) - end - end - end - end - - describe 'when concurrency is not valid' do - describe 'when set to 0' do - it 'raises argument error' do - assert_raises(ArgumentError) do - subject.parse(%w[sidekiq -r ./test/fake_env.rb -c 0]) - end - end - end - - describe 'when set to a negative number' do - it 'raises argument error' do - assert_raises(ArgumentError) do - subject.parse(%w[sidekiq -r ./test/fake_env.rb -c -2]) - end - end - end - end - - describe 'when timeout is not valid' do - describe 'when set to 0' do - it 'raises argument error' do - assert_raises(ArgumentError) do - subject.parse(%w[sidekiq -r ./test/fake_env.rb -t 0]) - end - end - end - - describe 'when set to a negative number' do - it 'raises argument error' do - assert_raises(ArgumentError) do - subject.parse(%w[sidekiq -r ./test/fake_env.rb -t -2]) - end - end - end - end - end - end - - describe '#run' do - before do - Sidekiq.options[:concurrency] = 2 - Sidekiq.options[:require] = './test/fake_env.rb' - end - - describe 'require workers' do - describe 'when path is a rails directory' do - before do - Sidekiq.options[:require] = './test/dummy' - subject.environment = 'test' - end - - it 'requires sidekiq railtie and rails application with environment' do - subject.stub(:launch, nil) do - subject.run - end - - assert defined?(Sidekiq::Rails) - assert defined?(Dummy::Application) - end - - it 'tags with the app directory name' do - subject.stub(:launch, nil) do - subject.run - end - - assert_equal 'dummy', Sidekiq.options[:tag] - end - end - - describe 'when path is file' do - it 'requires application' do - subject.stub(:launch, nil) do - subject.run - end - - assert $LOADED_FEATURES.any? { |x| x =~ /test\/fake_env/ } - end - end - end - - describe 'when development environment and stdout tty' do - it 'prints banner' do - subject.stub(:environment, 'development') do - assert_output(/#{Regexp.escape(Sidekiq::CLI.banner)}/) do - $stdout.stub(:tty?, true) do - subject.stub(:launch, nil) do - subject.run - end - end - end - end - end - end - - it 'prints rails info' do - subject.stub(:environment, 'production') do - subject.stub(:launch, nil) do - subject.run - end - assert_includes @logdev.string, "Booted Rails #{::Rails.version} application in production environment" - end - end - - describe 'checking maxmemory policy' do - it 'warns if the policy is not noeviction' do - redis_info = { "maxmemory_policy" => "allkeys-lru", "redis_version" => "6" } - - Sidekiq.stub(:redis_info, redis_info) do - subject.stub(:launch, nil) do - subject.run - end - end - - assert_includes @logdev.string, "allkeys-lru" - end - - it 'silent if the policy is noeviction' do - redis_info = { "maxmemory_policy" => "noeviction", "redis_version" => "6" } - - Sidekiq.stub(:redis_info, redis_info) do - subject.stub(:launch, nil) do - subject.run - end - end - - refute_includes @logdev.string, "noeviction" - end - end - end - - describe 'signal handling' do - %w(INT TERM).each do |sig| - describe sig do - it 'raises interrupt error' do - assert_raises Interrupt do - subject.handle_signal(sig) - end - end - end - end - - describe "TSTP" do - it 'quiets with a corresponding event' do - quiet = false - - Sidekiq.on(:quiet) do - quiet = true - end - - subject.launcher = Sidekiq::Launcher.new(Sidekiq.options) - subject.handle_signal("TSTP") - - assert_match(/Got TSTP signal/, logdev.string) - assert_equal true, quiet - end - end - - describe 'TTIN' do - it 'prints backtraces for all threads in the process to the logfile' do - subject.handle_signal('TTIN') - - assert_match(/Got TTIN signal/, logdev.string) - assert_match(/\bbacktrace\b/, logdev.string) - end - end - - describe 'UNKNOWN' do - it 'logs about' do - # subject.parse(%w[sidekiq -r ./test/fake_env.rb]) - subject.handle_signal('UNKNOWN') - - assert_match(/Got UNKNOWN signal/, logdev.string) - assert_match(/No signal handler registered/, logdev.string) - end - end - end - end -end diff --git a/test/test_client.rb b/test/test_client.rb deleted file mode 100644 index 443dc0e6..00000000 --- a/test/test_client.rb +++ /dev/null @@ -1,446 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/api' - -describe Sidekiq::Client do - describe 'errors' do - it 'raises ArgumentError with invalid params' do - assert_raises ArgumentError do - Sidekiq::Client.push('foo', 1) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('foo', :class => 'Foo', :noargs => [1, 2]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'noargs' => [1, 2]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('queue' => 'foo', 'class' => 42, 'args' => [1, 2]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => :not_an_array) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1], 'at' => :not_a_numeric) - end - - assert_raises ArgumentError do - Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1], 'tags' => :not_an_array) - end - end - end - - describe 'as instance' do - it 'handles nil queue' do - assert_raises ArgumentError do - Sidekiq::Client.push('class' => 'Blah', 'args' => [1,2,3], 'queue' => "") - end - end - - it 'can push' do - client = Sidekiq::Client.new - jid = client.push('class' => 'Blah', 'args' => [1,2,3]) - assert_equal 24, jid.size - end - - it 'allows middleware to stop bulk jobs' do - mware = Class.new do - def call(worker_klass,msg,q,r) - msg['args'][0] == 1 ? yield : false - end - end - client = Sidekiq::Client.new - client.middleware do |chain| - chain.add mware - end - q = Sidekiq::Queue.new - q.clear - result = client.push_bulk('class' => 'Blah', 'args' => [[1],[2],[3]]) - assert_equal 1, result.size - assert_equal 1, q.size - end - - it 'allows local middleware modification' do - $called = false - mware = Class.new { def call(worker_klass,msg,q,r); $called = true; msg;end } - client = Sidekiq::Client.new - client.middleware do |chain| - chain.add mware - end - client.push('class' => 'Blah', 'args' => [1,2,3]) - - assert $called - assert client.middleware.exists?(mware) - refute Sidekiq.client_middleware.exists?(mware) - end - end - - describe 'client' do - it 'pushes messages to redis' do - q = Sidekiq::Queue.new('foo') - pre = q.size - jid = Sidekiq::Client.push('queue' => 'foo', 'class' => MyWorker, 'args' => [1, 2]) - assert jid - assert_equal 24, jid.size - assert_equal pre + 1, q.size - end - - it 'pushes messages to redis using a String class' do - q = Sidekiq::Queue.new('foo') - pre = q.size - jid = Sidekiq::Client.push('queue' => 'foo', 'class' => 'MyWorker', 'args' => [1, 2]) - assert jid - assert_equal 24, jid.size - assert_equal pre + 1, q.size - end - - class MyWorker - include Sidekiq::Worker - end - - class QueuedWorker - include Sidekiq::Worker - sidekiq_options :queue => :flimflam - end - - it 'enqueues' do - Sidekiq.redis {|c| c.flushdb } - assert_equal Sidekiq.default_worker_options, MyWorker.get_sidekiq_options - assert MyWorker.perform_async(1, 2) - assert Sidekiq::Client.enqueue(MyWorker, 1, 2) - assert Sidekiq::Client.enqueue_to(:custom_queue, MyWorker, 1, 2) - assert_equal 1, Sidekiq::Queue.new('custom_queue').size - assert Sidekiq::Client.enqueue_to_in(:custom_queue, 3, MyWorker, 1, 2) - assert Sidekiq::Client.enqueue_to_in(:custom_queue, -3, MyWorker, 1, 2) - assert_equal 2, Sidekiq::Queue.new('custom_queue').size - assert Sidekiq::Client.enqueue_in(3, MyWorker, 1, 2) - assert QueuedWorker.perform_async(1, 2) - assert_equal 1, Sidekiq::Queue.new('flimflam').size - end - - describe 'argument checking' do - class InterestingWorker - include Sidekiq::Worker - - def perform(an_argument) - end - end - - it 'enqueues jobs with a symbol as an argument' do - InterestingWorker.perform_async(:symbol) - end - - it 'enqueues jobs with a Date as an argument' do - InterestingWorker.perform_async(Date.new(2021, 1, 1)) - end - - it 'enqueues jobs with a Hash with symbols and string as keys as an argument' do - InterestingWorker.perform_async( - { - some: 'hash', - 'with' => 'different_keys' - } - ) - end - - it 'enqueues jobs with a Struct as an argument' do - InterestingWorker.perform_async( - Struct.new(:x, :y).new(0, 0) - ) - end - - it 'works with a JSON-friendly deep, nested structure' do - InterestingWorker.perform_async( - { - 'foo' => ['a', 'b', 'c'], - 'bar' => ['x', 'y', 'z'] - } - ) - end - - describe 'strict args is enabled' do - before do - Sidekiq.strict_args! - end - - after do - Sidekiq.strict_args!(false) - end - - it 'raises an error when using a symbol as an argument' do - assert_raises ArgumentError do - InterestingWorker.perform_async(:symbol) - end - end - - it 'raises an error when using a Date as an argument' do - assert_raises ArgumentError do - InterestingWorker.perform_async(Date.new(2021, 1, 1)) - end - end - - it 'raises an error when using a Hash with symbols and string as keys as an argument' do - assert_raises ArgumentError do - InterestingWorker.perform_async( - { - some: 'hash', - 'with' => 'different_keys' - } - ) - end - end - - it 'raises an error when using a Struct as an argument' do - assert_raises ArgumentError do - InterestingWorker.perform_async( - Struct.new(:x, :y).new(0, 0) - ) - end - end - - it 'works with a JSON-friendly deep, nested structure' do - InterestingWorker.perform_async( - { - 'foo' => ['a', 'b', 'c'], - 'bar' => ['x', 'y', 'z'] - } - ) - end - - describe 'worker that takes deep, nested structures' do - it 'raises an error on JSON-unfriendly structures' do - assert_raises ArgumentError do - InterestingWorker.perform_async( - { - 'foo' => [:a, :b, :c], - bar: ['x', 'y', 'z'] - } - ) - end - end - end - end - end - end - - describe 'bulk' do - after do - Sidekiq::Queue.new.clear - end - - it 'can push a large set of jobs at once' do - jids = Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => (1..1_000).to_a.map { |x| Array(x) }) - assert_equal 1_000, jids.size - end - - it 'can push a large set of jobs at once using a String class' do - jids = Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => (1..1_000).to_a.map { |x| Array(x) }) - assert_equal 1_000, jids.size - end - - it 'can push jobs scheduled at different times' do - first_at = Time.new(2019, 1, 1) - second_at = Time.new(2019, 1, 2) - jids = Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => [[1], [2]], 'at' => [first_at.to_f, second_at.to_f]) - (first_jid, second_jid) = jids - assert_equal first_at, Sidekiq::ScheduledSet.new.find_job(first_jid).at - assert_equal second_at, Sidekiq::ScheduledSet.new.find_job(second_jid).at - end - - it 'can push jobs scheduled using ActiveSupport::Duration' do - jids = Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => [[1], [2]], 'at' => [1.seconds, 111.seconds]) - assert_equal 2, jids.size - end - - it 'returns the jids for the jobs' do - Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => (1..2).to_a.map { |x| Array(x) }).each do |jid| - assert_match(/[0-9a-f]{12}/, jid) - end - end - - it 'handles no jobs' do - result = Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => []) - assert_equal 0, result.size - end - - describe 'errors' do - it 'raises ArgumentError with invalid params' do - assert_raises ArgumentError do - Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => [[1], 2]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push_bulk('class' => 'QueuedWorker', 'args' => [[1], [2]], 'at' => [Time.now.to_f, :not_a_numeric]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => [[1], [2]], 'at' => [Time.now.to_f]) - end - - assert_raises ArgumentError do - Sidekiq::Client.push_bulk('class' => QueuedWorker, 'args' => [[1]], 'at' => [Time.now.to_f, Time.now.to_f]) - end - end - end - - describe '.perform_bulk' do - it 'pushes a large set of jobs' do - jids = MyWorker.perform_bulk((1..1_001).to_a.map { |x| Array(x) }) - assert_equal 1_001, jids.size - end - - it 'pushes a large set of jobs with a different batch size' do - jids = MyWorker.perform_bulk((1..1_001).to_a.map { |x| Array(x) }, batch_size: 100) - assert_equal 1_001, jids.size - end - - it 'handles no jobs' do - jids = MyWorker.perform_bulk([]) - assert_equal 0, jids.size - end - - describe 'errors' do - it 'raises ArgumentError with invalid params' do - assert_raises ArgumentError do - Sidekiq::Client.push_bulk('class' => 'MyWorker', 'args' => [[1], 2]) - end - end - end - - describe 'lazy enumerator' do - it 'enqueues the jobs by evaluating the enumerator' do - lazy_array = (1..1_001).to_a.map { |x| Array(x) }.lazy - jids = MyWorker.perform_bulk(lazy_array) - assert_equal 1_001, jids.size - end - end - end - end - - class BaseWorker - include Sidekiq::Worker - sidekiq_options 'retry' => 'base' - end - class AWorker < BaseWorker - end - class BWorker < BaseWorker - sidekiq_options 'retry' => 'b' - end - class CWorker < BaseWorker - sidekiq_options 'retry' => 2 - end - - describe 'client middleware' do - class Stopper - def call(worker_class, job, queue, r) - raise ArgumentError unless r - yield if job['args'].first.odd? - end - end - - it 'can stop some of the jobs from pushing' do - client = Sidekiq::Client.new - client.middleware do |chain| - chain.add Stopper - end - - assert_nil client.push('class' => MyWorker, 'args' => [0]) - assert_match(/[0-9a-f]{12}/, client.push('class' => MyWorker, 'args' => [1])) - client.push_bulk('class' => MyWorker, 'args' => [[0], [1]]).each do |jid| - assert_match(/[0-9a-f]{12}/, jid) - end - end - end - - describe 'inheritance' do - it 'inherits sidekiq options' do - assert_equal 'base', AWorker.get_sidekiq_options['retry'] - assert_equal 'b', BWorker.get_sidekiq_options['retry'] - end - end - - describe 'sharding' do - class DWorker < BaseWorker - end - - it 'allows sidekiq_options to point to different Redi' do - conn = MiniTest::Mock.new - conn.expect(:pipelined, [0, 1]) - DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn }) - DWorker.perform_async(1,2,3) - conn.verify - end - - it 'allows #via to point to same Redi' do - conn = MiniTest::Mock.new - conn.expect(:pipelined, [0, 1]) - sharded_pool = ConnectionPool.new(size: 1) { conn } - Sidekiq::Client.via(sharded_pool) do - Sidekiq::Client.via(sharded_pool) do - CWorker.perform_async(1,2,3) - end - end - conn.verify - end - - it 'allows #via to point to different Redi' do - default = Sidekiq::Client.new.redis_pool - - moo = MiniTest::Mock.new - moo.expect(:pipelined, [0, 1]) - beef = ConnectionPool.new(size: 1) { moo } - - oink = MiniTest::Mock.new - oink.expect(:pipelined, [0, 1]) - pork = ConnectionPool.new(size: 1) { oink } - - Sidekiq::Client.via(beef) do - CWorker.perform_async(1,2,3) - assert_equal beef, Sidekiq::Client.new.redis_pool - Sidekiq::Client.via(pork) do - assert_equal pork, Sidekiq::Client.new.redis_pool - CWorker.perform_async(1,2,3) - end - assert_equal beef, Sidekiq::Client.new.redis_pool - end - assert_equal default, Sidekiq::Client.new.redis_pool - moo.verify - oink.verify - end - - it 'allows Resque helpers to point to different Redi' do - conn = MiniTest::Mock.new - conn.expect(:pipelined, []) { |*args, &block| block.call(conn) } - conn.expect(:zadd, 1, [String, Array]) - DWorker.sidekiq_options('pool' => ConnectionPool.new(size: 1) { conn }) - Sidekiq::Client.enqueue_in(10, DWorker, 3) - conn.verify - end - end - - describe 'class attribute race conditions' do - new_class = -> { - Class.new do - class_eval('include Sidekiq::Worker') - - define_method(:foo) { get_sidekiq_options } - end - } - - it 'does not explode when new initializing classes from multiple threads' do - 100.times do - klass = new_class.call - - t1 = Thread.new { klass.sidekiq_options({}) } - t2 = Thread.new { klass.sidekiq_options({}) } - t1.join - t2.join - end - end - end -end diff --git a/test/test_csrf.rb b/test/test_csrf.rb deleted file mode 100644 index 1b2a6975..00000000 --- a/test/test_csrf.rb +++ /dev/null @@ -1,115 +0,0 @@ -require_relative './helper' -require 'sidekiq/web/csrf_protection' - -class TestCsrf < Minitest::Test - def session - @session ||= {} - end - - def env(method=:get, form_hash={}, rack_session=session) - imp = StringIO.new("") - { - "REQUEST_METHOD" => method.to_s.upcase, - "rack.session" => rack_session, - "rack.logger" => ::Logger.new(@logio ||= StringIO.new("")), - "rack.input" => imp, - "rack.request.form_input" => imp, - "rack.request.form_hash" => form_hash, - } - end - - def call(env, &block) - Sidekiq::Web::CsrfProtection.new(block).call(env) - end - - def test_get - ok = [200, {}, ["OK"]] - first = 1 - second = 1 - result = call(env) do |envy| - refute_nil envy[:csrf_token] - assert_equal 88, envy[:csrf_token].size - first = envy[:csrf_token] - ok - end - assert_equal ok, result - - result = call(env) do |envy| - refute_nil envy[:csrf_token] - assert_equal 88, envy[:csrf_token].size - second = envy[:csrf_token] - ok - end - assert_equal ok, result - - # verify masked token changes on every valid request - refute_equal first, second - end - - def test_bad_post - result = call(env(:post)) do - raise "Shouldnt be called" - end - refute_nil result - assert_equal 403, result[0] - assert_equal ["Forbidden"], result[2] - - @logio.rewind - assert_match(/attack prevented/, @logio.string) - end - - def test_good_and_bad_posts - # Make a GET to set up the session with a good token - goodtoken = call(env) do |envy| - envy[:csrf_token] - end - assert goodtoken - - # Make a POST with the known good token - result = call(env(:post, "authenticity_token" => goodtoken)) do - [200, {}, ["OK"]] - end - refute_nil result - assert_equal 200, result[0] - assert_equal ["OK"], result[2] - - # Make a POST with a known bad token - result = call(env(:post, "authenticity_token"=>"N0QRBD34tU61d7fi+0ZaF/35JLW/9K+8kk8dc1TZoK/0pTl7GIHap5gy7BWGsoKlzbMLRp1yaDpCDFwTJtxWAg==")) do - raise "shouldnt be called" - end - refute_nil result - assert_equal 403, result[0] - assert_equal ["Forbidden"], result[2] - end - - def test_empty_session_post - # Make a GET to set up the session with a good token - goodtoken = call(env) do |envy| - envy[:csrf_token] - end - assert goodtoken - - # Make a POST with an empty session data and good token - result = call(env(:post, { "authenticity_token" => goodtoken }, {})) do - raise "shouldnt be called" - end - refute_nil result - assert_equal 403, result[0] - assert_equal ["Forbidden"], result[2] - end - - def test_empty_csrf_session_post - goodtoken = call(env) do |envy| - envy[:csrf_token] - end - assert goodtoken - - # Make a POST without csrf session data and good token - result = call(env(:post, { "authenticity_token" => goodtoken }, { 'session_id' => 'foo' })) do - raise "shouldnt be called" - end - refute_nil result - assert_equal 403, result[0] - assert_equal ["Forbidden"], result[2] - end -end diff --git a/test/test_current_attributes.rb b/test/test_current_attributes.rb deleted file mode 100644 index 9c499726..00000000 --- a/test/test_current_attributes.rb +++ /dev/null @@ -1,64 +0,0 @@ -require_relative "./helper" -require "sidekiq/middleware/current_attributes" - -module Myapp - class Current < ActiveSupport::CurrentAttributes - attribute :user_id - end -end - -class TestCurrentAttributes < Minitest::Test - def test_save - cm = Sidekiq::CurrentAttributes::Save.new(Myapp::Current) - job = {} - with_context(:user_id, 123) do - cm.call(nil, job, nil, nil) do - assert_equal 123, job["cattr"][:user_id] - end - end - end - - def test_load - cm = Sidekiq::CurrentAttributes::Load.new(Myapp::Current) - - job = { "cattr" => { "user_id" => 123 } } - assert_nil Myapp::Current.user_id - cm.call(nil, job, nil) do - assert_equal 123, Myapp::Current.user_id - end - # the Rails reloader is responsible for reseting Current after every unit of work - end - - def test_persist - begin - Sidekiq::CurrentAttributes.persist(Myapp::Current) - job_hash = {} - with_context(:user_id, 16) do - Sidekiq.client_middleware.invoke(nil, job_hash, nil, nil) do - assert_equal 16, job_hash["cattr"][:user_id] - end - end - - assert_nil Myapp::Current.user_id - Sidekiq.server_middleware.invoke(nil, job_hash, nil) do - assert_equal 16, job_hash["cattr"][:user_id] - assert_equal 16, Myapp::Current.user_id - end - assert_nil Myapp::Current.user_id - ensure - Sidekiq.client_middleware.clear - Sidekiq.server_middleware.clear - end - end - - private - - def with_context(attr, value) - begin - Myapp::Current.send("#{attr}=", value) - yield - ensure - Myapp::Current.reset_all - end - end -end diff --git a/test/test_dead_set.rb b/test/test_dead_set.rb deleted file mode 100644 index e6e1d7be..00000000 --- a/test/test_dead_set.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/api' - -describe 'DeadSet' do - def dead_set - Sidekiq::DeadSet.new - end - - it 'should put passed serialized job to the "dead" sorted set' do - serialized_job = Sidekiq.dump_json(jid: '123123', class: 'SomeWorker', args: []) - dead_set.kill(serialized_job) - - assert_equal dead_set.find_job('123123').value, serialized_job - end - - it 'should remove dead jobs older than Sidekiq::DeadSet.timeout' do - Sidekiq::DeadSet.stub(:timeout, 10) do - Time.stub(:now, Time.now - 11) do - dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) # the oldest - end - - Time.stub(:now, Time.now - 9) do - dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) - end - - dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) - end - - assert_nil dead_set.find_job('000103') - assert dead_set.find_job('000102') - assert dead_set.find_job('000101') - end - - it 'should remove all but last Sidekiq::DeadSet.max_jobs-1 jobs' do - Sidekiq::DeadSet.stub(:max_jobs, 3) do - dead_set.kill(Sidekiq.dump_json(jid: '000101', class: 'MyWorker1', args: [])) - dead_set.kill(Sidekiq.dump_json(jid: '000102', class: 'MyWorker2', args: [])) - dead_set.kill(Sidekiq.dump_json(jid: '000103', class: 'MyWorker3', args: [])) - end - - assert_nil dead_set.find_job('000101') - assert dead_set.find_job('000102') - assert dead_set.find_job('000103') - end -end diff --git a/test/test_exception_handler.rb b/test/test_exception_handler.rb deleted file mode 100644 index cb945cdf..00000000 --- a/test/test_exception_handler.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/exception_handler' -require 'stringio' -require 'logger' - -ExceptionHandlerTestException = Class.new(StandardError) -TEST_EXCEPTION = ExceptionHandlerTestException.new("Something didn't work!") - -class Component - include Sidekiq::ExceptionHandler - - def invoke_exception(args) - raise TEST_EXCEPTION - rescue ExceptionHandlerTestException => e - handle_exception(e,args) - end -end - -describe Sidekiq::ExceptionHandler do - describe "with mock logger" do - before do - @old_logger = Sidekiq.logger - @str_logger = StringIO.new - Sidekiq.logger = Logger.new(@str_logger) - end - - after do - Sidekiq.logger = @old_logger - end - - it "logs the exception to Sidekiq.logger" do - Component.new.invoke_exception(:a => 1) - @str_logger.rewind - log = @str_logger.readlines - assert_match(/"a":1/, log[0], "didn't include the context") - assert_match(/Something didn't work!/, log[1], "didn't include the exception message") - assert_match(/test\/test_exception_handler.rb/, log[2], "didn't include the backtrace") - end - - describe "when the exception does not have a backtrace" do - it "does not fail" do - exception = ExceptionHandlerTestException.new - assert_nil exception.backtrace - - begin - Component.new.handle_exception exception - pass - rescue StandardError - flunk "failed handling a nil backtrace" - end - end - end - end - -end diff --git a/test/test_extensions.rb b/test/test_extensions.rb deleted file mode 100644 index dd9f65a3..00000000 --- a/test/test_extensions.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/api' -require 'active_record' -require 'action_mailer' -Sidekiq::Extensions.enable_delay! - -describe Sidekiq::Extensions do - before do - Sidekiq.redis {|c| c.flushdb } - end - - class MyModel < ActiveRecord::Base - def self.long_class_method - raise "Should not be called!" - end - end - - it 'allows delayed execution of ActiveRecord class methods' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new - assert_equal 0, q.size - MyModel.delay.long_class_method - assert_equal ['default'], Sidekiq::Queue.all.map(&:name) - assert_equal 1, q.size - end - - it 'uses and stringifies specified options' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new('notdefault') - assert_equal 0, q.size - MyModel.delay(queue: :notdefault).long_class_method - assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name) - assert_equal ['MyModel.long_class_method'], q.map(&:display_class) - assert_equal 1, q.size - end - - it 'allows delayed scheduling of AR class methods' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - MyModel.delay_for(5.days).long_class_method - assert_equal 1, ss.size - end - - it 'allows until delayed scheduling of AR class methods' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - MyModel.delay_until(1.day.from_now).long_class_method - assert_equal 1, ss.size - end - - class UserMailer < ActionMailer::Base - def greetings(a, b) - raise "Should not be called!" - end - end - - it 'allows delayed delivery of ActionMailer mails' do - assert_equal [], Sidekiq::Queue.all.map(&:name) - q = Sidekiq::Queue.new - assert_equal 0, q.size - UserMailer.delay.greetings(1, 2) - assert_equal ['default'], Sidekiq::Queue.all.map(&:name) - assert_equal 1, q.size - end - - it 'allows delayed scheduling of AM mails' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - UserMailer.delay_for(5.days).greetings(1, 2) - assert_equal 1, ss.size - end - - it 'allows until delay scheduling of AM mails' do - ss = Sidekiq::ScheduledSet.new - assert_equal 0, ss.size - UserMailer.delay_until(5.days.from_now).greetings(1, 2) - assert_equal 1, ss.size - end - - class SomeClass - def self.doit(arg) - end - end - - it 'allows delay of any ole class method' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - SomeClass.delay.doit(Date.today) - assert_equal 1, q.size - end - - module SomeModule - def self.doit(arg) - end - end - - it 'logs large payloads' do - output = capture_logging(Logger::WARN) do - SomeClass.delay.doit('a' * 8192) - end - assert_match(/#{SomeClass}.doit job argument is/, output) - end - - it 'allows delay of any module class method' do - q = Sidekiq::Queue.new - assert_equal 0, q.size - SomeModule.delay.doit(Date.today) - assert_equal 1, q.size - end - -end diff --git a/test/test_fetch.rb b/test/test_fetch.rb deleted file mode 100644 index 65d9106b..00000000 --- a/test/test_fetch.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/fetch' -require 'sidekiq/api' - -describe Sidekiq::BasicFetch do - before do - @prev_redis = Sidekiq.instance_variable_get(:@redis) || {} - Sidekiq.redis = { :namespace => 'fuzzy' } - Sidekiq.redis do |conn| - conn.redis.flushdb - conn.rpush('queue:basic', 'msg') - end - end - - after do - Sidekiq.redis = @prev_redis - end - - it 'retrieves' do - fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar']) - uow = fetch.retrieve_work - refute_nil uow - assert_equal 'basic', uow.queue_name - assert_equal 'msg', uow.job - q = Sidekiq::Queue.new('basic') - assert_equal 0, q.size - uow.requeue - assert_equal 1, q.size - assert_nil uow.acknowledge - end - - it 'retrieves with strict setting' do - fetch = Sidekiq::BasicFetch.new(:queues => ['basic', 'bar', 'bar'], :strict => true) - cmd = fetch.queues_cmd - assert_equal cmd, ['queue:basic', 'queue:bar', Sidekiq::BasicFetch::TIMEOUT] - end - - it 'bulk requeues' do - Sidekiq.redis do |conn| - conn.rpush('queue:foo', ['bob', 'bar']) - conn.rpush('queue:bar', 'widget') - end - - q1 = Sidekiq::Queue.new('foo') - q2 = Sidekiq::Queue.new('bar') - assert_equal 2, q1.size - assert_equal 1, q2.size - - fetch = Sidekiq::BasicFetch.new(:queues => ['foo', 'bar']) - works = 3.times.map { fetch.retrieve_work } - assert_equal 0, q1.size - assert_equal 0, q2.size - - fetch.bulk_requeue(works, {:queues => []}) - assert_equal 2, q1.size - assert_equal 1, q2.size - end - - it 'sleeps when no queues are active' do - fetch = Sidekiq::BasicFetch.new(:queues => []) - mock = Minitest::Mock.new - mock.expect(:call, nil, [Sidekiq::BasicFetch::TIMEOUT]) - fetch.stub(:sleep, mock) { assert_nil fetch.retrieve_work } - mock.verify - end -end diff --git a/test/test_job.rb b/test/test_job.rb deleted file mode 100644 index 5a6de693..00000000 --- a/test/test_job.rb +++ /dev/null @@ -1,13 +0,0 @@ -require_relative "helper" -require "sidekiq/job" - -class TestJob < Minitest::Test - class SomeJob - include Sidekiq::Job - end - - def test_sidekiq_job - SomeJob.perform_async - assert_equal "TestJob::SomeJob", Sidekiq::Queue.new.first.klass - end -end diff --git a/test/test_job_generator.rb b/test/test_job_generator.rb deleted file mode 100644 index 41d746cc..00000000 --- a/test/test_job_generator.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require_relative 'dummy/config/environment' -require 'rails/generators/test_case' -require 'generators/sidekiq/job_generator' - -class JobGeneratorTest < Rails::Generators::TestCase - tests Sidekiq::Generators::JobGenerator - destination File.expand_path('../../tmp', __FILE__) - setup :prepare_destination - - test 'all files are properly created' do - run_generator ['foo'] - assert_file 'app/sidekiq/foo_job.rb' - assert_file 'test/sidekiq/foo_job_test.rb' - end - - test 'gracefully handles extra job suffix' do - run_generator ['foo_job'] - assert_no_file 'app/sidekiq/foo_job_job.rb' - assert_no_file 'test/sidekiq/foo_job_job_test.rb' - - assert_file 'app/sidekiq/foo_job.rb' - assert_file 'test/sidekiq/foo_job_test.rb' - end - - test 'respects rails config test_framework option' do - Rails.application.config.generators do |g| - g.test_framework false - end - - run_generator ['foo'] - - assert_file 'app/sidekiq/foo_job.rb' - assert_no_file 'test/sidekiq/foo_job_test.rb' - ensure - Rails.application.config.generators do |g| - g.test_framework :test_case - end - end - - test 'respects rails config test_framework option for rspec' do - Rails.application.config.generators do |g| - g.test_framework :rspec - end - - run_generator ['foo'] - - assert_file 'app/sidekiq/foo_job.rb' - assert_file 'spec/sidekiq/foo_job_spec.rb' - ensure - Rails.application.config.generators do |g| - g.test_framework :test_case - end - end -end diff --git a/test/test_job_logger.rb b/test/test_job_logger.rb deleted file mode 100644 index d7065568..00000000 --- a/test/test_job_logger.rb +++ /dev/null @@ -1,96 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'sidekiq/job_logger' - -class TestJobLogger < Minitest::Test - def setup - @old = Sidekiq.logger - @output = StringIO.new - @logger = Sidekiq::Logger.new(@output, level: :info) - Sidekiq.logger = @logger - - Thread.current[:sidekiq_context] = nil - Thread.current[:sidekiq_tid] = nil - end - - def teardown - Thread.current[:sidekiq_context] = nil - Thread.current[:sidekiq_tid] = nil - Sidekiq.logger = @old - end - - def test_pretty_output - jl = Sidekiq::JobLogger.new(@logger) - - # pretty - p = @logger.formatter = Sidekiq::Logger::Formatters::Pretty.new - job = {"jid"=>"1234abc", "wrapped"=>"FooWorker", "class"=>"Wrapper", "tags" => ["bar", "baz"]} - # this mocks what Processor does - jl.prepare(job) do - jl.call(job, 'queue') {} - end - - a, b = @output.string.lines - assert a - assert b - - expected = /pid=#{$$} tid=#{p.tid} class=FooWorker jid=1234abc tags=bar,baz/ - assert_match(expected, a) - assert_match(expected, b) - assert_match(/#{Time.now.utc.to_date}.+Z pid=#{$$} tid=#{p.tid} .+INFO: done/, b) - end - - def test_json_output - # json - @logger.formatter = Sidekiq::Logger::Formatters::JSON.new - jl = Sidekiq::JobLogger.new(@logger) - job = {"jid"=>"1234abc", "wrapped"=>"Wrapper", "class"=>"FooWorker", "bid"=>"b-xyz", "tags" => ["bar", "baz"]} - # this mocks what Processor does - jl.prepare(job) do - jl.call(job, 'queue') {} - end - a, b = @output.string.lines - assert a - assert b - hsh = JSON.parse(a) - keys = hsh.keys.sort - assert_equal(["ctx", "lvl", "msg", "pid", "tid", "ts"], keys) - keys = hsh["ctx"].keys.sort - assert_equal(["bid", "class", "jid", "tags"], keys) - end - - def test_custom_log_level - jl = Sidekiq::JobLogger.new(@logger) - job = {"class"=>"FooWorker", "log_level"=>"debug"} - - assert @logger.info? - jl.prepare(job) do - jl.call(job, "queue") do - assert @logger.debug? - @logger.debug("debug message") - end - end - assert @logger.info? - - a, b, c = @output.string.lines - assert_match(/INFO: start/, a) - assert_match(/DEBUG: debug message/, b) - assert_match(/INFO: done/, c) - end - - def test_custom_log_level_uses_default_log_level_for_invalid_value - jl = Sidekiq::JobLogger.new(@logger) - job = {"class"=>"FooWorker", "log_level"=>"non_existent"} - - assert @logger.info? - jl.prepare(job) do - jl.call(job, "queue") do - assert @logger.info? - end - end - assert @logger.info? - log_level_warning = @output.string.lines[0] - assert_match(/WARN: Invalid log level/, log_level_warning) - end -end diff --git a/test/test_launcher.rb b/test/test_launcher.rb deleted file mode 100644 index 39f1de0d..00000000 --- a/test/test_launcher.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'sidekiq/launcher' - -describe Sidekiq::Launcher do - subject { Sidekiq::Launcher.new(options) } - before do - Sidekiq.redis {|c| c.flushdb } - end - - def new_manager(opts) - Sidekiq::Manager.new(opts) - end - - describe 'memory collection' do - it 'works in any test environment' do - kb = Sidekiq::Launcher::MEMORY_GRABBER.call($$) - refute_nil kb - assert kb > 0 - end - end - - describe 'heartbeat' do - before do - @mgr = new_manager(options) - @launcher = Sidekiq::Launcher.new(options) - @launcher.manager = @mgr - @id = @launcher.identity - - Sidekiq::Processor::WORKER_STATE.set('a', {'b' => 1}) - - @proctitle = $0 - end - - after do - Sidekiq::Processor::WORKER_STATE.clear - $0 = @proctitle - end - - describe '#heartbeat' do - describe 'run' do - it 'sets sidekiq version, tag and the number of busy workers to proctitle' do - subject.heartbeat - - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy]", $0 - end - - it 'stores process info in redis' do - subject.heartbeat - - workers, rtt = Sidekiq.redis { |c| c.hmget(subject.identity, 'busy', 'rtt_us') } - - assert_equal "1", workers - refute_nil rtt - assert_in_delta 1000, rtt.to_i, 1000 - - expires = Sidekiq.redis { |c| c.pttl(subject.identity) } - - assert_in_delta 60000, expires, 500 - end - - describe 'events' do - before do - @cnt = 0 - - Sidekiq.on(:heartbeat) do - @cnt += 1 - end - end - - it 'fires start heartbeat event only once' do - assert_equal 0, @cnt - subject.heartbeat - assert_equal 1, @cnt - subject.heartbeat - assert_equal 1, @cnt - end - end - end - - describe 'quiet' do - before do - subject.quiet - end - - it 'sets stopping proctitle' do - subject.heartbeat - - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0 - end - - it 'stores process info in redis' do - subject.heartbeat - - info = Sidekiq.redis { |c| c.hmget(subject.identity, 'busy') } - - assert_equal ["1"], info - - expires = Sidekiq.redis { |c| c.pttl(subject.identity) } - - assert_in_delta 60000, expires, 50 - end - end - - it 'fires new heartbeat events' do - i = 0 - Sidekiq.on(:heartbeat) do - i += 1 - end - assert_equal 0, i - @launcher.heartbeat - assert_equal 1, i - @launcher.heartbeat - assert_equal 1, i - end - - describe 'when manager is active' do - before do - Sidekiq::Launcher::PROCTITLES << proc { "xyz" } - @launcher.heartbeat - Sidekiq::Launcher::PROCTITLES.pop - end - - it 'sets useful info to proctitle' do - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] xyz", $0 - end - - it 'stores process info in redis' do - info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } - assert_equal ["1"], info - expires = Sidekiq.redis { |c| c.pttl(@id) } - assert_in_delta 60000, expires, 500 - end - end - end - - describe 'when manager is stopped' do - before do - @launcher.quiet - @launcher.heartbeat - end - - #after do - #puts system('redis-cli -n 15 keys "*" | while read LINE ; do TTL=`redis-cli -n 15 ttl "$LINE"`; if [ "$TTL" -eq -1 ]; then echo "$LINE"; fi; done;') - #end - - it 'indicates stopping status in proctitle' do - assert_equal "sidekiq #{Sidekiq::VERSION} myapp [1 of 3 busy] stopping", $0 - end - - it 'stores process info in redis' do - info = Sidekiq.redis { |c| c.hmget(@id, 'busy') } - assert_equal ["1"], info - expires = Sidekiq.redis { |c| c.pttl(@id) } - assert_in_delta 60000, expires, 50 - end - end - end - - def options - { :concurrency => 3, :queues => ['default'], :tag => 'myapp' } - end - -end diff --git a/test/test_logger.rb b/test/test_logger.rb deleted file mode 100644 index fbbea0ab..00000000 --- a/test/test_logger.rb +++ /dev/null @@ -1,147 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'sidekiq/logger' - -class TestLogger < Minitest::Test - def setup - @output = StringIO.new - @logger = Sidekiq::Logger.new(@output) - - Sidekiq.log_formatter = nil - Thread.current[:sidekiq_context] = nil - Thread.current[:sidekiq_tid] = nil - end - - def teardown - Sidekiq.log_formatter = nil - Thread.current[:sidekiq_context] = nil - Thread.current[:sidekiq_tid] = nil - end - - def test_default_log_formatter - assert_kind_of Sidekiq::Logger::Formatters::Pretty, Sidekiq::Logger.new(@output).formatter - end - - def test_heroku_log_formatter - begin - ENV['DYNO'] = 'dyno identifier' - assert_kind_of Sidekiq::Logger::Formatters::WithoutTimestamp, Sidekiq::Logger.new(@output).formatter - ensure - ENV['DYNO'] = nil - end - end - - def test_json_log_formatter - Sidekiq.log_formatter = Sidekiq::Logger::Formatters::JSON.new - - assert_kind_of Sidekiq::Logger::Formatters::JSON, Sidekiq::Logger.new(@output).formatter - end - - def test_with_context - subject = Sidekiq::Context - assert_equal({}, subject.current) - - subject.with(a: 1) do - assert_equal({ a: 1 }, subject.current) - end - - assert_equal({}, subject.current) - end - - def test_with_overlapping_context - subject = Sidekiq::Context - subject.current.merge!({ foo: 'bar' }) - assert_equal({ foo: 'bar' }, subject.current) - - subject.with(foo: 'bingo') do - assert_equal({ foo: 'bingo' }, subject.current) - end - - assert_equal({ foo: 'bar' }, subject.current) - end - - def test_nested_contexts - subject = Sidekiq::Context - assert_equal({}, subject.current) - - subject.with(a: 1) do - assert_equal({ a: 1 }, subject.current) - - subject.with(b: 2, c: 3) do - assert_equal({ a: 1, b: 2, c: 3 }, subject.current) - end - - assert_equal({ a: 1 }, subject.current) - end - - assert_equal({}, subject.current) - end - - def test_formatted_output - @logger.info("hello world") - assert_match(/INFO: hello world/, @output.string) - reset(@output) - - formats = [ Sidekiq::Logger::Formatters::Pretty, - Sidekiq::Logger::Formatters::WithoutTimestamp, - Sidekiq::Logger::Formatters::JSON, ] - formats.each do |fmt| - @logger.formatter = fmt.new - Sidekiq::Context.with(class: 'HaikuWorker', bid: 'b-1234abc') do - @logger.info("hello context") - end - assert_match(/INFO/, @output.string) - assert_match(/hello context/, @output.string) - assert_match(/b-1234abc/, @output.string) - reset(@output) - end - end - - def test_json_output_is_parsable - @logger.formatter = Sidekiq::Logger::Formatters::JSON.new - - @logger.debug("boom") - Sidekiq::Context.with(class: 'HaikuWorker', jid: '1234abc') do - @logger.info("json format") - end - a, b = @output.string.lines - hash = JSON.parse(a) - keys = hash.keys.sort - assert_equal ["lvl", "msg", "pid", "tid", "ts"], keys - assert_nil hash["ctx"] - assert_equal hash["lvl"], "DEBUG" - - hash = JSON.parse(b) - keys = hash.keys.sort - assert_equal ["ctx", "lvl", "msg", "pid", "tid", "ts"], keys - refute_nil hash["ctx"] - assert_equal "1234abc", hash["ctx"]["jid"] - assert_equal "INFO", hash["lvl"] - end - - def test_forwards_logger_kwargs - assert_silent do - logger = Sidekiq::Logger.new('/dev/null', level: Logger::INFO) - - assert_equal Logger::INFO, logger.level - end - end - - def test_log_level_query_methods - logger = Sidekiq::Logger.new('/dev/null', level: Logger::INFO) - - refute_predicate logger, :debug? - assert_predicate logger, :info? - assert_predicate logger, :warn? - - logger.level = Logger::WARN - refute_predicate logger, :info? - assert_predicate logger, :warn? - end - - def reset(io) - io.truncate(0) - io.rewind - end -end diff --git a/test/test_manager.rb b/test/test_manager.rb deleted file mode 100644 index 0d165985..00000000 --- a/test/test_manager.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/manager' - -describe Sidekiq::Manager do - before do - Sidekiq.redis {|c| c.flushdb } - end - - def new_manager(opts) - Sidekiq::Manager.new(opts.merge(fetch: Sidekiq::BasicFetch.new(opts))) - end - - it 'creates N processor instances' do - mgr = new_manager(options) - assert_equal options[:concurrency], mgr.workers.size - end - - it 'shuts down the system' do - mgr = new_manager(options) - mgr.stop(::Process.clock_gettime(::Process::CLOCK_MONOTONIC)) - end - - it 'throws away dead processors' do - mgr = new_manager(options) - init_size = mgr.workers.size - processor = mgr.workers.first - begin - mgr.processor_died(processor, 'ignored') - - assert_equal init_size, mgr.workers.size - refute mgr.workers.include?(processor) - ensure - mgr.workers.each {|p| p.terminate(true) } - end - end - - it 'does not support invalid concurrency' do - assert_raises(ArgumentError) { new_manager(concurrency: 0) } - assert_raises(ArgumentError) { new_manager(concurrency: -1) } - end - - def options - { :concurrency => 3, :queues => ['default'] } - end - -end diff --git a/test/test_middleware.rb b/test/test_middleware.rb deleted file mode 100644 index 4fdf3c18..00000000 --- a/test/test_middleware.rb +++ /dev/null @@ -1,169 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/middleware/chain' -require 'sidekiq/processor' - -describe Sidekiq::Middleware do - before do - $errors = [] - end - - class CustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end - - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end - end - - it 'supports custom middleware' do - chain = Sidekiq::Middleware::Chain.new - chain.add CustomMiddleware, 1, [] - - assert_equal CustomMiddleware, chain.entries.last.klass - end - - class CustomWorker - $recorder = [] - include Sidekiq::Worker - def perform(recorder) - $recorder << ['work_performed'] - end - end - - class NonYieldingMiddleware - def call(*args) - end - end - - class ArgumentYieldingMiddleware - def call(*args) - yield 1 - end - end - - class AnotherCustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end - - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end - end - - class YetAnotherCustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end - - def call(*args) - @recorder << [@name, 'before'] - yield - @recorder << [@name, 'after'] - end - end - - it 'executes middleware in the proper order' do - msg = Sidekiq.dump_json({ 'class' => CustomWorker.to_s, 'args' => [$recorder] }) - - Sidekiq.server_middleware do |chain| - # should only add once, second should replace the first - 2.times { |i| chain.add CustomMiddleware, i.to_s, $recorder } - chain.insert_before CustomMiddleware, AnotherCustomMiddleware, '2', $recorder - chain.insert_after AnotherCustomMiddleware, YetAnotherCustomMiddleware, '3', $recorder - end - - boss = Minitest::Mock.new - opts = {:queues => ['default'] } - processor = Sidekiq::Processor.new(boss, opts) - boss.expect(:processor_done, nil, [processor]) - processor.process(Sidekiq::BasicFetch::UnitOfWork.new('queue:default', msg)) - assert_equal %w(2 before 3 before 1 before work_performed 1 after 3 after 2 after), $recorder.flatten - end - - it 'correctly replaces middleware when using middleware with options in the initializer' do - chain = Sidekiq::Middleware::Chain.new - chain.add NonYieldingMiddleware - chain.add NonYieldingMiddleware, {:foo => 5} - assert_equal 1, chain.count - end - - it 'correctly prepends middleware' do - chain = Sidekiq::Middleware::Chain.new - chain_entries = chain.entries - chain.add CustomMiddleware - chain.prepend YetAnotherCustomMiddleware - assert_equal YetAnotherCustomMiddleware, chain_entries.first.klass - assert_equal CustomMiddleware, chain_entries.last.klass - end - - it 'allows middleware to abruptly stop processing rest of chain' do - recorder = [] - chain = Sidekiq::Middleware::Chain.new - chain.add NonYieldingMiddleware - chain.add CustomMiddleware, 1, recorder - - final_action = nil - chain.invoke { final_action = true } - assert_nil final_action - assert_equal [], recorder - end - - it 'allows middleware to yield arguments' do - chain = Sidekiq::Middleware::Chain.new - chain.add ArgumentYieldingMiddleware - - final_action = nil - chain.invoke { final_action = true } - assert_equal true, final_action - end - - describe 'I18n' do - before do - require 'i18n' - I18n.enforce_available_locales = false - require 'sidekiq/middleware/i18n' - end - - it 'saves and restores locale' do - I18n.locale = 'fr' - msg = {} - mw = Sidekiq::Middleware::I18n::Client.new - mw.call(nil, msg, nil, nil) { } - assert_equal :fr, msg['locale'] - - msg['locale'] = 'jp' - I18n.locale = I18n.default_locale - assert_equal :en, I18n.locale - mw = Sidekiq::Middleware::I18n::Server.new - mw.call(nil, msg, nil) do - assert_equal :jp, I18n.locale - end - assert_equal :en, I18n.locale - end - - it 'supports I18n.enforce_available_locales = true' do - I18n.enforce_available_locales = true - I18n.available_locales = [:en, :jp] - - msg = { 'locale' => 'jp' } - mw = Sidekiq::Middleware::I18n::Server.new - mw.call(nil, msg, nil) do - assert_equal :jp, I18n.locale - end - - I18n.enforce_available_locales = false - I18n.available_locales = nil - end - end -end diff --git a/test/test_processor.rb b/test/test_processor.rb deleted file mode 100644 index ae1fcc4d..00000000 --- a/test/test_processor.rb +++ /dev/null @@ -1,367 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/fetch' -require 'sidekiq/cli' -require 'sidekiq/processor' - -describe Sidekiq::Processor do - TestProcessorException = Class.new(StandardError) - TEST_PROC_EXCEPTION = TestProcessorException.new("kerboom!") - - before do - $invokes = 0 - @mgr = Minitest::Mock.new - opts = {:queues => ['default']} - opts[:fetch] = Sidekiq::BasicFetch.new(opts) - @processor = ::Sidekiq::Processor.new(@mgr, opts) - end - - class MockWorker - include Sidekiq::Worker - def perform(args) - raise TEST_PROC_EXCEPTION if args.to_s == 'boom' - args.pop if args.is_a? Array - $invokes += 1 - end - end - - def work(msg, queue='queue:default') - Sidekiq::BasicFetch::UnitOfWork.new(queue, msg) - end - - it 'processes as expected' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @processor.process(work(msg)) - assert_equal 1, $invokes - end - - it 'executes a worker as expected' do - worker = Minitest::Mock.new - worker.expect(:perform, nil, [1, 2, 3]) - @processor.execute_job(worker, [1, 2, 3]) - end - - it 're-raises exceptions after handling' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['boom'] }) - re_raise = false - - begin - @processor.process(work(msg)) - flunk "Expected exception" - rescue TestProcessorException - re_raise = true - end - - assert_equal 0, $invokes - assert re_raise, "does not re-raise exceptions after handling" - end - - it 'does not modify original arguments' do - msg = { 'class' => MockWorker.to_s, 'args' => [['myarg']] } - msgstr = Sidekiq.dump_json(msg) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work(msgstr)) - assert_equal [['myarg']], msg['args'] - end - - describe 'exception handling' do - let(:errors) { [] } - let(:error_handler) do - proc do |exception, context| - errors << { exception: exception, context: context } - end - end - - before do - Sidekiq.error_handlers << error_handler - end - - after do - Sidekiq.error_handlers.pop - end - - it 'handles invalid JSON' do - ds = Sidekiq::DeadSet.new - ds.clear - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - msg = Sidekiq.dump_json(job_hash) - job = work(msg[0...-2]) - ds = Sidekiq::DeadSet.new - assert_equal 0, ds.size - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue JSON::ParserError - end - assert_equal 1, ds.size - end - - it 'handles exceptions raised by the job' do - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'], 'jid' => '123987123' } - msg = Sidekiq.dump_json(job_hash) - job = work(msg) - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue TestProcessorException - end - assert_equal 1, errors.count - assert_instance_of TestProcessorException, errors.first[:exception] - assert_equal msg, errors.first[:context][:jobstr] - assert_equal job_hash['jid'], errors.first[:context][:job]['jid'] - end - - it 'handles exceptions raised by the reloader' do - job_hash = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - msg = Sidekiq.dump_json(job_hash) - @processor.instance_variable_set(:'@reloader', proc { raise TEST_PROC_EXCEPTION }) - job = work(msg) - begin - @processor.instance_variable_set(:'@job', job) - @processor.process(job) - rescue TestProcessorException - end - assert_equal 1, errors.count - assert_instance_of TestProcessorException, errors.first[:exception] - assert_equal msg, errors.first[:context][:jobstr] - assert_equal job_hash, errors.first[:context][:job] - end - - it 'handles exceptions raised during fetch' do - fetch_stub = lambda { raise StandardError, "fetch exception" } - # swallow logging because actually care about the added exception handler - capture_logging do - @processor.instance_variable_get('@strategy').stub(:retrieve_work, fetch_stub) do - @processor.process_one - end - end - - assert_instance_of StandardError, errors.last[:exception] - end - end - - describe 'acknowledgement' do - class ExceptionRaisingMiddleware - def initialize(raise_before_yield, raise_after_yield, skip) - @raise_before_yield = raise_before_yield - @raise_after_yield = raise_after_yield - @skip = skip - end - - def call(worker, item, queue) - raise TEST_PROC_EXCEPTION if @raise_before_yield - yield unless @skip - raise TEST_PROC_EXCEPTION if @raise_after_yield - end - end - - let(:raise_before_yield) { false } - let(:raise_after_yield) { false } - let(:skip_job) { false } - let(:worker_args) { ['myarg'] } - let(:work) { MiniTest::Mock.new } - - before do - work.expect(:queue_name, 'queue:default') - work.expect(:job, Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => worker_args })) - Sidekiq.server_middleware do |chain| - chain.prepend ExceptionRaisingMiddleware, raise_before_yield, raise_after_yield, skip_job - end - end - - after do - Sidekiq.server_middleware do |chain| - chain.remove ExceptionRaisingMiddleware - end - work.verify - end - - describe 'middleware throws an exception before processing the work' do - let(:raise_before_yield) { true } - - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestProcessorException - end - end - end - - describe 'middleware throws an exception after processing the work' do - let(:raise_after_yield) { true } - - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestProcessorException - end - end - end - - describe 'middleware decides to skip work' do - let(:skip_job) { true } - - it 'acks the job' do - work.expect(:acknowledge, nil) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work) - end - end - - describe 'worker raises an exception' do - let(:worker_args) { ['boom'] } - - it 'acks the job' do - work.expect(:acknowledge, nil) - begin - @processor.process(work) - flunk "Expected #process to raise exception" - rescue TestProcessorException - end - end - end - - describe 'everything goes well' do - it 'acks the job' do - work.expect(:acknowledge, nil) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work) - end - end - end - - describe 'retry' do - class ArgsMutatingServerMiddleware - def call(worker, item, queue) - item['args'] = item['args'].map do |arg| - arg.to_sym if arg.is_a?(String) - end - yield - end - end - - class ArgsMutatingClientMiddleware - def call(worker, item, queue, redis_pool) - item['args'] = item['args'].map do |arg| - arg.to_s if arg.is_a?(Symbol) - end - yield - end - end - - before do - Sidekiq.server_middleware do |chain| - chain.prepend ArgsMutatingServerMiddleware - end - Sidekiq.client_middleware do |chain| - chain.prepend ArgsMutatingClientMiddleware - end - end - - after do - Sidekiq.server_middleware do |chain| - chain.remove ArgsMutatingServerMiddleware - end - Sidekiq.client_middleware do |chain| - chain.remove ArgsMutatingClientMiddleware - end - end - - describe 'middleware mutates the job args and then fails' do - it 'requeues with original arguments' do - job_data = { 'class' => MockWorker.to_s, 'args' => ['boom'] } - - retry_stub_called = false - retry_stub = lambda { |worker, msg, queue, exception| - retry_stub_called = true - assert_equal 'boom', msg['args'].first - } - - @processor.instance_variable_get('@retrier').stub(:attempt_retry, retry_stub) do - msg = Sidekiq.dump_json(job_data) - begin - @processor.process(work(msg)) - flunk "Expected exception" - rescue TestProcessorException - end - end - - assert retry_stub_called - end - end - end - - describe 'stats' do - before do - Sidekiq.redis {|c| c.flushdb } - end - - describe 'when successful' do - let(:processed_today_key) { "stat:processed:#{Time.now.utc.strftime("%Y-%m-%d")}" } - - def successful_job - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work(msg)) - end - - it 'increments processed stat' do - Sidekiq::Processor::PROCESSED.reset - successful_job - assert_equal 1, Sidekiq::Processor::PROCESSED.reset - end - end - - describe 'custom job logger class' do - class CustomJobLogger < Sidekiq::JobLogger - def call(item, queue) - yield - rescue Exception - raise - end - end - - before do - opts = {:queues => ['default'], job_logger: CustomJobLogger} - @mgr = Minitest::Mock.new - @processor = ::Sidekiq::Processor.new(@mgr, opts) - end - end - end - - describe 'stats' do - before do - Sidekiq.redis {|c| c.flushdb } - end - - def successful_job - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @mgr.expect(:processor_done, nil, [@processor]) - @processor.process(work(msg)) - end - - it 'increments processed stat' do - Sidekiq::Processor::PROCESSED.reset - successful_job - assert_equal 1, Sidekiq::Processor::PROCESSED.reset - end - end - - describe 'custom job logger class' do - before do - opts = {:queues => ['default'], :job_logger => CustomJobLogger} - opts[:fetch] = Sidekiq::BasicFetch.new(opts) - @processor = ::Sidekiq::Processor.new(nil, opts) - end - - it 'is called instead default Sidekiq::JobLogger' do - msg = Sidekiq.dump_json({ 'class' => MockWorker.to_s, 'args' => ['myarg'] }) - @processor.process(work(msg)) - assert_equal 1, $invokes - end - end -end diff --git a/test/test_rails.rb b/test/test_rails.rb deleted file mode 100644 index c5051ea2..00000000 --- a/test/test_rails.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/rails' -require 'sidekiq/api' - -describe 'ActiveJob' do - before do - Sidekiq.redis {|c| c.flushdb } - # need to force this since we aren't booting a Rails app - ActiveJob::Base.queue_adapter = :sidekiq - ActiveJob::Base.logger = nil - ActiveJob::Base.send(:include, ::Sidekiq::Worker::Options) unless ActiveJob::Base.respond_to?(:sidekiq_options) - end - - it 'does not allow Sidekiq::Worker in AJ::Base classes' do - ex = assert_raises ArgumentError do - Class.new(ActiveJob::Base) do - include Sidekiq::Worker - end - end - assert_includes ex.message, "Sidekiq::Worker cannot be included" - end - - it 'loads Sidekiq::Worker::Options in AJ::Base classes' do - aj = Class.new(ActiveJob::Base) do - queue_as :bar - sidekiq_options retry: 4, queue: 'foo', backtrace: 5 - sidekiq_retry_in { |count, _exception| count * 10 } - sidekiq_retries_exhausted do |msg, _exception| - Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}" - end - end - - assert_equal 4, aj.get_sidekiq_options["retry"] - - # When using ActiveJobs, you cannot set the queue with sidekiq_options, you must use - # queue_as or set(queue: ...). This is to avoid duplicate ways of doing the same thing. - instance = aj.perform_later(1, 2, 3) - q = Sidekiq::Queue.new("foo") - assert_equal 0, q.size - q = Sidekiq::Queue.new("bar") - assert_equal 1, q.size - assert_equal 24, instance.provider_job_id.size - - job = q.first - assert_equal 4, job["retry"] - assert_equal 5, job["backtrace"] - # AJ's queue_as should take precedence over Sidekiq's queue option - assert_equal "bar", job["queue"] - end -end diff --git a/test/test_redis_connection.rb b/test/test_redis_connection.rb deleted file mode 100644 index 1782ecff..00000000 --- a/test/test_redis_connection.rb +++ /dev/null @@ -1,300 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'sidekiq/cli' - -describe Sidekiq::RedisConnection do - describe "create" do - before do - Sidekiq.options = Sidekiq::DEFAULTS.dup - @old = ENV['REDIS_URL'] - ENV['REDIS_URL'] = 'redis://localhost/15' - end - - after do - ENV['REDIS_URL'] = @old - end - - # To support both redis-rb 3.3.x #client and 4.0.x #_client - def client_for(redis) - if redis.respond_to?(:_client) - redis._client - else - redis.client - end - end - - it "creates a pooled redis connection" do - pool = Sidekiq::RedisConnection.create - assert_equal Redis, pool.checkout.class - end - - # Readers for these ivars should be available in the next release of - # `connection_pool`, until then we need to reach into the internal state to - # verify the setting. - describe "size" do - def client_connection(*args) - Sidekiq.stub(:server?, nil) do - Sidekiq::RedisConnection.create(*args) - end - end - - def server_connection(*args) - Sidekiq.stub(:server?, "constant") do - Sidekiq::RedisConnection.create(*args) - end - end - - it "uses the specified custom pool size" do - pool = client_connection(size: 42) - assert_equal 42, pool.instance_eval{ @size } - assert_equal 42, pool.instance_eval{ @available.length } - - pool = server_connection(size: 42) - assert_equal 42, pool.instance_eval{ @size } - assert_equal 42, pool.instance_eval{ @available.length } - end - - it "defaults server pool sizes based on concurrency with padding" do - _expected_padding = 5 - prev_concurrency = Sidekiq.options[:concurrency] - Sidekiq.options[:concurrency] = 6 - pool = server_connection - - assert_equal 11, pool.instance_eval{ @size } - assert_equal 11, pool.instance_eval{ @available.length } - ensure - Sidekiq.options[:concurrency] = prev_concurrency - end - - it "defaults client pool sizes to 5" do - pool = client_connection - - assert_equal 5, pool.instance_eval{ @size } - assert_equal 5, pool.instance_eval{ @available.length } - end - - it "changes client pool sizes with ENV" do - begin - ENV['RAILS_MAX_THREADS'] = '9' - pool = client_connection - - assert_equal 9, pool.instance_eval{ @size } - assert_equal 9, pool.instance_eval{ @available.length } - ensure - ENV.delete('RAILS_MAX_THREADS') - end - end - end - - it "disables client setname with nil id" do - pool = Sidekiq::RedisConnection.create(:id => nil) - assert_equal Redis, pool.checkout.class - assert_equal "redis://localhost:6379/15", pool.checkout.connection.fetch(:id) - end - - describe "network_timeout" do - it "sets a custom network_timeout if specified" do - pool = Sidekiq::RedisConnection.create(:network_timeout => 8) - redis = pool.checkout - - assert_equal 8, client_for(redis).timeout - end - - it "uses the default network_timeout if none specified" do - pool = Sidekiq::RedisConnection.create - redis = pool.checkout - - assert_equal 5, client_for(redis).timeout - end - end - - describe "namespace" do - it "uses a given :namespace set by a symbol key" do - pool = Sidekiq::RedisConnection.create(:namespace => "xxx") - assert_equal "xxx", pool.checkout.namespace - end - - it "uses a given :namespace set by a string key" do - pool = Sidekiq::RedisConnection.create("namespace" => "xxx") - assert_equal "xxx", pool.checkout.namespace - end - - it "uses given :namespace over :namespace from Sidekiq.options" do - Sidekiq.options[:namespace] = "xxx" - pool = Sidekiq::RedisConnection.create(:namespace => "yyy") - assert_equal "yyy", pool.checkout.namespace - end - end - - describe "socket path" do - it "uses a given :path" do - pool = Sidekiq::RedisConnection.create(:path => "/var/run/redis.sock") - assert_equal "unix", client_for(pool.checkout).scheme - assert_equal "/var/run/redis.sock", pool.checkout.connection.fetch(:location) - assert_equal 15, pool.checkout.connection.fetch(:db) - end - - it "uses a given :path and :db" do - pool = Sidekiq::RedisConnection.create(:path => "/var/run/redis.sock", :db => 8) - assert_equal "unix", client_for(pool.checkout).scheme - assert_equal "/var/run/redis.sock", pool.checkout.connection.fetch(:location) - assert_equal 8, pool.checkout.connection.fetch(:db) - end - end - - describe "pool_timeout" do - it "uses a given :timeout over the default of 1" do - pool = Sidekiq::RedisConnection.create(:pool_timeout => 5) - - assert_equal 5, pool.instance_eval{ @timeout } - end - - it "uses the default timeout of 1 if no override" do - pool = Sidekiq::RedisConnection.create - - assert_equal 1, pool.instance_eval{ @timeout } - end - end - - describe "driver" do - it "uses redis' ruby driver" do - pool = Sidekiq::RedisConnection.create - redis = pool.checkout - - assert_equal Redis::Connection::Ruby, redis.instance_variable_get(:@client).driver - end - - it "uses redis' default driver if there are many available" do - begin - redis_driver = Object.new - Redis::Connection.drivers << redis_driver - - pool = Sidekiq::RedisConnection.create - redis = pool.checkout - - assert_equal redis_driver, redis.instance_variable_get(:@client).driver - ensure - Redis::Connection.drivers.pop - end - end - - it "uses a given :driver" do - redis_driver = Object.new - pool = Sidekiq::RedisConnection.create(:driver => redis_driver) - redis = pool.checkout - - assert_equal redis_driver, redis.instance_variable_get(:@client).driver - end - end - - describe 'logging redis options' do - it 'redacts credentials' do - options = { - role: 'master', - master_name: 'mymaster', - sentinels: [ - { host: 'host1', port: 26379, password: 'secret'}, - { host: 'host2', port: 26379, password: 'secret'}, - { host: 'host3', port: 26379, password: 'secret'}, - ], - password: 'secret' - } - - output = capture_logging do - Sidekiq::RedisConnection.create(options) - end - - refute_includes(options.inspect, "REDACTED") - assert_includes(output, ':host=>"host1", :port=>26379, :password=>"REDACTED"') - assert_includes(output, ':host=>"host2", :port=>26379, :password=>"REDACTED"') - assert_includes(output, ':host=>"host3", :port=>26379, :password=>"REDACTED"') - assert_includes(output, ':password=>"REDACTED"') - end - - it 'prunes SSL parameters from the logging' do - options = { - ssl_params: { - cert_store: OpenSSL::X509::Store.new - } - } - - output = capture_logging do - Sidekiq::RedisConnection.create(options) - end - - assert_includes(options.inspect, "ssl_params") - refute_includes(output, "ssl_params") - end - end - end - - describe ".determine_redis_provider" do - - before do - @old_env = ENV.to_hash - end - - after do - ENV.update(@old_env) - end - - def with_env_var(var, uri, skip_provider=false) - vars = ['REDISTOGO_URL', 'REDIS_PROVIDER', 'REDIS_URL'] - [var] - vars.each do |v| - next if skip_provider - ENV[v] = nil - end - ENV[var] = uri - assert_equal uri, Sidekiq::RedisConnection.__send__(:determine_redis_provider) - ENV[var] = nil - end - - describe "with REDISTOGO_URL and a parallel REDIS_PROVIDER set" do - it "sets connection URI to the provider" do - uri = 'redis://sidekiq-redis-provider:6379/0' - provider = 'SIDEKIQ_REDIS_PROVIDER' - - ENV['REDIS_PROVIDER'] = provider - ENV[provider] = uri - ENV['REDISTOGO_URL'] = 'redis://redis-to-go:6379/0' - with_env_var provider, uri, true - - ENV[provider] = nil - end - end - - describe "with REDIS_PROVIDER set" do - it "rejects URLs in REDIS_PROVIDER" do - uri = 'redis://sidekiq-redis-provider:6379/0' - - ENV['REDIS_PROVIDER'] = uri - - assert_raises RuntimeError do - Sidekiq::RedisConnection.__send__(:determine_redis_provider) - end - - ENV['REDIS_PROVIDER'] = nil - end - - it "sets connection URI to the provider" do - uri = 'redis://sidekiq-redis-provider:6379/0' - provider = 'SIDEKIQ_REDIS_PROVIDER' - - ENV['REDIS_PROVIDER'] = provider - ENV[provider] = uri - - with_env_var provider, uri, true - - ENV[provider] = nil - end - end - - describe "with REDIS_URL set" do - it "sets connection URI to custom uri" do - with_env_var 'REDIS_URL', 'redis://redis-uri:6379/0' - end - end - - end -end diff --git a/test/test_retry.rb b/test/test_retry.rb deleted file mode 100644 index 30a1fe24..00000000 --- a/test/test_retry.rb +++ /dev/null @@ -1,373 +0,0 @@ -# encoding: utf-8 -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/scheduled' -require 'sidekiq/job_retry' -require 'sidekiq/api' - -describe Sidekiq::JobRetry do - describe 'middleware' do - class SomeWorker - include Sidekiq::Worker - end - - class BadErrorMessage < StandardError - def message - raise "Ahhh, this isn't supposed to happen" - end - end - - before do - Sidekiq.redis {|c| c.flushdb } - end - - def worker - @worker ||= SomeWorker.new - end - - def handler(options={}) - @handler ||= Sidekiq::JobRetry.new(options) - end - - def jobstr(options={}) - Sidekiq.dump_json({ 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true }.merge(options)) - end - - def job - Sidekiq::RetrySet.new.first - end - - it 'retries with a nil worker' do - assert_raises RuntimeError do - handler.global(jobstr, 'default') do - raise "boom" - end - end - assert_equal 1, Sidekiq::RetrySet.new.size - end - - it 'allows disabling retry' do - assert_raises RuntimeError do - handler.local(worker, jobstr('retry' => false), 'default') do - raise "kerblammo!" - end - end - assert_equal 0, Sidekiq::RetrySet.new.size - end - - it 'allows a numeric retry' do - assert_raises RuntimeError do - handler.local(worker, jobstr('retry' => 2), 'default') do - raise "kerblammo!" - end - end - assert_equal 1, Sidekiq::RetrySet.new.size - assert_equal 0, Sidekiq::DeadSet.new.size - end - - it 'allows 0 retry => no retry and dead queue' do - assert_raises RuntimeError do - handler.local(worker, jobstr('retry' => 0), 'default') do - raise "kerblammo!" - end - end - assert_equal 0, Sidekiq::RetrySet.new.size - assert_equal 1, Sidekiq::DeadSet.new.size - end - - it 'handles zany characters in error message, #1705' do - skip 'skipped! test requires ruby 2.1+' if RUBY_VERSION <= '2.1.0' - - assert_raises RuntimeError do - handler.local(worker, jobstr, 'default') do - raise "kerblammo! #{195.chr}" - end - end - assert_equal "kerblammo! �", job["error_message"] - end - - # In the rare event that an error message raises an error itself, - # allow the job to retry. This will likely only happen for custom - # error classes that override #message - it 'handles error message that raises an error' do - assert_raises RuntimeError do - handler.local(worker, jobstr, 'default') do - raise BadErrorMessage.new - end - end - - assert_equal 1, Sidekiq::RetrySet.new.size - refute_nil job["error_message"] - end - - it 'allows a max_retries option in initializer' do - max_retries = 7 - 1.upto(max_retries + 1) do |i| - assert_raises RuntimeError do - job = i > 1 ? jobstr('retry_count' => i - 2) : jobstr - handler(:max_retries => max_retries).local(worker, job, 'default') do - raise "kerblammo!" - end - end - end - - assert_equal max_retries, Sidekiq::RetrySet.new.size - assert_equal 1, Sidekiq::DeadSet.new.size - end - - it 'saves backtraces' do - c = nil - assert_raises RuntimeError do - handler.local(worker, jobstr('backtrace' => true), 'default') do - c = caller(0); raise "kerblammo!" - end - end - - job = Sidekiq::RetrySet.new.first - assert job.error_backtrace - assert_equal c[0], job.error_backtrace[0] - end - - it 'saves partial backtraces' do - c = nil - assert_raises RuntimeError do - handler.local(worker, jobstr('backtrace' => 3), 'default') do - c = caller(0)[0...3]; raise "kerblammo!" - end - end - - job = Sidekiq::RetrySet.new.first - assert job.error_backtrace - assert_equal c, job.error_backtrace - assert_equal 3, c.size - end - - it 'handles a new failed message' do - assert_raises RuntimeError do - handler.local(worker, jobstr, 'default') do - raise "kerblammo!" - end - end - assert_equal 'default', job["queue"] - assert_equal 'kerblammo!', job["error_message"] - assert_equal 'RuntimeError', job["error_class"] - assert_equal 0, job["retry_count"] - refute job["error_backtrace"] - assert job["failed_at"] - end - - it 'shuts down without retrying work-in-progress, which will resume' do - rs = Sidekiq::RetrySet.new - assert_equal 0, rs.size - msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true } - assert_raises Sidekiq::Shutdown do - handler.local(worker, msg, 'default') do - raise Sidekiq::Shutdown - end - end - assert_equal 0, rs.size - end - - it 'shuts down cleanly when shutdown causes exception' do - skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0' - - rs = Sidekiq::RetrySet.new - assert_equal 0, rs.size - msg = { 'class' => 'Bob', 'args' => [1,2,'foo'], 'retry' => true } - assert_raises Sidekiq::Shutdown do - handler.local(worker, msg, 'default') do - begin - raise Sidekiq::Shutdown - rescue Interrupt - raise "kerblammo!" - end - end - end - assert_equal 0, rs.size - end - - it 'shuts down cleanly when shutdown causes chained exceptions' do - skip('Not supported in Ruby < 2.1.0') if RUBY_VERSION < '2.1.0' - - rs = Sidekiq::RetrySet.new - assert_equal 0, rs.size - assert_raises Sidekiq::Shutdown do - handler.local(worker, jobstr, 'default') do - begin - raise Sidekiq::Shutdown - rescue Interrupt - begin - raise "kerblammo!" - rescue - raise "kablooie!" - end - end - end - end - assert_equal 0, rs.size - end - - it 'allows a retry queue' do - assert_raises RuntimeError do - handler.local(worker, jobstr("retry_queue" => 'retryx'), 'default') do - raise "kerblammo!" - end - end - assert_equal 'retryx', job["queue"] - assert_equal 'kerblammo!', job["error_message"] - assert_equal 'RuntimeError', job["error_class"] - assert_equal 0, job["retry_count"] - refute job["error_backtrace"] - assert job["failed_at"] - end - - it 'handles a recurring failed message' do - now = Time.now.to_f - msg = {"queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>10} - assert_raises RuntimeError do - handler.local(worker, jobstr(msg), 'default') do - raise "kerblammo!" - end - end - assert_equal 'default', job["queue"] - assert_equal 'kerblammo!', job["error_message"] - assert_equal 'RuntimeError', job["error_class"] - assert_equal 11, job["retry_count"] - assert job["failed_at"] - end - - it 'throws away old messages after too many retries (using the default)' do - q = Sidekiq::Queue.new - rs = Sidekiq::RetrySet.new - ds = Sidekiq::DeadSet.new - assert_equal 0, q.size - assert_equal 0, rs.size - assert_equal 0, ds.size - now = Time.now.to_f - msg = {"queue"=>"default", "error_message"=>"kerblammo!", "error_class"=>"RuntimeError", "failed_at"=>now, "retry_count"=>25} - assert_raises RuntimeError do - handler.local(worker, jobstr(msg), 'default') do - raise "kerblammo!" - end - end - assert_equal 0, q.size - assert_equal 0, rs.size - assert_equal 1, ds.size - end - - describe "custom retry delay" do - before do - @old_logger = Sidekiq.logger - @tmp_log_path = '/tmp/sidekiq-retries.log' - Sidekiq.logger = Logger.new(@tmp_log_path) - end - - after do - Sidekiq.logger = @old_logger - File.unlink @tmp_log_path if File.exist?(@tmp_log_path) - end - - class CustomWorkerWithoutException - include Sidekiq::Worker - - sidekiq_retry_in do |count| - count * 2 - end - end - - class SpecialError < StandardError - end - - class CustomWorkerWithException - include Sidekiq::Worker - - sidekiq_retry_in do |count, exception| - case exception - when SpecialError - nil - when ArgumentError - count * 4 - else - count * 2 - end - end - end - - class ErrorWorker - include Sidekiq::Worker - - sidekiq_retry_in do |count| - count / 0 - end - end - - it "retries with a default delay" do - refute_equal 4, handler.__send__(:delay_for, worker, 2, StandardError.new) - end - - it "retries with a custom delay and exception 1" do - assert_includes 4..35, handler.__send__(:delay_for, CustomWorkerWithException, 2, ArgumentError.new) - end - - it "retries with a custom delay and exception 2" do - assert_includes 4..35, handler.__send__(:delay_for, CustomWorkerWithException, 2, StandardError.new) - end - - it "retries with a default delay and exception in case of configured with nil" do - refute_equal 8, handler.__send__(:delay_for, CustomWorkerWithException, 2, SpecialError.new) - refute_equal 4, handler.__send__(:delay_for, CustomWorkerWithException, 2, SpecialError.new) - end - - it "retries with a custom delay without exception" do - assert_includes 4..35, handler.__send__(:delay_for, CustomWorkerWithoutException, 2, StandardError.new) - end - - it "falls back to the default retry on exception" do - refute_equal 4, handler.__send__(:delay_for, ErrorWorker, 2, StandardError.new) - assert_match(/Failure scheduling retry using the defined `sidekiq_retry_in`/, - File.read(@tmp_log_path), 'Log entry missing for sidekiq_retry_in') - end - end - - describe 'handles errors withouth cause' do - before do - @error = nil - begin - raise ::StandardError, 'Error' - rescue ::StandardError => e - @error = e - end - end - - it "does not recurse infinitely checking if it's a shutdown" do - assert(!Sidekiq::JobRetry.new.send( - :exception_caused_by_shutdown?, @error)) - end - end - - describe 'handles errors with circular causes' do - before do - @error = nil - begin - begin - raise ::StandardError, 'Error 1' - rescue ::StandardError => e1 - begin - raise ::StandardError, 'Error 2' - rescue ::StandardError - raise e1 - end - end - rescue ::StandardError => e - @error = e - end - end - - it "does not recurse infinitely checking if it's a shutdown" do - assert(!Sidekiq::JobRetry.new.send( - :exception_caused_by_shutdown?, @error)) - end - end - end - -end diff --git a/test/test_retry_exhausted.rb b/test/test_retry_exhausted.rb deleted file mode 100644 index 439eb45c..00000000 --- a/test/test_retry_exhausted.rb +++ /dev/null @@ -1,151 +0,0 @@ -# encoding: utf-8 -require_relative 'helper' -require 'sidekiq/job_retry' - -describe 'sidekiq_retries_exhausted' do - class NewWorker - include Sidekiq::Worker - - sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception - - sidekiq_retries_exhausted do |job, e| - self.exhausted_called = true - self.exhausted_job = job - self.exhausted_exception = e - end - end - - class OldWorker - include Sidekiq::Worker - - sidekiq_class_attribute :exhausted_called, :exhausted_job, :exhausted_exception - - sidekiq_retries_exhausted do |job| - self.exhausted_called = true - self.exhausted_job = job - end - end - - def cleanup - [NewWorker, OldWorker].each do |worker_class| - worker_class.exhausted_called = nil - worker_class.exhausted_job = nil - worker_class.exhausted_exception = nil - end - end - - before do - cleanup - end - - after do - cleanup - end - - def new_worker - @new_worker ||= NewWorker.new - end - - def old_worker - @old_worker ||= OldWorker.new - end - - def handler(options={}) - @handler ||= Sidekiq::JobRetry.new(options) - end - - def job(options={}) - @job ||= Sidekiq.dump_json({'class' => 'Bob', 'args' => [1, 2, 'foo']}.merge(options)) - end - - it 'does not run exhausted block when job successful on first run' do - handler.local(new_worker, job('retry' => 2), 'default') do - # successful - end - - refute NewWorker.exhausted_called - end - - it 'does not run exhausted block when job successful on last retry' do - handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - # successful - end - - refute NewWorker.exhausted_called - end - - it 'does not run exhausted block when retries not exhausted yet' do - assert_raises RuntimeError do - handler.local(new_worker, job('retry' => 1), 'default') do - raise 'kerblammo!' - end - end - - refute NewWorker.exhausted_called - end - - it 'runs exhausted block when retries exhausted' do - assert_raises RuntimeError do - handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end - end - - assert NewWorker.exhausted_called - end - - - it 'passes job and exception to retries exhausted block' do - raised_error = assert_raises RuntimeError do - handler.local(new_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end - end - raised_error = raised_error.cause - - assert new_worker.exhausted_called - assert_equal raised_error.message, new_worker.exhausted_job['error_message'] - assert_equal raised_error, new_worker.exhausted_exception - end - - it 'passes job to retries exhausted block' do - raised_error = assert_raises RuntimeError do - handler.local(old_worker, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end - end - raised_error = raised_error.cause - - assert old_worker.exhausted_called - assert_equal raised_error.message, old_worker.exhausted_job['error_message'] - assert_nil new_worker.exhausted_exception - end - - it 'allows global failure handlers' do - begin - class Foobar - include Sidekiq::Worker - end - - exhausted_job = nil - exhausted_exception = nil - Sidekiq.death_handlers.clear - Sidekiq.death_handlers << proc do |job, ex| - exhausted_job = job - exhausted_exception = ex - end - f = Foobar.new - raised_error = assert_raises RuntimeError do - handler.local(f, job('retry_count' => 0, 'retry' => 1), 'default') do - raise 'kerblammo!' - end - end - raised_error = raised_error.cause - - assert exhausted_job - assert_equal raised_error, exhausted_exception - ensure - Sidekiq.death_handlers.clear - end - end -end diff --git a/test/test_scheduled.rb b/test/test_scheduled.rb deleted file mode 100644 index 4fab6c5b..00000000 --- a/test/test_scheduled.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/scheduled' - -describe Sidekiq::Scheduled do - class ScheduledWorker - include Sidekiq::Worker - def perform(x) - end - end - - describe 'poller' do - before do - Sidekiq.redis{|c| c.flushdb} - @error_1 = { 'class' => ScheduledWorker.name, 'args' => [0], 'queue' => 'queue_1' } - @error_2 = { 'class' => ScheduledWorker.name, 'args' => [1], 'queue' => 'queue_2' } - @error_3 = { 'class' => ScheduledWorker.name, 'args' => [2], 'queue' => 'queue_3' } - @future_1 = { 'class' => ScheduledWorker.name, 'args' => [3], 'queue' => 'queue_4' } - @future_2 = { 'class' => ScheduledWorker.name, 'args' => [4], 'queue' => 'queue_5' } - @future_3 = { 'class' => ScheduledWorker.name, 'args' => [5], 'queue' => 'queue_6' } - - @retry = Sidekiq::RetrySet.new - @scheduled = Sidekiq::ScheduledSet.new - @poller = Sidekiq::Scheduled::Poller.new - end - - class MyStopper - def call(worker_class, job, queue, r) - yield if job['args'].first.odd? - end - end - - it 'executes client middleware' do - Sidekiq.client_middleware.add MyStopper - begin - @retry.schedule (Time.now - 60).to_f, @error_1 - @retry.schedule (Time.now - 60).to_f, @error_2 - @scheduled.schedule (Time.now - 60).to_f, @future_2 - @scheduled.schedule (Time.now - 60).to_f, @future_3 - - @poller.enqueue - - assert_equal 0, Sidekiq::Queue.new("queue_1").size - assert_equal 1, Sidekiq::Queue.new("queue_2").size - assert_equal 0, Sidekiq::Queue.new("queue_5").size - assert_equal 1, Sidekiq::Queue.new("queue_6").size - ensure - Sidekiq.client_middleware.remove MyStopper - end - end - - it 'should empty the retry and scheduled queues up to the current time' do - created_time = Time.new(2013, 2, 3) - enqueued_time = Time.new(2013, 2, 4) - - Time.stub(:now, created_time) do - @retry.schedule (enqueued_time - 60).to_f, @error_1.merge!('created_at' => created_time.to_f) - @retry.schedule (enqueued_time - 50).to_f, @error_2.merge!('created_at' => created_time.to_f) - @retry.schedule (enqueued_time + 60).to_f, @error_3.merge!('created_at' => created_time.to_f) - @scheduled.schedule (enqueued_time - 60).to_f, @future_1.merge!('created_at' => created_time.to_f) - @scheduled.schedule (enqueued_time - 50).to_f, @future_2.merge!('created_at' => created_time.to_f) - @scheduled.schedule (enqueued_time + 60).to_f, @future_3.merge!('created_at' => created_time.to_f) - end - - Time.stub(:now, enqueued_time) do - @poller.enqueue - - Sidekiq.redis do |conn| - %w(queue:queue_1 queue:queue_2 queue:queue_4 queue:queue_5).each do |queue_name| - assert_equal 1, conn.llen(queue_name) - job = Sidekiq.load_json(conn.lrange(queue_name, 0, -1)[0]) - assert_equal enqueued_time.to_f, job['enqueued_at'] - assert_equal created_time.to_f, job['created_at'] - end - end - - assert_equal 1, @retry.size - assert_equal 1, @scheduled.size - end - end - - it 'should not enqueue jobs when terminate has been called' do - created_time = Time.new(2013, 2, 3) - enqueued_time = Time.new(2013, 2, 4) - - Time.stub(:now, created_time) do - @retry.schedule (enqueued_time - 60).to_f, @error_1.merge!('created_at' => created_time.to_f) - @scheduled.schedule (enqueued_time - 60).to_f, @future_1.merge!('created_at' => created_time.to_f) - end - - Time.stub(:now, enqueued_time) do - @poller.terminate - @poller.enqueue - - Sidekiq.redis do |conn| - %w(queue:queue_1 queue:queue_4).each do |queue_name| - assert_equal 0, conn.llen(queue_name) - end - end - - assert_equal 1, @retry.size - assert_equal 1, @scheduled.size - end - end - - def with_sidekiq_option(name, value) - _original, Sidekiq.options[name] = Sidekiq.options[name], value - begin - yield - ensure - Sidekiq.options[name] = _original - end - end - - it 'generates random intervals that target a configured average' do - with_sidekiq_option(:poll_interval_average, 10) do - i = 500 - intervals = Array.new(i){ @poller.send(:random_poll_interval) } - - assert intervals.all?{|x| x >= 5} - assert intervals.all?{|x| x <= 15} - assert_in_delta 10, intervals.sum.to_f / i, 0.5 - end - end - - it 'calculates an average poll interval based on the number of known Sidekiq processes' do - with_sidekiq_option(:average_scheduled_poll_interval, 10) do - 3.times do |i| - Sidekiq.redis do |conn| - conn.sadd("processes", "process-#{i}") - conn.hset("process-#{i}", "info", nil) - end - end - - assert_equal 30, @poller.send(:scaled_poll_interval) - end - end - end -end diff --git a/test/test_scheduling.rb b/test/test_scheduling.rb deleted file mode 100644 index debaa57e..00000000 --- a/test/test_scheduling.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/scheduled' -require 'active_support/core_ext/integer/time' - -describe 'job scheduling' do - describe 'middleware' do - class SomeScheduledWorker - include Sidekiq::Worker - sidekiq_options :queue => :custom_queue - def perform(x) - end - end - - # Assume we can pass any class as time to perform_in - class TimeDuck - def to_f; 42.0 end - end - - it 'schedules jobs' do - ss = Sidekiq::ScheduledSet.new - ss.clear - - assert_equal 0, ss.size - - assert SomeScheduledWorker.perform_in(600, 'mike') - assert_equal 1, ss.size - - assert SomeScheduledWorker.perform_in(1.month, 'mike') - assert_equal 2, ss.size - - assert SomeScheduledWorker.perform_in(5.days.from_now, 'mike') - assert_equal 3, ss.size - - q = Sidekiq::Queue.new("custom_queue") - qs = q.size - assert SomeScheduledWorker.perform_in(-300, 'mike') - assert_equal 3, ss.size - assert_equal qs+1, q.size - - assert Sidekiq::Client.push_bulk('class' => SomeScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600) - assert_equal 5, ss.size - - assert SomeScheduledWorker.perform_in(TimeDuck.new, 'samwise') - assert_equal 6, ss.size - end - - it 'removes the enqueued_at field when scheduling' do - ss = Sidekiq::ScheduledSet.new - ss.clear - - assert SomeScheduledWorker.perform_in(1.month, 'mike') - job = ss.first - assert job['created_at'] - refute job['enqueued_at'] - end - - it 'removes the enqueued_at field when scheduling in bulk' do - ss = Sidekiq::ScheduledSet.new - ss.clear - - assert Sidekiq::Client.push_bulk('class' => SomeScheduledWorker, 'args' => [['mike'], ['mike']], 'at' => 600) - job = ss.first - assert job['created_at'] - refute job['enqueued_at'] - end - end - -end diff --git a/test/test_sidekiq.rb b/test/test_sidekiq.rb deleted file mode 100644 index 1852a626..00000000 --- a/test/test_sidekiq.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/cli' - -describe Sidekiq do - describe 'json processing' do - it 'handles json' do - assert_equal({"foo" => "bar"}, Sidekiq.load_json("{\"foo\":\"bar\"}")) - assert_equal "{\"foo\":\"bar\"}", Sidekiq.dump_json({ "foo" => "bar" }) - end - end - - describe "redis connection" do - it "returns error without creating a connection if block is not given" do - assert_raises(ArgumentError) do - Sidekiq.redis - end - end - end - - describe "❨╯°□°❩╯︵┻━┻" do - before { $stdout = StringIO.new } - after { $stdout = STDOUT } - - it "allows angry developers to express their emotional constitution and remedies it" do - Sidekiq.❨╯°□°❩╯︵┻━┻ - assert_equal "Calm down, yo.\n", $stdout.string - end - end - - describe 'lifecycle events' do - it 'handles invalid input' do - Sidekiq.options[:lifecycle_events][:startup].clear - - e = assert_raises ArgumentError do - Sidekiq.on(:startp) - end - assert_match(/Invalid event name/, e.message) - e = assert_raises ArgumentError do - Sidekiq.on('startup') - end - assert_match(/Symbols only/, e.message) - Sidekiq.on(:startup) do - 1 + 1 - end - - assert_equal 2, Sidekiq.options[:lifecycle_events][:startup].first.call - end - end - - describe 'default_worker_options' do - it 'stringifies keys' do - @old_options = Sidekiq.default_worker_options - begin - Sidekiq.default_worker_options = { queue: 'cat'} - assert_equal 'cat', Sidekiq.default_worker_options['queue'] - ensure - Sidekiq.default_worker_options = @old_options - end - end - end - - describe 'error handling' do - it 'deals with user-specified error handlers which raise errors' do - output = capture_logging do - begin - Sidekiq.error_handlers << proc {|x, hash| - raise 'boom' - } - cli = Sidekiq::CLI.new - cli.handle_exception(RuntimeError.new("hello")) - ensure - Sidekiq.error_handlers.pop - end - end - assert_includes output, "boom" - assert_includes output, "ERROR" - end - end - - describe 'redis connection' do - it 'does not continually retry' do - assert_raises Redis::CommandError do - Sidekiq.redis do |c| - raise Redis::CommandError, "READONLY You can't write against a replica." - end - end - end - - it 'reconnects if connection is flagged as readonly' do - counts = [] - Sidekiq.redis do |c| - counts << c.info['total_connections_received'].to_i - raise Redis::CommandError, "READONLY You can't write against a replica." if counts.size == 1 - end - assert_equal 2, counts.size - assert_equal counts[0] + 1, counts[1] - end - - it 'reconnects if instance state changed' do - counts = [] - Sidekiq.redis do |c| - counts << c.info['total_connections_received'].to_i - raise Redis::CommandError, "UNBLOCKED force unblock from blocking operation, instance state changed (master -> replica?)" if counts.size == 1 - end - assert_equal 2, counts.size - assert_equal counts[0] + 1, counts[1] - end - end - - describe 'redis info' do - it 'calls the INFO command which returns at least redis_version' do - output = Sidekiq.redis_info - assert_includes output.keys, "redis_version" - end - end -end diff --git a/test/test_sidekiqmon.rb b/test/test_sidekiqmon.rb deleted file mode 100644 index c50b6392..00000000 --- a/test/test_sidekiqmon.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/monitor' - -def capture_stdout - $stdout = StringIO.new - yield - $stdout.string.chomp -ensure - $stdout = STDOUT -end - -def output(section = 'all') - capture_stdout do - Sidekiq::Monitor::Status.new.display(section) - end -end - -describe Sidekiq::Monitor do - before do - Sidekiq.redis {|c| c.flushdb} - end - - describe 'status' do - describe 'version' do - it 'displays the current Sidekiq version' do - assert_includes output, "Sidekiq #{Sidekiq::VERSION}" - end - - it 'displays the current time' do - Time.stub(:now, Time.at(0)) do - assert_includes output, Time.at(0).utc.to_s - end - end - end - - describe 'overview' do - it 'has a heading' do - assert_includes output, '---- Overview ----' - end - - it 'displays the correct output' do - mock_stats = OpenStruct.new( - processed: 420710, - failed: 12, - workers_size: 34, - enqueued: 56, - retry_size: 78, - scheduled_size: 90, - dead_size: 666 - ) - Sidekiq::Stats.stub(:new, mock_stats) do - assert_includes output, 'Processed: 420,710' - assert_includes output, 'Failed: 12' - assert_includes output, 'Busy: 34' - assert_includes output, 'Enqueued: 56' - assert_includes output, 'Retries: 78' - assert_includes output, 'Scheduled: 90' - assert_includes output, 'Dead: 666' - end - end - end - - describe 'processes' do - it 'has a heading' do - assert_includes output, '---- Processes (0) ----' - end - - it 'displays the correct output' do - mock_processes = [{ - 'identity' => 'foobar', - 'tag' => 'baz', - 'started_at' => Time.now, - 'concurrency' => 5, - 'busy' => 2, - 'queues' => %w[low medium high] - }] - Sidekiq::ProcessSet.stub(:new, mock_processes) do - assert_includes output, 'foobar [baz]' - assert_includes output, "Started: #{mock_processes.first['started_at']} (just now)" - assert_includes output, 'Threads: 5 (2 busy)' - assert_includes output, 'Queues: high, low, medium' - end - end - end - - describe 'queues' do - it 'has a heading' do - assert_includes output, '---- Queues (0) ----' - end - - it 'displays the correct output' do - mock_queues = [ - OpenStruct.new(name: 'foobar', size: 12, latency: 12.3456), - OpenStruct.new(name: 'a_long_queue_name', size: 234, latency: 567.89999) - ] - Sidekiq::Queue.stub(:all, mock_queues) do - assert_includes output, 'NAME SIZE LATENCY' - assert_includes output, 'foobar 12 12.35' - assert_includes output, 'a_long_queue_name 234 567.90' - end - end - end - end -end diff --git a/test/test_systemd.rb b/test/test_systemd.rb deleted file mode 100644 index f5a3f18d..00000000 --- a/test/test_systemd.rb +++ /dev/null @@ -1,40 +0,0 @@ -require_relative 'helper' -require "sidekiq/sd_notify" -require 'sidekiq/systemd' - -class TestSystemd < Minitest::Test - def setup - ::Dir::Tmpname.create("sidekiq_socket") do |sockaddr| - @sockaddr = sockaddr - @socket = Socket.new(:UNIX, :DGRAM, 0) - socket_ai = Addrinfo.unix(sockaddr) - @socket.bind(socket_ai) - ENV["NOTIFY_SOCKET"] = sockaddr - end - end - - def teardown - @socket.close if @socket - File.unlink(@sockaddr) if @sockaddr - @socket = nil - @sockaddr = nil - end - - def socket_message - @socket.recvfrom(10)[0] - end - - def test_notify - count = Sidekiq::SdNotify.ready - assert_equal(socket_message, "READY=1") - assert_equal(ENV["NOTIFY_SOCKET"], @sockaddr) - assert_equal(count, 7) - - count = Sidekiq::SdNotify.stopping - assert_equal(socket_message, "STOPPING=1") - assert_equal(ENV["NOTIFY_SOCKET"], @sockaddr) - assert_equal(count, 10) - - refute Sidekiq::SdNotify.watchdog? - end -end diff --git a/test/test_testing.rb b/test/test_testing.rb deleted file mode 100644 index e43aa429..00000000 --- a/test/test_testing.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' - -describe 'Sidekiq::Testing' do - describe 'require/load sidekiq/testing.rb' do - before do - require 'sidekiq/testing' - end - - after do - Sidekiq::Testing.disable! - end - - it 'enables fake testing' do - Sidekiq::Testing.fake! - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.fake? - refute Sidekiq::Testing.inline? - end - - it 'enables fake testing in a block' do - Sidekiq::Testing.disable! - assert Sidekiq::Testing.disabled? - refute Sidekiq::Testing.fake? - - Sidekiq::Testing.fake! do - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.fake? - refute Sidekiq::Testing.inline? - end - - refute Sidekiq::Testing.enabled? - refute Sidekiq::Testing.fake? - end - - it 'disables testing in a block' do - Sidekiq::Testing.fake! - assert Sidekiq::Testing.fake? - - Sidekiq::Testing.disable! do - refute Sidekiq::Testing.fake? - assert Sidekiq::Testing.disabled? - end - - assert Sidekiq::Testing.fake? - assert Sidekiq::Testing.enabled? - end - end - - describe 'require/load sidekiq/testing/inline.rb' do - before do - require 'sidekiq/testing/inline' - end - - after do - Sidekiq::Testing.disable! - end - - it 'enables inline testing' do - Sidekiq::Testing.inline! - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.inline? - refute Sidekiq::Testing.fake? - end - - it 'enables inline testing in a block' do - Sidekiq::Testing.disable! - assert Sidekiq::Testing.disabled? - refute Sidekiq::Testing.fake? - - Sidekiq::Testing.inline! do - assert Sidekiq::Testing.enabled? - assert Sidekiq::Testing.inline? - end - - refute Sidekiq::Testing.enabled? - refute Sidekiq::Testing.inline? - refute Sidekiq::Testing.fake? - end - end - - describe 'with middleware' do - before do - require 'sidekiq/testing' - end - - after do - Sidekiq::Testing.disable! - end - - class AttributeWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - attr_accessor :foo - - def perform - self.class.count += 1 if foo == :bar - end - end - - class AttributeMiddleware - def call(worker, msg, queue) - worker.foo = :bar if worker.respond_to?(:foo=) - yield - end - end - - it 'wraps the inlined worker with middleware' do - Sidekiq::Testing.server_middleware do |chain| - chain.add AttributeMiddleware - end - - begin - Sidekiq::Testing.fake! do - AttributeWorker.perform_async - assert_equal 0, AttributeWorker.count - end - - AttributeWorker.perform_one - assert_equal 1, AttributeWorker.count - - Sidekiq::Testing.inline! do - AttributeWorker.perform_async - assert_equal 2, AttributeWorker.count - end - ensure - Sidekiq::Testing.server_middleware.clear - end - end - end - -end diff --git a/test/test_testing_fake.rb b/test/test_testing_fake.rb deleted file mode 100644 index 36b40218..00000000 --- a/test/test_testing_fake.rb +++ /dev/null @@ -1,365 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' - -describe 'Sidekiq::Testing.fake' do - class PerformError < RuntimeError; end - - class DirectWorker - include Sidekiq::Worker - def perform(a, b) - a + b - end - end - - class EnqueuedWorker - include Sidekiq::Worker - def perform(a, b) - a + b - end - end - - class StoredWorker - include Sidekiq::Worker - def perform(error) - raise PerformError if error - end - end - - before do - require 'sidekiq/testing' - Sidekiq::Testing.fake! - EnqueuedWorker.jobs.clear - DirectWorker.jobs.clear - end - - after do - Sidekiq::Testing.disable! - Sidekiq::Queues.clear_all - end - - it 'stubs the async call' do - assert_equal 0, DirectWorker.jobs.size - assert DirectWorker.perform_async(1, 2) - assert_in_delta Time.now.to_f, DirectWorker.jobs.last['enqueued_at'], 0.1 - assert_equal 1, DirectWorker.jobs.size - assert DirectWorker.perform_in(10, 1, 2) - refute DirectWorker.jobs.last['enqueued_at'] - assert_equal 2, DirectWorker.jobs.size - assert DirectWorker.perform_at(10, 1, 2) - assert_equal 3, DirectWorker.jobs.size - assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.1 - end - - describe 'delayed' do - require 'action_mailer' - class FooMailer < ActionMailer::Base - def bar(str) - str - end - end - - before do - Sidekiq::Extensions.enable_delay! - end - - it 'stubs the delay call on mailers' do - assert_equal 0, Sidekiq::Extensions::DelayedMailer.jobs.size - FooMailer.delay.bar('hello!') - assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs.size - end - - class Something - def self.foo(x) - end - end - - it 'stubs the delay call on classes' do - assert_equal 0, Sidekiq::Extensions::DelayedClass.jobs.size - Something.delay.foo(Date.today) - assert_equal 1, Sidekiq::Extensions::DelayedClass.jobs.size - end - - class BarMailer < ActionMailer::Base - def foo(str) - str - end - end - - it 'returns enqueued jobs for specific classes' do - assert_equal 0, Sidekiq::Extensions::DelayedClass.jobs.size - FooMailer.delay.bar('hello!') - BarMailer.delay.foo('hello!') - assert_equal 2, Sidekiq::Extensions::DelayedMailer.jobs.size - assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(FooMailer).size - assert_equal 1, Sidekiq::Extensions::DelayedMailer.jobs_for(BarMailer).size - end - end - - it 'stubs the enqueue call' do - assert_equal 0, EnqueuedWorker.jobs.size - assert Sidekiq::Client.enqueue(EnqueuedWorker, 1, 2) - assert_equal 1, EnqueuedWorker.jobs.size - end - - it 'stubs the enqueue_to call' do - assert_equal 0, EnqueuedWorker.jobs.size - assert Sidekiq::Client.enqueue_to('someq', EnqueuedWorker, 1, 2) - assert_equal 1, Sidekiq::Queues['someq'].size - end - - it 'executes all stored jobs' do - assert StoredWorker.perform_async(false) - assert StoredWorker.perform_async(true) - - assert_equal 2, StoredWorker.jobs.size - assert_raises PerformError do - StoredWorker.drain - end - assert_equal 0, StoredWorker.jobs.size - end - - class SpecificJidWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform(worker_jid) - return unless worker_jid == self.jid - self.class.count += 1 - end - end - - it 'execute only jobs with assigned JID' do - 4.times do |i| - jid = SpecificJidWorker.perform_async(nil) - if i % 2 == 0 - SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"] - else - SpecificJidWorker.jobs[-1]["args"] = [jid] - end - end - - SpecificJidWorker.perform_one - assert_equal 0, SpecificJidWorker.count - - SpecificJidWorker.perform_one - assert_equal 1, SpecificJidWorker.count - - SpecificJidWorker.drain - assert_equal 2, SpecificJidWorker.count - end - - it 'round trip serializes the job arguments' do - assert StoredWorker.perform_async(:mike) - job = StoredWorker.jobs.first - assert_equal "mike", job['args'].first - StoredWorker.clear - end - - it 'perform_one runs only one job' do - DirectWorker.perform_async(1, 2) - DirectWorker.perform_async(3, 4) - assert_equal 2, DirectWorker.jobs.size - - DirectWorker.perform_one - assert_equal 1, DirectWorker.jobs.size - - DirectWorker.clear - end - - it 'perform_one raise error upon empty queue' do - DirectWorker.clear - assert_raises Sidekiq::EmptyQueueError do - DirectWorker.perform_one - end - end - - class FirstWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform - self.class.count += 1 - end - end - - class SecondWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - self.count = 0 - def perform - self.class.count += 1 - end - end - - class ThirdWorker - include Sidekiq::Worker - sidekiq_class_attribute :count - def perform - FirstWorker.perform_async - SecondWorker.perform_async - end - end - - it 'clears jobs across all workers' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 - - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size - - FirstWorker.perform_async - SecondWorker.perform_async - - assert_equal 1, FirstWorker.jobs.size - assert_equal 1, SecondWorker.jobs.size - - Sidekiq::Worker.clear_all - - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size - - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count - end - - it 'drains jobs across all workers' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 - - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size - - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count - - FirstWorker.perform_async - SecondWorker.perform_async - - assert_equal 1, FirstWorker.jobs.size - assert_equal 1, SecondWorker.jobs.size - - Sidekiq::Worker.drain_all - - assert_equal 0, FirstWorker.jobs.size - assert_equal 0, SecondWorker.jobs.size - - assert_equal 1, FirstWorker.count - assert_equal 1, SecondWorker.count - end - - it 'drains jobs across all workers even when workers create new jobs' do - Sidekiq::Worker.jobs.clear - FirstWorker.count = 0 - SecondWorker.count = 0 - - assert_equal 0, ThirdWorker.jobs.size - - assert_equal 0, FirstWorker.count - assert_equal 0, SecondWorker.count - - ThirdWorker.perform_async - - assert_equal 1, ThirdWorker.jobs.size - - Sidekiq::Worker.drain_all - - assert_equal 0, ThirdWorker.jobs.size - - assert_equal 1, FirstWorker.count - assert_equal 1, SecondWorker.count - end - - it 'drains jobs of workers with symbolized queue names' do - Sidekiq::Worker.jobs.clear - - AltQueueWorker.perform_async(5,6) - assert_equal 1, AltQueueWorker.jobs.size - - Sidekiq::Worker.drain_all - assert_equal 0, AltQueueWorker.jobs.size - end - - it 'can execute a job' do - DirectWorker.execute_job(DirectWorker.new, [2, 3]) - end - - describe 'queue testing' do - before do - require 'sidekiq/testing' - Sidekiq::Testing.fake! - end - - after do - Sidekiq::Testing.disable! - Sidekiq::Queues.clear_all - end - - class QueueWorker - include Sidekiq::Worker - def perform(a, b) - a + b - end - end - - class AltQueueWorker - include Sidekiq::Worker - sidekiq_options queue: :alt - def perform(a, b) - a + b - end - end - - it 'finds enqueued jobs' do - assert_equal 0, Sidekiq::Queues["default"].size - - QueueWorker.perform_async(1, 2) - QueueWorker.perform_async(1, 2) - AltQueueWorker.perform_async(1, 2) - - assert_equal 2, Sidekiq::Queues["default"].size - assert_equal [1, 2], Sidekiq::Queues["default"].first["args"] - - assert_equal 1, Sidekiq::Queues["alt"].size - end - - it 'clears out all queues' do - assert_equal 0, Sidekiq::Queues["default"].size - - QueueWorker.perform_async(1, 2) - QueueWorker.perform_async(1, 2) - AltQueueWorker.perform_async(1, 2) - - Sidekiq::Queues.clear_all - - assert_equal 0, Sidekiq::Queues["default"].size - assert_equal 0, QueueWorker.jobs.size - assert_equal 0, Sidekiq::Queues["alt"].size - assert_equal 0, AltQueueWorker.jobs.size - end - - it 'finds jobs enqueued by client' do - Sidekiq::Client.push( - 'class' => 'NonExistentWorker', - 'queue' => 'missing', - 'args' => [1] - ) - - assert_equal 1, Sidekiq::Queues["missing"].size - end - - it 'respects underlying array changes' do - # Rspec expect change() syntax saves a reference to - # an underlying array. When the array containing jobs is - # derived, Rspec test using `change(QueueWorker.jobs, :size).by(1)` - # won't pass. This attempts to recreate that scenario - # by saving a reference to the jobs array and ensuring - # it changes properly on enqueueing - jobs = QueueWorker.jobs - assert_equal 0, jobs.size - QueueWorker.perform_async(1, 2) - assert_equal 1, jobs.size - end - end -end diff --git a/test/test_testing_inline.rb b/test/test_testing_inline.rb deleted file mode 100644 index f3cb4b11..00000000 --- a/test/test_testing_inline.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' - -describe 'Sidekiq::Testing.inline' do - class InlineError < RuntimeError; end - class ParameterIsNotString < RuntimeError; end - - class InlineWorker - include Sidekiq::Worker - def perform(pass) - raise ArgumentError, "no jid" unless jid - raise InlineError unless pass - end - end - - class InlineWorkerWithTimeParam - include Sidekiq::Worker - def perform(time) - raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric) - end - end - - before do - require 'sidekiq/testing/inline' - Sidekiq::Testing.inline! - end - - after do - Sidekiq::Testing.disable! - end - - it 'stubs the async call when in testing mode' do - assert InlineWorker.perform_async(true) - - assert_raises InlineError do - InlineWorker.perform_async(false) - end - end - - describe 'delay' do - require 'action_mailer' - class InlineFooMailer < ActionMailer::Base - def bar(str) - raise InlineError - end - end - - class InlineFooModel - def self.bar(str) - raise InlineError - end - end - - before do - Sidekiq::Extensions.enable_delay! - end - - it 'stubs the delay call on mailers' do - assert_raises InlineError do - InlineFooMailer.delay.bar('three') - end - end - - it 'stubs the delay call on models' do - assert_raises InlineError do - InlineFooModel.delay.bar('three') - end - end - end - - it 'stubs the enqueue call when in testing mode' do - assert Sidekiq::Client.enqueue(InlineWorker, true) - - assert_raises InlineError do - Sidekiq::Client.enqueue(InlineWorker, false) - end - end - - it 'stubs the push_bulk call when in testing mode' do - assert Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [true]]}) - - assert_raises InlineError do - Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [false]]}) - end - end - - it 'should relay parameters through json' do - assert Sidekiq::Client.enqueue(InlineWorkerWithTimeParam, Time.now.to_f) - end - -end diff --git a/test/test_util.rb b/test/test_util.rb deleted file mode 100644 index 9b1b9c2c..00000000 --- a/test/test_util.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/util' - -class TestUtil < Minitest::Test - class Helpers - include Sidekiq::Util - end - - def test_event_firing - before_handlers = Sidekiq.options[:lifecycle_events][:startup] - begin - Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] - h = Helpers.new - h.fire_event(:startup) - - Sidekiq.options[:lifecycle_events][:startup] = [proc { raise "boom" }] - assert_raises RuntimeError do - h.fire_event(:startup, reraise: true) - end - ensure - Sidekiq.options[:lifecycle_events][:startup] = before_handlers - end - end -end diff --git a/test/test_web.rb b/test/test_web.rb deleted file mode 100644 index 794b47f6..00000000 --- a/test/test_web.rb +++ /dev/null @@ -1,801 +0,0 @@ -# encoding: utf-8 -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/web' -require 'sidekiq/util' -require 'rack/test' - -describe Sidekiq::Web do - include Rack::Test::Methods - - def app - @app ||= Sidekiq::Web.new - end - - def job_params(job, score) - "#{score}-#{job['jid']}" - end - - before do - Sidekiq.redis {|c| c.flushdb } - app.middlewares.clear - end - - class WebWorker - include Sidekiq::Worker - - def perform(a, b) - a + b - end - end - - it 'can show text with any locales' do - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'ru,en'} - get '/', {}, rackenv - assert_match(/Панель управления/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'es,en'} - get '/', {}, rackenv - assert_match(/Panel de Control/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'en-us'} - get '/', {}, rackenv - assert_match(/Dashboard/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-cn'} - get '/', {}, rackenv - assert_match(/信息板/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'zh-tw'} - get '/', {}, rackenv - assert_match(/資訊主頁/, last_response.body) - rackenv = {'HTTP_ACCEPT_LANGUAGE' => 'nb'} - get '/', {}, rackenv - assert_match(/Oversikt/, last_response.body) - end - - it 'can provide a default, appropriate CSP for its content' do - get '/', {} - policies = last_response.headers["Content-Security-Policy"].split('; ') - assert_includes(policies, "connect-src 'self' https: http: wss: ws:") - assert_includes(policies, "style-src 'self' https: http: 'unsafe-inline'") - assert_includes(policies, "script-src 'self' https: http: 'unsafe-inline'") - assert_includes(policies, "object-src 'none'") - end - - describe 'busy' do - - it 'can display workers' do - Sidekiq.redis do |conn| - conn.incr('busy') - conn.sadd('processes', 'foo:1234') - conn.hmset('foo:1234', 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => [], 'concurrency' => 10), 'at', Time.now.to_f, 'busy', 4) - identity = 'foo:1234:workers' - hash = {:queue => 'critical', :payload => { 'class' => WebWorker.name, 'args' => [1,'abc'] }, :run_at => Time.now.to_i } - conn.hmset(identity, 1001, Sidekiq.dump_json(hash)) - end - assert_equal ['1001'], Sidekiq::Workers.new.map { |pid, tid, data| tid } - - get '/busy' - assert_equal 200, last_response.status - assert_match(/status-active/, last_response.body) - assert_match(/critical/, last_response.body) - assert_match(/WebWorker/, last_response.body) - end - - it 'can quiet a process' do - identity = 'identity' - signals_key = "#{identity}-signals" - - assert_nil Sidekiq.redis { |c| c.lpop signals_key } - post '/busy', 'quiet' => '1', 'identity' => identity - assert_equal 302, last_response.status - assert_equal 'TSTP', Sidekiq.redis { |c| c.lpop signals_key } - end - - it 'can stop a process' do - identity = 'identity' - signals_key = "#{identity}-signals" - - assert_nil Sidekiq.redis { |c| c.lpop signals_key } - post '/busy', 'stop' => '1', 'identity' => identity - assert_equal 302, last_response.status - assert_equal 'TERM', Sidekiq.redis { |c| c.lpop signals_key } - end - end - - it 'can display queues' do - assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3]) - - get '/queues' - assert_equal 200, last_response.status - assert_match(/foo/, last_response.body) - refute_match(/HardWorker/, last_response.body) - assert_match(/0.0/, last_response.body) - refute_match(/datetime/, last_response.body) - Sidekiq::Queue.new("foo").clear - - Time.stub(:now, Time.now - 65) do - assert Sidekiq::Client.push('queue' => :foo, 'class' => WebWorker, 'args' => [1, 3]) - end - - get '/queues' - assert_equal 200, last_response.status - assert_match(/foo/, last_response.body) - refute_match(/HardWorker/, last_response.body) - assert_match(/65.0/, last_response.body) - assert_match(/datetime/, last_response.body) - end - - it 'handles queue view' do - get '/queues/onmouseover=alert()' - assert_equal 404, last_response.status - - get '/queues/foo_bar:123-wow.' - assert_equal 200, last_response.status - assert_match(/foo_bar:123-wow\./, last_response.body) - - get '/queues/default' - assert_equal 200, last_response.status - end - - it 'can sort on enqueued_at column' do - Sidekiq.redis do |conn| - (1000..1005).each do |i| - conn.lpush('queue:default', Sidekiq.dump_json(args: [i], enqueued_at: Time.now.to_i + i)) - end - end - - get '/queues/default?count=3' # direction is 'desc' by default - assert_match(/1005/, last_response.body) - refute_match(/1002/, last_response.body) - - get '/queues/default?count=3&direction=asc' - assert_match(/1000/, last_response.body) - refute_match(/1003/, last_response.body) - end - - it 'can delete a queue' do - Sidekiq.redis do |conn| - conn.rpush('queue:foo', "{\"args\":[],\"enqueued_at\":1567894960}") - conn.sadd('queues', 'foo') - end - - get '/queues/foo' - assert_equal 200, last_response.status - - post '/queues/foo' - assert_equal 302, last_response.status - - Sidekiq.redis do |conn| - refute conn.smembers('queues').include?('foo') - refute conn.exists?('queue:foo') - end - end - - it 'can attempt to pause a queue' do - Sidekiq.stub(:pro?, true) do - mock = Minitest::Mock.new - mock.expect :pause!, true - - stub = lambda do |queue_name| - assert_equal 'foo', queue_name - mock - end - - Sidekiq::Queue.stub :new, stub do - post '/queues/foo', 'pause' => 'pause' - assert_equal 302, last_response.status - end - - assert_mock mock - end - end - - it 'can attempt to unpause a queue' do - Sidekiq.stub(:pro?, true) do - mock = Minitest::Mock.new - mock.expect :unpause!, true - - stub = lambda do |queue_name| - assert_equal 'foo', queue_name - mock - end - - Sidekiq::Queue.stub :new, stub do - post '/queues/foo', 'unpause' => 'unpause' - assert_equal 302, last_response.status - end - - assert_mock mock - end - end - - it 'ignores to attempt to pause a queue with pro disabled' do - mock = Minitest::Mock.new - mock.expect :clear, true - - stub = lambda do |queue_name| - assert_equal 'foo', queue_name - mock - end - - Sidekiq::Queue.stub :new, stub do - post '/queues/foo', 'pause' => 'pause' - assert_equal 302, last_response.status - end - - assert_mock mock - end - - it 'ignores to attempt to unpause a queue with pro disabled' do - mock = Minitest::Mock.new - mock.expect :clear, true - - stub = lambda do |queue_name| - assert_equal 'foo', queue_name - mock - end - - Sidekiq::Queue.stub :new, stub do - post '/queues/foo', 'unpause' => 'unpause' - assert_equal 302, last_response.status - end - - assert_mock mock - end - - it 'can delete a job' do - Sidekiq.redis do |conn| - conn.rpush('queue:foo', '{"args":[],"enqueued_at":1567894960}') - conn.rpush('queue:foo', '{"foo":"bar","args":[],"enqueued_at":1567894960}') - conn.rpush('queue:foo', '{"foo2":"bar2","args":[],"enqueued_at":1567894960}') - end - - get '/queues/foo' - assert_equal 200, last_response.status - - post '/queues/foo/delete', key_val: "{\"foo\":\"bar\"}" - assert_equal 302, last_response.status - - Sidekiq.redis do |conn| - refute conn.lrange('queue:foo', 0, -1).include?("{\"foo\":\"bar\"}") - end - end - - it 'can display retries' do - get '/retries' - assert_equal 200, last_response.status - assert_match(/found/, last_response.body) - refute_match(/HardWorker/, last_response.body) - - add_retry - - get '/retries' - assert_equal 200, last_response.status - refute_match(/found/, last_response.body) - assert_match(/HardWorker/, last_response.body) - end - - it 'can display a single retry' do - params = add_retry - get '/retries/0-shouldntexist' - assert_equal 302, last_response.status - get "/retries/#{job_params(*params)}" - assert_equal 200, last_response.status - assert_match(/HardWorker/, last_response.body) - end - - it 'handles missing retry' do - get "/retries/0-shouldntexist" - assert_equal 302, last_response.status - end - - it 'can delete a single retry' do - params = add_retry - post "/retries/#{job_params(*params)}", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - - get "/retries" - assert_equal 200, last_response.status - refute_match(/#{params.first['args'][2]}/, last_response.body) - end - - it 'can delete all retries' do - 3.times { add_retry } - - post "/retries/all/delete", 'delete' => 'Delete' - assert_equal 0, Sidekiq::RetrySet.new.size - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - end - - it 'can retry a single retry now' do - params = add_retry - post "/retries/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end - - it 'can kill a single retry now' do - params = add_retry - post "/retries/#{job_params(*params)}", 'kill' => 'Kill' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - - get '/morgue' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end - - it 'can display scheduled' do - get '/scheduled' - assert_equal 200, last_response.status - assert_match(/found/, last_response.body) - refute_match(/HardWorker/, last_response.body) - - add_scheduled - - get '/scheduled' - assert_equal 200, last_response.status - refute_match(/found/, last_response.body) - assert_match(/HardWorker/, last_response.body) - end - - it 'can display a single scheduled job' do - params = add_scheduled - get '/scheduled/0-shouldntexist' - assert_equal 302, last_response.status - get "/scheduled/#{job_params(*params)}" - assert_equal 200, last_response.status - assert_match(/HardWorker/, last_response.body) - end - - it 'can display a single scheduled job tags' do - params = add_scheduled - get "/scheduled/#{job_params(*params)}" - assert_match(/tag1/, last_response.body) - assert_match(/tag2/, last_response.body) - end - - it 'handles missing scheduled job' do - get "/scheduled/0-shouldntexist" - assert_equal 302, last_response.status - end - - it 'can add to queue a single scheduled job' do - params = add_scheduled - post "/scheduled/#{job_params(*params)}", 'add_to_queue' => true - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end - - it 'can delete a single scheduled job' do - params = add_scheduled - post "/scheduled/#{job_params(*params)}", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - - get "/scheduled" - assert_equal 200, last_response.status - refute_match(/#{params.first['args'][2]}/, last_response.body) - end - - it 'can delete scheduled' do - params = add_scheduled - Sidekiq.redis do |conn| - assert_equal 1, conn.zcard('schedule') - post '/scheduled', 'key' => [job_params(*params)], 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - assert_equal 0, conn.zcard('schedule') - end - end - - it "can move scheduled to default queue" do - q = Sidekiq::Queue.new - params = add_scheduled - Sidekiq.redis do |conn| - assert_equal 1, conn.zcard('schedule') - assert_equal 0, q.size - post '/scheduled', 'key' => [job_params(*params)], 'add_to_queue' => 'AddToQueue' - assert_equal 302, last_response.status - assert_equal 'http://example.org/scheduled', last_response.header['Location'] - assert_equal 0, conn.zcard('schedule') - assert_equal 1, q.size - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{params[0]['args'][2]}/, last_response.body) - end - end - - it 'can retry all retries' do - msg = add_retry.first - add_retry - - post "/retries/all/retry", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 'http://example.org/retries', last_response.header['Location'] - assert_equal 2, Sidekiq::Queue.new("default").size - - get '/queues/default' - assert_equal 200, last_response.status - assert_match(/#{msg['args'][2]}/, last_response.body) - end - - it 'escape job args and error messages' do - # on /retries page - params = add_xss_retry - get '/retries' - assert_equal 200, last_response.status - assert_match(/FailWorker/, last_response.body) - - assert last_response.body.include?( "fail message: <a>hello</a>" ) - assert !last_response.body.include?( "fail message:
hello" ) - - assert last_response.body.include?( "args\">"<a>hello</a>"<" ) - assert !last_response.body.include?( "args\">hello<" ) - - # on /workers page - Sidekiq.redis do |conn| - pro = 'foo:1234' - conn.sadd('processes', pro) - conn.hmset(pro, 'info', Sidekiq.dump_json('started_at' => Time.now.to_f, 'labels' => ['frumduz'], 'queues' =>[], 'concurrency' => 10), 'busy', 1, 'beat', Time.now.to_f) - identity = "#{pro}:workers" - hash = {:queue => 'critical', :payload => { 'class' => "FailWorker", 'args' => ["hello"] }, :run_at => Time.now.to_i } - conn.hmset(identity, 100001, Sidekiq.dump_json(hash)) - conn.incr('busy') - end - - get '/busy' - assert_equal 200, last_response.status - assert_match(/FailWorker/, last_response.body) - assert_match(/frumduz/, last_response.body) - assert last_response.body.include?( "<a>hello</a>" ) - assert !last_response.body.include?( "hello" ) - - # on /queues page - params = add_xss_retry # sorry, don't know how to easily make this show up on queues page otherwise. - post "/retries/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - - get '/queues/foo' - assert_equal 200, last_response.status - assert last_response.body.include?( "<a>hello</a>" ) - assert !last_response.body.include?( "hello" ) - end - - it 'can show user defined tab' do - begin - Sidekiq::Web.tabs['Custom Tab'] = '/custom' - - get '/' - assert_match 'Custom Tab', last_response.body - - ensure - Sidekiq::Web.tabs.delete 'Custom Tab' - end - end - - it 'can display home' do - get '/' - assert_equal 200, last_response.status - end - - describe 'custom locales' do - before do - Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "fixtures") - Sidekiq::Web.tabs['Custom Tab'] = '/custom' - Sidekiq::WebApplication.get('/custom') do - clear_caches # ugly hack since I can't figure out how to access WebHelpers outside of this context - t('translated_text') - end - end - - after do - Sidekiq::Web.tabs.delete 'Custom Tab' - Sidekiq::Web.settings.locales.pop - end - - it 'can show user defined tab with custom locales' do - get '/custom' - - assert_match(/Changed text/, last_response.body) - end - end - - describe 'dashboard/stats' do - it 'redirects to stats' do - get '/dashboard/stats' - assert_equal 302, last_response.status - assert_equal 'http://example.org/stats', last_response.header['Location'] - end - end - - describe 'stats' do - include Sidekiq::Util - - before do - Sidekiq.redis do |conn| - conn.set("stat:processed", 5) - conn.set("stat:failed", 2) - conn.sadd("queues", "default") - end - 2.times { add_retry } - 3.times { add_scheduled } - 4.times { add_worker } - end - - it 'works' do - get '/stats' - @response = Sidekiq.load_json(last_response.body) - - assert_equal 200, last_response.status - assert_includes @response.keys, "sidekiq" - assert_equal 5, @response["sidekiq"]["processed"] - assert_equal 2, @response["sidekiq"]["failed"] - assert_equal 4, @response["sidekiq"]["busy"] - assert_equal 1, @response["sidekiq"]["processes"] - assert_equal 2, @response["sidekiq"]["retries"] - assert_equal 3, @response["sidekiq"]["scheduled"] - assert_equal 0, @response["sidekiq"]["default_latency"] - assert_includes @response.keys, "redis" - assert_includes @response["redis"].keys, "redis_version" - assert_includes @response["redis"].keys, "uptime_in_days" - assert_includes @response["redis"].keys, "connected_clients" - assert_includes @response["redis"].keys, "used_memory_human" - assert_includes @response["redis"].keys, "used_memory_peak_human" - assert_includes @response.keys, "server_utc_time" - end - end - - describe 'bad JSON' do - it 'displays without error' do - s = Sidekiq::DeadSet.new - (_, score) = kill_bad - assert_equal 1, s.size - - get '/morgue' - assert_equal 200, last_response.status - assert_match(/#{score.to_i}/, last_response.body) - assert_match("something bad", last_response.body) - assert_equal 1, s.size - - post "/morgue/#{score}-", 'delete' => 'Delete' - assert_equal 302, last_response.status - assert_equal 1, s.size - end - end - - describe 'stats/queues' do - include Sidekiq::Util - - before do - Sidekiq.redis do |conn| - conn.set("stat:processed", 5) - conn.set("stat:failed", 2) - conn.sadd("queues", "default") - conn.sadd("queues", "queue2") - end - 2.times { add_retry } - 3.times { add_scheduled } - 4.times { add_worker } - - get '/stats/queues' - @response = Sidekiq.load_json(last_response.body) - end - - it 'reports the queue depth' do - assert_equal 0, @response["default"] - assert_equal 0, @response["queue2"] - end - end - - describe 'dead jobs' do - it 'shows empty index' do - get 'morgue' - assert_equal 200, last_response.status - end - - it 'shows index with jobs' do - (_, score) = add_dead - get 'morgue' - assert_equal 200, last_response.status - assert_match(/#{score}/, last_response.body) - end - - it 'can delete all dead' do - 3.times { add_dead } - - assert_equal 3, Sidekiq::DeadSet.new.size - post "/morgue/all/delete", 'delete' => 'Delete' - assert_equal 0, Sidekiq::DeadSet.new.size - assert_equal 302, last_response.status - assert_equal 'http://example.org/morgue', last_response.header['Location'] - end - - it 'can display a dead job' do - params = add_dead - get "/morgue/#{job_params(*params)}" - assert_equal 200, last_response.status - end - - it 'can retry a dead job' do - params = add_dead - post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 'http://example.org/morgue', last_response.header['Location'] - assert_equal 0, Sidekiq::DeadSet.new.size - - params = add_dead('jid-with-hyphen') - post "/morgue/#{job_params(*params)}", 'retry' => 'Retry' - assert_equal 302, last_response.status - assert_equal 0, Sidekiq::DeadSet.new.size - - get '/queues/foo' - assert_equal 200, last_response.status - assert_match(/#{params.first['args'][2]}/, last_response.body) - end - end - - def add_scheduled - score = Time.now.to_f - msg = { 'class' => 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'jid' => SecureRandom.hex(12), - 'tags' => ['tag1', 'tag2'], } - Sidekiq.redis do |conn| - conn.zadd('schedule', score, Sidekiq.dump_json(msg)) - end - [msg, score] - end - - def add_retry - msg = { 'class' => 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'queue' => 'default', - 'error_message' => 'Some fake message', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.to_f, - 'jid' => SecureRandom.hex(12) } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('retry', score, Sidekiq.dump_json(msg)) - end - - [msg, score] - end - - def add_dead(jid = SecureRandom.hex(12)) - msg = { 'class' => 'HardWorker', - 'args' => ['bob', 1, Time.now.to_f], - 'queue' => 'foo', - 'error_message' => 'Some fake message', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.utc, - 'jid' => jid } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('dead', score, Sidekiq.dump_json(msg)) - end - [msg, score] - end - - def kill_bad - job = "{ something bad }" - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('dead', score, job) - end - [job, score] - end - - def add_xss_retry(job_id=SecureRandom.hex(12)) - msg = { 'class' => 'FailWorker', - 'args' => ['hello'], - 'queue' => 'foo', - 'error_message' => 'fail message: hello', - 'error_class' => 'RuntimeError', - 'retry_count' => 0, - 'failed_at' => Time.now.to_f, - 'jid' => SecureRandom.hex(12) } - score = Time.now.to_f - Sidekiq.redis do |conn| - conn.zadd('retry', score, Sidekiq.dump_json(msg)) - end - - [msg, score] - end - - def add_worker - key = "#{hostname}:#{$$}" - msg = "{\"queue\":\"default\",\"payload\":{\"retry\":true,\"queue\":\"default\",\"timeout\":20,\"backtrace\":5,\"class\":\"HardWorker\",\"args\":[\"bob\",10,5],\"jid\":\"2b5ad2b016f5e063a1c62872\"},\"run_at\":1361208995}" - Sidekiq.redis do |conn| - conn.multi do |transaction| - transaction.sadd("processes", key) - transaction.hmset(key, 'info', Sidekiq.dump_json('hostname' => 'foo', 'started_at' => Time.now.to_f, "queues" => []), 'at', Time.now.to_f, 'busy', 4) - transaction.hmset("#{key}:workers", Time.now.to_f, msg) - end - end - end - - describe 'basic auth' do - include Rack::Test::Methods - - def app - app = Sidekiq::Web.new - app.use(Rack::Auth::Basic) { |user, pass| user == "a" && pass == "b" } - app.use(Rack::Session::Cookie, secret: SecureRandom.hex(32)) - - app - end - - it 'requires basic authentication' do - get '/' - - assert_equal 401, last_response.status - refute_nil last_response.header["WWW-Authenticate"] - end - - it 'authenticates successfuly' do - basic_authorize 'a', 'b' - - get '/' - assert_equal 200, last_response.status - get '/?days=1000000' - assert_equal 401, last_response.status - end - end - - describe 'custom session' do - include Rack::Test::Methods - - def app - app = Sidekiq::Web.new - app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' - app - end - - it 'requires uses session options' do - get '/' - - session_options = last_request.env['rack.session'].options - - assert_equal 'v3rys3cr31', session_options[:secret] - assert_equal 'nicehost.org', session_options[:host] - end - end - - describe "redirecting in before" do - include Rack::Test::Methods - - before do - Sidekiq::WebApplication.before { Thread.current[:some_setting] = :before } - Sidekiq::WebApplication.before { redirect '/' } - Sidekiq::WebApplication.after { Thread.current[:some_setting] = :after } - end - - after do - Sidekiq::WebApplication.remove_instance_variable(:@befores) - Sidekiq::WebApplication.remove_instance_variable(:@afters) - end - - def app - app = Sidekiq::Web.new - app.use Rack::Session::Cookie, secret: 'v3rys3cr31', host: 'nicehost.org' - app - end - - it "allows afters to run" do - get '/' - assert_equal :after, Thread.current[:some_setting] - end - end -end diff --git a/test/test_web_helpers.rb b/test/test_web_helpers.rb deleted file mode 100644 index cf939a50..00000000 --- a/test/test_web_helpers.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true -require_relative 'helper' -require 'sidekiq/web' - -class TestWebHelpers < Minitest::Test - class Helpers - include Sidekiq::WebHelpers - - def initialize(params={}) - @thehash = default.merge(params) - end - - def request - self - end - - def settings - self - end - - def locales - ['web/locales'] - end - - def env - @thehash - end - - def default - { - } - end - end - - def test_locale_determination - obj = Helpers.new - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2') - assert_equal 'fr', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4,ru;q=0.2') - assert_equal 'zh-cn', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-US,sv-SE;q=0.8,sv;q=0.6,en;q=0.4') - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'nb-NO,nb;q=0.2') - assert_equal 'nb', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-us') - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'sv-se') - assert_equal 'sv', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'pt-BR,pt;q=0.8,en-US;q=0.6,en;q=0.4') - assert_equal 'pt-br', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'pt-PT,pt;q=0.8,en-US;q=0.6,en;q=0.4') - assert_equal 'pt', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'pt-br') - assert_equal 'pt-br', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'pt-pt') - assert_equal 'pt', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'pt') - assert_equal 'pt', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-us; *') - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8') - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'en-GB,en-US;q=0.8,en;q=0.6') - assert_equal 'en', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'ru,en') - assert_equal 'ru', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => 'lt') - assert_equal 'lt', obj.locale - - obj = Helpers.new('HTTP_ACCEPT_LANGUAGE' => '*') - assert_equal 'en', obj.locale - end - - def test_available_locales - obj = Helpers.new - expected = %w( - ar cs da de el en es fa fr he hi it ja - ko lt nb nl pl pt pt-br ru sv ta uk ur - vi zh-cn zh-tw - ) - assert_equal expected, obj.available_locales.sort - end - - def test_display_illegal_args - o = Helpers.new - s = o.display_args([1,2,3]) - assert_equal "1, 2, 3", s - s = o.display_args(["", 12]) - assert_equal ""<html>", 12", s - s = o.display_args("") - assert_equal "Invalid job payload, args must be an Array, not String", s - s = o.display_args(nil) - assert_equal "Invalid job payload, args is nil", s - end - - def test_to_query_string_escapes_bad_query_input - obj = Helpers.new - assert_equal "page=B%3CH", obj.to_query_string("page" => "B "H>B" } - end - end - assert_equal "direction=H%3EB&page=B%3CH", obj.qparams("page" => "B 12 - end - - def setup - Sidekiq.redis {|c| c.flushdb } - end - - it "provides basic ActiveJob compatibilility" do - q = Sidekiq::ScheduledSet.new - assert_equal 0, q.size - jid = SetWorker.set(wait_until: 1.hour.from_now).perform_async(123) - assert jid - assert_equal 1, q.size - jid = SetWorker.set(wait: 1.hour).perform_async(123) - assert jid - assert_equal 2, q.size - - q = Sidekiq::Queue.new("foo") - assert_equal 0, q.size - SetWorker.perform_async - assert_equal 1, q.size - - SetWorker.set(queue: 'xyz').perform_async - assert_equal 1, Sidekiq::Queue.new("xyz").size - end - - it 'can be memoized' do - q = Sidekiq::Queue.new('bar') - assert_equal 0, q.size - set = SetWorker.set(queue: :bar, foo: 'qaaz') - set.perform_async(1) - set.perform_async(1) - set.perform_async(1) - set.perform_async(1) - assert_equal 4, q.size - assert_equal 4, q.map{|j| j['jid'] }.uniq.size - set.perform_in(10, 1) - end - - it 'allows option overrides' do - q = Sidekiq::Queue.new('bar') - assert_equal 0, q.size - assert SetWorker.set(queue: :bar).perform_async(1) - job = q.first - assert_equal 'bar', job['queue'] - assert_equal 12, job['retry'] - end - - it 'handles symbols and strings' do - q = Sidekiq::Queue.new('bar') - assert_equal 0, q.size - assert SetWorker.set('queue' => 'bar', :retry => 11).perform_async(1) - job = q.first - assert_equal 'bar', job['queue'] - assert_equal 11, job['retry'] - - q.clear - assert SetWorker.perform_async(1) - assert_equal 0, q.size - - q = Sidekiq::Queue.new('foo') - job = q.first - assert_equal 'foo', job['queue'] - assert_equal 12, job['retry'] - end - - it 'allows multiple calls' do - SetWorker.set(queue: :foo).set(bar: 'xyz').perform_async - - q = Sidekiq::Queue.new('foo') - job = q.first - assert_equal 'foo', job['queue'] - assert_equal 'xyz', job['bar'] - end - - it 'works with .perform_bulk' do - q = Sidekiq::Queue.new('bar') - assert_equal 0, q.size - - set = SetWorker.set(queue: 'bar') - jids = set.perform_bulk((1..1_001).to_a.map { |x| Array(x) }) - - assert_equal 1_001, q.size - assert_equal 1_001, jids.size - end - - describe '.perform_bulk and lazy enumerators' do - it 'evaluates lazy enumerators' do - q = Sidekiq::Queue.new('bar') - assert_equal 0, q.size - - set = SetWorker.set('queue' => 'bar') - lazy_args = (1..1_001).to_a.map { |x| Array(x) }.lazy - jids = set.perform_bulk(lazy_args) - - assert_equal 1_001, q.size - assert_equal 1_001, jids.size - end - end - end - - describe '#perform_inline' do - $my_recorder = [] - - class MyCustomWorker - include Sidekiq::Worker - - def perform(recorder) - $my_recorder << ['work_performed'] - end - end - - class MyCustomMiddleware - def initialize(name, recorder) - @name = name - @recorder = recorder - end - - def call(*args) - @recorder << "#{@name}-before" - response = yield - @recorder << "#{@name}-after" - return response - end - end - - it 'executes middleware & runs job inline' do - server_chain = Sidekiq::Middleware::Chain.new - server_chain.add MyCustomMiddleware, "1-server", $my_recorder - client_chain = Sidekiq::Middleware::Chain.new - client_chain.add MyCustomMiddleware, "1-client", $my_recorder - Sidekiq.stub(:server_middleware, server_chain) do - Sidekiq.stub(:client_middleware, client_chain) do - MyCustomWorker.perform_inline($my_recorder) - assert_equal $my_recorder.flatten, %w(1-client-before 1-client-after 1-server-before work_performed 1-server-after) - end - end - end - end -end diff --git a/web/assets/images/apple-touch-icon.png b/web/assets/images/apple-touch-icon.png deleted file mode 100644 index 588ac508fd8e9e2fdd2ac92c88e6e829c285a622..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15594 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[i]&&i(0===i?9:1)&&(i+=1),d[n](e,i)[agoin].replace("%s",e)}function r(t,n){return n=n?e(n):new Date,(n-e(t))/1e3}function i(e){for(var t=1,n=0,r=e;e>=l[n]&&n1&&(n+="s"),[e+" "+n+" ago","in "+e+" "+n]},zh_CN:function(e,t){if(0===t)return["刚刚","片刻后"];var n=s[parseInt(t/2)];return[e+n+"前",e+n+"后"]}},l=[60,60,24,7,365/7/12,12],p=6,_="datetime";return a.register=function(e,t){d[e]=t},a}); -!function(s){function n(a){if(e[a])return e[a].exports;var r=e[a]={exports:{},id:a,loaded:!1};return s[a].call(r.exports,r,r.exports,n),r.loaded=!0,r.exports}var e={};return n.m=s,n.c=e,n.p="",n(0)}([function(s,n,e){for(var a=e(1),r=null,t=a.length-1;t>=0;t--)r=a[t],"en"!=r&&"zh_CN"!=r&&timeago.register(r,e(2)("./"+r))},function(s,n){s.exports=["ar","be","bg","ca","da","de","el","en","en_short","es","eu","fr","hu","in_BG","in_HI","in_ID","it","ja","ko","ml","nb_NO","nl","nn_NO","pl","pt_BR","ru","sv","ta","th","uk","vi","zh_CN","zh_TW"]},function(s,n,e){function a(s){return e(r(s))}function r(s){return t[s]||function(){throw new Error("Cannot find module '"+s+"'.")}()}var t={"./ar":3,"./ar.js":3,"./be":4,"./be.js":4,"./bg":5,"./bg.js":5,"./ca":6,"./ca.js":6,"./da":7,"./da.js":7,"./de":8,"./de.js":8,"./el":9,"./el.js":9,"./en":10,"./en.js":10,"./en_short":11,"./en_short.js":11,"./es":12,"./es.js":12,"./eu":13,"./eu.js":13,"./fr":14,"./fr.js":14,"./hu":15,"./hu.js":15,"./in_BG":16,"./in_BG.js":16,"./in_HI":17,"./in_HI.js":17,"./in_ID":18,"./in_ID.js":18,"./it":19,"./it.js":19,"./ja":20,"./ja.js":20,"./ko":21,"./ko.js":21,"./locales":1,"./locales.js":1,"./ml":22,"./ml.js":22,"./nb_NO":23,"./nb_NO.js":23,"./nl":24,"./nl.js":24,"./nn_NO":25,"./nn_NO.js":25,"./pl":26,"./pl.js":26,"./pt_BR":27,"./pt_BR.js":27,"./ru":28,"./ru.js":28,"./sv":29,"./sv.js":29,"./ta":30,"./ta.js":30,"./th":31,"./th.js":31,"./uk":32,"./uk.js":32,"./vi":33,"./vi.js":33,"./zh_CN":34,"./zh_CN.js":34,"./zh_TW":35,"./zh_TW.js":35};a.keys=function(){return Object.keys(t)},a.resolve=r,s.exports=a,a.id=2},function(s,n){function e(s,n){return 1===n?a[s][0]:2==n?a[s][1]:n>=3&&n<=10?a[s][2]:a[s][3]}s.exports=function(s,n){if(0===n)return["منذ لحظات","بعد لحظات"];var a;switch(n){case 1:a=0;break;case 2:case 3:a=1;break;case 4:case 5:a=2;break;case 6:case 7:a=3;break;case 8:case 9:a=4;break;case 10:case 11:a=5;break;case 12:case 13:a=6}var r=e(a,s);return["منذ "+r,"بعد "+r]};var a=[["ثانية","ثانيتين","%s ثوان","%s ثانية"],["دقيقة","دقيقتين","%s دقائق","%s دقيقة"],["ساعة","ساعتين","%s ساعات","%s ساعة"],["يوم","يومين","%s أيام","%s يوماً"],["أسبوع","أسبوعين","%s أسابيع","%s أسبوعاً"],["شهر","شهرين","%s أشهر","%s شهراً"],["عام","عامين","%s أعوام","%s عاماً"]]},function(s,n){function e(s,n,e,a,r){var t=r%10,u=a;return 1===r?u=s:1===t&&r>20?u=n:t>1&&t<5&&(r>20||r<10)&&(u=e),u}var a=e.bind(null,"секунду","%s секунду","%s секунды","%s секунд"),r=e.bind(null,"хвіліну","%s хвіліну","%s хвіліны","%s хвілін"),t=e.bind(null,"гадзіну","%s гадзіну","%s гадзіны","%s гадзін"),u=e.bind(null,"дзень","%s дзень","%s дні","%s дзён"),i=e.bind(null,"тыдзень","%s тыдзень","%s тыдні","%s тыдняў"),o=e.bind(null,"месяц","%s месяц","%s месяцы","%s месяцаў"),d=e.bind(null,"год","%s год","%s гады","%s гадоў");s.exports=function(s,n){switch(n){case 0:return["толькі што","праз некалькі секунд"];case 1:return[a(s)+" таму","праз "+a(s)];case 2:case 3:return[r(s)+" таму","праз "+r(s)];case 4:case 5:return[t(s)+" таму","праз "+t(s)];case 6:case 7:return[u(s)+" таму","праз "+u(s)];case 8:case 9:return[i(s)+" таму","праз "+i(s)];case 10:case 11:return[o(s)+" таму","праз "+o(s)];case 12:case 13:return[d(s)+" таму","праз "+d(s)];default:return["",""]}}},function(s,n){s.exports=function(s,n){return[["току що","съвсем скоро"],["преди %s секунди","след %s секунди"],["преди 1 минута","след 1 минута"],["преди %s минути","след %s минути"],["преди 1 час","след 1 час"],["преди %s часа","след %s часа"],["преди 1 ден","след 1 ден"],["преди %s дни","след %s дни"],["преди 1 седмица","след 1 седмица"],["преди %s седмици","след %s седмици"],["преди 1 месец","след 1 месец"],["преди %s месеца","след %s месеца"],["преди 1 година","след 1 година"],["преди %s години","след %s години"]][n]}},function(s,n){s.exports=function(s,n){return[["fa un moment","d'aquí un moment"],["fa %s segons","d'aquí %s segons"],["fa 1 minut","d'aquí 1 minut"],["fa %s minuts","d'aquí %s minuts"],["fa 1 hora","d'aquí 1 hora"],["fa %s hores","d'aquí %s hores"],["fa 1 dia","d'aquí 1 dia"],["fa %s dies","d'aquí %s dies"],["fa 1 setmana","d'aquí 1 setmana"],["fa %s setmanes","d'aquí %s setmanes"],["fa 1 mes","d'aquí 1 mes"],["fa %s mesos","d'aquí %s mesos"],["fa 1 any","d'aquí 1 any"],["fa %s anys","d'aquí %s anys"]][n]}},function(s,n){s.exports=function(s,n){return[["for et øjeblik siden","om et øjeblik"],["for %s sekunder siden","om %s sekunder"],["for 1 minut siden","om 1 minut"],["for %s minutter siden","om %s minutter"],["for 1 time siden","om 1 time"],["for %s timer siden","om %s timer"],["for 1 dag siden","om 1 dag"],["for %s dage siden","om %s dage"],["for 1 uge siden","om 1 uge"],["for %s uger siden","om %s uger"],["for 1 måned siden","om 1 måned"],["for %s måneder siden","om %s måneder"],["for 1 år siden","om 1 år"],["for %s år siden","om %s år"]][n]}},function(s,n){s.exports=function(s,n){return[["gerade eben","vor einer Weile"],["vor %s Sekunden","in %s Sekunden"],["vor 1 Minute","in 1 Minute"],["vor %s Minuten","in %s Minuten"],["vor 1 Stunde","in 1 Stunde"],["vor %s Stunden","in %s Stunden"],["vor 1 Tag","in 1 Tag"],["vor %s Tagen","in %s Tagen"],["vor 1 Woche","in 1 Woche"],["vor %s Wochen","in %s Wochen"],["vor 1 Monat","in 1 Monat"],["vor %s Monaten","in %s Monaten"],["vor 1 Jahr","in 1 Jahr"],["vor %s Jahren","in %s Jahren"]][n]}},function(s,n){s.exports=function(s,n){return[["μόλις τώρα","σε λίγο"],["%s δευτερόλεπτα πριν","σε %s δευτερόλεπτα"],["1 λεπτό πριν","σε 1 λεπτό"],["%s λεπτά πριν","σε %s λεπτά"],["1 ώρα πριν","σε 1 ώρα"],["%s ώρες πριν","σε %s ώρες"],["1 μέρα πριν","σε 1 μέρα"],["%s μέρες πριν","σε %s μέρες"],["1 εβδομάδα πριν","σε 1 εβδομάδα"],["%s εβδομάδες πριν","σε %s εβδομάδες"],["1 μήνα πριν","σε 1 μήνα"],["%s μήνες πριν","σε %s μήνες"],["1 χρόνο πριν","σε 1 χρόνο"],["%s χρόνια πριν","σε %s χρόνια"]][n]}},function(s,n){s.exports=function(s,n){return[["just now","a while"],["%s seconds ago","in %s seconds"],["1 minute ago","in 1 minute"],["%s minutes ago","in %s minutes"],["1 hour ago","in 1 hour"],["%s hours ago","in %s hours"],["1 day ago","in 1 day"],["%s days ago","in %s days"],["1 week ago","in 1 week"],["%s weeks ago","in %s weeks"],["1 month ago","in 1 month"],["%s months ago","in %s months"],["1 year ago","in 1 year"],["%s years ago","in %s years"]][n]}},function(s,n){s.exports=function(s,n){return[["just now","a while"],["%ss ago","in %ss"],["1m ago","in 1m"],["%sm ago","in %sm"],["1h ago","in 1h"],["%sh ago","in %sh"],["1d ago","in 1d"],["%sd ago","in %sd"],["1w ago","in 1w"],["%sw ago","in %sw"],["1mo ago","in 1mo"],["%smo ago","in %smo"],["1yr ago","in 1yr"],["%syr ago","in %syr"]][n]}},function(s,n){s.exports=function(s,n){return[["justo ahora","en un rato"],["hace %s segundos","en %s segundos"],["hace 1 minuto","en 1 minuto"],["hace %s minutos","en %s minutos"],["hace 1 hora","en 1 hora"],["hace %s horas","en %s horas"],["hace 1 día","en 1 día"],["hace %s días","en %s días"],["hace 1 semana","en 1 semana"],["hace %s semanas","en %s semanas"],["hace 1 mes","en 1 mes"],["hace %s meses","en %s meses"],["hace 1 año","en 1 año"],["hace %s años","en %s años"]][n]}},function(s,n){s.exports=function(s,n){return[["orain","denbora bat barru"],["duela %s segundu","%s segundu barru"],["duela minutu 1","minutu 1 barru"],["duela %s minutu","%s minutu barru"],["duela ordu 1","ordu 1 barru"],["duela %s ordu","%s ordu barru"],["duela egun 1","egun 1 barru"],["duela %s egun","%s egun barru"],["duela aste 1","aste 1 barru"],["duela %s aste","%s aste barru"],["duela hillabete 1","hillabete 1 barru"],["duela %s hillabete","%s hillabete barru"],["duela urte 1","urte 1 barru"],["duela %s urte","%s urte barru"]][n]}},function(s,n){s.exports=function(s,n){return[["à l'instant","dans un instant"],["il y a %s secondes","dans %s secondes"],["il y a 1 minute","dans 1 minute"],["il y a %s minutes","dans %s minutes"],["il y a 1 heure","dans 1 heure"],["il y a %s heures","dans %s heures"],["il y a 1 jour","dans 1 jour"],["il y a %s jours","dans %s jours"],["il y a 1 semaine","dans 1 semaine"],["il y a %s semaines","dans %s semaines"],["il y a 1 mois","dans 1 mois"],["il y a %s mois","dans %s mois"],["il y a 1 an","dans 1 an"],["il y a %s ans","dans %s ans"]][n]}},function(s,n){s.exports=function(s,n){return[["éppen most","éppen most"],["%s másodperce","%s másodpercen belül"],["1 perce","1 percen belül"],["%s perce","%s percen belül"],["1 órája","1 órán belül"],["%s órája","%s órán belül"],["1 napja","1 napon belül"],["%s napja","%s napon belül"],["1 hete","1 héten belül"],["%s hete","%s héten belül"],["1 hónapja","1 hónapon belül"],["%s hónapja","%s hónapon belül"],["1 éve","1 éven belül"],["%s éve","%s éven belül"]][n]}},function(s,n){s.exports=function(s,n){return[["এইমাত্র","একটা সময়"],["%s সেকেন্ড আগে","%s এর সেকেন্ডের মধ্যে"],["1 মিনিট আগে","1 মিনিটে"],["%s এর মিনিট আগে","%s এর মিনিটের মধ্যে"],["1 ঘন্টা আগে","1 ঘন্টা"],["%s ঘণ্টা আগে","%s এর ঘন্টার মধ্যে"],["1 দিন আগে","1 দিনের মধ্যে"],["%s এর দিন আগে","%s এর দিন"],["1 সপ্তাহ আগে","1 সপ্তাহের মধ্যে"],["%s এর সপ্তাহ আগে","%s সপ্তাহের মধ্যে"],["1 মাস আগে","1 মাসে"],["%s মাস আগে","%s মাসে"],["1 বছর আগে","1 বছরের মধ্যে"],["%s বছর আগে","%s বছরে"]][n]}},function(s,n){s.exports=function(s,n){return[["अभी","कुछ समय"],["%s सेकंड पहले","%s सेकंड में"],["1 मिनट पहले","1 मिनट में"],["%s मिनट पहले","%s मिनट में"],["1 घंटे पहले","1 घंटे में"],["%s घंटे पहले","%s घंटे में"],["1 दिन पहले","1 दिन में"],["%s दिन पहले","%s दिनों में"],["1 सप्ताह पहले","1 सप्ताह में"],["%s हफ्ते पहले","%s हफ्तों में"],["1 महीने पहले","1 महीने में"],["%s महीने पहले","%s महीनों में"],["1 साल पहले","1 साल में"],["%s साल पहले","%s साल में"]][n]}},function(s,n){s.exports=function(s,n){return[["baru saja","sebentar"],["%s detik yang lalu","dalam %s detik"],["1 menit yang lalu","dalam 1 menit"],["%s menit yang lalu","dalam %s menit"],["1 jam yang lalu","dalam 1 jam"],["%s jam yang lalu","dalam %s jam"],["1 hari yang lalu","dalam 1 hari"],["%s hari yang lalu","dalam %s hari"],["1 minggu yang lalu","dalam 1 minggu"],["%s minggu yang lalu","dalam %s minggu"],["1 bulan yang lalu","dalam 1 bulan"],["%s bulan yang lalu","dalam %s bulan"],["1 tahun yang lalu","dalam 1 tahun"],["%s tahun yang lalu","dalam %s tahun"]][n]}},function(s,n){s.exports=function(s,n){return[["poco fa","tra poco"],["%s secondi fa","%s secondi da ora"],["un minuto fa","un minuto da ora"],["%s minuti fa","%s minuti da ora"],["un'ora fa","un'ora da ora"],["%s ore fa","%s ore da ora"],["un giorno fa","un giorno da ora"],["%s giorni fa","%s giorni da ora"],["una settimana fa","una settimana da ora"],["%s settimane fa","%s settimane da ora"],["un mese fa","un mese da ora"],["%s mesi fa","%s mesi da ora"],["un anno fa","un anno da ora"],["%s anni fa","%s anni da ora"]][n]}},function(s,n){s.exports=function(s,n){return[["すこし前","すぐに"],["%s秒前","%s秒以内"],["1分前","1分以内"],["%s分前","%s分以内"],["1時間前","1時間以内"],["%s時間前","%s時間以内"],["1日前","1日以内"],["%s日前","%s日以内"],["1週間前","1週間以内"],["%s週間前","%s週間以内"],["1ヶ月前","1ヶ月以内"],["%sヶ月前","%sヶ月以内"],["1年前","1年以内"],["%s年前","%s年以内"]][n]}},function(s,n){s.exports=function(s,n){return[["방금","곧"],["%s초 전","%s초 후"],["1분 전","1분 후"],["%s분 전","%s분 후"],["1시간 전","1시간 후"],["%s시간 전","%s시간 후"],["1일 전","1일 후"],["%s일 전","%s일 후"],["1주일 전","1주일 후"],["%s주일 전","%s주일 후"],["1개월 전","1개월 후"],["%s개월 전","%s개월 후"],["1년 전","1년 후"],["%s년 전","%s년 후"]][n]}},function(s,n){s.exports=function(s,n){return[["ഇപ്പോള്‍","കുറച്ചു മുന്‍പ്"],["%s സെക്കന്റ്‌കള്‍ക്ക് മുന്‍പ്","%s സെക്കന്റില്‍"],["1 മിനിറ്റിനു മുന്‍പ്","1 മിനിറ്റില്‍"],["%s മിനിറ്റുകള്‍ക്ക് മുന്‍പ","%s മിനിറ്റില്‍"],["1 മണിക്കൂറിനു മുന്‍പ്","1 മണിക്കൂറില്‍"],["%s മണിക്കൂറുകള്‍ക്കു മുന്‍പ്","%s മണിക്കൂറില്‍"],["1 ഒരു ദിവസം മുന്‍പ്","1 ദിവസത്തില്‍"],["%s ദിവസങ്ങള്‍ക് മുന്‍പ്","%s ദിവസങ്ങള്‍ക്കുള്ളില്‍"],["1 ആഴ്ച മുന്‍പ്","1 ആഴ്ചയില്‍"],["%s ആഴ്ചകള്‍ക്ക് മുന്‍പ്","%s ആഴ്ചകള്‍ക്കുള്ളില്‍"],["1 മാസത്തിനു മുന്‍പ്","1 മാസത്തിനുള്ളില്‍"],["%s മാസങ്ങള്‍ക്ക് മുന്‍പ്","%s മാസങ്ങള്‍ക്കുള്ളില്‍"],["1 വര്‍ഷത്തിനു മുന്‍പ്","1 വര്‍ഷത്തിനുള്ളില്‍"],["%s വര്‍ഷങ്ങള്‍ക്കു മുന്‍പ്","%s വര്‍ഷങ്ങള്‍ക്കുല്ല്ളില്‍"]][n]}},function(s,n){s.exports=function(s,n){return[["akkurat nå","om litt"],["%s sekunder siden","om %s sekunder"],["1 minutt siden","om 1 minutt"],["%s minutter siden","om %s minutter"],["1 time siden","om 1 time"],["%s timer siden","om %s timer"],["1 dag siden","om 1 dag"],["%s dager siden","om %s dager"],["1 uke siden","om 1 uke"],["%s uker siden","om %s uker"],["1 måned siden","om 1 måned"],["%s måneder siden","om %s måneder"],["1 år siden","om 1 år"],["%s år siden","om %s år"]][n]}},function(s,n){s.exports=function(s,n){return[["recent","binnenkort"],["%s seconden geleden","binnen %s seconden"],["1 minuut geleden","binnen 1 minuut"],["%s minuten geleden","binnen %s minuten"],["1 uur geleden","binnen 1 uur"],["%s uren geleden","binnen %s uren"],["1 dag geleden","binnen 1 dag"],["%s dagen geleden","binnen %s dagen"],["1 week geleden","binnen 1 week"],["%s weken geleden","binnen %s weken"],["1 maand geleden","binnen 1 maand"],["%s maanden geleden","binnen %s maanden"],["1 jaar geleden","binnen 1 jaar"],["%s jaren geleden","binnen %s jaren"]][n]}},function(s,n){s.exports=function(s,n){return[["nett no","om litt"],["%s sekund sidan","om %s sekund"],["1 minutt sidan","om 1 minutt"],["%s minutt sidan","om %s minutt"],["1 time sidan","om 1 time"],["%s timar sidan","om %s timar"],["1 dag sidan","om 1 dag"],["%s dagar sidan","om %s dagar"],["1 veke sidan","om 1 veke"],["%s veker sidan","om %s veker"],["1 månad sidan","om 1 månad"],["%s månadar sidan","om %s månadar"],["1 år sidan","om 1 år"],["%s år sidan","om %s år"]][n]}},function(s,n){s.exports=function(s,n){var e=[["w tej chwili","za chwilę"],["%s sekund temu","za %s sekund"],["1 minutę temu","za 1 minutę"],["%s minut temu","za %s minut"],["1 godzina temu","za 1 godzinę"],["%s godzin temu","za %s godzin"],["1 dzień temu","za 1 dzień"],["%s dni temu","za %s dni"],["1 tydzień temu","za 1 tydzień"],["%s tygodni temu","za %s tygodni"],["1 miesiąc temu","za 1 miesiąc"],["%s miesiące temu","za %s miesiące"],["1 rok temu","za 1 rok"],["%s lata temu","za %s lata"]],a=s.toString();return 1==n&&(2==a.length&&"1"==a[0]&&"0"!=a[1]||[2,3,4].indexOf(s%10)!=-1||[2,3,4].indexOf(s)!=-1)?["%s sekundy temu","za %s sekundy"]:3!=n||[2,3,4].indexOf(s%10)==-1&&[2,3,4].indexOf(s)==-1?5!=n||[2,3,4].indexOf(s%10)==-1&&[2,3,4].indexOf(s)==-1?9==n&&[2,3,4].indexOf(s)!=-1?["%s tygodnie temu","za %s tygodnie"]:11==n&&(s%10==0||2==a.length&&"1"==a[0]||[1,5,6,7,8,9].indexOf(s%10)!=-1)?["%s miesięcy temu","za %s miesięcy"]:13==n&&(s%10==0||2==a.length&&"1"==a[0]||[1,5,6,7,8,9].indexOf(s%10)!=-1)?["%s lat temu","za %s lat"]:e[n]:["%s godziny temu","za %s godziny"]:["%s minuty temu","za %s minuty"]}},function(s,n){s.exports=function(s,n){return[["agora mesmo","daqui um pouco"],["há %s segundos","em %s segundos"],["há um minuto","em um minuto"],["há %s minutos","em %s minutos"],["há uma hora","em uma hora"],["há %s horas","em %s horas"],["há um dia","em um dia"],["há %s dias","em %s dias"],["há uma semana","em uma semana"],["há %s semanas","em %s semanas"],["há um mês","em um mês"],["há %s meses","em %s meses"],["há um ano","em um ano"],["há %s anos","em %s anos"]][n]}},function(s,n){function e(s,n,e,a,r){var t=r%10,u=a;return 1===r?u=s:1===t&&r>20?u=n:t>1&&t<5&&(r>20||r<10)&&(u=e),u}var a=e.bind(null,"секунду","%s секунду","%s секунды","%s секунд"),r=e.bind(null,"минуту","%s минуту","%s минуты","%s минут"),t=e.bind(null,"час","%s час","%s часа","%s часов"),u=e.bind(null,"день","%s день","%s дня","%s дней"),i=e.bind(null,"неделю","%s неделю","%s недели","%s недель"),o=e.bind(null,"месяц","%s месяц","%s месяца","%s месяцев"),d=e.bind(null,"год","%s год","%s года","%s лет");s.exports=function(s,n){switch(n){case 0:return["только что","через несколько секунд"];case 1:return[a(s)+" назад","через "+a(s)];case 2:case 3:return[r(s)+" назад","через "+r(s)];case 4:case 5:return[t(s)+" назад","через "+t(s)];case 6:return["вчера","завтра"];case 7:return[u(s)+" назад","через "+u(s)];case 8:case 9:return[i(s)+" назад","через "+i(s)];case 10:case 11:return[o(s)+" назад","через "+o(s)];case 12:case 13:return[d(s)+" назад","через "+d(s)];default:return["",""]}}},function(s,n){s.exports=function(s,n){return[["just nu","om en stund"],["%s sekunder sedan","om %s seconder"],["1 minut sedan","om 1 minut"],["%s minuter sedan","om %s minuter"],["1 timme sedan","om 1 timme"],["%s timmar sedan","om %s timmar"],["1 dag sedan","om 1 day"],["%s dagar sedan","om %s days"],["1 vecka sedan","om 1 vecka"],["%s veckor sedan","om %s veckor"],["1 månad sedan","om 1 månad"],["%s månader sedan","om %s månader"],["1 år sedan","om 1 år"],["%s år sedan","om %s år"]][n]}},function(s,n){s.exports=function(s,n){return[["இப்போது","சற்று நேரம் முன்பு"],["%s நொடிக்கு முன்","%s நொடிகளில்"],["1 நிமிடத்திற்க்கு முன்","1 நிமிடத்தில்"],["%s நிமிடத்திற்க்கு முன்","%s நிமிடங்களில்"],["1 மணி நேரத்திற்கு முன்","1 மணி நேரத்திற்குள்"],["%s மணி நேரத்திற்கு முன்","%s மணி நேரத்திற்குள்"],["1 நாளுக்கு முன்","1 நாளில்"],["%s நாட்களுக்கு முன்","%s நாட்களில்"],["1 வாரத்திற்கு முன்","1 வாரத்தில்"],["%s வாரங்களுக்கு முன்","%s வாரங்களில்"],["1 மாதத்திற்கு முன்","1 மாதத்தில்"],["%s மாதங்களுக்கு முன்","%s மாதங்களில்"],["1 வருடத்திற்கு முன்","1 வருடத்தில்"],["%s வருடங்களுக்கு முன்","%s வருடங்களில்"]][n]}},function(s,n){s.exports=function(s,n){return[["เมื่อสักครู่นี้","อีกสักครู่"],["%s วินาทีที่แล้ว","ใน %s วินาที"],["1 นาทีที่แล้ว","ใน 1 นาที"],["%s นาทีที่แล้ว","ใน %s นาที"],["1 ชั่วโมงที่แล้ว","ใน 1 ชั่วโมง"],["%s ชั่วโมงที่แล้ว","ใน %s ชั่วโมง"],["1 วันที่แล้ว","ใน 1 วัน"],["%s วันที่แล้ว","ใน %s วัน"],["1 อาทิตย์ที่แล้ว","ใน 1 อาทิตย์"],["%s อาทิตย์ที่แล้ว","ใน %s อาทิตย์"],["1 เดือนที่แล้ว","ใน 1 เดือน"],["%s เดือนที่แล้ว","ใน %s เดือน"],["1 ปีที่แล้ว","ใน 1 ปี"],["%s ปีที่แล้ว","ใน %s ปี"]][n]}},function(s,n){function e(s,n,e,a,r){var t=r%10,u=a;return 1===r?u=s:1===t&&r>20?u=n:t>1&&t<5&&(r>20||r<10)&&(u=e),u}var a=e.bind(null,"секунду","%s секунду","%s секунди","%s секунд"),r=e.bind(null,"хвилину","%s хвилину","%s хвилини","%s хвилин"),t=e.bind(null,"годину","%s годину","%s години","%s годин"),u=e.bind(null,"день","%s день","%s дні","%s днів"),i=e.bind(null,"тиждень","%s тиждень","%s тиждні","%s тижднів"),o=e.bind(null,"місяць","%s місяць","%s місяці","%s місяців"),d=e.bind(null,"рік","%s рік","%s роки","%s років");s.exports=function(s,n){switch(n){case 0:return["щойно","через декілька секунд"];case 1:return[a(s)+" тому","через "+a(s)];case 2:case 3:return[r(s)+" тому","через "+r(s)];case 4:case 5:return[t(s)+" тому","через "+t(s)];case 6:case 7:return[u(s)+" тому","через "+u(s)];case 8:case 9:return[i(s)+" тому","через "+i(s)];case 10:case 11:return[o(s)+" тому","через "+o(s)];case 12:case 13:return[d(s)+" тому","через "+d(s)];default:return["",""]}}},function(s,n){s.exports=function(s,n){return[["vừa xong","một lúc"],["%s giây trước","trong %s giây"],["1 phút trước","trong 1 phút"],["%s phút trước","trong %s phút"],["1 giờ trước","trong 1 giờ"],["%s giờ trước","trong %s giờ"],["1 ngày trước","trong 1 ngày"],["%s ngày trước","trong %s ngày"],["1 tuần trước","trong 1 tuần"],["%s tuần trước","trong %s tuần"],["1 tháng trước","trong 1 tháng"],["%s tháng trước","trong %s tháng"],["1 năm trước","trong 1 năm"],["%s năm trước","trong %s năm"]][n]}},function(s,n){s.exports=function(s,n){return[["刚刚","片刻后"],["%s秒前","%s秒后"],["1分钟前","1分钟后"],["%s分钟前","%s分钟后"],["1小时前","1小时后"],["%s小时前","%s小时后"],["1天前","1天后"],["%s天前","%s天后"],["1周前","1周后"],["%s周前","%s周后"],["1月前","1月后"],["%s月前","%s月后"],["1年前","1年后"],["%s年前","%s年后"]][n]}},function(s,n){s.exports=function(s,n){return[["剛剛","片刻後"],["%s秒前","%s秒後"],["1分鐘前","1分鐘後"],["%s分鐘前","%s分鐘後"],["1小時前","1小時後"],["%s小時前","%s小時後"],["1天前","1天後"],["%s天前","%s天後"],["1周前","1周後"],["%s周前","%s周後"],["1月前","1月後"],["%s月前","%s月後"],["1年前","1年後"],["%s年前","%s年後"]][n]}}]); - -var livePollTimer = null; - -var ready = (callback) => { - if (document.readyState != "loading") callback(); - else document.addEventListener("DOMContentLoaded", callback); -} - -ready(() => { - document.querySelectorAll(".check_all").forEach(node => { - node.addEventListener("click", event => { - node.closest('table').querySelectorAll('input[type=checkbox]').forEach(inp => { inp.checked = !!node.checked; }); - }) - }); - - document.querySelectorAll("input[data-confirm]").forEach(node => { - node.addEventListener("click", event => { - if (!window.confirm(node.getAttribute("data-confirm"))) { - event.preventDefault(); - event.stopPropagation(); - } - }) - }) - - document.querySelectorAll("[data-toggle]").forEach(node => { - node.addEventListener("click", event => { - var targName = node.getAttribute("data-toggle"); - var full = document.getElementById(targName + "_full"); - if (full.style.display == "block") { - full.style.display = 'none'; - } else { - full.style.display = 'block'; - } - }) - }) - - updateFuzzyTimes(); - - var buttons = document.querySelectorAll(".live-poll"); - if (buttons.length > 0) { - buttons.forEach(node => { - node.addEventListener("click", event => { - if (localStorage.sidekiqLivePoll == "enabled") { - localStorage.sidekiqLivePoll = "disabled"; - clearTimeout(livePollTimer); - livePollTimer = null; - } else { - localStorage.sidekiqLivePoll = "enabled"; - livePollCallback(); - } - - updateLivePollButton(); - }) - }); - - updateLivePollButton(); - if (localStorage.sidekiqLivePoll == "enabled") { - scheduleLivePoll(); - } - } -}) - -function updateFuzzyTimes() { - var locale = document.body.getAttribute("data-locale"); - var parts = locale.split('-'); - if (typeof parts[1] !== 'undefined') { - parts[1] = parts[1].toUpperCase(); - locale = parts.join('_'); - } - - var t = timeago() - t.render(document.querySelectorAll('time'), locale); - t.cancel(); -} - -function updateLivePollButton() { - if (localStorage.sidekiqLivePoll == "enabled") { - document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "inline-block" }) - document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "none" }) - } else { - document.querySelectorAll('.live-poll-start').forEach(box => { box.style.display = "inline-block" }) - document.querySelectorAll('.live-poll-stop').forEach(box => { box.style.display = "none" }) - } -} - -function livePollCallback() { - clearTimeout(livePollTimer); - - fetch(window.location.href).then(resp => resp.text()).then(replacePage).finally(scheduleLivePoll) -} - -function scheduleLivePoll() { - let ti = parseInt(localStorage.sidekiqTimeInterval) || 5000; - livePollTimer = setTimeout(livePollCallback, ti); -} - -function replacePage(text) { - var parser = new DOMParser(); - var doc = parser.parseFromString(text, "text/html"); - - var page = doc.querySelector('#page') - document.querySelector("#page").replaceWith(page) - - var header_status = doc.querySelector('.status') - document.querySelector('.status').replaceWith(header_status) - - updateFuzzyTimes(); -} diff --git a/web/assets/javascripts/dashboard.js b/web/assets/javascripts/dashboard.js deleted file mode 100644 index 79262af6..00000000 --- a/web/assets/javascripts/dashboard.js +++ /dev/null @@ -1,296 +0,0 @@ -// D3 3.5.16 -!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,La),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t0&&(n=n.slice(0,o));var c=qa.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ra,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ta&&(Ta="onselectstart"in e?!1:x(e.style,"userSelect")),Ta){var a=n(e).style,o=a[Ta];a[Ta]="none"}return function(n){if(i.on(r,null),Ta&&(a[Ta]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Da){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Da=!(a.f||a.e),r.remove()}}return Da?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?ja:Math.acos(n)}function tn(n){return n>1?Oa:-1>n?-Oa:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Ia)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Qa,r=vn(r)*no,i=vn(i)*to,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ya,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=uo.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Qa),u=dn((.2126729*n+.7151522*t+.072175*e)/no),i=dn((.0193339*n+.119192*t+.9503041*e)/to);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return ao?ao.n=i:io=i,ao=i,oo||(lo=clearTimeout(lo),oo=1,co(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(lo),lo=setTimeout(Tn,t)),oo=0):(oo=1,co(Tn))}function Rn(){for(var n=Date.now(),t=io;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=io,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=fo.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=ho.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new po(e-1)),1),e}function i(n,e){return t(n=new po(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{po=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{po=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{po=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{po=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++oo;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in mo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{po=Hn;var t=new po;return t._=n,r(t)}finally{po=Date}}var r=t(n);return e.parse=function(n){try{po=Hn;var t=r.parse(n);return t&&t._}finally{po=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+go.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(go.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(go.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:ot,"%":function(){return"%"}},C={a:r,A:u,b:i,B:a,c:o,d:tt,e:tt,H:rt,I:rt,j:et,L:at,m:nt,M:ut,p:s,S:it,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Vn(n){return new RegExp("^(?:"+n.map(oa.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ut(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function it(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function at(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ot(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Zn(r,"0",2)+Zn(u,"0",2)}function lt(n,t,e){Mo.lastIndex=0;var r=Mo.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,o=a*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(o),h=s*a*Math.sin(o);ko.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;No.point=function(a,o){No.point=n,r=(t=a)*Ia,u=Math.cos(o=(e=o)*Ia/2+ja/4),i=Math.sin(o)},No.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function mt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function yt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return Ma(n[0]-t[0])o;++o)u.point((e=n[o])[0],e[1]);return void u.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,i.push(l),a.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,i.push(l),a.push(c)}}),a.sort(t),qt(i),qt(a),i.length){for(var o=0,l=e,c=a.length;c>o;++o)a[o].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var o=0,c=s.length;c>o;++o)u.point((f=s[o])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var o=s.length-1;o>=0;--o)u.point((f=s[o])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++a1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Dt))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:a,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=a,y.lineStart=l,y.lineEnd=c,g=oa.merge(g);var n=Ot(m,p);g.length?(b||(i.polygonStart(),b=!0),Lt(g,Ut,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Pt(),x=t(M),b=!1;return y}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Oa-Pa:Oa-n[1])-((t=t.x)[0]<0?t[1]-Oa-Pa:Oa-t[1])}function jt(n){var t,e=NaN,r=NaN,u=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(i,a){var o=i>0?ja:-ja,l=Ma(i-e);Ma(l-ja)0?Oa:-Oa),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(i,r),t=0):u!==o&&l>=ja&&(Ma(e-u)Pa?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*a)):(t+r)/2}function Ht(n,t,e,r){var u;if(null==n)u=e*Oa,r.point(-ja,u),r.point(0,u),r.point(ja,u),r.point(ja,0),r.point(ja,-u),r.point(0,-u),r.point(-ja,-u),r.point(-ja,0),r.point(-ja,u);else if(Ma(n[0]-t[0])>Pa){var i=n[0]o;++o){var c=t[o],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+ja/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+ja/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>ja,k=p*M;if(ko.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*Fa:b,S^h>=e^m>=e){var N=yt(dt(f),dt(n));bt(N);var E=yt(u,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(a+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Pa>i||Pa>i&&0>ko)^1&a}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=a?v?0:u(f,h):v?u(f+(0>f?ja:-ja),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(wt(e,g)||wt(p,g))&&(p[0]+=Pa,p[1]+=Pa,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(o&&e&&a^v){var m;d&i||!(m=r(p,e,!0))||(s=0,a?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&wt(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),u=dt(t),a=[1,0,0],o=yt(r,u),l=mt(o,o),c=o[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=yt(a,o),p=xt(a,f),v=xt(o,h);Mt(p,v);var d=g,m=mt(p,d),y=mt(d,d),M=m*m-y*(mt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-m-x)/y);if(Mt(b,p),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=Ma(E-ja)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(Ma(b[0]-w)ja^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-m+x)/y);return Mt(z,p),[b,_t(z)]}}}function u(t,e){var r=a?n:ja-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),a=i>0,o=Ma(i)>Pa,l=ve(n,6*Ia);return Rt(t,e,l,a?[0,-n]:[-ja,n-ja])}function Yt(n,t,e,r){return function(u){var i,a=u.a,o=u.b,l=a.x,c=a.y,s=o.x,f=o.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Zt(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(o){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,a=1,o=d[u],l=o.length,c=o[0];l>a;++a)i=o[a],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,o,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(o,l))||a(i,o)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(o[0],o[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&o.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=NaN}function g(){v&&(p(y,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=f,w&&o.lineEnd()}function p(n,t){n=Math.max(-Ho,Math.min(Ho,n)),t=Math.max(-Ho,Math.min(Ho,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(o.lineStart(),o.point(n,t));else if(e&&w)o.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(o.lineStart(),o.point(r.a.x,r.a.y)),o.point(r.b.x,r.b.y),e||o.lineEnd(),k=!1):e&&(o.lineStart(),o.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,N=o,E=Pt(),A=Yt(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){o=E,v=[],d=[],k=!0},polygonEnd:function(){o=N,v=oa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(o.polygonStart(),e&&(o.lineStart(),c(null,null,1,o),o.lineEnd()),u&&Lt(v,i,t,c,o),o.polygonEnd()),v=d=m=null}};return C}}function Vt(n){var t=0,e=ja/3,r=oe(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*ja/180,e=n[1]*ja/180):[t/ja*180,e/ja*180]},u}function Xt(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),a-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),a=Math.sqrt(i)/u;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/u,tn((i-(n*n+e*e)*u*u)/(2*u))]},e}function $t(){function n(n,t){Io+=u*n-r*t,r=n,u=t}var t,e,r,u;$o.point=function(i,a){$o.point=n,t=r=i,e=u=a},$o.lineEnd=function(){n(t,e)}}function Bt(n,t){Yo>n&&(Yo=n),n>Vo&&(Vo=n),Zo>t&&(Zo=t),t>Xo&&(Xo=t)}function Wt(){function n(n,t){a.push("M",n,",",t,i)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function u(){a.push("Z")}var i=Jt(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return i=Jt(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Co+=n,zo+=t,++Lo}function Kt(){function n(n,r){var u=n-t,i=r-e,a=Math.sqrt(u*u+i*i);qo+=a*(t+n)/2,To+=a*(e+r)/2,Ro+=a,Gt(t=n,e=r)}var t,e;Wo.point=function(r,u){Wo.point=n,Gt(t=r,e=u)}}function Qt(){Wo.point=Gt}function ne(){function n(n,t){var e=n-r,i=t-u,a=Math.sqrt(e*e+i*i);qo+=a*(r+n)/2,To+=a*(u+t)/2,Ro+=a,a=u*n-r*t,Do+=a*(r+n),Po+=a*(u+t),Uo+=3*a,Gt(r=n,u=t)}var t,e,r,u;Wo.point=function(i,a){Wo.point=n,Gt(t=r=i,e=u=a)},Wo.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+a,e),n.arc(t,e,a,0,Fa)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function u(){o.point=t}function i(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:u,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=u,o.point=t},pointRadius:function(n){return a=n,o},result:b};return o}function ee(n){function t(n){return(o?r:e)(n)}function e(t){return ie(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=i,t.lineStart()}function i(e,r){var i=dt([e,r]),a=n(e,r);u(M,x,y,b,_,w,M=a[0],x=a[1],y=e,b=i[0],_=i[1],w=i[2],o,t),t.point(M,x)}function a(){S.point=e,t.lineEnd()}function l(){ -r(),S.point=c,S.lineEnd=s}function c(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,o,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=o+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||a>o*g+l*p+c*v)&&(u(t,e,r,o,l,c,A,C,N,b/=S,_/=S,w,d,m),m.point(A,C),u(A,C,N,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,a=Math.cos(30*Ia),o=16;return t.precision=function(n){return arguments.length?(o=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function re(n){var t=ee(function(t,e){return n([t*Ya,e*Ya])});return function(n){return le(t(n))}}function ue(n){this.stream=n}function ie(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ae(n){return oe(function(){return n})()}function oe(n){function t(n){return n=o(n[0]*Ia,n[1]*Ia),[n[0]*h+l,c-n[1]*h]}function e(n){return n=o.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ya,n[1]*Ya]}function r(){o=Ct(a=fe(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,a,o,l,c,s,f=ee(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Fo,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=le(b(a,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fo):It((w=+n)*Ia),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Ia,d=n[1]%360*Ia,r()):[v*Ya,d*Ya]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Ia,M=n[1]%360*Ia,x=n.length>2?n[2]%360*Ia:0,r()):[m*Ya,M*Ya,x*Ya]},oa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function le(n){return ie(n,function(t,e){n.point(t*Ia,e*Ia)})}function ce(n,t){return[n,t]}function se(n,t){return[n>ja?n-Fa:-ja>n?n+Fa:n,t]}function fe(n,t,e){return n?t||e?Ct(ge(n),pe(t,e)):ge(n):t||e?pe(t,e):se}function he(n){return function(t,e){return t+=n,[t>ja?t-Fa:-ja>t?t+Fa:t,e]}}function ge(n){var t=he(n);return t.invert=he(-n),t}function pe(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+o*u;return[Math.atan2(l*i-s*a,o*r-c*u),tn(s*i+l*a)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*a;return[Math.atan2(l*i+c*a,o*r+s*u),tn(s*r-o*u)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,a,o){var l=a*t;null!=u?(u=de(e,u),i=de(e,i),(a>0?i>u:u>i)&&(u+=a*Fa)):(u=n+a*Fa,i=n-.5*l);for(var c,s=u;a>0?s>i:i>s;s-=l)o.point((c=_t([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Pa)%(2*Math.PI)}function me(n,t,e){var r=oa.range(n,t-Pa,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ye(n,t,e){var r=oa.range(n,t-Pa,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),a=Math.cos(r),o=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(an(r-t)+u*a*an(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,a=e*i+t*o;return[Math.atan2(u,r)*Ya,Math.atan2(a,Math.sqrt(r*r+u*u))*Ya]}:function(){return[n*Ya,t*Ya]};return p.distance=h,p}function _e(){function n(n,u){var i=Math.sin(u*=Ia),a=Math.cos(u),o=Ma((n*=Ia)-t),l=Math.cos(o);Jo+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*i-e*a*l)*o),e*i+r*a*l),t=n,e=i,r=a}var t,e,r;Go.point=function(u,i){t=u*Ia,e=Math.sin(i*=Ia),r=Math.cos(i),Go.point=n},Go.lineEnd=function(){Go.point=Go.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),a=Math.cos(u);return[Math.atan2(n*i,r*a),Math.asin(r&&e*i/r)]},e}function Se(n,t){function e(n,t){a>0?-Oa+Pa>t&&(t=-Oa+Pa):t>Oa-Pa&&(t=Oa-Pa);var e=a/Math.pow(u(t),i);return[e*Math.sin(i*n),a-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(ja/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),a=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=a-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(a/r,1/i))-Oa]},e):Ne}function ke(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var u=n[0],i=e[0],a=t[0]-u,o=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(o*(l-c)-f*(u-i))/(f*a-o*s);return[u+h*a,l+h*s]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),al.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,a=n.N,o=[n];je(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=o[s],l=o[s-1],nr(c.edge,l.site,c.site,u);l=o[0],c=o[f-1],c.edge=Ke(l.site,c.site,null,u),$e(l),$e(c)}function He(n){for(var t,e,r,u,i=n.x,a=n.y,o=al._;o;)if(r=Oe(o,a)-i,r>Pa)o=o.L;else{if(u=i-Ie(o,a),!(u>Pa)){r>-Pa?(t=o.P,e=o):u>-Pa?(t=o,e=o.N):t=e=o;break}if(!o.R){t=o;break}o=o.R}var l=Ue(n);if(al.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),al.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};nr(e.edge,c,p,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,p,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var a=n.P;if(!a)return-(1/0);e=a.site;var o=e.x,l=e.y,c=l-t;if(!c)return o;var s=o-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+o)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,u,i,a,o,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=il,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(o=i.edges,l=o.length,a=0;l>a;)s=o[a].end(),r=s.x,u=s.y,c=o[++a%l].start(),t=c.x,e=c.y,(Ma(r-t)>Pa||Ma(u-e)>Pa)&&(o.splice(a,0,new tr(Qe(i.site,s,Ma(r-f)Pa?{x:f,y:Ma(t-f)Pa?{x:Ma(e-p)Pa?{x:h,y:Ma(t-h)Pa?{x:Ma(e-g)=-Ua)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+o,m=sl.pop()||new Xe;m.arc=n,m.site=u,m.x=v+a,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=ll._;M;)if(m.yd||d>=o)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}else{if(i){if(i.xi||f>a||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,a=e+y,o=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,a),o}function vr(n,t){n=oa.rgb(n),t=oa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,a=t.g-r,o=t.b-u;return function(n){return"#"+bn(Math.round(e+i*n))+bn(Math.round(r+a*n))+bn(Math.round(u+o*n))}}function dr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function mr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function yr(n,t){var e,r,u,i=hl.lastIndex=gl.lastIndex=0,a=-1,o=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=gl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),o[a]?o[a]+=u:o[++a]=u),(e=e[0])===(r=r[0])?o[a]?o[a]+=r:o[++a]=r:(o[++a]=null,l.push({i:a,x:mr(e,r)})),i=gl.lastIndex;return ir;++r)o[(e=l[r]).i]=e.x(n);return o.join("")})}function Mr(n,t){for(var e,r=oa.interpolators.length;--r>=0&&!(e=oa.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],u=[],i=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Mr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;a>e;++e)u[e]=t[e];return function(n){for(e=0;o>e;++e)u[e]=r[e](n);return u}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Oa)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Fa*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Fa/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=oa.hcl(n),t=oa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,a=t.c-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return fn(e+i*n,r+a*n,u+o*n)+""}}function Dr(n,t){n=oa.hsl(n),t=oa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,a=t.s-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return cn(e+i*n,r+a*n,u+o*n)+""}}function Pr(n,t){n=oa.lab(n),t=oa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,a=t.a-r,o=t.b-u;return function(n){return gn(e+i*n,r+a*n,u+o*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),u=Fr(t,e),i=Hr(Or(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:mr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:mr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:u-4,x:mr(n[0],t[0])},{i:u-2,x:mr(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=oa.transform(n),t=oa.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function au(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,a=-1;++ae;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function mu(n){return n.reduce(yu,0)}function yu(n,t){return n+t[1]}function Mu(n,t){return xu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function bu(n){return[oa.min(n),oa.max(n)]}function _u(n,t){return n.value-t.value}function wu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Su(n,t){n._pack_next=t,t._pack_prev=n}function ku(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Nu(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,a,o,l,c,s=1/0,f=-(1/0),h=1/0,g=-(1/0);if(e.forEach(Eu),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],zu(r,u,i),t(i),wu(r,i),r._pack_prev=i,wu(i,u),u=r._pack_next,a=3;c>a;a++){zu(r,u,i=e[a]);var p=0,v=1,d=1;for(o=u._pack_next;o!==u;o=o._pack_next,v++)if(ku(o,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==o._pack_prev&&!ku(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ra;a++)i=e[a],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Au)}}function Eu(n){n._pack_next=n._pack_prev=n}function Au(n){delete n._pack_next,delete n._pack_prev}function Cu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,a=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pu(n,t,e){return n.a.parent===t.parent?n.a:e}function Uu(n){return 1+oa.max(n,function(n){return n.y})}function ju(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fu(n){var t=n.children;return t&&t.length?Fu(t[0]):n}function Hu(n){var t,e=n.children;return e&&(t=e.length)?Hu(e[t-1]):n}function Ou(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Iu(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Yu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zu(n){return n.rangeExtent?n.rangeExtent():Yu(n.range())}function Vu(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Xu(n,t){var e,r=0,u=n.length-1,i=n[r],a=n[u];return i>a&&(e=r,r=u,u=e,e=i,i=a,a=e),n[r]=t.floor(i),n[u]=t.ceil(a),n}function $u(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bu(n,t,e,r){var u=[],i=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Bu:Vu,l=r?Wr:Br;return a=u(n,t,l,e),o=u(t,n,l,Mr),i}function i(n){return a(n)}var a,o;return i.invert=function(n){return o(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Ur)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Qu(n,t)},i.tickFormat=function(t,e){return ni(n,t,e)},i.nice=function(t){return Gu(n,t),u()},i.copy=function(){return Wu(n,t,e,r)},u()}function Ju(n,t){return oa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gu(n,t){return Xu(n,$u(Ku(n,t)[2])),Xu(n,$u(Ku(n,t)[2])),n}function Ku(n,t){null==t&&(t=10);var e=Yu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Qu(n,t){return oa.range.apply(oa,Ku(n,t))}function ni(n,t,e){var r=Ku(n,t);if(e){var u=fo.exec(e);if(u.shift(),"s"===u[8]){var i=oa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+ti(i.scale(r[2]))),u[8]="f",e=oa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+ei(u[8],r)),e=u.join("")}else e=",."+ti(r[2])+"f";return oa.format(e)}function ti(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function ei(n,t){var e=ti(t[2]);return n in kl?Math.abs(e-ti(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ri(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(u(t))}return a.invert=function(t){return i(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),a):t},a.nice=function(){var t=Xu(r.map(u),e?Math:El);return n.domain(t),r=t.map(i),a},a.ticks=function(){var n=Yu(r),a=[],o=n[0],l=n[1],c=Math.floor(u(o)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)a.push(i(c)*h);a.push(i(c))}else for(a.push(i(c));c++0;h--)a.push(i(c)*h);for(c=0;a[c]l;s--);a=a.slice(c,s)}return a},a.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=oa.format(e));var r=Math.max(1,t*n/a.ticks().length);return function(n){var a=n/i(Math.round(u(n)));return t-.5>a*t&&(a*=t),r>=a?e(n):""}},a.copy=function(){return ri(n.copy(),t,e,r)},Ju(a,n)}function ui(n,t,e){function r(t){return n(u(t))}var u=ii(t),i=ii(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Qu(e,n)},r.tickFormat=function(n,t){return ni(e,n,t)},r.nice=function(n){return r.domain(Gu(e,n))},r.exponent=function(a){return arguments.length?(u=ii(t=a),i=ii(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ui(n.copy(),t,e)},Ju(r,n)}function ii(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ai(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):NaN))-1)%i.length]}function r(t,e){return oa.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,a=-1,o=r.length;++ae?[NaN,NaN]:[e>0?o[e-1]:n[0],et?NaN:t/i+n,[t,t+1/i]},r.copy=function(){return li(n,t,e)},u()}function ci(n,t){function e(e){return e>=e?t[oa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return ci(n,t)},e}function si(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qu(n,t)},t.tickFormat=function(t,e){return ni(n,t,e)},t.copy=function(){return si(n)},t}function fi(){return 0}function hi(n){return n.innerRadius}function gi(n){return n.outerRadius}function pi(n){return n.startAngle}function vi(n){return n.endAngle}function di(n){return n&&n.padAngle}function mi(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yi(n,t,e,r,u){var i=n[0]-t[0],a=n[1]-t[1],o=(u?r:-r)/Math.sqrt(i*i+a*a),l=o*a,c=-o*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,N=_-p,E=w-v,A=S-p,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mi(n){function t(t){function a(){c.push("M",i(n(s),o))}for(var l,c=[],s=[],f=-1,h=t.length,g=En(e),p=En(r);++f1?n.join("L"):n+"Z"}function bi(n){return n.join("L")+"Z"}function _i(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wi(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){o=t[1],i=n[l],l++,r+="C"+(u[0]+a[0])+","+(u[1]+a[1])+","+(i[0]-o[0])+","+(i[1]-o[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),a[o]=u*e,a[o+1]=u*r));for(o=-1;++o<=l;)u=(n[Math.min(l,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),i.push([u||0,a[o]*u||0]);return i}function Fi(n){return n.length<3?xi(n):n[0]+Ai(n,ji(n))}function Hi(n){for(var t,e,r,u=-1,i=n.length;++u=t?a(n-t):void(s.c=a)}function a(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=NaN,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var a in p)if(r>+a){var c=p[a];c.timer.c=null,c.timer.t=NaN,--p.count,delete p[a]}s.c=o,qn(function(){return s.c&&o(e||1)&&(s.c=null,s.t=NaN),1},0,l),p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration}function o(u){for(var i=u/f,a=h(i),o=g.length;o>0;)g[--o].call(n,a);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=qn(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=oa.bisect(Kl,u);return i==Kl.length?[t.year,Ku(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Kl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Ju(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function aa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var oa={version:"3.5.16"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}oa.ascending=e,oa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},oa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},oa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},oa.extent=function(n,t){var e,r,u,i=-1,a=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},oa.sum=function(n,t){var e,r=0,i=n.length,a=-1;if(1===arguments.length)for(;++a1?l/(s-1):void 0},oa.deviation=function(){var n=oa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);oa.bisectLeft=ya.left,oa.bisect=oa.bisectRight=ya.right,oa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},oa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},oa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},oa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},oa.transpose=function(n){if(!(u=n.length))return[];for(var t=-1,e=oa.min(n,a),r=new Array(e);++t=0;)for(r=n[u],t=r.length;--t>=0;)e[--a]=r[t];return e};var Ma=Math.abs;oa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=o(Ma(e)),a=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++a)>t;)u.push(r/i);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var l,s,f,h,g=-1,p=a.length,v=i[o++],d=new c;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),za.hasOwnProperty(e)?{space:za[e],local:n}:n}},Aa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=oa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Aa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Aa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Aa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Aa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Aa.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Aa.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Aa.remove=function(){return this.each(F)},Aa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new c,y=new Array(a);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,o.push(p),l.push(g),s.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return E(u)},Aa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Aa.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Aa.size=function(){var n=0;return Y(this,function(){++n}),n};var La=[];oa.selection.enter=Z,oa.selection.enter.prototype=La,La.append=Aa.append,La.empty=Aa.empty,La.node=Aa.node,La.call=Aa.call,La.size=Aa.size,La.select=function(n){for(var t,e,r,u,i,a=[],o=-1,l=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var qa=oa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&qa.forEach(function(n){"on"+n in sa&&qa.remove(n)});var Ta,Ra=0;oa.mouse=function(n){return J(n,k())};var Da=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;oa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},oa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",a)}function e(n,t,e,i,a){return function(){function o(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(a+d,null),y(p),g({type:"dragend"}))}var c,s=this,f=oa.event.target.correspondingElement||oa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=oa.select(e(f)).on(i+d,o).on(a+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),u=null,i=e(b,oa.mouse,t,"mousemove","mouseup"),a=e(G,oa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},oa.rebind(n,r,"on")},oa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Pa=1e-6,Ua=Pa*Pa,ja=Math.PI,Fa=2*ja,Ha=Fa-Pa,Oa=ja/2,Ia=ja/180,Ya=180/ja,Za=Math.SQRT2,Va=2,Xa=4;oa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],a=n[2],o=t[0],l=t[1],c=t[2],s=o-u,f=l-i,h=s*s+f*f;if(Ua>h)r=Math.log(c/a)/Za,e=function(n){return[u+n*s,i+n*f,a*Math.exp(Za*n*r)]};else{var g=Math.sqrt(h),p=(c*c-a*a+Xa*h)/(2*a*Va*g),v=(c*c-a*a-Xa*h)/(2*c*Va*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Za,e=function(n){var t=n*r,e=rn(d),o=a/(Va*g)*(e*un(Za*t+d)-en(d));return[u+o*s,i+o*f,a*e/rn(Za*t+d)]}}return e.duration=1e3*r,e},oa.behavior.zoom=function(){function n(n){n.on(L,f).on(Ba+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(A[0],Math.min(A[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function a(t,e,r,a){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,a)),i(d=e,r),t=oa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function o(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){o=1,i(oa.mouse(u),h),c(a)}function r(){f.on(q,null).on(T,null),g(o),s(a)}var u=this,a=D.of(u,arguments),o=0,f=oa.select(t(u)).on(q,n).on(T,r),h=e(oa.mouse(u)),g=W(u);Il.call(u),l(a)}function h(){function n(){var n=oa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=oa.event.target;oa.select(t).on(x,r).on(b,o),_.push(t);for(var e=oa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];a(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,a=oa.touches(p);Il.call(p);for(var o=0,l=a.length;l>o;++o,r=null)if(e=a[o],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function o(){if(oa.event.touches.length){for(var t=oa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}oa.selectAll(_).on(y,null),w.on(L,f).on(R,h),N(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+oa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=oa.select(p),N=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Il.call(this),v=e(d=m||oa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*$a())*k.k),i(d,v),c(n)}function p(){var n=oa.mouse(this),t=Math.log(k.k)/Math.LN2;a(this,n,e(n),oa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Wa,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Ba||(Ba="onwheel"in sa?($a=function(){return-oa.event.deltaY*(oa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?($a=function(){return oa.event.wheelDelta},"mousewheel"):($a=function(){return-oa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?oa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=d?d[0]:e/2,i=d?d[1]:r/2,a=oa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=a(t),o=e/r[2];this.__chart__=k={x:u-r[0]*o,y:i-r[1]*o,k:o},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Wa:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},oa.rebind(n,D,"on")};var $a,Ba,Wa=[0,1/0];oa.color=on,on.prototype.toString=function(){return this.rgb()+""},oa.hsl=ln;var Ja=ln.prototype=new on;Ja.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Ja.rgb=function(){return cn(this.h,this.s,this.l)},oa.hcl=sn;var Ga=sn.prototype=new on;Ga.brighter=function(n){return new sn(this.h,this.c,Math.min(100,this.l+Ka*(arguments.length?n:1)))},Ga.darker=function(n){return new sn(this.h,this.c,Math.max(0,this.l-Ka*(arguments.length?n:1)))},Ga.rgb=function(){return fn(this.h,this.c,this.l).rgb()},oa.lab=hn;var Ka=18,Qa=.95047,no=1,to=1.08883,eo=hn.prototype=new on;eo.brighter=function(n){return new hn(Math.min(100,this.l+Ka*(arguments.length?n:1)),this.a,this.b)},eo.darker=function(n){return new hn(Math.max(0,this.l-Ka*(arguments.length?n:1)),this.a,this.b)},eo.rgb=function(){return gn(this.l,this.a,this.b)},oa.rgb=yn;var ro=yn.prototype=new on;ro.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new yn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new yn(u,u,u)},ro.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new yn(n*this.r,n*this.g,n*this.b)},ro.hsl=function(){return wn(this.r,this.g,this.b)},ro.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var uo=oa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});uo.forEach(function(n,t){uo.set(n,Mn(t))}),oa.functor=En,oa.xhr=An(y),oa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var a=Cn(n,t,null==e?r:u(e),i);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:u(n)):e},a}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(a).join(n)}function a(n){return o.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var o=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.slice(t,s-o)}return n.slice(t)}for(var r,u,i={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==i&&r!==a;)h.push(r),r=e();t&&null==(h=t(h,f++))||o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},oa.csv=oa.dsv(",","text/csv"),oa.tsv=oa.dsv(" ","text/tab-separated-values");var io,ao,oo,lo,co=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};oa.timer=function(){qn.apply(this,arguments)},oa.timer.flush=function(){Rn(),Dn()},oa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var so=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);oa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=oa.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),so[8+e/3]};var fo=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,ho=oa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=oa.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),go=oa.time={},po=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){vo.setUTCDate.apply(this._,arguments)},setDay:function(){vo.setUTCDay.apply(this._,arguments)},setFullYear:function(){vo.setUTCFullYear.apply(this._,arguments)},setHours:function(){vo.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){vo.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){vo.setUTCMinutes.apply(this._,arguments)},setMonth:function(){vo.setUTCMonth.apply(this._,arguments)},setSeconds:function(){vo.setUTCSeconds.apply(this._,arguments)},setTime:function(){vo.setTime.apply(this._,arguments)}};var vo=Date.prototype;go.year=On(function(n){return n=go.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),go.years=go.year.range,go.years.utc=go.year.utc.range,go.day=On(function(n){var t=new po(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),go.days=go.day.range,go.days.utc=go.day.utc.range,go.dayOfYear=function(n){var t=go.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=go[n]=On(function(n){return(n=go.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=go.year(n).getDay();return Math.floor((go.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});go[n+"s"]=e.range,go[n+"s"].utc=e.utc.range,go[n+"OfYear"]=function(n){var e=go.year(n).getDay();return Math.floor((go.dayOfYear(n)+(e+t)%7)/7)}}),go.week=go.sunday,go.weeks=go.sunday.range,go.weeks.utc=go.sunday.utc.range,go.weekOfYear=go.sundayOfYear;var mo={"-":"",_:" ",0:"0"},yo=/^\s*\d+/,Mo=/^%/;oa.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xo=oa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], -shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});oa.format=xo.numberFormat,oa.geo={},st.prototype={s:0,t:0,add:function(n){ft(n,this.t,bo),ft(bo.s,this.s,this),this.s?this.t+=bo.t:this.s=bo.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var bo=new st;oa.geo.stream=function(n,t){n&&_o.hasOwnProperty(n.type)?_o[n.type](n,t):ht(n,t)};var _o={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*ja+n:n,No.lineStart=No.lineEnd=No.point=b}};oa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=dt([t*Ia,e*Ia]);if(m){var u=yt(m,r),i=[u[1],-u[0],0],a=yt(i,u);bt(a),a=_t(a);var l=t-p,c=l>0?1:-1,v=a[0]*Ya*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=a[1]*Ya;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-a[1]*Ya;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;No.point(n,e),t(n,e)}function i(){No.lineStart()}function a(){u(v,d),No.lineEnd(),Ma(y)>Pa&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function o(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nko?(s=-(h=180),f=-(g=90)):y>Pa?g=90:-Pa>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],oa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(o(u[0],e[1])>o(u[0],u[1])&&(u[1]=e[1]),o(e[0],u[1])>o(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var a,e,p=-(1/0),t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(a=o(u[1],e[0]))>p&&(p=a,s=e[0],h=u[1])}return M=x=null,s===1/0||f===1/0?[[NaN,NaN],[NaN,NaN]]:[[s,f],[h,g]]}}(),oa.geo.centroid=function(n){Eo=Ao=Co=zo=Lo=qo=To=Ro=Do=Po=Uo=0,oa.geo.stream(n,jo);var t=Do,e=Po,r=Uo,u=t*t+e*e+r*r;return Ua>u&&(t=qo,e=To,r=Ro,Pa>Ao&&(t=Co,e=zo,r=Lo),u=t*t+e*e+r*r,Ua>u)?[NaN,NaN]:[Math.atan2(e,t)*Ya,tn(r/Math.sqrt(u))*Ya]};var Eo,Ao,Co,zo,Lo,qo,To,Ro,Do,Po,Uo,jo={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){jo.lineStart=At},polygonEnd:function(){jo.lineStart=Nt}},Fo=Rt(zt,jt,Ht,[-ja,-ja/2]),Ho=1e9;oa.geo.clipExtent=function(){var n,t,e,r,u,i,a={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(o){return arguments.length?(i=Zt(n=+o[0][0],t=+o[0][1],e=+o[1][0],r=+o[1][1]),u&&(u.valid=!1,u=null),a):[[n,t],[e,r]]}};return a.extent([[0,0],[960,500]])},(oa.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,oa.geo.albers=function(){return oa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},oa.geo.albersUsa=function(){function n(n){var i=n[0],a=n[1];return t=null,e(i,a),t||(r(i,a),t)||u(i,a),t}var t,e,r,u,i=oa.geo.albers(),a=oa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=oa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?a:u>=.166&&.234>u&&r>=-.214&&-.115>r?o:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),a.precision(t),o.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),a.scale(.35*t),o.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=a.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Pa,f+.12*c+Pa],[s-.214*c-Pa,f+.234*c-Pa]]).stream(l).point,u=o.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Pa,f+.166*c+Pa],[s-.115*c-Pa,f+.234*c-Pa]]).stream(l).point,n},n.scale(1070)};var Oo,Io,Yo,Zo,Vo,Xo,$o={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Io=0,$o.lineStart=$t},polygonEnd:function(){$o.lineStart=$o.lineEnd=$o.point=b,Oo+=Ma(Io/2)}},Bo={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wo={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wo.lineStart=ne},polygonEnd:function(){Wo.point=Gt,Wo.lineStart=Kt,Wo.lineEnd=Qt}};oa.geo.path=function(){function n(n){return n&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=u(i)),oa.geo.stream(n,a)),i.result()}function t(){return a=null,n}var e,r,u,i,a,o=4.5;return n.area=function(n){return Oo=0,oa.geo.stream(n,u($o)),Oo},n.centroid=function(n){return Co=zo=Lo=qo=To=Ro=Do=Po=Uo=0,oa.geo.stream(n,u(Wo)),Uo?[Do/Uo,Po/Uo]:Ro?[qo/Ro,To/Ro]:Lo?[Co/Lo,zo/Lo]:[NaN,NaN]},n.bounds=function(n){return Vo=Xo=-(Yo=Zo=1/0),oa.geo.stream(n,u(Bo)),[[Yo,Zo],[Vo,Xo]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||re(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Wt:new te(n),"function"!=typeof o&&i.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),n):o},n.projection(oa.geo.albersUsa()).context(null)},oa.geo.transform=function(n){return{stream:function(t){var e=new ue(t);for(var r in n)e[r]=n[r];return e}}},ue.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},oa.geo.projection=ae,oa.geo.projectionMutator=oe,(oa.geo.equirectangular=function(){return ae(ce)}).raw=ce.invert=ce,oa.geo.rotation=function(n){function t(t){return t=n(t[0]*Ia,t[1]*Ia),t[0]*=Ya,t[1]*=Ya,t}return n=fe(n[0]%360*Ia,n[1]*Ia,n.length>2?n[2]*Ia:0),t.invert=function(t){return t=n.invert(t[0]*Ia,t[1]*Ia),t[0]*=Ya,t[1]*=Ya,t},t},se.invert=ce,oa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=fe(-n[0]*Ia,-n[1]*Ia,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ya,n[1]*=Ya}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Ia,u*Ia),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Ia,(u=+r)*Ia),n):u},n.angle(90)},oa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Ia,u=n[1]*Ia,i=t[1]*Ia,a=Math.sin(r),o=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*a)*e+(e=c*s-l*f*o)*e),l*s+c*f*o)},oa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return oa.range(Math.ceil(i/d)*d,u,d).map(h).concat(oa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(oa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Pa}).map(s)).concat(oa.range(Math.ceil(o/v)*v,a,v).filter(function(n){return Ma(n%m)>Pa}).map(f))}var e,r,u,i,a,o,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=me(o,a,90),f=ye(r,e,y),h=me(c,l,90),g=ye(i,u,y),n):y},n.majorExtent([[-180,-90+Pa],[180,90-Pa]]).minorExtent([[-180,-80-Pa],[180,80+Pa]])},oa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Me,u=xe;return n.distance=function(){return oa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},oa.geo.interpolate=function(n,t){return be(n[0]*Ia,n[1]*Ia,t[0]*Ia,t[1]*Ia)},oa.geo.length=function(n){return Jo=0,oa.geo.stream(n,Go),Jo};var Jo,Go={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ko=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(oa.geo.azimuthalEqualArea=function(){return ae(Ko)}).raw=Ko;var Qo=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(oa.geo.azimuthalEquidistant=function(){return ae(Qo)}).raw=Qo,(oa.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(oa.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(oa.geo.gnomonic=function(){return ae(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Oa]},(oa.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(oa.geo.orthographic=function(){return ae(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(oa.geo.stereographic=function(){return ae(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Oa]},(oa.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,oa.geom={},oa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=En(e),i=En(r),a=n.length,o=[],l=[];for(t=0;a>t;t++)o.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(o.sort(qe),t=0;a>t;t++)l.push([o[t][0],-o[t][1]]);var c=Le(o),s=Le(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[o[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=a?[[r,a],[i,a],[i,u],[r,u]]:[];s.point=n[o]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Pa)*Pa,y:Math.round(a(n,t)/Pa)*Pa,i:t}})}var r=Ce,u=ze,i=r,a=u,o=fl;return n?t(n):(t.links=function(n){return or(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return or(e(n)).cells.forEach(function(e,r){for(var u,i,a=e.site,o=e.edges.sort(Ve),l=-1,c=o.length,s=o[c-1].edge,f=s.l===a?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=hr()),f?u=c:o=c,h?a=s:l=s,i(n,t,e,r,u,a,o,l)}var s,f,h,g,p,v,d,m,y,M=En(o),x=En(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=hr();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){gr(n,k,v,d,m,y)},k.find=function(n){return pr(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||pl,r=dl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=Ur,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:ml)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var ml={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++eo*o/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=a*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=oa.event.x,n.py=oa.event.y,l.resume()}var e,r,u,i,a,o,l={},c=oa.dispatch("start","tick","end"),s=[1,1],f=.9,h=yl,g=Ml,p=-30,v=xl,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*a[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ru(t=oa.geom.quadtree(M),u,o),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=qn(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var a,o=e[t],l=-1,s=o.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var yl=20,Ml=1,xl=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=bl,r=0,u=Fa,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var bl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var _l=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),wl=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&o<=s[1]&&(a=l[oa.bisect(f,o,1,g)-1],a.y+=p,a.push(n[i]));return l}var t=!0,e=Number,r=bu,u=Mu;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return xu(n,t)}:En(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},oa.layout.pack=function(){function n(n,i){var a=e.call(this,n,i),o=a[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,au(o,function(n){n.r=+s(n.value)}),au(o,Nu),r){var f=r*(t?1:Math.max(2*o.r/l,2*o.r/c))/2;au(o,function(n){n.r+=f}),au(o,Nu),au(o,function(n){n.r-=f})}return Cu(o,l/2,c/2,t?1:1/Math.max(2*o.r/l,2*o.r/c)),a}var t,e=oa.layout.hierarchy().sort(_u),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},uu(n,e)},oa.layout.tree=function(){function n(n,u){var s=a.call(this,n,u),f=s[0],h=t(f);if(au(h,e),h.parent.m=-h.z,iu(h,r),c)iu(f,i);else{var g=f,p=f,v=f;iu(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=o(g,p)/2-g.x,m=l[0]/(p.x+o(p,g)/2+d),y=l[1]/(v.depth||1);iu(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,a=0,o=i.length;o>a;++a)r.push((i[a]=u={_:i[a],parent:t,children:(u=i[a].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:a}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Du(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+o(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+o(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,a=t,l=u.parent.children[0],c=u.m,s=i.m,f=a.m,h=l.m;a=Tu(a),u=qu(u),a&&u;)l=qu(l),i=Tu(i),i.a=n,r=a.z+f-u.z-c+o(a._,u._),r>0&&(Ru(Pu(a,n,e),n,r),c+=r,s+=r),f+=a.m,c+=u.m,h+=l.m,s+=i.m;a&&!Tu(i)&&(i.t=a,i.m+=f-s),u&&!qu(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var a=oa.layout.hierarchy().sort(null).value(null),o=Lu,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(o=t,n):o},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},uu(n,a)},oa.layout.cluster=function(){function n(n,i){var a,o=t.call(this,n,i),l=o[0],c=0;au(l,function(n){var t=n.children;t&&t.length?(n.x=ju(t),n.y=Uu(t)):(n.x=a?c+=e(n,a):0,n.y=0,a=n)});var s=Fu(l),f=Hu(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return au(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),o}var t=oa.layout.hierarchy().sort(null).value(null),e=Lu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},uu(n,t)},oa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var a,o,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(a=h[l-1]),s.area+=a.area,"squarify"!==g||(o=r(s,v))<=p?(h.pop(),p=o):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,a=f(t),o=r.slice(),l=[];for(n(o,a.dx*a.dy/t.value),l.area=0;i=o.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?a.dx:a.dy,a,!o.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,a=-1,o=n.length;++ae&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,a=n.length,o=e.x,c=e.y,s=t?l(n.area/t):0; -if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=oa.random.normal.apply(oa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=oa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},oa.scale={};var Sl={floor:y,ceil:y};oa.scale.linear=function(){return Wu([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};oa.scale.log=function(){return ri(oa.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=oa.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};oa.scale.pow=function(){return ui(oa.scale.linear(),1,[0,1])},oa.scale.sqrt=function(){return oa.scale.pow().exponent(.5)},oa.scale.ordinal=function(){return ai([],{t:"range",a:[[]]})},oa.scale.category10=function(){return oa.scale.ordinal().range(Al)},oa.scale.category20=function(){return oa.scale.ordinal().range(Cl)},oa.scale.category20b=function(){return oa.scale.ordinal().range(zl)},oa.scale.category20c=function(){return oa.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);oa.scale.quantile=function(){return oi([],[])},oa.scale.quantize=function(){return li(0,1,[0,1])},oa.scale.threshold=function(){return ci([.5],[0,1])},oa.scale.identity=function(){return si([0,1])},oa.svg={},oa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=a.apply(this,arguments)-Oa,f=o.apply(this,arguments)-Oa,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Ha)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,N=0,E=0,A=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===ql?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(E*=-1),c&&(E=tn(d/c*Math.sin(m))),n&&(N=tn(d/n*Math.sin(m)))),c){y=c*Math.cos(s+E),M=c*Math.sin(s+E),x=c*Math.cos(f-E),b=c*Math.sin(f-E);var C=Math.abs(f-s-2*E)<=ja?0:1;if(E&&mi(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-N),w=n*Math.sin(f-N),S=n*Math.cos(s+N),k=n*Math.sin(s+N);var L=Math.abs(s-f+2*N)<=ja?0:1;if(N&&mi(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Pa&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(ja>h){var D=null==S?[_,w]:null==x?[y,M]:Re([y,M],[S,k],[x,b],[_,w]),P=y-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yi(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yi([x,b],[_,w],c,T,g);p===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mi(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",y,",",M);if(null!=S){var Z=yi([y,M],[S,k],n,-R,g),V=yi([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mi(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",y,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hi,r=gi,u=fi,i=ql,a=pi,o=vi,l=di;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=En(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==ql?ql:En(t),n):i},n.startAngle=function(t){return arguments.length?(a=En(t),n):a},n.endAngle=function(t){return arguments.length?(o=En(t),n):o},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+a.apply(this,arguments)+ +o.apply(this,arguments))/2-Oa;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";oa.svg.line=function(){return Mi(y)};var Tl=oa.map({linear:xi,"linear-closed":bi,step:_i,"step-before":wi,"step-after":Si,basis:zi,"basis-open":Li,"basis-closed":qi,bundle:Ti,cardinal:Ei,"cardinal-open":ki,"cardinal-closed":Ni,monotone:Fi});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];oa.svg.line.radial=function(){var n=Mi(Hi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wi.reverse=Si,Si.reverse=wi,oa.svg.area=function(){return Oi(y)},oa.svg.area.radial=function(){var n=Oi(Hi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},oa.svg.chord=function(){function n(n,o){var l=t(this,i,n,o),c=t(this,a,n,o);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=o.call(n,u,r),a=l.call(n,u,r)-Oa,s=c.call(n,u,r)-Oa;return{r:i,a0:a,a1:s,p0:[i*Math.cos(a),i*Math.sin(a)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>ja)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Me,a=xe,o=Ii,l=pi,c=vi;return n.radius=function(t){return arguments.length?(o=En(t),n):o},n.source=function(t){return arguments.length?(i=En(t),n):i},n.target=function(t){return arguments.length?(a=En(t),n):a},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},oa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),a=e.call(this,n,u),o=(i.y+a.y)/2,l=[i,{x:i.x,y:o},{x:a.x,y:o},a];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yi;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},oa.svg.diagonal.radial=function(){var n=oa.svg.diagonal(),t=Yi,e=n.projection;return n.projection=function(n){return arguments.length?e(Zi(t=n)):t},n},oa.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$i)(e.call(this,n,r))}var t=Xi,e=Vi;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=oa.map({circle:$i,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});oa.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Ia);Aa.transition=function(n){for(var t,e,r=Hl||++Zl,u=Ki(n),i=[],a=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},o=-1,l=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return Wi(u,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?$r:Mr,o=oa.ns.qualify(n);return Ji(this,"attr."+n,t,o.local?i:u)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=oa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Yl.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=Mr(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var a=arguments.length;if(3>a){if("string"!=typeof n){2>a&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ji(this,"style."+n,e,i)},Yl.styleTween=function(n,e,r){function u(u,i){var a=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return a&&function(t){this.style.setProperty(n,a(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Yl.text=function(n){return Ji(this,"text",n,Gi)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=oa.ease.apply(oa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Ol,i=Hl;try{Hl=e,Y(this,function(t,u,i){Ol=t[r][e],n.call(t,t.__data__,u,i)})}finally{Ol=u,Hl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=oa.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,u=this.id,i=++Zl,a=this.namespace,o=[],l=0,c=this.length;c>l;l++){o.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[a][u],Qi(e,s,a,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wi(o,a,i)},oa.svg.axis=function(){function n(n){n.each(function(){var n,c=oa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,o):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,o):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Pa),d=oa.transition(p.exit()).style("opacity",Pa).remove(),m=oa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+a,x=Zu(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),oa.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),E.attr(N,q*u),z.attr(k,q*M),A.attr(S,0).attr(N,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=oa.scale.linear(),r=Vl,u=6,i=6,a=3,o=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(o=ca(arguments),n):o},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(a=+t,n):a},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};oa.svg.brush=function(){function n(t){t.each(function(){var t=oa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),a=t.selectAll(".background").data([0]);a.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var o=t.selectAll(".resize").data(v,y);o.exit().remove(),o.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),o.style("display",n.empty()?"none":null);var l,f=oa.transition(t),h=oa.transition(a);c&&(l=Zu(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Zu(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==oa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==oa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=oa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(oa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?o=null:a=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),oa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=oa.select(oa.event.target),w=l.of(b,arguments),k=oa.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&s,C=_.classed("extent"),z=W(b),L=oa.mouse(b),q=oa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(oa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else oa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),oa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var a,o,l=N(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:a,j:o},e=this.__chart__||t;this.__chart__=t,Hl?oa.select(this).transition().each("start.brush",function(){a=e.i,o=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(f,t.x),r=xr(h,t.y);return a=o=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){a=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=Bl[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),a=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),o=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(a?(e=a[0],r=a[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(o?(u=o[0],i=o[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],a=o=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},oa.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=go.format=xo.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Gl,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Gl.toString,go.second=On(function(n){return new po(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),go.seconds=go.second.range,go.seconds.utc=go.second.utc.range,go.minute=On(function(n){return new po(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),go.minutes=go.minute.range,go.minutes.utc=go.minute.utc.range,go.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new po(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),go.hours=go.hour.range,go.hours.utc=go.hour.utc.range,go.month=On(function(n){return n=go.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),go.months=go.month.range,go.months.utc=go.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[go.second,1],[go.second,5],[go.second,15],[go.second,30],[go.minute,1],[go.minute,5],[go.minute,15],[go.minute,30],[go.hour,1],[go.hour,3],[go.hour,6],[go.hour,12],[go.day,1],[go.day,2],[go.week,1],[go.month,1],[go.month,3],[go.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return oa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Ql.year=go.year,go.scale=function(){return ra(oa.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=go.year.utc,go.scale.utc=function(){return ra(oa.scale.linear(),ec,rc)},oa.text=An(function(n){return n.responseText}),oa.json=function(n,t){return Cn(n,"application/json",ia,t)},oa.html=function(n,t){return Cn(n,"text/html",aa,t)},oa.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=oa,define(oa)):"object"==typeof module&&module.exports?module.exports=oa:this.d3=oa}(); - -// Match width of graphs with summary bar -var responsiveWidth = function() { - return document.getElementsByClassName('summary_bar')[0].clientWidth - 30; -}; - -// Rickshaw 1.6.0 -(function(root,factory){if(typeof define==="function"&&define.amd){define(["d3"],function(d3){return root.Rickshaw=factory(d3)})}else if(typeof exports==="object"){module.exports=factory(require("d3"))}else{root.Rickshaw=factory(d3)}})(this,function(d3){var Rickshaw={namespace:function(namespace,obj){var parts=namespace.split(".");var parent=Rickshaw;for(var i=1,length=parts.length;i0){var x=s.data[0].x;var y=s.data[0].y;if(typeof x!="number"||typeof y!="number"&&y!==null){throw"x and y properties of points should be numbers instead of "+typeof x+" and "+typeof y}}if(s.data.length>=3){if(s.data[2].xthis.window.xMax)isInRange=false;return isInRange}return true};this.onUpdate=function(callback){this.updateCallbacks.push(callback)};this.onConfigure=function(callback){this.configureCallbacks.push(callback)};this.registerRenderer=function(renderer){this._renderers=this._renderers||{};this._renderers[renderer.name]=renderer};this.configure=function(args){this.config=this.config||{};if(args.width||args.height){this.setSize(args)}Rickshaw.keys(this.defaults).forEach(function(k){this.config[k]=k in args?args[k]:k in this?this[k]:this.defaults[k]},this);Rickshaw.keys(this.config).forEach(function(k){this[k]=this.config[k]},this);if("stack"in args)args.unstack=!args.stack;var renderer=args.renderer||this.renderer&&this.renderer.name||"stack";this.setRenderer(renderer,args);this.configureCallbacks.forEach(function(callback){callback(args)})};this.setRenderer=function(r,args){if(typeof r=="function"){this.renderer=new r({graph:self});this.registerRenderer(this.renderer)}else{if(!this._renderers[r]){throw"couldn't find renderer "+r}this.renderer=this._renderers[r]}if(typeof args=="object"){this.renderer.configure(args)}};this.setSize=function(args){args=args||{};if(typeof window!=="undefined"){var style=window.getComputedStyle(this.element,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);var elementHeight=parseInt(style.getPropertyValue("height"),10)}this.width=args.width||elementWidth||400;this.height=args.height||elementHeight||250;this.vis&&this.vis.attr("width",this.width).attr("height",this.height)};this.initialize(args)};Rickshaw.namespace("Rickshaw.Fixtures.Color");Rickshaw.Fixtures.Color=function(){this.schemes={};this.schemes.spectrum14=["#ecb796","#dc8f70","#b2a470","#92875a","#716c49","#d2ed82","#bbe468","#a1d05d","#e7cbe6","#d8aad6","#a888c2","#9dc2d3","#649eb9","#387aa3"].reverse();this.schemes.spectrum2000=["#57306f","#514c76","#646583","#738394","#6b9c7d","#84b665","#a7ca50","#bfe746","#e2f528","#fff726","#ecdd00","#d4b11d","#de8800","#de4800","#c91515","#9a0000","#7b0429","#580839","#31082b"];this.schemes.spectrum2001=["#2f243f","#3c2c55","#4a3768","#565270","#6b6b7c","#72957f","#86ad6e","#a1bc5e","#b8d954","#d3e04e","#ccad2a","#cc8412","#c1521d","#ad3821","#8a1010","#681717","#531e1e","#3d1818","#320a1b"];this.schemes.classic9=["#423d4f","#4a6860","#848f39","#a2b73c","#ddcb53","#c5a32f","#7d5836","#963b20","#7c2626","#491d37","#2f254a"].reverse();this.schemes.httpStatus={503:"#ea5029",502:"#d23f14",500:"#bf3613",410:"#efacea",409:"#e291dc",403:"#f457e8",408:"#e121d2",401:"#b92dae",405:"#f47ceb",404:"#a82a9f",400:"#b263c6",301:"#6fa024",302:"#87c32b",307:"#a0d84c",304:"#28b55c",200:"#1a4f74",206:"#27839f",201:"#52adc9",202:"#7c979f",203:"#a5b8bd",204:"#c1cdd1"};this.schemes.colorwheel=["#b5b6a9","#858772","#785f43","#96557e","#4682b4","#65b9ac","#73c03a","#cb513a"].reverse();this.schemes.cool=["#5e9d2f","#73c03a","#4682b4","#7bc3b8","#a9884e","#c1b266","#a47493","#c09fb5"];this.schemes.munin=["#00cc00","#0066b3","#ff8000","#ffcc00","#330099","#990099","#ccff00","#ff0000","#808080","#008f00","#00487d","#b35a00","#b38f00","#6b006b","#8fb300","#b30000","#bebebe","#80ff80","#80c9ff","#ffc080","#ffe680","#aa80ff","#ee00cc","#ff8080","#666600","#ffbfff","#00ffcc","#cc6699","#999900"]};Rickshaw.namespace("Rickshaw.Fixtures.RandomData");Rickshaw.Fixtures.RandomData=function(timeInterval){var addData;timeInterval=timeInterval||1;var lastRandomValue=200;var timeBase=Math.floor((new Date).getTime()/1e3);this.addData=function(data){var randomValue=Math.random()*100+15+lastRandomValue;var index=data[0].length;var counter=1;data.forEach(function(series){var randomVariance=Math.random()*20;var v=randomValue/25+counter++ +(Math.cos(index*counter*11/960)+2)*15+(Math.cos(index/7)+2)*7+(Math.cos(index/17)+2)*1;series.push({x:index*timeInterval+timeBase,y:v+randomVariance})});lastRandomValue=randomValue*.85};this.removeData=function(data){data.forEach(function(series){series.shift()});timeBase+=timeInterval}};Rickshaw.namespace("Rickshaw.Fixtures.Time");Rickshaw.Fixtures.Time=function(){var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getUTCFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getUTCFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getUTCMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getUTCDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getUTCMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getUTCSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getUTCMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getUTCMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toUTCString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var date,floor,year;if(unit.name=="month"){date=new Date(time*1e3);floor=Date.UTC(date.getUTCFullYear(),date.getUTCMonth())/1e3;if(floor==time)return time;year=date.getUTCFullYear();var month=date.getUTCMonth();if(month==11){month=0;year=year+1}else{month+=1}return Date.UTC(year,month)/1e3}if(unit.name=="year"){date=new Date(time*1e3);floor=Date.UTC(date.getUTCFullYear(),0)/1e3;if(floor==time)return time;year=date.getUTCFullYear()+1;return Date.UTC(year,0)/1e3}return Math.ceil(time/unit.seconds)*unit.seconds}};Rickshaw.namespace("Rickshaw.Fixtures.Time.Local");Rickshaw.Fixtures.Time.Local=function(){var self=this;this.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];this.units=[{name:"decade",seconds:86400*365.25*10,formatter:function(d){return parseInt(d.getFullYear()/10,10)*10}},{name:"year",seconds:86400*365.25,formatter:function(d){return d.getFullYear()}},{name:"month",seconds:86400*30.5,formatter:function(d){return self.months[d.getMonth()]}},{name:"week",seconds:86400*7,formatter:function(d){return self.formatDate(d)}},{name:"day",seconds:86400,formatter:function(d){return d.getDate()}},{name:"6 hour",seconds:3600*6,formatter:function(d){return self.formatTime(d)}},{name:"hour",seconds:3600,formatter:function(d){return self.formatTime(d)}},{name:"15 minute",seconds:60*15,formatter:function(d){return self.formatTime(d)}},{name:"minute",seconds:60,formatter:function(d){return d.getMinutes()}},{name:"15 second",seconds:15,formatter:function(d){return d.getSeconds()+"s"}},{name:"second",seconds:1,formatter:function(d){return d.getSeconds()+"s"}},{name:"decisecond",seconds:1/10,formatter:function(d){return d.getMilliseconds()+"ms"}},{name:"centisecond",seconds:1/100,formatter:function(d){return d.getMilliseconds()+"ms"}}];this.unit=function(unitName){return this.units.filter(function(unit){return unitName==unit.name}).shift()};this.formatDate=function(d){return d3.time.format("%b %e")(d)};this.formatTime=function(d){return d.toString().match(/(\d+:\d+):/)[1]};this.ceil=function(time,unit){var date,floor,year,offset;if(unit.name=="day"){var nearFuture=new Date((time+unit.seconds-1)*1e3);var rounded=new Date(0);rounded.setFullYear(nearFuture.getFullYear());rounded.setMonth(nearFuture.getMonth());rounded.setDate(nearFuture.getDate());rounded.setMilliseconds(0);rounded.setSeconds(0);rounded.setMinutes(0);rounded.setHours(0);return rounded.getTime()/1e3}if(unit.name=="month"){date=new Date(time*1e3);floor=new Date(date.getFullYear(),date.getMonth()).getTime()/1e3;if(floor==time)return time;year=date.getFullYear();var month=date.getMonth();if(month==11){month=0;year=year+1}else{month+=1}return new Date(year,month).getTime()/1e3}if(unit.name=="year"){date=new Date(time*1e3);floor=new Date(date.getUTCFullYear(),0).getTime()/1e3;if(floor==time)return time;year=date.getFullYear()+1;return new Date(year,0).getTime()/1e3}offset=new Date(time*1e3).getTimezoneOffset()*60;return Math.ceil((time-offset)/unit.seconds)*unit.seconds+offset}};Rickshaw.namespace("Rickshaw.Fixtures.Number");Rickshaw.Fixtures.Number.formatKMBT=function(y){var abs_y=Math.abs(y);if(abs_y>=1e12){return y/1e12+"T"}else if(abs_y>=1e9){return y/1e9+"B"}else if(abs_y>=1e6){return y/1e6+"M"}else if(abs_y>=1e3){return y/1e3+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.Fixtures.Number.formatBase1024KMGTP=function(y){var abs_y=Math.abs(y);if(abs_y>=0x4000000000000){return y/0x4000000000000+"P"}else if(abs_y>=1099511627776){return y/1099511627776+"T"}else if(abs_y>=1073741824){return y/1073741824+"G"}else if(abs_y>=1048576){return y/1048576+"M"}else if(abs_y>=1024){return y/1024+"K"}else if(abs_y<1&&y>0){return y.toFixed(2)}else if(abs_y===0){return""}else{return y}};Rickshaw.namespace("Rickshaw.Color.Palette");Rickshaw.Color.Palette=function(args){var color=new Rickshaw.Fixtures.Color;args=args||{};this.schemes={};this.scheme=color.schemes[args.scheme]||args.scheme||color.schemes.colorwheel;this.runningIndex=0;this.generatorIndex=0;if(args.interpolatedStopCount){var schemeCount=this.scheme.length-1;var i,j,scheme=[];for(i=0;iself.graph.x.range()[1]){if(annotation.element){annotation.line.classList.add("offscreen");annotation.element.style.display="none"}annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.add("offscreen")});return}if(!annotation.element){var element=annotation.element=document.createElement("div");element.classList.add("annotation");this.elements.timeline.appendChild(element);element.addEventListener("click",function(e){element.classList.toggle("active");annotation.line.classList.toggle("active");annotation.boxes.forEach(function(box){if(box.rangeElement)box.rangeElement.classList.toggle("active")})},false)}annotation.element.style.left=left+"px";annotation.element.style.display="block";annotation.boxes.forEach(function(box){var element=box.element;if(!element){element=box.element=document.createElement("div");element.classList.add("content");element.innerHTML=box.content;annotation.element.appendChild(element);annotation.line=document.createElement("div");annotation.line.classList.add("annotation_line");self.graph.element.appendChild(annotation.line);if(box.end){box.rangeElement=document.createElement("div");box.rangeElement.classList.add("annotation_range");self.graph.element.appendChild(box.rangeElement)}}if(box.end){var annotationRangeStart=left;var annotationRangeEnd=Math.min(self.graph.x(box.end),self.graph.x.range()[1]);if(annotationRangeStart>annotationRangeEnd){annotationRangeEnd=left;annotationRangeStart=Math.max(self.graph.x(box.end),self.graph.x.range()[0])}var annotationRangeWidth=annotationRangeEnd-annotationRangeStart;box.rangeElement.style.left=annotationRangeStart+"px";box.rangeElement.style.width=annotationRangeWidth+"px";box.rangeElement.classList.remove("offscreen")}annotation.line.classList.remove("offscreen");annotation.line.style.left=left+"px"})},this)};this.graph.onUpdate(function(){self.update()})};Rickshaw.namespace("Rickshaw.Graph.Axis.Time");Rickshaw.Graph.Axis.Time=function(args){var self=this;this.graph=args.graph;this.elements=[];this.ticksTreatment=args.ticksTreatment||"plain";this.fixedTimeUnit=args.timeUnit;var time=args.timeFixture||new Rickshaw.Fixtures.Time;this.appropriateTimeUnit=function(){var unit;var units=time.units;var domain=this.graph.x.domain();var rangeSeconds=domain[1]-domain[0];units.forEach(function(u){if(Math.floor(rangeSeconds/u.seconds)>=2){unit=unit||u}});return unit||time.units[time.units.length-1]};this.tickOffsets=function(){var domain=this.graph.x.domain();var unit=this.fixedTimeUnit||this.appropriateTimeUnit();var count=Math.ceil((domain[1]-domain[0])/unit.seconds);var runningTick=domain[0];var offsets=[];for(var i=0;iself.graph.x.range()[1])return;var element=document.createElement("div");element.style.left=self.graph.x(o.value)+"px";element.classList.add("x_tick");element.classList.add(self.ticksTreatment);var title=document.createElement("div");title.classList.add("title");title.innerHTML=o.unit.formatter(new Date(o.value*1e3));element.appendChild(title);self.graph.element.appendChild(element);self.elements.push(element)})};this.graph.onUpdate(function(){self.render()})};Rickshaw.namespace("Rickshaw.Graph.Axis.X");Rickshaw.Graph.Axis.X=function(args){var self=this;var berthRate=.1;this.initialize=function(args){this.graph=args.graph;this.orientation=args.orientation||"top";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";if(args.element){this.element=args.element;this._discoverSize(args.element,args);this.vis=d3.select(args.element).append("svg:svg").attr("height",this.height).attr("width",this.width).attr("class","rickshaw_graph x_axis_d3");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}this.graph.onUpdate(function(){self.render()})};this.setSize=function(args){args=args||{};if(!this.element)return;this._discoverSize(this.element.parentNode,args);this.vis.attr("height",this.height).attr("width",this.width*(1+berthRate));var berth=Math.floor(this.width*berthRate/2);this.element.style.left=-1*berth+"px"};this.render=function(){if(this._renderWidth!==undefined&&this.graph.width!==this._renderWidth)this.setSize({auto:true});var axis=d3.svg.axis().scale(this.graph.x).orient(this.orientation);axis.tickFormat(args.tickFormat||function(x){return x});if(this.tickValues)axis.tickValues(this.tickValues);this.ticks=this.staticTicks||Math.floor(this.graph.width/this.pixelsPerTick);var berth=Math.floor(this.width*berthRate/2)||0;var bar_offset=this.graph.renderer.name=="bar"&&Math.ceil(this.graph.width*.95/this.graph.series[0].data.length/2)||0;var transform;if(this.orientation=="top"){var yOffset=this.height||this.graph.height;transform="translate("+(berth+bar_offset)+","+yOffset+")"}else{transform="translate("+(berth+bar_offset)+", 0)"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["x_ticks_d3",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));var gridSize=(this.orientation=="bottom"?1:-1)*this.graph.height;this.graph.vis.append("svg:g").attr("class","x_grid_d3").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function(){this.parentNode.setAttribute("data-x-value",this.textContent)});this._renderHeight=this.graph.height};this._discoverSize=function(element,args){if(typeof window!=="undefined"){var style=window.getComputedStyle(element,null);var elementHeight=parseInt(style.getPropertyValue("height"),10);if(!args.auto){var elementWidth=parseInt(style.getPropertyValue("width"),10)}}this.width=(args.width||elementWidth||this.graph.width)*(1+berthRate);this.height=args.height||elementHeight||40};this.initialize(args)};Rickshaw.namespace("Rickshaw.Graph.Axis.Y");Rickshaw.Graph.Axis.Y=Rickshaw.Class.create({initialize:function(args){this.graph=args.graph;this.orientation=args.orientation||"right";this.pixelsPerTick=args.pixelsPerTick||75;if(args.ticks)this.staticTicks=args.ticks;if(args.tickValues)this.tickValues=args.tickValues;this.tickSize=args.tickSize||4;this.ticksTreatment=args.ticksTreatment||"plain";this.tickFormat=args.tickFormat||function(y){return y};this.berthRate=.1;if(args.element){this.element=args.element;this.vis=d3.select(args.element).append("svg:svg").attr("class","rickshaw_graph y_axis");this.element=this.vis[0][0];this.element.style.position="relative";this.setSize({width:args.width,height:args.height})}else{this.vis=this.graph.vis}var self=this;this.graph.onUpdate(function(){self.render()})},setSize:function(args){args=args||{};if(!this.element)return;if(typeof window!=="undefined"){var style=window.getComputedStyle(this.element.parentNode,null);var elementWidth=parseInt(style.getPropertyValue("width"),10);if(!args.auto){var elementHeight=parseInt(style.getPropertyValue("height"),10)}}this.width=args.width||elementWidth||this.graph.width*this.berthRate;this.height=args.height||elementHeight||this.graph.height;this.vis.attr("width",this.width).attr("height",this.height*(1+this.berthRate));var berth=this.height*this.berthRate;if(this.orientation=="left"){this.element.style.top=-1*berth+"px"}},render:function(){if(this._renderHeight!==undefined&&this.graph.height!==this._renderHeight)this.setSize({auto:true});this.ticks=this.staticTicks||Math.floor(this.graph.height/this.pixelsPerTick);var axis=this._drawAxis(this.graph.y);this._drawGrid(axis);this._renderHeight=this.graph.height},_drawAxis:function(scale){var axis=d3.svg.axis().scale(scale).orient(this.orientation);axis.tickFormat(this.tickFormat);if(this.tickValues)axis.tickValues(this.tickValues);if(this.orientation=="left"){var berth=this.height*this.berthRate;var transform="translate("+this.width+", "+berth+")"}if(this.element){this.vis.selectAll("*").remove()}this.vis.append("svg:g").attr("class",["y_ticks",this.ticksTreatment].join(" ")).attr("transform",transform).call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize));return axis},_drawGrid:function(axis){ -var gridSize=(this.orientation=="right"?1:-1)*this.graph.width;this.graph.vis.append("svg:g").attr("class","y_grid").call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)).selectAll("text").each(function(){this.parentNode.setAttribute("data-y-value",this.textContent)})}});Rickshaw.namespace("Rickshaw.Graph.Axis.Y.Scaled");Rickshaw.Graph.Axis.Y.Scaled=Rickshaw.Class.create(Rickshaw.Graph.Axis.Y,{initialize:function($super,args){if(typeof args.scale==="undefined"){throw new Error("Scaled requires scale")}this.scale=args.scale;if(typeof args.grid==="undefined"){this.grid=true}else{this.grid=args.grid}$super(args)},_drawAxis:function($super,scale){var domain=this.scale.domain();var renderDomain=this.graph.renderer.domain().y;var extents=[Math.min.apply(Math,domain),Math.max.apply(Math,domain)];var extentMap=d3.scale.linear().domain([0,1]).range(extents);var adjExtents=[extentMap(renderDomain[0]),extentMap(renderDomain[1])];var adjustment=d3.scale.linear().domain(extents).range(adjExtents);var adjustedScale=this.scale.copy().domain(domain.map(adjustment)).range(scale.range());return $super(adjustedScale)},_drawGrid:function($super,axis){if(this.grid){$super(axis)}}});Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Highlight");Rickshaw.Graph.Behavior.Series.Highlight=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;var colorSafe={};var activeLine=null;var disabledColor=args.disabledColor||function(seriesColor){return d3.interpolateRgb(seriesColor,d3.rgb("#d8d8d8"))(.8).toString()};this.addHighlightEvents=function(l){l.element.addEventListener("mouseover",function(e){if(activeLine)return;else activeLine=l;self.legend.lines.forEach(function(line){if(l===line){if(self.graph.renderer.unstack&&(line.series.renderer?line.series.renderer.unstack:true)){var seriesIndex=self.graph.series.indexOf(line.series);line.originalIndex=seriesIndex;var series=self.graph.series.splice(seriesIndex,1)[0];self.graph.series.push(series)}return}colorSafe[line.series.name]=colorSafe[line.series.name]||line.series.color;line.series.color=disabledColor(line.series.color)});self.graph.update()},false);l.element.addEventListener("mouseout",function(e){if(!activeLine)return;else activeLine=null;self.legend.lines.forEach(function(line){if(l===line&&line.hasOwnProperty("originalIndex")){var series=self.graph.series.pop();self.graph.series.splice(line.originalIndex,0,series);delete line.originalIndex}if(colorSafe[line.series.name]){line.series.color=colorSafe[line.series.name]}});self.graph.update()},false)};if(this.legend){this.legend.lines.forEach(function(l){self.addHighlightEvents(l)})}};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Order");Rickshaw.Graph.Behavior.Series.Order=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;if(typeof window.jQuery=="undefined"){throw"couldn't find jQuery at window.jQuery"}if(typeof window.jQuery.ui=="undefined"){throw"couldn't find jQuery UI at window.jQuery.ui"}jQuery(function(){jQuery(self.legend.list).sortable({containment:"parent",tolerance:"pointer",update:function(event,ui){var series=[];jQuery(self.legend.list).find("li").each(function(index,item){if(!item.series)return;series.push(item.series)});for(var i=self.graph.series.length-1;i>=0;i--){self.graph.series[i]=series.shift()}self.graph.update()}});jQuery(self.legend.list).disableSelection()});this.graph.onUpdate(function(){var h=window.getComputedStyle(self.legend.element).height;self.legend.element.style.height=h})};Rickshaw.namespace("Rickshaw.Graph.Behavior.Series.Toggle");Rickshaw.Graph.Behavior.Series.Toggle=function(args){this.graph=args.graph;this.legend=args.legend;var self=this;this.addAnchor=function(line){var anchor=document.createElement("a");anchor.innerHTML="✔";anchor.classList.add("action");line.element.insertBefore(anchor,line.element.firstChild);anchor.onclick=function(e){if(line.series.disabled){line.series.enable();line.element.classList.remove("disabled")}else{if(this.graph.series.filter(function(s){return!s.disabled}).length<=1)return;line.series.disable();line.element.classList.add("disabled")}self.graph.update()}.bind(this);var label=line.element.getElementsByTagName("span")[0];label.onclick=function(e){var disableAllOtherLines=line.series.disabled;if(!disableAllOtherLines){for(var i=0;idomainX){dataIndex=Math.abs(domainX-data[i].x)0){alignables.forEach(function(el){el.classList.remove("left");el.classList.add("right")});var rightAlignError=this._calcLayoutError(alignables);if(rightAlignError>leftAlignError){alignables.forEach(function(el){el.classList.remove("right");el.classList.add("left")})}}if(typeof this.onRender=="function"){this.onRender(args)}},_calcLayoutError:function(alignables){var parentRect=this.element.parentNode.getBoundingClientRect();var error=0;var alignRight=alignables.forEach(function(el){var rect=el.getBoundingClientRect();if(!rect.width){return}if(rect.right>parentRect.right){error+=rect.right-parentRect.right}if(rect.left=self.previewWidth){frameAfterDrag[0]-=frameAfterDrag[1]-self.previewWidth;frameAfterDrag[1]=self.previewWidth}}self.graphs.forEach(function(graph){var domainScale=d3.scale.linear().interpolate(d3.interpolateNumber).domain([0,self.previewWidth]).range(graph.dataDomain());var windowAfterDrag=[domainScale(frameAfterDrag[0]),domainScale(frameAfterDrag[1])];self.slideCallbacks.forEach(function(callback){callback(graph,windowAfterDrag[0],windowAfterDrag[1])});if(frameAfterDrag[0]===0){windowAfterDrag[0]=undefined}if(frameAfterDrag[1]===self.previewWidth){windowAfterDrag[1]=undefined}graph.window.xMin=windowAfterDrag[0];graph.window.xMax=windowAfterDrag[1];graph.update()})}function onMousedown(){drag.target=d3.event.target;drag.start=self._getClientXFromEvent(d3.event,drag);self.frameBeforeDrag=self.currentFrame.slice();d3.event.preventDefault?d3.event.preventDefault():d3.event.returnValue=false;d3.select(document).on("mousemove.rickshaw_range_slider_preview",onMousemove);d3.select(document).on("mouseup.rickshaw_range_slider_preview",onMouseup);d3.select(document).on("touchmove.rickshaw_range_slider_preview",onMousemove);d3.select(document).on("touchend.rickshaw_range_slider_preview",onMouseup);d3.select(document).on("touchcancel.rickshaw_range_slider_preview",onMouseup)}function onMousedownLeftHandle(datum,index){drag.left=true;onMousedown()}function onMousedownRightHandle(datum,index){drag.right=true;onMousedown()}function onMousedownMiddleHandle(datum,index){drag.left=true;drag.right=true;drag.rigid=true;onMousedown()}function onMouseup(datum,index){d3.select(document).on("mousemove.rickshaw_range_slider_preview",null);d3.select(document).on("mouseup.rickshaw_range_slider_preview",null);d3.select(document).on("touchmove.rickshaw_range_slider_preview",null);d3.select(document).on("touchend.rickshaw_range_slider_preview",null);d3.select(document).on("touchcancel.rickshaw_range_slider_preview",null);delete self.frameBeforeDrag;drag.left=false;drag.right=false;drag.rigid=false}element.select("rect.left_handle").on("mousedown",onMousedownLeftHandle);element.select("rect.right_handle").on("mousedown",onMousedownRightHandle);element.select("rect.middle_handle").on("mousedown",onMousedownMiddleHandle);element.select("rect.left_handle").on("touchstart",onMousedownLeftHandle);element.select("rect.right_handle").on("touchstart",onMousedownRightHandle);element.select("rect.middle_handle").on("touchstart",onMousedownMiddleHandle)},_getClientXFromEvent:function(event,drag){switch(event.type){case"touchstart":case"touchmove":var touchList=event.changedTouches;var touch=null;for(var touchIndex=0;touchIndexyMax)yMax=y});if(!series.length)return;if(series[0].xxMax)xMax=series[series.length-1].x});xMin-=(xMax-xMin)*this.padding.left;xMax+=(xMax-xMin)*this.padding.right;yMin=this.graph.min==="auto"?yMin:this.graph.min||0;yMax=this.graph.max===undefined?yMax:this.graph.max;if(this.graph.min==="auto"||yMin<0){yMin-=(yMax-yMin)*this.padding.bottom}if(this.graph.max===undefined){yMax+=(yMax-yMin)*this.padding.top}return{x:[xMin,xMax],y:[yMin,yMax]}},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var data=series.filter(function(s){return!s.disabled}).map(function(s){return s.stack});var pathNodes=vis.selectAll("path.path").data(data).enter().append("svg:path").classed("path",true).attr("d",this.seriesPathFactory());if(this.stroke){var strokeNodes=vis.selectAll("path.stroke").data(data).enter().append("svg:path").classed("stroke",true).attr("d",this.seriesStrokeFactory())}var i=0;series.forEach(function(series){if(series.disabled)return;series.path=pathNodes[0][i];if(this.stroke)series.stroke=strokeNodes[0][i];this._styleSeries(series);i++},this)},_styleSeries:function(series){var fill=this.fill?series.color:"none";var stroke=this.stroke?series.color:"none";var strokeWidth=series.strokeWidth?series.strokeWidth:this.strokeWidth;var opacity=series.opacity?series.opacity:this.opacity;series.path.setAttribute("fill",fill);series.path.setAttribute("stroke",stroke);series.path.setAttribute("stroke-width",strokeWidth);series.path.setAttribute("opacity",opacity);if(series.className){d3.select(series.path).classed(series.className,true)}if(series.className&&this.stroke){d3.select(series.stroke).classed(series.className,true)}},configure:function(args){args=args||{};Rickshaw.keys(this.defaults()).forEach(function(key){if(!args.hasOwnProperty(key)){this[key]=this[key]||this.graph[key]||this.defaults()[key];return}if(typeof this.defaults()[key]=="object"){Rickshaw.keys(this.defaults()[key]).forEach(function(k){this[key][k]=args[key][k]!==undefined?args[key][k]:this[key][k]!==undefined?this[key][k]:this.defaults()[key][k]},this)}else{this[key]=args[key]!==undefined?args[key]:this[key]!==undefined?this[key]:this.graph[key]!==undefined?this.graph[key]:this.defaults()[key]}},this)},setStrokeWidth:function(strokeWidth){if(strokeWidth!==undefined){this.strokeWidth=strokeWidth}},setTension:function(tension){if(tension!==undefined){this.tension=tension}}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Line");Rickshaw.Graph.Renderer.Line=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"line",defaults:function($super){return Rickshaw.extend($super(),{unstack:true,fill:false,stroke:true})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.line().x(function(d){return graph.x(d.x)}).y(function(d){return graph.y(d.y)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Stack");Rickshaw.Graph.Renderer.Stack=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"stack",defaults:function($super){return Rickshaw.extend($super(),{fill:true,stroke:false,unstack:false})},seriesPathFactory:function(){var graph=this.graph;var factory=d3.svg.area().x(function(d){return graph.x(d.x)}).y0(function(d){return graph.y(d.y0)}).y1(function(d){return graph.y(d.y+d.y0)}).interpolate(this.graph.interpolation).tension(this.tension);factory.defined&&factory.defined(function(d){return d.y!==null});return factory}});Rickshaw.namespace("Rickshaw.Graph.Renderer.Bar");Rickshaw.Graph.Renderer.Bar=Rickshaw.Class.create(Rickshaw.Graph.Renderer,{name:"bar",defaults:function($super){var defaults=Rickshaw.extend($super(),{gapSize:.05,unstack:false,opacity:1});delete defaults.tension;return defaults},initialize:function($super,args){args=args||{};this.gapSize=args.gapSize||this.gapSize;$super(args)},domain:function($super){var domain=$super();var frequentInterval=this._frequentInterval(this.graph.stackedData.slice(-1).shift());domain.x[1]+=Number(frequentInterval.magnitude);return domain},barWidth:function(series){var frequentInterval=this._frequentInterval(series.stack);var barWidth=this.graph.x.magnitude(frequentInterval.magnitude)*(1-this.gapSize);return barWidth},render:function(args){args=args||{};var graph=this.graph;var series=args.series||graph.series;var vis=args.vis||graph.vis;vis.selectAll("*").remove();var barWidth=this.barWidth(series.active()[0]);var barXOffset=0;var activeSeriesCount=series.filter(function(s){return!s.disabled}).length;var seriesBarWidth=this.unstack?barWidth/activeSeriesCount:barWidth;var transform=function(d){var matrix=[1,0,0,d.y<0?-1:1,0,d.y<0?graph.y.magnitude(Math.abs(d.y))*2:0];return"matrix("+matrix.join(",")+")"};series.forEach(function(series){if(series.disabled)return;var barWidth=this.barWidth(series); -var nodes=vis.selectAll("path").data(series.stack.filter(function(d){return d.y!==null})).enter().append("svg:rect").attr("x",function(d){return graph.x(d.x)+barXOffset}).attr("y",function(d){return graph.y(d.y0+Math.abs(d.y))*(d.y<0?-1:1)}).attr("width",seriesBarWidth).attr("height",function(d){return graph.y.magnitude(Math.abs(d.y))}).attr("opacity",series.opacity).attr("transform",transform);Array.prototype.forEach.call(nodes[0],function(n){n.setAttribute("fill",series.color)});if(this.unstack)barXOffset+=seriesBarWidth},this)},_frequentInterval:function(data){var intervalCounts={};for(var i=0;i0){this[0].data.forEach(function(plot){item.data.push({x:plot.x,y:0})})}else if(item.data.length===0){item.data.push({x:this.timeBase-(this.timeInterval||0),y:0})}this.push(item);if(this.legend){this.legend.addLine(this.itemByName(item.name))}},addData:function(data,x){var index=this.getIndex();Rickshaw.keys(data).forEach(function(name){if(!this.itemByName(name)){this.addItem({name:name})}},this);this.forEach(function(item){item.data.push({x:x||(index*this.timeInterval||1)+this.timeBase,y:data[item.name]||0})},this)},getIndex:function(){return this[0]&&this[0].data&&this[0].data.length?this[0].data.length:0},itemByName:function(name){for(var i=0;i1;i--){this.currentSize+=1;this.currentIndex+=1;this.forEach(function(item){item.data.unshift({x:((i-1)*this.timeInterval||1)+this.timeBase,y:0,i:i})},this)}}},addData:function($super,data,x){$super(data,x);this.currentSize+=1;this.currentIndex+=1;if(this.maxDataPoints!==undefined){while(this.currentSize>this.maxDataPoints){this.dropData()}}},dropData:function(){this.forEach(function(item){item.data.splice(0,1)});this.currentSize-=1},getIndex:function(){return this.currentIndex}});return Rickshaw}); - -Sidekiq = {}; - -var nf = new Intl.NumberFormat(); -var poller; -var realtimeGraph = function(updatePath) { - var timeInterval = parseInt(localStorage.sidekiqTimeInterval) || 5000; - var graphElement = document.getElementById("realtime"); - - var graph = new Rickshaw.Graph( { - element: graphElement, - width: responsiveWidth(), - height: 200, - renderer: 'line', - interpolation: 'linear', - - series: new Rickshaw.Series.FixedDuration([{ name: graphElement.dataset.failedLabel, color: '#af0014' }, { name: graphElement.dataset.processedLabel, color: '#006f68' }], undefined, { - timeInterval: timeInterval, - maxDataPoints: 100, - }) - }); - - var y_axis = new Rickshaw.Graph.Axis.Y( { - graph: graph, - tickFormat: Rickshaw.Fixtures.Number.formatKMBT, - ticksTreatment: 'glow' - }); - - graph.render(); - - var legend = document.getElementById('realtime-legend'); - var Hover = Rickshaw.Class.create(Rickshaw.Graph.HoverDetail, { - render: function(args) { - legend.innerHTML = ""; - - var timestamp = document.createElement('div'); - timestamp.className = 'timestamp'; - timestamp.innerHTML = args.formattedXValue; - legend.appendChild(timestamp); - - args.detail.sort(function(a, b) { return a.order - b.order }).forEach( function(d) { - var line = document.createElement('div'); - line.className = 'line'; - - var swatch = document.createElement('div'); - swatch.className = 'swatch'; - swatch.style.backgroundColor = d.series.color; - - var label = document.createElement('div'); - label.className = 'tag'; - label.innerHTML = d.name + ": " + nf.format(Math.floor(d.formattedYValue)); - - line.appendChild(swatch); - line.appendChild(label); - legend.appendChild(line); - - var dot = document.createElement('div'); - dot.className = 'dot'; - dot.style.top = graph.y(d.value.y0 + d.value.y) + 'px'; - dot.style.borderColor = d.series.color; - - this.element.appendChild(dot); - dot.className = 'dot active'; - this.show(); - }, this ); - } - }); - var hover = new Hover( { graph: graph } ); - - var i = 0; - poller = setInterval(function() { - var url = document.getElementById("history").getAttribute("data-update-url"); - - fetch(url).then(response => response.json()).then(data => { - if (i === 0) { - var processed = data.sidekiq.processed; - var failed = data.sidekiq.failed; - } else { - var processed = data.sidekiq.processed - Sidekiq.processed; - var failed = data.sidekiq.failed - Sidekiq.failed; - } - - dataPoint = {}; - dataPoint[graphElement.dataset.failedLabel] = failed; - dataPoint[graphElement.dataset.processedLabel] = processed; - - graph.series.addData(dataPoint); - graph.render(); - - Sidekiq.processed = data.sidekiq.processed; - Sidekiq.failed = data.sidekiq.failed; - - updateStatsSummary(data.sidekiq); - updateRedisStats(data.redis); - updateFooterUTCTime(data.server_utc_time) - - pulseBeacon(); - }); - - i++; - }, timeInterval); -} - -var historyGraph = function() { - var h = document.getElementById("history"); - processed = createSeries(h.getAttribute("data-processed")); - failed = createSeries(h.getAttribute("data-failed")); - - var graphElement = h; - var graph = new Rickshaw.Graph( { - element: graphElement, - width: responsiveWidth(), - height: 200, - renderer: 'line', - interpolation: 'linear', - series: [ - { - color: "#af0014", - data: failed, - name: graphElement.dataset.failedLabel - }, { - color: "#006f68", - data: processed, - name: graphElement.dataset.processedLabel - } - ] - } ); - var x_axis = new Rickshaw.Graph.Axis.Time( { graph: graph } ); - var y_axis = new Rickshaw.Graph.Axis.Y({ - graph: graph, - tickFormat: Rickshaw.Fixtures.Number.formatKMBT, - ticksTreatment: 'glow' - }); - - graph.render(); - - var legend = document.getElementById('history-legend'); - var Hover = Rickshaw.Class.create(Rickshaw.Graph.HoverDetail, { - render: function(args) { - legend.innerHTML = ""; - - var timestamp = document.createElement('div'); - timestamp.className = 'timestamp'; - timestamp.innerHTML = args.formattedXValue; - legend.appendChild(timestamp); - - args.detail.sort(function(a, b) { return a.order - b.order }).forEach( function(d) { - var line = document.createElement('div'); - line.className = 'line'; - - var swatch = document.createElement('div'); - swatch.className = 'swatch'; - swatch.style.backgroundColor = d.series.color; - - var label = document.createElement('div'); - label.className = 'tag'; - label.innerHTML = d.name + ": " + nf.format(Math.floor(d.formattedYValue)); - - line.appendChild(swatch); - line.appendChild(label); - legend.appendChild(line); - - var dot = document.createElement('div'); - dot.className = 'dot'; - dot.style.top = graph.y(d.value.y0 + d.value.y) + 'px'; - dot.style.borderColor = d.series.color; - - this.element.appendChild(dot); - dot.className = 'dot active'; - this.show(); - }, this ); - } - }); - var hover = new Hover( { graph: graph } ); -} - -var createSeries = function(data) { - var obj = JSON.parse(data); - var series = []; - for (var date in obj) { - var value = obj[date]; - var point = { x: Date.parse(date)/1000, y: value }; - series.unshift(point); - } - return series; -}; - -var updateStatsSummary = function(data) { - document.getElementById("txtProcessed").innerText = nf.format(data.processed); - document.getElementById("txtFailed").innerText = nf.format(data.failed); - document.getElementById("txtBusy").innerText = nf.format(data.busy); - document.getElementById("txtScheduled").innerText = nf.format(data.scheduled); - document.getElementById("txtRetries").innerText = nf.format(data.retries); - document.getElementById("txtEnqueued").innerText = nf.format(data.enqueued); - document.getElementById("txtDead").innerText = nf.format(data.dead); -} - -var updateRedisStats = function(data) { - document.getElementById('redis_version').innerText = data.redis_version; - document.getElementById('uptime_in_days').innerText = data.uptime_in_days; - document.getElementById('connected_clients').innerText = data.connected_clients; - document.getElementById('used_memory_human').innerText = data.used_memory_human; - document.getElementById('used_memory_peak_human').innerText = data.used_memory_peak_human; -} - -var updateFooterUTCTime = function(time) { - document.getElementById('serverUtcTime').innerText = time; -} - -var pulseBeacon = function() { - document.getElementById('beacon').classList.add('pulse'); - window.setTimeout(() => { document.getElementById('beacon').classList.remove('pulse'); }, 1000); -} - -// Render graphs -var renderGraphs = function() { - realtimeGraph(); - historyGraph(); -}; - -var setSliderLabel = function(val) { - document.getElementById('sldr-text').innerText = Math.round(parseFloat(val) / 1000) + ' sec'; -} - -var ready = (callback) => { - if (document.readyState != "loading") callback(); - else document.addEventListener("DOMContentLoaded", callback); -} - -ready(() => { - renderGraphs(); - - var sldr = document.getElementById('sldr'); - if (typeof localStorage.sidekiqTimeInterval !== 'undefined') { - sldr.value = localStorage.sidekiqTimeInterval; - setSliderLabel(localStorage.sidekiqTimeInterval); - } - - sldr.addEventListener("change", event => { - clearInterval(poller); - localStorage.sidekiqTimeInterval = sldr.value; - setSliderLabel(sldr.value); - resetGraphs(); - renderGraphs(); - }); - - sldr.addEventListener("mousemove", event => { - setSliderLabel(sldr.value); - }); -}); - -// Reset graphs -var resetGraphs = function() { - document.getElementById('realtime').innerHTML = ''; - document.getElementById('history').innerHTML = ''; -}; - -// Resize graphs after resizing window -var debounce = function(fn, timeout) { - var timeoutID = -1; - return function() { - if (timeoutID > -1) { - window.clearTimeout(timeoutID); - } - timeoutID = window.setTimeout(fn, timeout); - } -}; - -window.onresize = function() { - var prevWidth = window.innerWidth; - return debounce(function () { - var currWidth = window.innerWidth; - if (prevWidth !== currWidth) { - prevWidth = currWidth; - clearInterval(poller); - resetGraphs(); - renderGraphs(); - } - }, 125); -}(); diff --git a/web/assets/stylesheets/application-dark.css b/web/assets/stylesheets/application-dark.css deleted file mode 100644 index 65e27948..00000000 --- a/web/assets/stylesheets/application-dark.css +++ /dev/null @@ -1,143 +0,0 @@ -html, body { - background-color: #171717 !important; - color: #DEDEDE; -} - -a, -.title, -.summary_bar ul .count, -span.current-interval, -.navbar .navbar-brand { - color: #d04; -} - -.history-graph.active, -.beacon .dot { - background-color: #d04; -} - -.navbar .navbar-brand:hover { - color: #ddd; -} - -.navbar .navbar-brand .status { - color: #ddd; -} - -.navbar-default .navbar-nav > li > a { - color: #ddd; -} - -.navbar-inverse { - background-color: #222; - border-color: #444; -} - -table { - background-color: #1D1D1D; -} - -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: #2E2E2E; -} - -.table-bordered, -.table-bordered > tbody > tr > td, -.table-bordered > tbody > tr > th, -.table-bordered > tfoot > tr > td, -.table-bordered > tfoot > tr > th, -.table-bordered > thead > tr > td, -.table-bordered > thead > tr > th { - border: 1px solid #444; -} - -.table-hover > tbody > tr:hover { - background-color: #444; -} - -.alert { - border: none; - color: #ddd; -} - -.alert-success { - background-color: #484; -} - -.alert-danger { - background-color: #980035; -} - -.alert-info { - background-color: #31708f; -} - -a:link, a:active, a:hover, a:visited { - color: #ddd; -} - -input { - background-color: #444; - color: #ccc; - padding: 3px; -} - -.summary_bar .summary { - background-color: #232323; - border: 1px solid #444; -} - -.navbar-default { - background-color: #0F0F0F; - border-color: #444; -} - -.navbar-default .navbar-nav > .active > a, -.navbar-default .navbar-nav > .active > a:focus, -.navbar-default .navbar-nav > .active > a:hover { - color: #ddd; - background-color: #333; -} - -.navbar-default .navbar-nav > li > a:hover { - color: #ddd; -} - -.pagination > li > a, -.pagination > li > a:hover, -.pagination > li > span { - color: #ddd; - background-color: #333; - border-color: #444; -} -.pagination > .disabled > a, -.pagination > .disabled > a:focus, -.pagination > .disabled > a:hover, -.pagination > .disabled > span, -.pagination > .disabled > span:focus, -.pagination > .disabled > span:hover { - color: #ddd; - background-color: #333; - border-color: #444; -} - -.stat { - border: 1px solid #888; -} - -.rickshaw_graph .detail { - background: #888; -} -.rickshaw_graph .x_tick { - border-color: #888; -} - -.rickshaw_graph .y_ticks.glow text { - fill: #ddd; - color: #ddd; -} - -.info-circle { - color: #222; - background-color: #888; -} diff --git a/web/assets/stylesheets/application-rtl.css b/web/assets/stylesheets/application-rtl.css deleted file mode 100644 index d26f5e54..00000000 --- a/web/assets/stylesheets/application-rtl.css +++ /dev/null @@ -1,242 +0,0 @@ -.navbar-right { - float: left !important; -} - -footer .edits { - margin-right: unset; - margin-left: 40px; -} - -.summary_bar .status { - margin-left: unset; - margin-right: 10px; -} -@media (max-width: 767px) and (min-width: 400px) { - .summary_bar ul .desc { - text-align: right; - } - .summary_bar ul .count { - text-align: left; - } -} -@media (max-width: 979px) and (min-width: 768px) { -} -.summary_bar ul .count { - float: left; -} - -form .btn { - margin-right: unset; - margin-left: 5px; -} - -form .btn-group .btn { - margin-right: unset; - margin-left: 1px; -} - -@media (max-width: 768px) { - .navbar.navbar-fixed-top ul { - margin-right: unset; - margin-left: -15px!important; - } -} - -@media (width: 768px) { - .navbar .poll-wrapper { - margin: 4px 0 0 4px; - } -} - -.navbar-footer .navbar ul.nav a.navbar-brand { - padding: 0 0 0 15px; -} - -.navbar-fixed-bottom li { - margin-right: unset; - margin-left: 20px; -} - -.status-active { - background-position: 100% 0; -} - -.status-idle { - background-position: 100% -54px; -} - -.stat { - float: right; - margin-right: unset; - margin-left: 20px; -} - -.stat:last-child { - margin-left: 0; -} - -@media (max-width: 767px) { - .stat { - float: right; - margin-right: unset; - margin-left: 10px; - text-align: right; - } - .stat h3{ - float: left; - margin: 5px 5px 5px 10px; - } - .stat p{ - font-size: 1em; - margin: 5px 10px 5px 5px; - } -} - -/* Dashboard -********************************** */ -div.dashboard h3 { - float: right; -} - -div.interval-slider { - float: left; -} - -#realtime-legend, -#history-legend { - text-align: right; - float: left; -} -#realtime-legend .timestamp, -#history-legend .timestamp { - text-align: left; -} -#realtime-legend .line, -#history-legend .line { - margin: 0 20px 0 0; -} -#realtime-legend .swatch, -#history-legend .swatch { - margin: 0 0 0 8px; -} - -/* Beacon -********************************** */ - -.beacon .dot, -.beacon .ring { - right: 50%; -} - -.beacon .dot { - margin: -5px -5px 0 0; -} - -.beacon .ring { - margin: -14px -14px 0 0; -} - -.history-heading { - padding-right: unset; - padding-left: 15px; -} - -@media (max-width: 767px) { - .navbar.navbar-fixed-top ul { - margin-left: 0; - } - - .navbar.navbar-fixed-top li { - margin-left: 0; - } - - .navbar.navbar-fixed-bottom ul { - margin-left: 0; - } - - .navbar.navbar-fixed-bottom li { - margin-left: 0; - } -} - -@media (max-width: 500px) { - .navbar-footer .navbar ul.nav a.navbar-brand { - padding-right: unset; - padding-left: 5px; - } -} - -/* Rickshaw */ - -.rickshaw_graph .detail .x_label.left { - right: 0 -} -.rickshaw_graph .detail .x_label.right { - left: 0 -} -.rickshaw_graph .detail .item.left { - right: 0 -} -.rickshaw_graph .detail .item.right { - left: 0 -} -.rickshaw_graph .detail .item.left:after { - left: 0; - right: -5px; - border-right-color: unset; - border-left-color: rgba(0, 0, 0, .8); - border-right-width: 0; - border-left-width: unset; -} -.rickshaw_graph .detail .item.right:after { - right: 0; - left: -5px; - border-left-color: unset; - border-right-color: rgba(0, 0, 0, .8); - border-left-width: 0; - border-right-width: unset; -} -.rickshaw_graph .detail .dot { - margin-right: -3px; - margin-left: unset; -} -.rickshaw_graph .x_tick { - border-left: unset; - border-right: 1px dotted rgba(0, 0, 0, .2); -} -.rickshaw_graph .x_tick .title { - margin-right: 3px; - margin-left: unset; -} -.rickshaw_annotation_timeline .annotation { - margin-right: -2px; - margin-left: unset; -} -.rickshaw_graph .annotation_line { - border-right: 2px solid rgba(0, 0, 0, .3); - border-left: unset; -} -.rickshaw_annotation_timeline .annotation .content { - left: unset; - right: -11px; -} -.rickshaw_graph .x_tick.glow .title, -.rickshaw_graph .y_ticks.glow text { - text-shadow: 1px 1px 0 rgba(255, 255, 255, .1), -1px -1px 0 rgba(255, 255, 255, .1), -1px 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1), 0 -1px 0 rgba(255, 255, 255, .1), -1px 0 0 rgba(255, 255, 255, .1), 1px 0 0 rgba(255, 255, 255, .1), 1px -1px 0 rgba(255, 255, 255, .1) -} -.rickshaw_graph .x_tick.inverse .title, -.rickshaw_graph .y_ticks.inverse text { - text-shadow: 1px 1px 0 rgba(0, 0, 0, .8), -1px -1px 0 rgba(0, 0, 0, .8), -1px 1px 0 rgba(0, 0, 0, .8), 0 1px 0 rgba(0, 0, 0, .8), 0 -1px 0 rgba(0, 0, 0, .8), -1px 0 0 rgba(0, 0, 0, .8), 1px 0 0 rgba(0, 0, 0, .8), 1px -1px 0 rgba(0, 0, 0, .8) -} -.rickshaw_legend .line { - padding-left: 15px; - padding-right: unset; -} -.rickshaw_legend .line .swatch { - margin-left: 3px; - margin-right: unset; -} -.rickshaw_legend .action { - margin-left: .2em; - margin-right: unset; -} diff --git a/web/assets/stylesheets/application.css b/web/assets/stylesheets/application.css deleted file mode 100644 index 7a3d20f1..00000000 --- a/web/assets/stylesheets/application.css +++ /dev/null @@ -1,957 +0,0 @@ -* { - box-sizing: border-box; -} - -body { - color: #585454; - padding: 0; - text-rendering: optimizeLegibility; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - background: #f3f3f3 url(data:image/png;base64,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); -} - -.ltr { - direction: ltr; -} - -.rtl { - direction: rtl; -} - -a { - color: #b1003e; -} -a:active, a:hover, a:focus { - color: #4b001a; -} - -h1, h2, h3, h4, h5, h6, strong { - font-weight: 700; -} - -.navbar-brand, .navbar .navbar-brand, h5, h4, h3, h2, h1 { - font-family: Armata, "Helvetica Neue", Helvetica, Arial, sans-serif; - font-weight: 400; -} - -.title { - color: #b1003e; -} - -pre { - font-size: 11px; -} - -code { - background: none; - border: none; -} - -section { - padding-top: 10px; -} - -code { - padding: 0; -} - -footer { - padding: 40px 20px; - text-align: center; -} -footer .edits { - margin-right: 40px; -} - -body { - padding: 0 20px; -} - -h3 { - line-height: 45px; -} - -.centered { - text-align: center; -} - -.admin #page { - padding: 60px 0; -} - -header.row .pagination { - margin: 12px 0; -} - -.summary_bar .status { - margin-left: 10px; -} -.summary_bar .summary { - margin-top: 12px; - background-color: #fff; - border-radius: 4px; - border: 1px solid rgba(0, 0, 0, 0.1); - padding: 8px; - margin-bottom: 10px; -} -.poll-wrapper { - margin: 9px; -} -.live-poll.active { - background-color: #009300; -} -.live-poll.active:hover { - background-color: #777; -} -.summary_bar ul { - margin: 0 0 38px 0; -} -.summary_bar ul h3 { - font-size: 1em; - margin: 0; - font-weight: normal; - line-height: 1em; -} -.summary_bar ul li { - padding: 4px 0 2px 0; - text-align: center; - width: 14%; -} -@media (max-width: 767px) and (min-width: 200px) { - .summary_bar { - font-size: 1.5em; - } - - .summary_bar ul li { - width: 100%; - } - - .summary_bar ul li span { - width: 50% !important; - } - .summary_bar ul .desc { - text-align: left; - } - .summary_bar ul .count { - text-align: right; - } -} -@media (max-width: 979px) and (min-width: 768px) { - .summary_bar ul li.col-sm-2 { - margin: 0 10px; - width: 96px !important; - } -} -.summary_bar ul .desc { - display: block; - font-size: 1em; - font-weight: normal; - width: 100%; -} -.summary_bar ul .count { - color: #b1003e; - display: block; - font-size: 1em; - font-weight: bold; - float: right; - padding: 0 0 2px 0; - width: 100%; -} - -.table_container { - overflow: overlay; -} - -.queues form { - margin: 0; -} - -form .btn { - margin-right: 5px; -} - -form .btn-group .btn { - margin-right: 4px; -} - -td form { - margin-bottom: 0; -} - -.table tr > td.table-checkbox, .table tr > th.table-checkbox { - padding: 0; -} - -.jobtag, .jobtag a { - color: black; -} - -table .table-checkbox label { - height: 100%; - width: 100%; - padding: 0 16px; - margin-bottom: 0; - line-height: 32px; -} - -.navbar .navbar-brand { - color: #b1003e; - padding: 13px; - text-shadow: none; -} - -.navbar .navbar-brand .status { - color: #585454; - display: inline-block; - width: 75px; -} - - -.nav.navbar-nav{ - display: flex; - width: 100%; -} - -.navbar-livereload{ - margin-left: auto; - white-space: nowrap; -} - -.navbar-livereload .poll-wrapper a:last-child{ - margin-left: 8px; -} - -.navbar-right{ - margin-right: 0; -} - -.navbar-collapse.collapse{ - overflow-x: auto !important; -} - -@media (max-width: 768px) { - .navbar .navbar-header .navbar-livereload { - border: none; - margin: 9px 10px 0; - padding: 0; - } - - .navbar .navbar-collapse { - max-height: 400px; - } - - .navbar .navbar-collapse .navbar-livereload { - display: none; - } - - .nav.navbar-nav{ - display: block; - width: auto; - } - - .navbar.navbar-fixed-top ul { - margin-right: -15px!important; - } - - .navbar .nav a { - text-align: center; - } -} - -@media (width: 768px) { - .navbar .navbar-collapse .navbar-livereload { - display: block; - margin-top: 5px; - } - - .navbar .poll-wrapper { - margin: 4px 4px 0 0; - } -} - -.navbar-footer .navbar ul.nav { - text-align: center; - float: none; -} -.navbar-footer .navbar ul.nav a { - font-weight: 700; - font-size: 16px; - padding: 15px; -} - -.navbar-footer .navbar ul.nav a.navbar-brand { - font-weight: 400; - padding: 0 15px 0 0; -} - -.navbar-footer .navbar ul.nav li { - display: inline-block; - float: none; -} -.navbar-footer .navbar.affix { - top: 0; - width: 100%; - z-index: 10; -} - -img.smallogo { - width: 30px; - margin: 0 0 6px 0; -} - -.navbar-fixed-bottom li { - margin-right: 20px; -} - -.status-sprite { - background-image: url(../images/status.png); - height: 19px; - width: 20px; - display: inline-block; - background-size: 20px; -} -.status-active { - background-position: 0 0; -} - -.status-idle { - background-position: 0 -54px; -} - -.btn { - font-weight: 700; - border: none; - border-radius: 3px; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.4); - background-image: linear-gradient(#b1003e, #980035); - background-color: #980035; - color: #ddd; -} -.btn:hover { - color: #000; - background-image: none; - background-color: #ddd; -} - -.poll-status { - padding: 10px 0; -} - -.stats-wrapper { - width: 100%; - text-align: center; -} - -.stats-container { - display: inline-block; -} - -.stat { - float: left; - text-align: center; - margin-right: 20px; - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 4px; - padding: 5px; - width: 150px; - margin-bottom: 20px; -} - -.stat:last-child { - margin-right: 0; -} - -.stat p { - font-size: 0.9em; -} -@media (max-width: 767px) { - .stats-container { - display: block; - } - .stat { - float: left; - margin-right: 10px; - width: 100%; - text-align: left; - line-height: 45px; - } - .stat h3{ - float: right; - margin: 5px 10px 5px 5px; - } - .stat p{ - font-size: 1.5em; - margin: 5px 5px 5px 10px; - } -} - -/* Dashboard -********************************** */ -div.dashboard h3 { - float: left; -} - -div.interval-slider { - float: right; - line-height: 1.3; - font-size: 0.95em; - padding: 15px 0 0; -} - -span.current-interval { - min-width: 40px; - display: inline-block; - padding: 0 0 5px 0; - color: #B1003E; -} - -div.interval-slider input { - width: 160px; - border-radius: 2px; - background: currentcolor; -} - -#realtime-legend, -#history-legend { - width: 580px; - text-align: left; - margin-top: 5px; - float: right; -} -#realtime-legend .timestamp, -#history-legend .timestamp { - display: inline-block; - width: 220px; - text-align: right; -} -#realtime-legend .line, -#history-legend .line { - display: inline-block; - margin: 0 0 0 20px; -} -#realtime-legend .swatch, -#history-legend .swatch { - display: inline-block; - width: 10px; - height: 10px; - margin: 0 8px 0 0; -} -#realtime-legend .tag, -#history-legend .tag { - display: inline-block; -} - -@media (max-width: 790px) { - #realtime-legend, - #history-legend { - float: none; - width: 100%; - margin-bottom: 20px; - } -} - -@media (max-width: 500px) { - #realtime-legend, - #history-legend { - text-align: center; - } -} -/* Beacon -********************************** */ - -.beacon { - position: relative; - width: 20px; - height: 20px; - display: inline-block; -} - -.beacon .dot, -.beacon .ring { - position: absolute; - top: 50%; - left: 50%; -} - -.beacon .dot { - width: 10px; - height: 10px; - margin: -5px 0 0 -5px; - background-color: #80002d; - border-radius: 10px; - box-shadow: 0 0 9px #666; - border: 3px solid transparent; - z-index: 10; -} - -.beacon.pulse .dot { - animation: beacon-dot-pulse 1s ease-out; -} - -@keyframes beacon-dot-pulse { - from { - background-color: #50002d; - box-shadow: 0 0 9px #666; - } - 50% { - background-color: #c90047; - box-shadow: 0 0 18px #666; - } - to { - background-color: #50002d; - box-shadow: 0 0 9px #666; - } -} - -.beacon .ring { - width: 28px; - height: 28px; - margin: -14px 0 0 -14px; - border-radius: 28px; - border: 3px solid #80002d; - z-index: 5; - opacity: 0; -} - -.beacon.pulse .ring { - animation: beacon-ring-pulse 1s; -} - -@keyframes beacon-ring-pulse { - 0% { - opacity: 1; - transform: scale(0.3); - } - 100% { - opacity: 0; - transform: scale(1); - } -} - -.chart { - margin: 0; -} - -.history-heading { - padding-right: 15px; -} - -.history-graph { - padding: 3px; - border-radius: 3px; -} - -.history-graph.active { - background-color: #B1003E; - color: white; -} - -.history-graph.active:hover { - text-decoration: none; -} - -@media (max-width: 767px) { - .navbar .navbar-brand { - float: none; - display: block; - } - - .navbar.navbar-fixed-top ul { - margin-right: 0; - } - - .navbar.navbar-fixed-top li { - margin-right: 0; - } - - .navbar #navbar-menu{ - display: none; - } - - .poll-wrapper { - width: 100%; - text-align: center; - } - - .poll-wrapper > a { - display: inline-block; - margin: 5px; - } - - .navbar.navbar-fixed-bottom ul { - float: none; - margin-right: 0; - } - - .navbar.navbar-fixed-bottom li { - float: none; - margin-right: 0; - } - - .navbar-text { - float:none; - line-height: 30px; - margin: 15px auto; - } -} - -@media (max-width: 767px) { - .navbar-fixed-top, .navbar-fixed-bottom { - margin: 0 -20px; - } - - .navbar ul.nav li a { - padding: 0 8px; - } - - .admin #page { - padding-top: 10px; - } -} - -@media (max-width: 500px) { - .navbar-footer .navbar ul.nav a.navbar-brand { - padding-right: 5px; - } -} - -.rickshaw_graph .detail { - pointer-events: none; - position: absolute; - top: 0; - z-index: 2; - background: rgba(0, 0, 0, .9); - bottom: 0; - width: 1px; - transition: opacity .25s linear; -} -.rickshaw_graph .detail.inactive { - opacity: 0 -} -.rickshaw_graph .detail .item.active { - opacity: 1 -} -.rickshaw_graph .detail .x_label { - font-family: Arial, sans-serif; - border-radius: 3px; - padding: 6px; - opacity: .7; - border: 1px solid #e0e0e0; - font-size: 12px; - position: absolute; - background: #fff; - white-space: nowrap -} -.rickshaw_graph .detail .x_label.left { - left: 0 -} -.rickshaw_graph .detail .x_label.right { - right: 0 -} -.rickshaw_graph .detail .item { - position: absolute; - z-index: 2; - border-radius: 3px; - padding: .25em; - font-size: 12px; - font-family: Arial, sans-serif; - opacity: 0; - background: rgba(0, 0, 0, .4); - color: #fff; - border: 1px solid rgba(0, 0, 0, .4); - margin-left: 1em; - margin-right: 1em; - margin-top: -1em; - white-space: nowrap -} -.rickshaw_graph .detail .item.left { - left: 0 -} -.rickshaw_graph .detail .item.right { - right: 0 -} -.rickshaw_graph .detail .item.active { - opacity: 1; - background: rgba(0, 0, 0, .8) -} -.rickshaw_graph .detail .item:after { - position: absolute; - display: block; - width: 0; - height: 0; - content: ""; - border: 5px solid transparent -} -.rickshaw_graph .detail .item.left:after { - top: 1em; - left: -5px; - margin-top: -5px; - border-right-color: rgba(0, 0, 0, .8); - border-left-width: 0 -} -.rickshaw_graph .detail .item.right:after { - top: 1em; - right: -5px; - margin-top: -5px; - border-left-color: rgba(0, 0, 0, .8); - border-right-width: 0 -} -.rickshaw_graph .detail .dot { - width: 4px; - height: 4px; - margin-left: -3px; - margin-top: -3.5px; - border-radius: 5px; - position: absolute; - box-shadow: 0 0 2px rgba(0, 0, 0, .6); - box-sizing: content-box; - background: #fff; - border-width: 2px; - border-style: solid; - display: none; - background-clip: padding-box -} -.rickshaw_graph .detail .dot.active { - display: block -} -.rickshaw_graph { - position: relative -} -.rickshaw_graph svg { - display: block; - overflow: hidden -} -.rickshaw_graph .x_tick { - position: absolute; - top: 0; - bottom: 0; - width: 0; - border-left: 1px dotted rgba(0, 0, 0, .5); - pointer-events: none -} -.rickshaw_graph .x_tick .title { - position: absolute; - font-family: Arial, sans-serif; - white-space: nowrap; - margin-left: 3px; - bottom: 1px -} -.rickshaw_graph .y_axis, -.rickshaw_graph .x_axis_d3 { - fill: none -} -.rickshaw_graph .y_ticks .tick line, -.rickshaw_graph .x_ticks_d3 .tick { - stroke: rgba(0, 0, 0, .16); - stroke-width: 2px; - shape-rendering: crisp-edges; - pointer-events: none -} -.rickshaw_graph .y_grid .tick, -.rickshaw_graph .x_grid_d3 .tick { - z-index: -1; - stroke: rgba(0, 0, 0, .2); - stroke-width: 1px; - stroke-dasharray: 1 1 -} -.rickshaw_graph .y_grid .tick[data-y-value="0"] { - stroke-dasharray: 1 0 -} -.rickshaw_graph .y_grid path, -.rickshaw_graph .x_grid_d3 path { - fill: none; - stroke: none -} -.rickshaw_graph .y_ticks path, -.rickshaw_graph .x_ticks_d3 path { - fill: none; - stroke: gray -} -.rickshaw_graph .y_ticks text, -.rickshaw_graph .x_ticks_d3 text { - opacity: .7; - font-size: 12px; - pointer-events: none -} -.rickshaw_graph .x_tick.glow .title, -.rickshaw_graph .y_ticks.glow text { - fill: #000; - color: #000; - text-shadow: -1px 1px 0 rgba(255, 255, 255, .1), 1px -1px 0 rgba(255, 255, 255, .1), 1px 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1), 0 -1px 0 rgba(255, 255, 255, .1), 1px 0 0 rgba(255, 255, 255, .1), -1px 0 0 rgba(255, 255, 255, .1), -1px -1px 0 rgba(255, 255, 255, .1) -} -.rickshaw_graph .x_tick.inverse .title, -.rickshaw_graph .y_ticks.inverse text { - fill: #fff; - color: #fff; - text-shadow: -1px 1px 0 rgba(0, 0, 0, .8), 1px -1px 0 rgba(0, 0, 0, .8), 1px 1px 0 rgba(0, 0, 0, .8), 0 1px 0 rgba(0, 0, 0, .8), 0 -1px 0 rgba(0, 0, 0, .8), 1px 0 0 rgba(0, 0, 0, .8), -1px 0 0 rgba(0, 0, 0, .8), -1px -1px 0 rgba(0, 0, 0, .8) -} -.rickshaw_legend { - font-family: Arial; - color: #fff; - background: #404040; - display: inline-block; - padding: 12px 5px; - border-radius: 2px; - position: relative -} -.rickshaw_legend:hover { - z-index: 10 -} -.rickshaw_legend .swatch { - width: 10px; - height: 10px; - border: 1px solid rgba(0, 0, 0, .2) -} -.rickshaw_legend .line { - clear: both; - line-height: 140%; - padding-right: 15px -} -.rickshaw_legend .line .swatch { - display: inline-block; - margin-right: 3px; - border-radius: 2px -} -.rickshaw_legend .label { - margin: 0; - white-space: nowrap; - display: inline; - font-size: inherit; - background-color: transparent; - color: inherit; - font-weight: 400; - line-height: normal; - padding: 0; - text-shadow: none -} -.rickshaw_legend .action:hover { - opacity: .6 -} -.rickshaw_legend .action { - margin-right: .2em; - opacity: .5; - cursor: pointer; -} -.rickshaw_legend .line.disabled { - opacity: .4 -} -.rickshaw_legend ul { - list-style-type: none; - margin: 0; - padding: 0; - margin: 2px; - cursor: pointer -} -.rickshaw_legend li { - padding: 0 0 0 2px; - min-width: 80px; - white-space: nowrap -} -.rickshaw_legend li:hover { - background: rgba(255, 255, 255, .08); - border-radius: 3px -} -.rickshaw_legend li:active { - background: rgba(255, 255, 255, .2); - border-radius: 3px -} - -.code-wrap { - white-space: normal; -} -.args { - overflow-y: auto; - max-height: 100px; - word-break: break-all; -} -.args-extended { - overflow-y: scroll; - max-height: 500px; - word-break: break-all; -} - - -/* BOOTSTRAP 3 FIXES */ -/* @grid-float-breakpoint -1 */ -.container { - padding: 0; -} -@media (max-width: 767px) { - .navbar-fixed-top, .navbar-fixed-bottom { - position: relative; - top: auto; - } -} - -.redis-url, .redis-namespace { - overflow: hidden; - white-space: nowrap; -} - -@media (min-width: 768px) { - .redis-url { - max-width: 200px; - } - - .redis-namespace { - max-width: 150px; - } -} - -@media (min-width: 992px) { - .redis-url { - max-width: 390px; - } - - .redis-namespace { - max-width: 300px; - } -} -@media (min-width: 1200px) { - .redis-url { - max-width: 480px; - } - - .redis-namespace { - max-width: 350px; - } -} - -.redis-url { - text-overflow: ellipsis; -} - -.product-version { - color:white; -} - -.warning-messages { - margin-top: 20px; - margin-bottom: 10px; -} - -.toggle { - display: none; -} - -.box { - width: 50%; -} - -.checkbox-column { - width: 20px; -} - -.delete-confirm { - width: 20%; -} - -.info-circle { - color: #ccc; - background-color: #000; - border-radius: 50%; - text-align: center; - vertical-align: middle; - padding: 3px 7px; - margin-left: 5px; -} diff --git a/web/assets/stylesheets/bootstrap-rtl.min.css b/web/assets/stylesheets/bootstrap-rtl.min.css deleted file mode 100644 index 7f3b13ee..00000000 --- a/web/assets/stylesheets/bootstrap-rtl.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/******************************************************************************* - * bootstrap-rtl (version 3.3.4) - * Author: Morteza Ansarinia (http://github.com/morteza) - * Created on: August 13,2015 - * Project: bootstrap-rtl - * Copyright: Unlicensed Public Domain - *******************************************************************************/ - -html{direction:rtl}body{direction:rtl}.flip.text-left{text-align:right}.flip.text-right{text-align:left}.list-unstyled{padding-right:0;padding-left:initial}.list-inline{padding-right:0;padding-left:initial;margin-right:-5px;margin-left:0}dd{margin-right:0;margin-left:initial}@media (min-width:768px){.dl-horizontal dt{float:right;clear:right;text-align:left}.dl-horizontal dd{margin-right:180px;margin-left:0}}blockquote{border-right:5px solid #eee;border-left:0}.blockquote-reverse,blockquote.pull-left{padding-left:15px;padding-right:0;border-left:5px solid #eee;border-right:0;text-align:left}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:right}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{left:100%;right:auto}.col-xs-pull-11{left:91.66666667%;right:auto}.col-xs-pull-10{left:83.33333333%;right:auto}.col-xs-pull-9{left:75%;right:auto}.col-xs-pull-8{left:66.66666667%;right:auto}.col-xs-pull-7{left:58.33333333%;right:auto}.col-xs-pull-6{left:50%;right:auto}.col-xs-pull-5{left:41.66666667%;right:auto}.col-xs-pull-4{left:33.33333333%;right:auto}.col-xs-pull-3{left:25%;right:auto}.col-xs-pull-2{left:16.66666667%;right:auto}.col-xs-pull-1{left:8.33333333%;right:auto}.col-xs-pull-0{left:auto;right:auto}.col-xs-push-12{right:100%;left:0}.col-xs-push-11{right:91.66666667%;left:0}.col-xs-push-10{right:83.33333333%;left:0}.col-xs-push-9{right:75%;left:0}.col-xs-push-8{right:66.66666667%;left:0}.col-xs-push-7{right:58.33333333%;left:0}.col-xs-push-6{right:50%;left:0}.col-xs-push-5{right:41.66666667%;left:0}.col-xs-push-4{right:33.33333333%;left:0}.col-xs-push-3{right:25%;left:0}.col-xs-push-2{right:16.66666667%;left:0}.col-xs-push-1{right:8.33333333%;left:0}.col-xs-push-0{right:auto;left:0}.col-xs-offset-12{margin-right:100%;margin-left:0}.col-xs-offset-11{margin-right:91.66666667%;margin-left:0}.col-xs-offset-10{margin-right:83.33333333%;margin-left:0}.col-xs-offset-9{margin-right:75%;margin-left:0}.col-xs-offset-8{margin-right:66.66666667%;margin-left:0}.col-xs-offset-7{margin-right:58.33333333%;margin-left:0}.col-xs-offset-6{margin-right:50%;margin-left:0}.col-xs-offset-5{margin-right:41.66666667%;margin-left:0}.col-xs-offset-4{margin-right:33.33333333%;margin-left:0}.col-xs-offset-3{margin-right:25%;margin-left:0}.col-xs-offset-2{margin-right:16.66666667%;margin-left:0}.col-xs-offset-1{margin-right:8.33333333%;margin-left:0}.col-xs-offset-0{margin-right:0;margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:right}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{left:100%;right:auto}.col-sm-pull-11{left:91.66666667%;right:auto}.col-sm-pull-10{left:83.33333333%;right:auto}.col-sm-pull-9{left:75%;right:auto}.col-sm-pull-8{left:66.66666667%;right:auto}.col-sm-pull-7{left:58.33333333%;right:auto}.col-sm-pull-6{left:50%;right:auto}.col-sm-pull-5{left:41.66666667%;right:auto}.col-sm-pull-4{left:33.33333333%;right:auto}.col-sm-pull-3{left:25%;right:auto}.col-sm-pull-2{left:16.66666667%;right:auto}.col-sm-pull-1{left:8.33333333%;right:auto}.col-sm-pull-0{left:auto;right:auto}.col-sm-push-12{right:100%;left:0}.col-sm-push-11{right:91.66666667%;left:0}.col-sm-push-10{right:83.33333333%;left:0}.col-sm-push-9{right:75%;left:0}.col-sm-push-8{right:66.66666667%;left:0}.col-sm-push-7{right:58.33333333%;left:0}.col-sm-push-6{right:50%;left:0}.col-sm-push-5{right:41.66666667%;left:0}.col-sm-push-4{right:33.33333333%;left:0}.col-sm-push-3{right:25%;left:0}.col-sm-push-2{right:16.66666667%;left:0}.col-sm-push-1{right:8.33333333%;left:0}.col-sm-push-0{right:auto;left:0}.col-sm-offset-12{margin-right:100%;margin-left:0}.col-sm-offset-11{margin-right:91.66666667%;margin-left:0}.col-sm-offset-10{margin-right:83.33333333%;margin-left:0}.col-sm-offset-9{margin-right:75%;margin-left:0}.col-sm-offset-8{margin-right:66.66666667%;margin-left:0}.col-sm-offset-7{margin-right:58.33333333%;margin-left:0}.col-sm-offset-6{margin-right:50%;margin-left:0}.col-sm-offset-5{margin-right:41.66666667%;margin-left:0}.col-sm-offset-4{margin-right:33.33333333%;margin-left:0}.col-sm-offset-3{margin-right:25%;margin-left:0}.col-sm-offset-2{margin-right:16.66666667%;margin-left:0}.col-sm-offset-1{margin-right:8.33333333%;margin-left:0}.col-sm-offset-0{margin-right:0;margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:right}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{left:100%;right:auto}.col-md-pull-11{left:91.66666667%;right:auto}.col-md-pull-10{left:83.33333333%;right:auto}.col-md-pull-9{left:75%;right:auto}.col-md-pull-8{left:66.66666667%;right:auto}.col-md-pull-7{left:58.33333333%;right:auto}.col-md-pull-6{left:50%;right:auto}.col-md-pull-5{left:41.66666667%;right:auto}.col-md-pull-4{left:33.33333333%;right:auto}.col-md-pull-3{left:25%;right:auto}.col-md-pull-2{left:16.66666667%;right:auto}.col-md-pull-1{left:8.33333333%;right:auto}.col-md-pull-0{left:auto;right:auto}.col-md-push-12{right:100%;left:0}.col-md-push-11{right:91.66666667%;left:0}.col-md-push-10{right:83.33333333%;left:0}.col-md-push-9{right:75%;left:0}.col-md-push-8{right:66.66666667%;left:0}.col-md-push-7{right:58.33333333%;left:0}.col-md-push-6{right:50%;left:0}.col-md-push-5{right:41.66666667%;left:0}.col-md-push-4{right:33.33333333%;left:0}.col-md-push-3{right:25%;left:0}.col-md-push-2{right:16.66666667%;left:0}.col-md-push-1{right:8.33333333%;left:0}.col-md-push-0{right:auto;left:0}.col-md-offset-12{margin-right:100%;margin-left:0}.col-md-offset-11{margin-right:91.66666667%;margin-left:0}.col-md-offset-10{margin-right:83.33333333%;margin-left:0}.col-md-offset-9{margin-right:75%;margin-left:0}.col-md-offset-8{margin-right:66.66666667%;margin-left:0}.col-md-offset-7{margin-right:58.33333333%;margin-left:0}.col-md-offset-6{margin-right:50%;margin-left:0}.col-md-offset-5{margin-right:41.66666667%;margin-left:0}.col-md-offset-4{margin-right:33.33333333%;margin-left:0}.col-md-offset-3{margin-right:25%;margin-left:0}.col-md-offset-2{margin-right:16.66666667%;margin-left:0}.col-md-offset-1{margin-right:8.33333333%;margin-left:0}.col-md-offset-0{margin-right:0;margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:right}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{left:100%;right:auto}.col-lg-pull-11{left:91.66666667%;right:auto}.col-lg-pull-10{left:83.33333333%;right:auto}.col-lg-pull-9{left:75%;right:auto}.col-lg-pull-8{left:66.66666667%;right:auto}.col-lg-pull-7{left:58.33333333%;right:auto}.col-lg-pull-6{left:50%;right:auto}.col-lg-pull-5{left:41.66666667%;right:auto}.col-lg-pull-4{left:33.33333333%;right:auto}.col-lg-pull-3{left:25%;right:auto}.col-lg-pull-2{left:16.66666667%;right:auto}.col-lg-pull-1{left:8.33333333%;right:auto}.col-lg-pull-0{left:auto;right:auto}.col-lg-push-12{right:100%;left:0}.col-lg-push-11{right:91.66666667%;left:0}.col-lg-push-10{right:83.33333333%;left:0}.col-lg-push-9{right:75%;left:0}.col-lg-push-8{right:66.66666667%;left:0}.col-lg-push-7{right:58.33333333%;left:0}.col-lg-push-6{right:50%;left:0}.col-lg-push-5{right:41.66666667%;left:0}.col-lg-push-4{right:33.33333333%;left:0}.col-lg-push-3{right:25%;left:0}.col-lg-push-2{right:16.66666667%;left:0}.col-lg-push-1{right:8.33333333%;left:0}.col-lg-push-0{right:auto;left:0}.col-lg-offset-12{margin-right:100%;margin-left:0}.col-lg-offset-11{margin-right:91.66666667%;margin-left:0}.col-lg-offset-10{margin-right:83.33333333%;margin-left:0}.col-lg-offset-9{margin-right:75%;margin-left:0}.col-lg-offset-8{margin-right:66.66666667%;margin-left:0}.col-lg-offset-7{margin-right:58.33333333%;margin-left:0}.col-lg-offset-6{margin-right:50%;margin-left:0}.col-lg-offset-5{margin-right:41.66666667%;margin-left:0}.col-lg-offset-4{margin-right:33.33333333%;margin-left:0}.col-lg-offset-3{margin-right:25%;margin-left:0}.col-lg-offset-2{margin-right:16.66666667%;margin-left:0}.col-lg-offset-1{margin-right:8.33333333%;margin-left:0}.col-lg-offset-0{margin-right:0;margin-left:0}}caption{text-align:right}th{text-align:right}@media screen and (max-width:767px){.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-right:0;border-left:initial}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-left:0;border-right:initial}}.radio label,.checkbox label{padding-right:20px;padding-left:initial}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{margin-right:-20px;margin-left:auto}.radio-inline,.checkbox-inline{padding-right:20px;padding-left:0}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-right:10px;margin-left:0}.has-feedback .form-control{padding-left:42.5px;padding-right:12px}.form-control-feedback{left:0;right:auto}@media (min-width:768px){.form-inline label{padding-right:0;padding-left:initial}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{margin-right:0;margin-left:auto}}@media (min-width:768px){.form-horizontal .control-label{text-align:left}}.form-horizontal .has-feedback .form-control-feedback{left:15px;right:auto}.caret{margin-right:2px;margin-left:0}.dropdown-menu{right:0;left:auto;float:left;text-align:right}.dropdown-menu.pull-right{left:0;right:auto;float:right}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group>.btn,.btn-group-vertical>.btn{float:right}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-right:-1px;margin-left:0}.btn-toolbar{margin-right:-5px;margin-left:0}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:right}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-right:5px;margin-left:0}.btn-group>.btn:first-child{margin-right:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group{float:right}.btn-group.btn-group-justified>.btn,.btn-group.btn-group-justified>.btn-group{float:none}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:4px;border-bottom-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-top-left-radius:4px;border-bottom-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0}.btn .caret{margin-right:0}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-right:0}.input-group .form-control{float:right}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:first-child{border-left:0;border-right:1px solid}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:last-child{border-left-width:1px;border-left-style:solid;border-right:0}.input-group-btn>.btn+.btn{margin-right:-1px;margin-left:auto}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-left:-1px;margin-right:auto}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-right:-1px;margin-left:auto}.nav{padding-right:0;padding-left:initial}.nav-tabs>li{float:right}.nav-tabs>li>a{margin-left:auto;margin-right:-2px;border-radius:4px 4px 0 0}.nav-pills>li{float:right}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-right:2px;margin-left:auto}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-right:0;margin-left:auto}.nav-justified>.dropdown .dropdown-menu{right:auto}.nav-tabs-justified>li>a{margin-left:0;margin-right:auto}@media (min-width:768px){.nav-tabs-justified>li>a{border-radius:4px 4px 0 0}}@media (min-width:768px){.navbar-header{float:right}}.navbar-collapse{padding-right:15px;padding-left:15px}.navbar-brand{float:right}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-right:-15px;margin-left:auto}}.navbar-toggle{float:left;margin-left:15px;margin-right:auto}@media (max-width:767px){.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 25px 5px 15px}}@media (min-width:768px){.navbar-nav{float:right}.navbar-nav>li{float:right}}@media (min-width:768px){.navbar-left.flip{float:right!important}.navbar-right:last-child{margin-left:-15px;margin-right:auto}.navbar-right.flip{float:left!important;margin-left:-15px;margin-right:auto}.navbar-right .dropdown-menu{left:0;right:auto}}@media (min-width:768px){.navbar-text{float:right}.navbar-text.navbar-right:last-child{margin-left:0;margin-right:auto}}.pagination{padding-right:0}.pagination>li>a,.pagination>li>span{float:right;margin-right:-1px;margin-left:0}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{margin-right:-1px;border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-right:0;padding-left:initial}.pager .next>a,.pager .next>span{float:left}.pager .previous>a,.pager .previous>span{float:right}.nav-pills>li>a>.badge{margin-left:0;margin-right:3px}.list-group-item>.badge{float:left}.list-group-item>.badge+.badge{margin-left:5px;margin-right:auto}.alert-dismissable,.alert-dismissible{padding-left:35px;padding-right:15px}.alert-dismissable .close,.alert-dismissible .close{right:auto;left:-21px}.progress-bar{float:right}.media>.pull-left{margin-right:10px}.media>.pull-left.flip{margin-right:0;margin-left:10px}.media>.pull-right{margin-left:10px}.media>.pull-right.flip{margin-left:0;margin-right:10px}.media-right,.media>.pull-right{padding-right:10px;padding-left:initial}.media-left,.media>.pull-left{padding-left:10px;padding-right:initial}.media-list{padding-right:0;padding-left:initial;list-style:none}.list-group{padding-right:0;padding-left:initial}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-right-radius:3px;border-top-left-radius:0}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-left-radius:3px;border-top-right-radius:0}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px;border-top-right-radius:0}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px;border-top-left-radius:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-right:0;border-left:none}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:none;border-left:0}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object{right:0;left:auto}.close{float:left}.modal-footer{text-align:left}.modal-footer.flip{text-align:right}.modal-footer .btn+.btn{margin-left:auto;margin-right:5px}.modal-footer .btn-group .btn+.btn{margin-right:-1px;margin-left:auto}.modal-footer .btn-block+.btn-block{margin-right:0;margin-left:auto}.popover{left:auto;text-align:right}.popover.top>.arrow{right:50%;left:auto;margin-right:-11px;margin-left:auto}.popover.top>.arrow:after{margin-right:-10px;margin-left:auto}.popover.bottom>.arrow{right:50%;left:auto;margin-right:-11px;margin-left:auto}.popover.bottom>.arrow:after{margin-right:-10px;margin-left:auto}.carousel-control{right:0;bottom:0}.carousel-control.left{right:auto;left:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;right:auto;margin-right:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;left:auto;margin-left:-10px}.carousel-indicators{right:50%;left:0;margin-right:-30%;margin-left:0;padding-left:0}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:0;margin-right:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-left:0;margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}}.pull-right.flip{float:left!important}.pull-left.flip{float:right!important} \ No newline at end of file diff --git a/web/assets/stylesheets/bootstrap.css b/web/assets/stylesheets/bootstrap.css deleted file mode 100644 index 6b8c2562..00000000 --- a/web/assets/stylesheets/bootstrap.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - Glyphicons - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */.label,sub,sup{vertical-align:baseline}hr,img{border:0}body,figure{margin:0}.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu{float:left}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative}sup{top:-.5em}sub{bottom:-.25em}img{vertical-align:middle}svg:not(:root){overflow:hidden}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{blockquote,img,pre,tr{page-break-inside:avoid}*,:after,:before{background:0 0!important;color:#000!important;-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999}thead{display:table-header-group}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none}.img-thumbnail,body{background-color:#fff}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}dt,kbd kbd,label{font-weight:700}address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{padding-left:0;list-style:none}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}pre code,table{background-color:transparent}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}dl,ol,ul{margin-top:0}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}address,dl{margin-bottom:20px}ol,ul{margin-bottom:10px}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.container{width:750px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#777}legend,pre{display:block;color:#333}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}code,kbd{padding:2px 4px;font-size:90%}caption,th{text-align:left}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{font-style:normal}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none}pre{padding:9.5px;margin:0 0 10px;font-size:13px;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}.container,.container-fluid{margin-right:auto;margin-left:auto}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0}.container,.container-fluid{padding-left:15px;padding-right:15px}.pre-scrollable{overflow-y:scroll}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}caption{padding-top:8px;padding-bottom:8px;color:#777}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset,legend{padding:0;border:0}fieldset{margin:0;min-width:0}legend{width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}.form-control,output{font-size:14px;line-height:1.42857143;color:#555;display:block}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}output{padding-top:7px}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.form-group-sm .form-control,.input-sm{padding:5px 10px;border-radius:3px;font-size:12px}.input-sm{height:30px;line-height:1.5}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;line-height:1.5}.form-group-lg .form-control,.input-lg{border-radius:6px;padding:10px 16px;font-size:18px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;line-height:1.3333333}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;line-height:1.3333333}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block}.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle,.btn.active,.btn:active,.dropdown-toggle:focus,.navbar-toggle:focus,.open>a{outline:0}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#337ab7;font-weight:400;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu-right,.dropdown-menu.pull-right{right:0;left:auto}.dropdown-header,.dropdown-menu>li>a{display:block;padding:3px 20px;line-height:1.42857143;white-space:nowrap}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{clear:both;font-weight:400;color:#333}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.dropdown-menu-left{right:auto;left:0}.dropdown-header{font-size:12px;color:#777}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn .caret,.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn-lg .caret{border-width:5px 5px 0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.nav>li,.nav>li>a{display:block;position:relative}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li>a{padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-justified>li,.nav-stacked>li{float:none}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.navbar{border-radius:4px}.navbar-header{float:left}.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}.navbar-static-top{border-radius:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}@media (min-width:768px){.navbar-toggle{display:none}.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px}@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block}.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.breadcrumb>li,.pagination{display:inline-block}.btn .badge,.btn .label{top:-1px;position:relative}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#777}.pagination{padding-left:0;margin:20px 0;border-radius:4px}.pager li,.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#337ab7;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.badge,.label{font-weight:700;line-height:1;white-space:nowrap;text-align:center}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;background-color:#fff;cursor:not-allowed}a.badge:focus,a.badge:hover,a.label:focus,a.label:hover{color:#fff;cursor:pointer;text-decoration:none}.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff;border-radius:.25em}.label:empty{display:none}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;color:#fff;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.media-object,.thumbnail{display:block}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.center-block,.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.alert,.thumbnail{margin-bottom:20px}.alert .alert-link,.close{font-weight:700}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-striped .progress-bar-info,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#eee;color:#777;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;font-size:16px}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:3px;border-bottom-right-radius:3px}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}.visible-xs-block{display:block!important}.visible-xs-inline{display:inline!important}.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}.visible-sm-block{display:block!important}.visible-sm-inline{display:inline!important}.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}.visible-md-block{display:block!important}.visible-md-inline{display:inline!important}.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}.visible-lg-block{display:block!important}.visible-lg-inline{display:inline!important}.visible-lg-inline-block{display:inline-block!important}.hidden-lg{display:none!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}.hidden-print{display:none!important}} \ No newline at end of file diff --git a/web/locales/ar.yml b/web/locales/ar.yml deleted file mode 100644 index a9143ac4..00000000 --- a/web/locales/ar.yml +++ /dev/null @@ -1,87 +0,0 @@ -# elements like %{queue} are variables and should not be translated -ar: - TextDirection: 'rtl' - Dashboard: لوحة التحكم - Status: حالة - Time: وقت - Namespace: مساحة الاسم - Realtime: الزمن الفعلي - History: تاريخ - Busy: مشغول - Utilization: الاستخدام - Processed: تمت المعالجة - Failed: فشل - Scheduled: مجدول - Retries: إعادة محاولة - Enqueued: في الرتل - Worker: عامل - LivePoll: استعلام مباشر - StopPolling: إيقاف الاستعلامات - Queue: رتل - Class: نوع - Job: وظيفة - Arguments: مدخلات - Extras: إضافات - Started: بدأت - ShowAll: عرض الكل - CurrentMessagesInQueue: الرسائل الحالية في الرتل %{queue} - AddToQueue: إضافة إلى الرتل - AreYouSureDeleteJob: هل أنت متأكد من حذف الوظيفة؟ - AreYouSureDeleteQueue: هل أنت متأكد من حذف الرتل %{queue}؟ - Delete: حذف - Queues: أرتال - Size: حجم - Actions: إجراءات - NextRetry: إعادة المحاولة القادمة - RetryCount: عدد مرات إعادة المحاولة - RetryNow: إعادة المحاولة الآن - Kill: إبطال - LastRetry: إعادة المحاولة الأخيرة - OriginallyFailed: فشل أصلا - AreYouSure: هل انت متأكد؟ - DeleteAll: حذف الكل - RetryAll: إعادة المحاولة للكل - KillAll: إبطال الكل - NoRetriesFound: لاتوجد أي إعادة محاولة - Error: خطأ - ErrorClass: نوع الخطأ - ErrorMessage: رسالة الخطأ - ErrorBacktrace: تتبع الخطأ - GoBack: إلى الخلف - NoScheduledFound: لايوجد وظائف مجدولة - When: متى - ScheduledJobs: وظائف مجدولة - idle: خامل - active: نشيط - Version: إصدار - Connections: اتصالات - MemoryUsage: استخدام الذاكرة - PeakMemoryUsage: ذروة استخدام الذاكرة - Uptime: زمن العمل - OneWeek: أسبوع - OneMonth: شهر - ThreeMonths: ثلاثة أشهر - SixMonths: ستة أشهر - Failures: فشل - DeadJobs: وظائف ميتة - NoDeadJobsFound: لاتوجد وظائف ميتة - Dead: ميتة - Process: عملية - Processes: عمليات - Name: الاسم - Thread: نيسب - Threads: نياسب - Jobs: وظائف - Paused: موقفة مؤقتاً - Stop: إيقاف - Quiet: هدوء - StopAll: إيقاف الكل - QuietAll: هدوء الكل - PollingInterval: الفاصل الزمني بين الاستعلامات - Plugins: الإضافات - NotYetEnqueued: لم تدخل الرتل بعد - CreatedAt: أنشئت في - BackToApp: العودة إلى التطبيق - Latency: زمن الانتظار - Pause: إيقاف مؤقت - Unpause: متابعة \ No newline at end of file diff --git a/web/locales/cs.yml b/web/locales/cs.yml deleted file mode 100644 index d258b30c..00000000 --- a/web/locales/cs.yml +++ /dev/null @@ -1,78 +0,0 @@ -# elements like %{queue} are variables and should not be translated -cs: - Dashboard: Kontrolní panel - Status: Stav - Time: Čas - Namespace: Jmenný prostor - Realtime: V reálném čase - History: Historie - Busy: Zaneprázdněné - Processed: Zpracované - Failed: Nezdařené - Scheduled: Naplánované - Retries: Opakování - Enqueued: Zařazené - Worker: Worker - LivePoll: Průběžně aktualizovat - StopPolling: Zastavit průběžnou aktualizaci - Queue: Fronta - Class: Třída - Job: Úkol - Arguments: Argumenty - Extras: Doplňky - Started: Začal - ShowAll: Ukázat všechny - CurrentMessagesInQueue: Aktuální úkoly ve frontě %{queue} - Delete: Odstranit - AddToQueue: Přidat do fronty - AreYouSureDeleteJob: Jste si jisti, že chcete odstranit tento úkol? - AreYouSureDeleteQueue: Jste si jisti, že chcete odstranit frontu %{queue}? - Queues: Fronty - Size: Velikost - Actions: Akce - NextRetry: Další opakování - RetryCount: Počet opakování - RetryNow: Opakovat teď - Kill: Zabít - LastRetry: Poslední opakování - OriginallyFailed: Původně se nezdařilo - AreYouSure: Jste si jisti? - DeleteAll: Odstranit vše - RetryAll: Opakovat vše - NoRetriesFound: Nebyla nalezena žádná opakování - Error: Chyba - ErrorClass: Třída chyby - ErrorMessage: Chybová zpráva - ErrorBacktrace: Chybový výstup - GoBack: ← Zpět - NoScheduledFound: Nebyly nalezeny žádné naplánované úkoly - When: Kdy - ScheduledJobs: Naplánované úkoly - idle: nečinný - active: aktivní - Version: Verze - Connections: Připojení - MemoryUsage: Využití paměti - PeakMemoryUsage: Nejvyšší využití paměti - Uptime: Uptime (dny) - OneWeek: 1 týden - OneMonth: 1 měsíc - ThreeMonths: 3 měsíce - SixMonths: 6 měsíců - Failures: Selhání - DeadJobs: Mrtvé úkoly - NoDeadJobsFound: Nebyly nalezeny žádné mrtvé úkoly - Dead: Mrtvé - Processes: Procesy - Thread: Vlákno - Threads: Vlákna - Jobs: Úkoly - Paused: Pozastavené - Stop: Zastavit - Quiet: Ztišit - StopAll: Zastavit vše - QuietAll: Ztišit vše - PollingInterval: Interval obnovení - Plugins: Doplňky - NotYetEnqueued: Ještě nezařazeno - CreatedAt: Vytvořeno diff --git a/web/locales/da.yml b/web/locales/da.yml deleted file mode 100644 index 0ec298c0..00000000 --- a/web/locales/da.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -da: - Dashboard: Instrumentbræt - Status: Status - Time: Tid - Namespace: Namespace - Realtime: Real-time - History: Historik - Busy: Travl - Processed: Processeret - Failed: Fejlet - Scheduled: Planlagt - Retries: Forsøg - Enqueued: I kø - Worker: Arbejder - LivePoll: Live Poll - StopPolling: Stop Polling - Queue: Kø - Class: Klasse - Job: Job - Arguments: Argumenter - Extras: Ekstra - Started: Startet - ShowAll: Vis alle - CurrentMessagesInQueue: Nuværende beskeder i %{queue} - Delete: Slet - AddToQueue: Tilføj til kø - AreYouSureDeleteJob: Er du sikker på at du vil slette dette job? - AreYouSureDeleteQueue: Er du sikker på at du vil slette %{queue} køen? - Queues: Køer - Size: Størrelse - Actions: Actions - NextRetry: Næste forsøg - RetryCount: Antal forsøg - RetryNow: Prøv igen nu - LastRetry: Sidste forsøg - OriginallyFailed: Oprindeligt fejlet - AreYouSure: Er du sikker? - DeleteAll: Slet alle - RetryAll: Forsøg alle - NoRetriesFound: Ingen gen-forsøg var fundet - Error: Fejl - ErrorClass: Fejl klasse - ErrorMessage: Fejl besked - ErrorBacktrace: Fejl backtrace - GoBack: ← Tilbage - NoScheduledFound: Ingen jobs i kø fundet - When: Når - ScheduledJobs: Jobs i kø - idle: idle - active: aktiv - Version: Version - Connections: Forbindelser - MemoryUsage: RAM forbrug - PeakMemoryUsage: Peak RAM forbrug - Uptime: Oppetid (dage) - OneWeek: 1 uge - OneMonth: 1 måned - ThreeMonths: 3 måneder - SixMonths: 6 måneder - Failures: Fejl - DeadJobs: Døde jobs - NoDeadJobsFound: Ingen døde jobs fundet - Dead: Død - Processes: Processer - Thread: Tråd - Threads: Tråde - Jobs: Jobs diff --git a/web/locales/de.yml b/web/locales/de.yml deleted file mode 100644 index 2caad4ae..00000000 --- a/web/locales/de.yml +++ /dev/null @@ -1,81 +0,0 @@ -# elements like %{queue} are variables and should not be translated -de: - Dashboard: Dashboard - Status: Status - Time: Zeit - Namespace: Namensraum - Realtime: Echtzeit - History: Verlauf - Busy: Beschäftigt - Processed: Verarbeitet - Failed: Fehlgeschlagen - Scheduled: Geplant - Retries: Versuche - Enqueued: In der Warteschlange - Worker: Arbeiter - LivePoll: Echtzeitabfrage - StopPolling: Abfrage stoppen - Queue: Warteschlange - Class: Klasse - Job: Job - Extras: Extras - Arguments: Argumente - Started: Gestartet - ShowAll: Alle anzeigen - CurrentMessagesInQueue: Aktuelle Nachrichten in %{queue} - Delete: Löschen - AddToQueue: In Warteschlange einreihen - AreYouSureDeleteJob: Möchtest du diesen Job wirklich löschen? - AreYouSureDeleteQueue: Möchtest du %{queue} wirklich löschen? - Queues: Warteschlangen - Size: Größe - Actions: Aktionen - NextRetry: Nächster Versuch - RetryCount: Anzahl der Versuche - RetryNow: Jetzt erneut versuchen - Kill: Vernichten - LastRetry: Letzter Versuch - OriginallyFailed: Ursprünglich fehlgeschlagen - AreYouSure: Bist du sicher? - DeleteAll: Alle löschen - RetryAll: Alle erneut versuchen - KillAll: Alle vernichten - NoRetriesFound: Keine erneuten Versuche gefunden - Error: Fehler - ErrorClass: Fehlerklasse - ErrorMessage: Fehlernachricht - ErrorBacktrace: Fehlerbericht - GoBack: ← Zurück - NoScheduledFound: Keine geplanten Jobs gefunden - When: Wann - ScheduledJobs: Jobs in der Warteschlange - idle: untätig - active: aktiv - Version: Version - Connections: Verbindungen - MemoryUsage: RAM-Nutzung - PeakMemoryUsage: Maximale RAM-Nutzung - Uptime: Laufzeit - OneWeek: 1 Woche - OneMonth: 1 Monat - ThreeMonths: 3 Monate - SixMonths: 6 Monate - Failures: Ausfälle - DeadJobs: Gestorbene Jobs - NoDeadJobsFound: Keine toten Jobs gefunden - Dead: Tot - Processes: Prozesse - Thread: Thread - Threads: Threads - Jobs: Jobs - Paused: Pausiert - Stop: Stopp - Quiet: Leise - StopAll: Alle stoppen - QuietAll: Alle leise - PollingInterval: Abfrageintervall - Plugins: Erweiterungen - NotYetEnqueued: Noch nicht in der Warteschlange - CreatedAt: Erstellt - BackToApp: Zurück zur Anwendung - Latency: Latenz diff --git a/web/locales/el.yml b/web/locales/el.yml deleted file mode 100644 index 65d38f5e..00000000 --- a/web/locales/el.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -el: # <---- change this to your locale code - Dashboard: Πίνακας Ελέγχου - Status: Κατάσταση - Time: Χρόνος - Namespace: Namespace - Realtime: Τρέχουσα Κατάσταση - History: Ιστορικό - Busy: Απασχολημένο - Processed: Επεξεργάστηκε - Failed: Απέτυχε - Scheduled: Προγραματίστηκε - Retries: Προσπάθειες - Enqueued: Μπήκαν στην στοίβα - Worker: Εργάτης - LivePoll: Τρέχουσα Κατάσταση - StopPolling: Διακοπή Τρέχουσας Κατάστασης - Queue: Στοίβα - Class: Κλάση - Job: Εργασία - Arguments: Ορίσματα - Extras: Extras - Started: Ξεκίνησαν - ShowAll: Εμφάνιση Όλων - CurrentMessagesInQueue: Τρέχουσες εργασίες %{queue} - Delete: Διαγραφή - AddToQueue: Προσθήκη στην στοίβα - AreYouSureDeleteJob: Θέλετε να διαγράψετε την εργασία αυτη; - AreYouSureDeleteQueue: Θέλετε να διαγράψετε την %{queue} στοίβα? - Queues: Στοίβες - Size: Μέγεθος - Actions: Ενέργειες - NextRetry: Επόμενη προσπάθεια - RetryCount: Αριθμός προσπαθειών - RetryNow: Προσπάθησε τώρα - LastRetry: Τελευταία προσπάθεια - OriginallyFailed: Αρχικές Αποτυχίες - AreYouSure: Είστε σίγουρος? - DeleteAll: Διαγραφή όλων - RetryAll: Επανάληψη Όλων - NoRetriesFound: Δεν βρέθηκαν προσπάθειες - Error: Σφάλμα - ErrorClass: Κλάση σφάλματος - ErrorMessage: Μήνυμα Σφάλματος - ErrorBacktrace: Σφάλμα Backtrace - GoBack: ← Πίσω - NoScheduledFound: Δεν βρέθηκαν προγραμματισμένες εργασίες - When: Πότε - ScheduledJobs: Προγραμματισμένες Εργασίες - idle: αδρανής - active: ενεργή - Version: Έκδοση - Connections: Συνδέσεις - MemoryUsage: Χρήση Μνήμης - PeakMemoryUsage: Μέγιστη Χρήση Μνήμης - Uptime: Διάρκεια Λειτουργείας (ημέρες) - OneWeek: 1 εβδομάδα - OneMonth: 1 μήνας - ThreeMonths: 3 μήνες - SixMonths: 6 μήνες - Failures: Αποτυχίες - DeadJobs: Αδρανείς Εργασίες - NoDeadJobsFound: Δεν βρέθηκαν αδρανείς εργασίες - Dead: Αδρανείς - Processes: Διεργασίες - Thread: Νήμα - Threads: Νήματα - Jobs: Εργασίες diff --git a/web/locales/en.yml b/web/locales/en.yml deleted file mode 100644 index 35159eab..00000000 --- a/web/locales/en.yml +++ /dev/null @@ -1,86 +0,0 @@ -# elements like %{queue} are variables and should not be translated -en: # <---- change this to your locale code - Dashboard: Dashboard - Status: Status - Time: Time - Namespace: Namespace - Realtime: Real-time - History: History - Busy: Busy - Utilization: Utilization - Processed: Processed - Failed: Failed - Scheduled: Scheduled - Retries: Retries - Enqueued: Enqueued - Worker: Worker - LivePoll: Live Poll - StopPolling: Stop Polling - Queue: Queue - Class: Class - Job: Job - Arguments: Arguments - Extras: Extras - Started: Started - ShowAll: Show All - CurrentMessagesInQueue: Current jobs in %{queue} - Delete: Delete - AddToQueue: Add to queue - AreYouSureDeleteJob: Are you sure you want to delete this job? - AreYouSureDeleteQueue: Are you sure you want to delete the %{queue} queue? This will delete all jobs within the queue, it will reappear if you push more jobs to it in the future. - Queues: Queues - Size: Size - Actions: Actions - NextRetry: Next Retry - RetryCount: Retry Count - RetryNow: Retry Now - Kill: Kill - LastRetry: Last Retry - OriginallyFailed: Originally Failed - AreYouSure: Are you sure? - DeleteAll: Delete All - RetryAll: Retry All - KillAll: Kill All - NoRetriesFound: No retries were found - Error: Error - ErrorClass: Error Class - ErrorMessage: Error Message - ErrorBacktrace: Error Backtrace - GoBack: ← Back - NoScheduledFound: No scheduled jobs were found - When: When - ScheduledJobs: Scheduled Jobs - idle: idle - active: active - Version: Version - Connections: Connections - MemoryUsage: Memory Usage - PeakMemoryUsage: Peak Memory Usage - Uptime: Uptime (days) - OneWeek: 1 week - OneMonth: 1 month - ThreeMonths: 3 months - SixMonths: 6 months - Failures: Failures - DeadJobs: Dead Jobs - NoDeadJobsFound: No dead jobs were found - Dead: Dead - Process: Process - Processes: Processes - Name: Name - Thread: Thread - Threads: Threads - Jobs: Jobs - Paused: Paused - Stop: Stop - Quiet: Quiet - StopAll: Stop All - QuietAll: Quiet All - PollingInterval: Polling interval - Plugins: Plugins - NotYetEnqueued: Not yet enqueued - CreatedAt: Created At - BackToApp: Back to App - Latency: Latency - Pause: Pause - Unpause: Unpause diff --git a/web/locales/es.yml b/web/locales/es.yml deleted file mode 100644 index 1f0ed522..00000000 --- a/web/locales/es.yml +++ /dev/null @@ -1,86 +0,0 @@ -# elements like %{queue} are variables and should not be translated -es: - Dashboard: Panel de Control - Status: Estatus - Time: Tiempo - Namespace: Espacio de Nombre - Realtime: Tiempo Real - History: Historial - Busy: Ocupado - Utilization: Utilización - Processed: Procesadas - Failed: Fallidas - Scheduled: Programadas - Retries: Reintentos - Enqueued: En Cola - Worker: Trabajador - LivePoll: Sondeo en Vivo - StopPolling: Detener Sondeo - Queue: Cola - Class: Clase - Job: Trabajo - Arguments: Argumentos - Extras: Extras - Started: Hora de Inicio - ShowAll: Mostrar Todo - CurrentMessagesInQueue: Mensajes actualmente en %{queue} - Delete: Eliminar - AddToQueue: Añadir a la cola - AreYouSureDeleteJob: ¿Estás seguro de eliminar este trabajo? - AreYouSureDeleteQueue: ¿Estás seguro de eliminar la cola %{queue}? - Queues: Colas - Size: Tamaño - Actions: Acciones - NextRetry: Siguiente Intento - RetryCount: Numero de Reintentos - RetryNow: Reintentar Ahora - Kill: Matar - LastRetry: Último Reintento - OriginallyFailed: Falló Originalmente - AreYouSure: ¿Estás seguro? - DeleteAll: Borrar Todo - RetryAll: Reintentar Todo - KillAll: Matar Todo - NoRetriesFound: No se encontraron reintentos - Error: Error - ErrorClass: Clase del Error - ErrorMessage: Mensaje de Error - ErrorBacktrace: Trazado del Error - GoBack: ← Regresar - NoScheduledFound: No se encontraron trabajos pendientes - When: Cuando - ScheduledJobs: Trabajos programados - idle: inactivo - active: activo - Version: Versión - Connections: Conexiones - MemoryUsage: Uso de Memoria - PeakMemoryUsage: Máximo Uso de Memoria - Uptime: Tiempo de Funcionamiento (días) - OneWeek: 1 semana - OneMonth: 1 mes - ThreeMonths: 3 meses - SixMonths: 6 meses - Failures: Fallas - DeadJobs: Trabajos muertos - NoDeadJobsFound: No hay trabajos muertos - Dead: Muerto - Process: Proceso - Processes: Procesos - Name: Nombre - Thread: Hilo - Threads: Hilos - Jobs: Trabajos - Paused: Pausado - Stop: Detener - Quiet: Silenciar - StopAll: Detener Todo - QuietAll: Silenciar Todo - PollingInterval: Intervalo de Sondeo - Plugins: Plugins - NotYetEnqueued: Aún no en cola - CreatedAt: Creado en - BackToApp: Volver a la Aplicación - Latency: Latencia - Pause: Pausar - Unpause: Reanudar diff --git a/web/locales/fa.yml b/web/locales/fa.yml deleted file mode 100644 index f3ec44e2..00000000 --- a/web/locales/fa.yml +++ /dev/null @@ -1,80 +0,0 @@ -# elements like %{queue} are variables and should not be translated -fa: # <---- change this to your locale code - TextDirection: 'rtl' - Dashboard: داشبورد - Status: اعلان - Time: رمان - Namespace: فضای نام - Realtime: زنده - History: تاریخچه - Busy: مشغول - Processed: پردازش شده - Failed: ناموفق - Scheduled: زمان بندی - Retries: تکرار - Enqueued: صف بندی نشدند - Worker: کارگزار - LivePoll: Live Poll - StopPolling: Stop Polling - Queue: صف - Class: کلاس - Job: کار - Arguments: آرگومنت - Extras: اضافی - Started: شروع شده - ShowAll: نمایش همه - CurrentMessagesInQueue: کار فعلی در %{queue} - Delete: حذف - AddToQueue: افزودن به صف - AreYouSureDeleteJob: آیا شما مطمعن هستید از حذف این کار ؟ - AreYouSureDeleteQueue: ایا شما مطمعنید از حذف %{queue} ? - Queues: صف ها - Size: سایز - Actions: اعمال - NextRetry: بار دیگر تلاش کنید - RetryCount: تعداد تلاش ها - RetryNow: تلاش مجدد - Kill: کشتن - LastRetry: آخرین تلاش - OriginallyFailed: Originally Failed - AreYouSure: آیا مطمعن هستید? - DeleteAll: حذف همه - RetryAll: تلاش مجدد برای همه - NoRetriesFound: هیچ تلاش پیدا نشد - Error: خطا - ErrorClass: خطا کلاس - ErrorMessage: پیغام خطا - ErrorBacktrace: خطای معکوس - GoBack: ← برگشت - NoScheduledFound: هیچ کار برنامه ریزی شده ای یافت نشد - When: وقتی که - ScheduledJobs: کار برنامه ریزی شده - idle: بیهودی - active: فعال - Version: ورژن - Connections: ارتباطات - MemoryUsage: حافظه استفاده شده - PeakMemoryUsage: اوج حافظه استفاده شده - Uptime: آپ تایم (روز) - OneWeek: ۱ هفته - OneMonth: ۱ ماه - ThreeMonths: ۳ ماه - SixMonths: ۶ ماه - Failures: شکست ها - DeadJobs: کار مرده - NoDeadJobsFound: کار مرده ای یافت نشد - Dead: مرده - Processes: پردازش ها - Thread: رشته - Threads: رشته ها - Jobs: کار ها - Paused: مکث - Stop: توقف - Quiet: خروج - StopAll: توقف همه - QuietAll: خروج همه - PollingInterval: Polling interval - Plugins: پلاگین ها - NotYetEnqueued: بدون صف بندی - CreatedAt: ساخته شده در - BackToApp: برگشت به برنامه diff --git a/web/locales/fr.yml b/web/locales/fr.yml deleted file mode 100644 index db402b56..00000000 --- a/web/locales/fr.yml +++ /dev/null @@ -1,85 +0,0 @@ -# elements like %{queue} are variables and should not be translated -fr: - Dashboard: Tableau de Bord - Status: État - Time: Heure - Namespace: Namespace - Realtime: Temps réel - History: Historique - Busy: En cours - Utilization: Utilisation - Processed: Traitées - Failed: Échouées - Scheduled: Planifiées - Retries: Tentatives - Enqueued: En attente - Worker: Travailleur - LivePoll: Temps réel - StopPolling: Arrêt du temps réel - Queue: Queue - Class: Classe - Job: Tâche - Arguments: Arguments - Extras: Extras - Started: Démarrée - ShowAll: Tout montrer - CurrentMessagesInQueue: Messages actuellement dans %{queue} - Delete: Supprimer - AddToQueue: Ajouter à la queue - AreYouSureDeleteJob: Êtes-vous certain de vouloir supprimer cette tâche ? - AreYouSureDeleteQueue: Êtes-vous certain de vouloir supprimer la queue %{queue} ? - Queues: Queues - Size: Taille - Actions: Actions - NextRetry: Prochain essai - RetryCount: Nombre d'essais - RetryNow: Réessayer maintenant - Kill: Tuer - LastRetry: Dernier essai - OriginallyFailed: Échec initial - AreYouSure: Êtes-vous certain ? - DeleteAll: Tout supprimer - RetryAll: Tout réessayer - KillAll: Tout tuer - NoRetriesFound: Aucune tâche à réessayer n’a été trouvée - Error: Erreur - ErrorClass: Classe d’erreur - ErrorMessage: Message d’erreur - ErrorBacktrace: Backtrace d’erreur - GoBack: ← Retour - NoScheduledFound: Aucune tâche planifiée n'a été trouvée - When: Quand - ScheduledJobs: Tâches planifiées - idle: inactif - active: actif - Version: Version - Connections: Connexions - MemoryUsage: Mémoire utilisée - PeakMemoryUsage: Mémoire utilisée (max.) - Uptime: Uptime (jours) - OneWeek: 1 semaine - OneMonth: 1 mois - ThreeMonths: 3 mois - SixMonths: 6 mois - Failures: Echecs - DeadJobs: Tâches mortes - NoDeadJobsFound: Aucune tâche morte n'a été trouvée - Dead: Mortes - Process: Processus - Processes: Processus - Thread: Thread - Threads: Threads - Jobs: Tâches - Paused: Mise en pause - Stop: Arrêter - Quiet: Clore - StopAll: Tout arrêter - QuietAll: Tout clore - PollingInterval: Intervalle de rafraîchissement - Plugins: Plugins - NotYetEnqueued: Pas encore en file d'attente - CreatedAt: Créée le - Back to App: Retour à l'application - Latency: Latence - Pause: Pause - Unpause: Unpause diff --git a/web/locales/he.yml b/web/locales/he.yml deleted file mode 100644 index 6555c6c7..00000000 --- a/web/locales/he.yml +++ /dev/null @@ -1,79 +0,0 @@ -he: - TextDirection: 'rtl' - Dashboard: לוח מחוונים - Status: מצב - Time: שעה - Namespace: מרחב שם - Realtime: זמן אמת - History: היסטוריה - Busy: עסוקים - Processed: עובדו - Failed: נכשלו - Scheduled: מתוכננים - Retries: נסיונות חוזרים - Enqueued: בתור - Worker: עובד - LivePoll: תשאול חי - StopPolling: עצור תשאול - Queue: תור - Class: מחלקה - Job: עבודה - Arguments: ארגומנטים - Extras: תוספות - Started: הותחלו - ShowAll: הצג את הכל - CurrentMessagesInQueue: עבודות נוכחיות בתור %{queue} - Delete: מחק - AddToQueue: הוסף לתור - AreYouSureDeleteJob: האם אתם בטוחים שברצונכם למחוק את העבודה הזאת? - AreYouSureDeleteQueue: האם אתם בטוחים שברצונכם למחוק את התור %{queue}? - Queues: תורים - Size: אורך - Actions: פעולות - NextRetry: ניסיון חוזר הבא - RetryCount: מספר נסיונות חוזרים - RetryNow: נסה שוב עכשיו - Kill: הרוג - LastRetry: ניסיון חוזר אחרון - OriginallyFailed: נכשל בניסיון הראשון - AreYouSure: אתם בטוחים? - DeleteAll: מחק הכל - RetryAll: נסה שוב את הכל - NoRetriesFound: לא נמצאו נסיונות חוזרים - Error: שגיאה - ErrorClass: סוג השגיאה - ErrorMessage: הודעת השגיאה - ErrorBacktrace: מעקב לאחור של השגיאה - GoBack: ← אחורה - NoScheduledFound: לא נמצאו עבודות מתוכננות - When: מתי - ScheduledJobs: עבודות מתוכננות - idle: במנוחה - active: פעיל - Version: גירסה - Connections: חיבורים - MemoryUsage: שימוש בזיכרון - PeakMemoryUsage: שיא השימוש בזיכרון - Uptime: זמן פעילות (ימים) - OneWeek: שבוע 1 - OneMonth: חודש 1 - ThreeMonths: 3 חדשים - SixMonths: 6 חדשים - Failures: כשלונות - DeadJobs: עבודות מתות - NoDeadJobsFound: לא נמצאו עבודות מתות - Dead: מתים - Processes: תהליכים - Thread: חוט - Threads: חוטים - Jobs: עבודות - Paused: הופסקו - Stop: עצור - Quiet: שקט - StopAll: עצור הכל - QuietAll: השקט את כולם - PollingInterval: מרווח זמן בין תשאולים - Plugins: תוספים - NotYetEnqueued: עוד לא בתור - CreatedAt: נוצר ב - BackToApp: חזרה לאפליקציה diff --git a/web/locales/hi.yml b/web/locales/hi.yml deleted file mode 100644 index 65c446c9..00000000 --- a/web/locales/hi.yml +++ /dev/null @@ -1,75 +0,0 @@ -# elements like %{queue} are variables and should not be translated -hi: - Dashboard: डैशबोर्ड - Status: स्थिती - Time: समय - Namespace: नामस्थान - Realtime: रिअल टाईम - History: वृत्तान्त - Busy: व्यस्थ - Processed: कार्रवाई कृत - Failed: असफल - Scheduled: परिगणित - Retries: पुनर्प्रयास - Enqueued: कतारबद्ध - Worker: वर्कर - LivePoll: लाईव सर्वेक्षण - StopPolling: सर्वेक्षण रोको - Queue: कतार - Class: क्लास - Job: कार्य - Arguments: अर्गुमेन्ट्स् - Extras: अतिरिक्त - Started: शुरु हुआ - ShowAll: सब दिखाएं - CurrentMessagesInQueue: %{queue} कतार मे वर्तमान कार्य - Delete: हटाओ - AddToQueue: कतार मे जोड़ें - AreYouSureDeleteJob: क्या आप इस कार्य को हटाना चाहते है? - AreYouSureDeleteQueue: क्या आप %{queue} कतार को हटाना चाहते है? - Queues: कतारे - Size: आकार - Actions: कार्रवाई - NextRetry: अगला पुन:प्रयास - RetryCount: पुन:प्रयास संख्या - RetryNow: पुन:प्रयास करे - Kill: नष्ट करे - LastRetry: अंतिम पुन:प्रयास - OriginallyFailed: पहिले से विफल - AreYouSure: क्या आपको यकीन है? - DeleteAll: सब हटाओ - RetryAll: सब पुन:प्रयास करे - NoRetriesFound: कोई पुनर्प्रयास नही पाए गए - Error: एरर - ErrorClass: एरर क्लास - ErrorMessage: एरर संदेश - ErrorBacktrace: एरर बैकट्रेस - GoBack: ← पीछे - NoScheduledFound: कोई परिगणित कार्य नही पाए गए - When: कब - ScheduledJobs: परिगणित कार्य - idle: निष्क्रिय - active: सक्रिय - Version: वर्जन - Connections: कनेक्श्न - MemoryUsage: मेमरी उपयोग - PeakMemoryUsage: अधिकतम मेमरी उपयोग - Uptime: उपरिकाल (दिवस) - OneWeek: १ सप्ताह - OneMonth: १ महीना - ThreeMonths: ३ महीने - SixMonths: ६ महीने - Failures: असफलता - DeadJobs: निष्प्राण कार्य - NoDeadJobsFound: कोई निष्प्राण कार्य नही पाए गए - Dead: निष्प्राण - Processes: प्रोसेसेस् - Thread: थ्रेड - Threads: थ्रेड्स् - Jobs: कार्य - Paused: थमे हुए - Stop: रोको - Quiet: शांत करो - StopAll: सब रोको - QuietAll: सब शांत करो - PollingInterval: सर्वेक्षण अंतराल diff --git a/web/locales/it.yml b/web/locales/it.yml deleted file mode 100644 index a2b24d5e..00000000 --- a/web/locales/it.yml +++ /dev/null @@ -1,69 +0,0 @@ -# elements like %{queue} are variables and should not be translated -it: - Dashboard: Dashboard - Status: Stato - Time: Ora - Namespace: Namespace - Realtime: Tempo reale - History: Storia - Busy: Occupato - Processed: Processato - Failed: Fallito - Scheduled: Pianificato - Retries: Nuovi tentativi - Enqueued: In coda - Worker: Lavoratore - LivePoll: Live poll - StopPolling: Ferma il polling - Queue: Coda - Class: Classe - Job: Lavoro - Arguments: Argomenti - Extras: Extra - Started: Iniziato - ShowAll: Mostra tutti - CurrentMessagesInQueue: Messaggi in %{queue} - Delete: Cancella - AddToQueue: Aggiungi alla coda - AreYouSureDeleteJob: Sei sicuro di voler cancellare questo lavoro? - AreYouSureDeleteQueue: Sei sicuro di voler cancellare la coda %{queue}? - Queues: Code - Size: Dimensione - Actions: Azioni - NextRetry: Prossimo tentativo - RetryCount: Totale tentativi - RetryNow: Riprova - Kill: Uccidere - LastRetry: Ultimo tentativo - OriginallyFailed: Primo fallimento - AreYouSure: Sei sicuro? - DeleteAll: Cancella tutti - RetryAll: Riprova tutti - NoRetriesFound: Non sono stati trovati nuovi tentativi - Error: Errore - ErrorClass: Classe dell'errore - ErrorMessage: Messaggio di errore - ErrorBacktrace: Backtrace dell'errore - GoBack: ← Indietro - NoScheduledFound: Non ci sono lavori pianificati - When: Quando - ScheduledJobs: Lavori pianificati - idle: inattivo - active: attivo - Version: Versione - Connections: Connessioni - MemoryUsage: Memoria utilizzata - PeakMemoryUsage: Memoria utilizzata (max.) - Uptime: Uptime (giorni) - OneWeek: 1 settimana - OneMonth: 1 mese - ThreeMonths: 3 mesi - SixMonths: 6 mesi - Failures: Fallimenti - DeadJobs: Lavori arrestati - NoDeadJobsFound: Non ci sono lavori arrestati - Dead: Arrestato - Processes: Processi - Thread: Thread - Threads: Thread - Jobs: Lavori diff --git a/web/locales/ja.yml b/web/locales/ja.yml deleted file mode 100644 index 9d7ad260..00000000 --- a/web/locales/ja.yml +++ /dev/null @@ -1,86 +0,0 @@ -# elements like %{queue} are variables and should not be translated -ja: - Dashboard: ダッシュボード - Status: 状態 - Time: 時間 - Namespace: ネームスペース - Realtime: リアルタイム - History: 履歴 - Busy: 実行中 - Utilization: 使用率 - Processed: 完了 - Failed: 失敗 - Scheduled: 予定 - Retries: 再試行 - Enqueued: 待機状態 - Worker: 動作中の作業 - LivePoll: ポーリング開始 - StopPolling: ポーリング停止 - Queue: キュー - Class: クラス - Job: ジョブ - Arguments: 引数 - Extras: エクストラ - Started: 開始 - ShowAll: 全て見せる - CurrentMessagesInQueue: %{queue}に メッセージがあります - Delete: 削除 - AddToQueue: キューに追加 - AreYouSureDeleteJob: このジョブを削除しますか? - AreYouSureDeleteQueue: この %{queue} キューを削除しますか? - Queues: キュー - Size: サイズ - Actions: アクション - NextRetry: 再試行 - RetryCount: 再試行 - RetryNow: 今すぐ再試行 - Kill: 強制終了 - LastRetry: 再試行履歴 - OriginallyFailed: 失敗 - AreYouSure: よろしいですか? - DeleteAll: 全て削除 - RetryAll: 全て再試行 - KillAll: 全て強制終了 - NoRetriesFound: 再試行するジョブはありません - Error: エラー - ErrorClass: エラークラス - ErrorMessage: エラーメッセージ - ErrorBacktrace: エラーバックトレース - GoBack: ← 戻る - NoScheduledFound: 予定されたジョブはありません - When: いつ - ScheduledJobs: 予定されたジョブ - idle: アイドル - active: アクティブ - Version: バージョン - Connections: 接続 - MemoryUsage: メモリー使用量 - PeakMemoryUsage: 最大メモリー使用量 - Uptime: 連続稼働時間 (日) - OneWeek: 1 週 - OneMonth: 1 ヶ月 - ThreeMonths: 3 ヶ月 - SixMonths: 6 ヶ月 - Failures: 失敗 - DeadJobs: デッドジョブ - NoDeadJobsFound: デッドジョブはありません - Dead: デッド - Process: プロセス - Processes: プロセス - Name: 名前 - Thread: スレッド - Threads: スレッド - Jobs: ジョブ - Paused: 一時停止中 - Stop: 停止 - Quiet: 処理終了 - StopAll: すべて停止 - QuietAll: すべて処理終了 - PollingInterval: ポーリング間隔 - Plugins: プラグイン - NotYetEnqueued: キューに入っていません - CreatedAt: 作成日時 - BackToApp: アプリに戻る - Latency: レイテンシ - Pause: 一時停止 - Unpause: 一時停止を解除 diff --git a/web/locales/ko.yml b/web/locales/ko.yml deleted file mode 100644 index 42422ce7..00000000 --- a/web/locales/ko.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -ko: - Dashboard: 대시보드 - Status: 상태 - Time: 시간 - Namespace: 네임스페이스 - Realtime: 실시간 - History: 히스토리 - Busy: 작동 - Processed: 처리완료 - Failed: 실패 - Scheduled: 예약 - Retries: 재시도 - Enqueued: 대기 중 - Worker: 워커 - LivePoll: 폴링 시작 - StopPolling: 폴링 중단 - Queue: 큐 - Class: 클래스 - Job: 작업 - Arguments: 인자 - Started: 시작 - ShowAll: 모두 보기 - CurrentMessagesInQueue: %{queue}에 대기 중인 메시지 - Delete: 삭제 - AddToQueue: 큐 추가 - AreYouSureDeleteJob: 이 작업을 삭제하시겠습니까? - AreYouSureDeleteQueue: 이 %{queue} 큐를 삭제하시겠습니까? - Queues: 큐 - Size: 크기 - Actions: 동작 - NextRetry: 다음 재시도 - RetryCount: 재시도 횟수 - RetryNow: 지금 재시도 - LastRetry: 최근 재시도 - OriginallyFailed: 실패 - AreYouSure: 정말입니까? - DeleteAll: 모두 삭제 - RetryAll: 모두 재시도 - NoRetriesFound: 재시도 내역이 없습니다 - Error: 에러 - ErrorClass: 에러 클래스 - ErrorMessage: 에러 메시지 - ErrorBacktrace: 에러 Backtrace - GoBack: ← 뒤로 - NoScheduledFound: 예약된 작업이 없습니다 - When: 언제 - ScheduledJobs: 예약된 작업 - idle: 대기 중 - active: 동작 중 - Version: 버전 - Connections: 커넥션 - MemoryUsage: 메모리 사용량 - PeakMemoryUsage: 최대 메모리 사용량 - Uptime: 업타임 (일) - OneWeek: 1 주 - OneMonth: 1 달 - ThreeMonths: 3 달 - SixMonths: 6 달 - Batches: 배치 - Failures: 실패 - DeadJobs: 죽은 작업 - NoDeadJobsFound: 죽은 작업이 없습니다 - Dead: 죽음 - Processes: 프로세스 - Thread: 스레드 - Threads: 스레드 - Jobs: 작업 diff --git a/web/locales/lt.yml b/web/locales/lt.yml deleted file mode 100644 index 2f45d8c4..00000000 --- a/web/locales/lt.yml +++ /dev/null @@ -1,83 +0,0 @@ -# elements like %{queue} are variables and should not be translated -lt: - Dashboard: Valdymo skydas - Status: Būsena - Time: Laikas - Namespace: Vardų erdvė - Realtime: Realiu laiku - History: Istorija - Busy: Užimti - Processed: Įvykdyti - Failed: Nepavykę - Scheduled: Suplanuoti - Retries: Kartojami - Enqueued: Eilėje - Worker: Darbuotojas - LivePoll: Užklausti gyvai - StopPolling: Stabdyti užklausas - Queue: Eilė - Class: Klasė - Job: Darbas - Arguments: Parametrai - Extras: Papildomi - Started: Pradėti - ShowAll: Rodyti Visus - CurrentMessagesInQueue: Esami darbai eilėje %{queue} - Delete: Pašalinti - AddToQueue: Pridėti į eilę - AreYouSureDeleteJob: Ar tikrai norite pašalinti šį darbą? - AreYouSureDeleteQueue: Ar tikrai norite pašalinti šią eilę %{queue}? - Queues: Eilės - Size: Dydis - Actions: Veiksmai - NextRetry: Sekantis Kartojimas - RetryCount: Kartojimų Skaičius - RetryNow: Kartoti Dabar - Kill: Priverstinai Nutraukti - LastRetry: Paskutinis Kartojimas - OriginallyFailed: Iš pradžių Nepavykę - AreYouSure: Ar jūs įsitikinę? - DeleteAll: Pašalinti Visus - RetryAll: Kartoti Visus - KillAll: Priverstinai Nutraukti Visus - NoRetriesFound: Nerasta kartojimų - Error: Klaida - ErrorClass: Klaidos Klasė - ErrorMessage: Klaidos Žinutė - ErrorBacktrace: Klaidos Pėdsakai - GoBack: ← Atgal - NoScheduledFound: Planuojamų darbų nerasta - When: Kada - ScheduledJobs: Planuojami Darbai - idle: neveiksnus - active: aktyvus - Version: Versija - Connections: Ryšiai - MemoryUsage: Atminties Vartojimas - PeakMemoryUsage: Atminties Vartojimo Pikas - Uptime: Gyvavimo laikas (dienomis) - OneWeek: 1 savaitė - OneMonth: 1 mėnuo - ThreeMonths: 3 mėnesiai - SixMonths: 6 mėnesiai - Failures: Nesėkmingi vykdymai - DeadJobs: Negyvi Darbai - NoDeadJobsFound: Negyvų darbų nerasta - Dead: Negyvi - Processes: Procesai - Thread: Gija - Threads: Gijos - Jobs: Darbai - Paused: Pristabdytas - Stop: Sustabdyti - Quiet: Nutildyti - StopAll: Sustabdyti Visus - QuietAll: Nutildyti Visus - PollingInterval: Užklausimų intervalas - Plugins: Įskiepiai - NotYetEnqueued: Dar neįtraukti į eilę - CreatedAt: Sukurta - BackToApp: Atgal į Aplikaciją - Latency: Vėlavimas - Pause: Pristabdyti - Unpause: Pratęsti diff --git a/web/locales/nb.yml b/web/locales/nb.yml deleted file mode 100644 index 60caf2b2..00000000 --- a/web/locales/nb.yml +++ /dev/null @@ -1,77 +0,0 @@ -# elements like %{queue} are variables and should not be translated -nb: - Dashboard: Oversikt - Status: Status - Time: Tid - Namespace: Navnerom - Realtime: Sanntid - History: Historikk - Busy: Opptatt - Processed: Prosessert - Failed: Mislykket - Scheduled: Planlagt - Retries: Forsøk - Enqueued: I kø - Worker: Arbeider - LivePoll: Automatisk oppdatering - StopPolling: Stopp automatisk oppdatering - Queue: Kø - Class: Klasse - Job: Jobb - Arguments: Argumenter - Extras: Ekstra - Started: Startet - ShowAll: Vis alle - CurrentMessagesInQueue: Nåværende melding i %{queue} - Delete: Slett - AddToQueue: Legg til i kø - AreYouSureDeleteJob: Er du sikker på at du vil slette denne jobben? - AreYouSureDeleteQueue: Er du sikker på at du vil slette køen %{queue}? - Queues: Køer - Size: Størrelse - Actions: Handlinger - NextRetry: Neste forsøk - RetryCount: Antall forsøk - RetryNow: Forsøk igjen nå - Kill: Kill - LastRetry: Forrige forsøk - OriginallyFailed: Feilet opprinnelig - AreYouSure: Er du sikker? - DeleteAll: Slett alle - RetryAll: Forsøk alle på nytt - NoRetriesFound: Ingen forsøk funnet - Error: Feil - ErrorClass: Feilklasse - ErrorMessage: Feilmelding - ErrorBacktrace: Feilbakgrunn - GoBack: ← Tilbake - NoScheduledFound: Ingen planlagte jobber funnet - When: Når - ScheduledJobs: Planlagte jobber - idle: uvirksom - active: aktiv - Version: Versjon - Connections: Tilkoblinger - MemoryUsage: Minneforbruk - PeakMemoryUsage: Høyeste minneforbruk - Uptime: Oppetid (dager) - OneWeek: 1 uke - OneMonth: 1 måned - ThreeMonths: 3 måneder - SixMonths: 6 måneder - Failures: Feil - DeadJobs: Døde jobber - NoDeadJobsFound: Ingen døde jobber funnet - Dead: Død - Processes: Prosesser - Thread: Tråd - Threads: Tråder - Jobs: Jobber - Paused: Pauset - Stop: Stopp - Quiet: Demp - StopAll: Stopp alle - QuietAll: Demp alle - PollingInterval: Oppdateringsintervall - Plugins: Innstikk - NotYetEnqueued: Ikke køet enda diff --git a/web/locales/nl.yml b/web/locales/nl.yml deleted file mode 100644 index 4915e08d..00000000 --- a/web/locales/nl.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -nl: - Dashboard: Dashboard - Status: Status - Time: Tijd - Namespace: Namespace - Realtime: Real-time - History: Geschiedenis - Busy: Bezet - Processed: Verwerkt - Failed: Mislukt - Scheduled: Gepland - Retries: Opnieuw proberen - Enqueued: In de wachtrij - Worker: Werker - LivePoll: Live bijwerken - StopPolling: Stop live bijwerken - Queue: Wachtrij - Class: Klasse - Job: Taak - Arguments: Argumenten - Extras: Extra's - Started: Gestart - ShowAll: Toon alle - CurrentMessagesInQueue: Aantal berichten in %{queue} - Delete: Verwijderen - AddToQueue: Toevoegen aan wachtrij - AreYouSureDeleteJob: Weet u zeker dat u deze taak wilt verwijderen? - AreYouSureDeleteQueue: Weet u zeker dat u wachtrij %{queue} wilt verwijderen? - Queues: Wachtrijen - Size: Grootte - Actions: Acties - NextRetry: Volgende opnieuw proberen - RetryCount: Aantal opnieuw geprobeerd - RetryNow: Nu opnieuw proberen - LastRetry: Laatste poging - OriginallyFailed: Oorspronkelijk mislukt - AreYouSure: Weet u het zeker? - DeleteAll: Alle verwijderen - RetryAll: Alle opnieuw proberen - NoRetriesFound: Geen opnieuw te proberen taken gevonden - Error: Fout - ErrorClass: Fout Klasse - ErrorMessage: Foutmelding - ErrorBacktrace: Fout Backtrace - GoBack: ← Terug - NoScheduledFound: Geen geplande taken gevonden - When: Wanneer - ScheduledJobs: Geplande taken - idle: inactief - active: actief - Version: Versie - Connections: Verbindingen - MemoryUsage: Geheugengebruik - PeakMemoryUsage: Piek geheugengebruik - Uptime: Looptijd (dagen) - OneWeek: 1 week - OneMonth: 1 maand - ThreeMonths: 3 maanden - SixMonths: 6 maanden - Failures: Mislukt - DeadJobs: Overleden taken - NoDeadJobsFound: Geen overleden taken gevonden - Dead: Overleden - Processes: Processen - Thread: Thread - Threads: Threads - Jobs: Taken diff --git a/web/locales/pl.yml b/web/locales/pl.yml deleted file mode 100644 index a6161cf4..00000000 --- a/web/locales/pl.yml +++ /dev/null @@ -1,59 +0,0 @@ -# elements like %{queue} are variables and should not be translated -pl: - Dashboard: Kokpit - Status: Status - Time: Czas - Namespace: Przestrzeń nazw - Realtime: Czas rzeczywisty - History: Historia - Busy: Zajęte - Processed: Ukończone - Failed: Nieudane - Scheduled: Zaplanowane - Retries: Do ponowienia - Enqueued: Zakolejkowane - Worker: Worker - LivePoll: Wczytuj na żywo - StopPolling: Zatrzymaj wczytywanie na żywo - Queue: Kolejka - Class: Klasa - Job: Zadanie - Arguments: Argumenty - Started: Rozpoczęte - ShowAll: Pokaż wszystko - CurrentMessagesInQueue: Aktualne wiadomości w kolejce %{queue} - Delete: Usuń - AddToQueue: dodaj do kolejki - AreYouSureDeleteJob: Czy na pewno usunąć to zadanie? - AreYouSureDeleteQueue: Czy na pewno usunąć kolejkę %{queue}? - Queues: Kolejki - Size: Rozmiar - Actions: Akcje - NextRetry: Następne ponowienie - RetryCount: Ilość ponowień - RetryNow: Ponów teraz - LastRetry: Ostatnie ponowienie - OriginallyFailed: Ostatnio nieudane - AreYouSure: Na pewno? - DeleteAll: Usuń wszystko - RetryAll: Powtórz wszystko - NoRetriesFound: Brak zadań do ponowienia - Error: Błąd - ErrorClass: Klasa błędu - ErrorMessage: Wiadomosć błędu - ErrorBacktrace: Wyjście błędu - GoBack: ← Wróć - NoScheduledFound: Brak zaplanowanych zadań - When: Kiedy - ScheduledJobs: Zaplanowane zadania - idle: bezczynne - active: aktywne - Version: Wersja - Connections: Połączenia - MemoryUsage: Wykorzystanie pamięci - PeakMemoryUsage: Największe wykorzystanie pamięci - Uptime: Uptime (dni) - OneWeek: 1 tydzień - OneMonth: 1 miesiąc - ThreeMonths: 3 miesiące - SixMonths: 6 miesięcy diff --git a/web/locales/pt-br.yml b/web/locales/pt-br.yml deleted file mode 100644 index a6731148..00000000 --- a/web/locales/pt-br.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -"pt-br": - Dashboard: Painel - Status: Status - Time: Tempo - Namespace: Namespace - Realtime: Tempo real - History: Histórico - Busy: Ocupados - Processed: Processados - Failed: Falhas - Scheduled: Agendados - Retries: Tentativas - Enqueued: Na fila - Worker: Trabalhador - LivePoll: Live Poll - StopPolling: Parar Polling - Queue: Fila - Class: Classe - Job: Tarefa - Arguments: Argumentos - Extras: Extras - Started: Iniciados - ShowAll: Mostrar todos - CurrentMessagesInQueue: Mensagens atualmente na %{queue} - Delete: Apagar - AddToQueue: Adicionar à fila - AreYouSureDeleteJob: Deseja deletar esta tarefa? - AreYouSureDeleteQueue: Deseja deletar a %{queue} fila? - Queues: Filas - Size: Tamanho - Actions: Ações - NextRetry: Próxima Tentativa - RetryCount: Número de Tentativas - RetryNow: Tentar novamente agora - LastRetry: Última tentativa - OriginallyFailed: Falhou originalmente - AreYouSure: Tem certeza? - DeleteAll: Apagar tudo - RetryAll: Tentar tudo novamente - NoRetriesFound: Nenhuma tentativa encontrada - Error: Erro - ErrorClass: Classe de erro - ErrorMessage: Mensagem de erro - ErrorBacktrace: Rastreamento do erro - GoBack: ← Voltar - NoScheduledFound: Nenhuma tarefa agendada foi encontrada - When: Quando - ScheduledJobs: Tarefas agendadas - idle: ocioso - active: ativo - Version: Versão - Connections: Conexões - MemoryUsage: Uso de memória - PeakMemoryUsage: Pico de uso de memória - Uptime: Dias rodando - OneWeek: 1 semana - OneMonth: 1 mês - ThreeMonths: 3 meses - SixMonths: 6 meses - Failures : Falhas - DeadJobs : Tarefas mortas - NoDeadJobsFound : Nenhuma tarefa morta foi encontrada - Dead : Morta - Processes : Processos - Thread : Thread - Threads : Threads - Jobs : Tarefas diff --git a/web/locales/pt.yml b/web/locales/pt.yml deleted file mode 100644 index c8174f03..00000000 --- a/web/locales/pt.yml +++ /dev/null @@ -1,67 +0,0 @@ -# elements like %{queue} are variables and should not be translated -pt: - Dashboard: Dashboard - Status: Estado - Time: Tempo - Namespace: Namespace - Realtime: Tempo real - History: Histórico - Busy: Ocupado - Processed: Processados - Failed: Falhados - Scheduled: Agendados - Retries: Tentativas - Enqueued: Em espera - Worker: Worker - LivePoll: Live Poll - StopPolling: Desactivar Live Poll - Queue: Fila - Class: Classe - Job: Tarefa - Arguments: Argumentos - Started: Iniciados - ShowAll: Mostrar todos - CurrentMessagesInQueue: Mensagens na fila %{queue} - Delete: Apagar - AddToQueue: Adicionar à fila - AreYouSureDeleteJob: Tem a certeza que deseja eliminar esta tarefa? - AreYouSureDeleteQueue: Tem a certeza que deseja eliminar a fila %{queue}? - Queues: Filas - Size: Tamanho - Actions: Acções - NextRetry: Próxima Tentativa - RetryCount: Tentativas efectuadas - RetryNow: Tentar novamente - LastRetry: Última Tentativa - OriginallyFailed: Falhou inicialmente - AreYouSure: Tem a certeza? - DeleteAll: Eliminar todos - RetryAll: Tentar tudo novamente - NoRetriesFound: Não foram encontradas tentativas - Error: Erro - ErrorClass: Classe de Erro - ErrorMessage: Mensagem de erro - ErrorBacktrace: Backtrace do Erro - GoBack: ← Voltar - NoScheduledFound: Não foram encontradas tarefas agendadas - When: Quando - ScheduledJobs: Tarefas agendadas - idle: livre - active: activo - Version: Versão - Connections: Conexões - MemoryUsage: Utilização de Memória - PeakMemoryUsage: Pico de utilização de memória - Uptime: Uptime (em dias) - OneWeek: 1 semana - OneMonth: 1 mês - ThreeMonths: 3 meses - SixMonths: 6 meses - Failures: Falhas - DeadJobs: Tarefas mortas - NoDeadJobsFound: Não foram encontradas tarefas mortas - Dead: Morto - Processes: Processos - Thread: Thread - Threads: Threads - Jobs: Tarefas diff --git a/web/locales/ru.yml b/web/locales/ru.yml deleted file mode 100644 index 0c28f790..00000000 --- a/web/locales/ru.yml +++ /dev/null @@ -1,82 +0,0 @@ -ru: - Dashboard: Панель управления - Status: Статус - Time: Время - Namespace: Пространство имен - Realtime: Сейчас - History: История - Busy: Занят - Processed: Обработано - Failed: Провалено - Scheduled: Запланировано - Retries: Попытки - Enqueued: В очереди - Worker: Обработчик - LivePoll: Постоянный опрос - StopPolling: Остановить опрос - Queue: Очередь - Class: Класс - Job: Задача - Arguments: Аргументы - Extras: Дополнительно - Started: Запущено - ShowAll: Показать все - CurrentMessagesInQueue: Текущие задачи в очереди %{queue} - Delete: Удалить - AddToQueue: Добавить в очередь - AreYouSureDeleteJob: Вы уверены, что хотите удалить эту задачу? - AreYouSureDeleteQueue: Вы уверены, что хотите удалить очередь %{queue}? - Queues: Очереди - Size: Размер - Actions: Действия - NextRetry: Следующая попытка - RetryCount: Кол-во попыток - RetryNow: Повторить сейчас - Kill: Убиваем - LastRetry: Последняя попытка - OriginallyFailed: Первый провал - AreYouSure: Вы уверены? - DeleteAll: Удалить все - RetryAll: Повторить все - KillAll: Убить всё - NoRetriesFound: Нет попыток - Error: Ошибка - ErrorClass: Класс ошибки - ErrorMessage: Сообщение об ошибке - ErrorBacktrace: Трассировка ошибки - GoBack: ← Назад - NoScheduledFound: Нет запланированных задач - When: Когда - ScheduledJobs: Запланированные задачи - idle: отдыхает - active: активен - Version: Версия - Connections: Соединения - MemoryUsage: Использование памяти - PeakMemoryUsage: Максимальный расход памяти - Uptime: Дня(ей) бесперебойной работы - OneWeek: 1 неделя - OneMonth: 1 месяц - ThreeMonths: 3 месяца - SixMonths: 6 месяцев - Failures: Провалы - DeadJobs: Убитые задачи - NoDeadJobsFound: Нет убитых задач - Dead: Убито - Processes: Процессы - Thread: Поток - Threads: Потоки - Jobs: Задачи - Paused: Приостановлено - Stop: Остановить - Quiet: Отдыхать - StopAll: Остановить все - QuietAll: Отдыхать всем - PollingInterval: Интервал опроса - Plugins: Плагины - NotYetEnqueued: Пока не в очереди - CreatedAt: Создан - BackToApp: Назад - Latency: Задержка - Pause: Пауза - Unpause: Возобновить diff --git a/web/locales/sv.yml b/web/locales/sv.yml deleted file mode 100644 index 224c2f6e..00000000 --- a/web/locales/sv.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -sv: # <---- change this to your locale code - Dashboard: Panel - Status: Status - Time: Tid - Namespace: Namnrymd - Realtime: Realtid - History: Historik - Busy: Upptagen - Processed: Processerad - Failed: Misslyckad - Scheduled: Schemalagd - Retries: Försök - Enqueued: Köad - Worker: Worker - LivePoll: Live poll - StopPolling: Stoppa polling - Queue: Kö - Class: Klass - Job: Jobb - Arguments: Argument - Extras: Extra - Started: Startad - ShowAll: Visa alla - CurrentMessagesInQueue: Jobb i %{queue} - Delete: Ta bort - AddToQueue: Lägg till i kö - AreYouSureDeleteJob: Är du säker på att du vill ta bort detta jobb? - AreYouSureDeleteQueue: Är du säker på att du vill ta bort kön %{queue}? - Queues: Köer - Size: Storlek - Actions: Åtgärder - NextRetry: Nästa försök - RetryCount: Antal försök - RetryNow: Försök nu - LastRetry: Senaste försök - OriginallyFailed: Misslyckades ursprungligen - AreYouSure: Är du säker? - DeleteAll: Ta bort alla - RetryAll: Försök alla igen - NoRetriesFound: Inga försök hittades - Error: Fel - ErrorClass: Felklass - ErrorMessage: Felmeddelande - ErrorBacktrace: Backtrace för fel - GoBack: ← Bakåt - NoScheduledFound: Inga schemalagda jobb hittades - When: När - ScheduledJobs: Schemalagda jobb - idle: avvaktande - active: aktiv - Version: Version - Connections: Anslutningar - MemoryUsage: Minnesanvändning - PeakMemoryUsage: Minnesanvändning (peak) - Uptime: Upptid (dagar) - OneWeek: 1 vecka - OneMonth: 1 månad - ThreeMonths: 3 månader - SixMonths: 6 månader - Failures: Failures - DeadJobs: Döda jobb - NoDeadJobsFound: Inga döda jobb hittades - Dead: Död - Processes: Processer - Thread: Tråd - Threads: Trådar - Jobs: Jobb diff --git a/web/locales/ta.yml b/web/locales/ta.yml deleted file mode 100644 index bede7fec..00000000 --- a/web/locales/ta.yml +++ /dev/null @@ -1,75 +0,0 @@ -# elements like %{queue} are variables and should not be translated -ta: # <---- change this to your locale code - Dashboard: டாஷ்போர்டு - Status: நிலைமை - Time: நேரம் - Namespace: பெயர்வெளி - Realtime: நேரலை - History: வரலாறு - Busy: பணிமிகுதி - Processed: நிறையுற்றது - Failed: தோல்வி - Scheduled: திட்டமிடப்பட்ட - Retries: மீண்டும் முயற்சிக்க, - Enqueued: வரிசைப்படுத்தப்பட்டவை - Worker: பணியாளர் - LivePoll: நேரடி கணிப்பு - StopPolling: நிறுத்து வாக்குப்பதிவு - Queue: வரிசை - Class: வகுப்பு - Job: வேலை - Arguments: வாதங்கள், - Extras: உபரி - Started: தொடங்குதல் - ShowAll: அனைத்து காட்டு - CurrentMessagesInQueue: தற்போதைய வேலைகள் %{queue} - Delete: நீக்கு - AddToQueue: வரிசையில் சேர் - AreYouSureDeleteJob: நீ இந்த வேலையை நீக்க வேண்டும் என்று உறுதியாக இருக்கிறீர்களா? - AreYouSureDeleteQueue: நீங்கள் %{queue} வரிசையில் நீக்க வேண்டும் என்பதில் உறுதியாக இருக்கிறீர்களா? - Queues: வரிசை - Size: அளவு - Actions: செயல்கள் - NextRetry: அடுத்த, மீண்டும் முயற்சிக்கவும் - RetryCount: கணிப்பீடு, மீண்டும் முயற்சிக்கவும் - RetryNow: இப்போது மீண்டும் முயற்சி செய்க - Kill: கொல் - LastRetry: கடைசியாக, மீண்டும் முயற்சிக்கவும் - OriginallyFailed: முதலில் தோல்வி - AreYouSure: நீங்கள் உறுதியாக இருக்கிறீர்களா? - DeleteAll: அனைத்து நீக்கு - RetryAll: அனைத்து, மீண்டும் முயற்சிக்கவும் - NoRetriesFound: இல்லை மீண்டும் காணப்படவில்லை - Error: பிழை - ErrorClass: பிழை வகுப்பு - ErrorMessage: பிழை செய்தி - ErrorBacktrace: பிழை பின்தேடுலை - GoBack: பின்புறம் - NoScheduledFound: திட்டமிட்ட வேலைகள் காணப்படவில்லை - When: எப்பொழுது? - ScheduledJobs: திட்டமிட்ட வேலைகள் - idle: முடங்கு நேரம் - active: செயலில் - Version: பதிப்பு - Connections: இணைப்புகள் - MemoryUsage: நினைவக பயன்பாடு - PeakMemoryUsage: உச்ச நினைவக பயன்பாடு - Uptime: இயக்க நேரம் (நாட்கள்) - OneWeek: 1 வாரம் - OneMonth: 1 மாதம் - ThreeMonths: 3 மாதங்கள் - SixMonths: 6 மாதங்கள் - Failures: தோல்விகள் - DeadJobs: டெட் வேலைகள் - NoDeadJobsFound: இறந்த வேலை எதுவும் இல்லை - Dead: இறந்துபோன - Processes: செயல்முறைகள் - Thread: நூல் - Threads: நூல்கள் - Jobs: வேலை வாய்ப்புகள் - Paused: தற்காலிக பணிநிறுத்தம் - Stop: நிறுத்து - Quiet: அமைதியான - StopAll: நிறுத்து அனைத்து - QuietAll: அமைதியான அனைத்து - PollingInterval: வாக்குப்பதிவு இடைவெளி \ No newline at end of file diff --git a/web/locales/uk.yml b/web/locales/uk.yml deleted file mode 100644 index 3bb14abe..00000000 --- a/web/locales/uk.yml +++ /dev/null @@ -1,76 +0,0 @@ -uk: - Dashboard: Панель керування - Status: Статус - Time: Час - Namespace: Простір імен - Realtime: Зараз - History: Історія - Busy: Зайнятих - Processed: Опрацьовано - Failed: Невдалих - Scheduled: Заплановано - Retries: Спроби - Enqueued: У черзі - Worker: Обробник - LivePoll: Постійне опитування - StopPolling: Зупинити опитування - Queue: Черга - Class: Клас - Job: Задача - Arguments: Аргументи - Extras: Додатково - Started: Запущено - ShowAll: Відобразити усі - CurrentMessagesInQueue: Поточні задачі у черзі %{queue} - Delete: Видалити - AddToQueue: Додати до черги - AreYouSureDeleteJob: Ви впевнені у тому, що хочете видалити задачу? - AreYouSureDeleteQueue: Ви впевнені у тому, що хочете видалити чергу %{queue}? - Queues: Черги - Size: Розмір - Actions: Дії - NextRetry: Наступна спроба - RetryCount: Кількість спроб - RetryNow: Повторити зараз - Kill: Вбиваємо - LastRetry: Остання спроба - OriginallyFailed: Перша невдала спроба - AreYouSure: Ви впевнені? - DeleteAll: Видалити усі - RetryAll: Повторити усі - NoRetriesFound: Спроб не знайдено - Error: Помилка - ErrorClass: Клас помилки - ErrorMessage: Повідомлення про помилку - ErrorBacktrace: Трасування помилки - GoBack: ← Назад - NoScheduledFound: Запланованих задач не знайдено - When: Коли - ScheduledJobs: Заплановані задачі - idle: незайнятий - active: активний - Version: Версія - Connections: З'єднань - MemoryUsage: Використання пам'яті - PeakMemoryUsage: Максимальне використання пам'яті - Uptime: Днів безперебійної роботи - OneWeek: 1 тиждень - OneMonth: 1 місяць - ThreeMonths: 3 місяці - SixMonths: 6 місяців - Failures: Невдачі - DeadJobs: Вбиті задачі - NoDeadJobsFound: Вбитих задач не знайдено - Dead: Вбитих - Processes: Процеси - Thread: Потік - Threads: Потоки - Jobs: Задачі - Paused: Призупинено - Stop: Зупинити - Quiet: Призупинити - StopAll: Зупинити усі - QuietAll: Призупинити усі - PollingInterval: Інтервал опитування - Plugins: Плагіни - NotYetEnqueued: Ще не в черзі diff --git a/web/locales/ur.yml b/web/locales/ur.yml deleted file mode 100644 index b6cd1516..00000000 --- a/web/locales/ur.yml +++ /dev/null @@ -1,80 +0,0 @@ -# elements like %{queue} are variables and should not be translated -ur: - TextDirection: 'rtl' - Dashboard: صفحۂ اول - Status: اسٹیٹس - Time: ﻭﻗﺖ - Namespace: Namespace - Realtime: ﺑﺮاﮦ ﺭاﺳﺖ - History: ﺗﺎﺭﻳﺦ - Busy: مصروف - Processed: مکمل شدہ - Failed: ﻧﺎکاﻡ ﺷﺪﮦ - Scheduled: ﻁےﺷﺪﮦ - Retries: ﺩﻭﺑﺎﺭﮦ کﻭﺷﻴﺶ - Enqueued: قطار ميں شامل - Worker: ورکر - LivePoll: ﺑﺮاﮦ ﺭاﺳﺖ - StopPolling: ﺑﺮاﮦ ﺭاﺳﺖ روکيے - Queue: قطار - Class: کلاس - Job: جاب - Arguments: دلائل - Extras: اﺻﺎﻑی - Started: شروع - ShowAll: سارے دکھاو - CurrentMessagesInQueue: قطار ميں موجود تمام پيغامات %{queue} - AddToQueue: ﻗﻄﺎﺭ ميں شامل کريں - AreYouSureDeleteJob: کيا آپ یقین جاب حتم کرنا چاھتے ہيں ؟ - AreYouSureDeleteQueue: کيا آپ یقین قطار حتم کرنا چاھتے ہيں ؟ - Delete: ﺣﺬﻑ - Queues: قطاريں - Size: ﺣﺠﻢ - Actions: ﻋﻮاﻣﻞ - NextRetry: اگلی کﻭﺷﻴﺶ - RetryCount: دوبارہ کوشش کا مکمل شمار - RetryNow: ابھی دوبارہ کوشش - Kill: ختم کرديں - LastRetry: گزشتہ کوشش - OriginallyFailed: ابتادائ ناکامی - AreYouSure: کيا یقین ؟ - DeleteAll: ﺗﻤﺎﻡ ﺣﺬﻑ کر ديں - RetryAll: ﺗﻤﺎﻡ کی ﺩﻭﺑﺎﺭﮦ کﻭﺷﻴﺶ کﺭيں - NoRetriesFound: کویٔ ﺩﻭﺑﺎﺭﮦ کﻭﺷﻴﺶ نھيں ملی - Error: مسئلہ - ErrorClass: مسئلہ کی کلاس - ErrorMessage: مسئلہ کی وجہ - ErrorBacktrace: مسئلہ کی کی تحقیقات کريں - GoBack: واپس جايں - NoScheduledFound: کویٔ ﻁےﺷﺪﮦچيز نہیں ملی - When: ﺏک - ScheduledJobs: ﻁےﺷﺪﮦجاب - idle: بیکار - active: فعال - Version: ورژن - Connections: کنکشنز - MemoryUsage: یاداشت کا استعمال - PeakMemoryUsage: سب سے زيادہ یاداشت کا استعمال - Uptime: اپ ٹائم - OneWeek: ایک ہفتہ - OneMonth: ایک مہینہ - ThreeMonths: تین ماہ - SixMonths: چھ ماہ - Failures: ناکامیاں - DeadJobs: ختم شدہ جاب - NoDeadJobsFound: کویٔ ختم شدہ جاب نہيی ملی - Dead: ختم شدہ - Processes: ﻋﻤﻠﻴﺎﺕ - Thread: موضوع - Threads: موضوع - Jobs: جابز - Paused: موقوف - Stop: بند کرو - Quiet: ﺣﺘﻢ کﺭﻭ - StopAll: ﺗﻤﺎﻡ ﺑﻨﺪ کﺭﻭ - QuietAll: ﺗﻤﺎﻡ ﺣﺘﻢ کﺭﻭ - PollingInterval: ﺑﺮاﮦ ﺭاﺳﺖ کا ﺩﻭﺭاﻧﻴﮧ - Plugins: پلگ انز - NotYetEnqueued: ﻗﺘﺎﺭميں شامل نھيں - CreatedAt: ﺗﺎﺭﻳﺢ آﻏﺎﺯ - BackToApp: ﻭاپﺱ صفحۂ اﻭﻝ پر \ No newline at end of file diff --git a/web/locales/vi.yml b/web/locales/vi.yml deleted file mode 100644 index 00169d5a..00000000 --- a/web/locales/vi.yml +++ /dev/null @@ -1,83 +0,0 @@ -# elements like %{queue} are variables and should not be translated -vi: # <---- change this to your locale code - Dashboard: Bảng điều khiển - Status: Trạng thái - Time: Thời gian - Namespace: Không gian tên - Realtime: Thời gian thực - History: Lịch sử - Busy: Bận rộn - Processed: Đã xử lí - Failed: Đã thất bại - Scheduled: Đã lên lịch - Retries: Số lần thử - Enqueued: Đã xếp hàng đợi - Worker: Máy xử lí - LivePoll: Thăm dò trực tiếp - StopPolling: Ngừng thăm dò - Queue: Hàng đợi - Class: Lớp - Job: Tác vụ - Arguments: Tham số - Extras: Thêm - Started: Đã bắt đầu - ShowAll: Hiện tất cả - CurrentMessagesInQueue: Số lượng công việc trong %{queue} - Delete: Xóa - AddToQueue: Thêm vào hàng đợi - AreYouSureDeleteJob: Bạn có chắc là muốn xóa tác vụ này? - AreYouSureDeleteQueue: Bạn có chắc là muốn xóa %{queue} này? - Queues: Các hàng đợi - Size: Kích thước - Actions: Những hành động - NextRetry: Lần thử lại tiếp theo - RetryCount: Số lần thử lại - RetryNow: Thử lại ngay bây giờ - Kill: Giết - LastRetry: Lần thử cuối - OriginallyFailed: Đã thất bại từ đầu - AreYouSure: Bạn chắc chứ? - DeleteAll: Xóa hết - RetryAll: Thử lại tất cả - KillAll: Giết hết - NoRetriesFound: Không có lần thử nào được tìm thấy - Error: Lỗi - ErrorClass: Lớp lỗi - ErrorMessage: Tin nhắn lỗi - ErrorBacktrace: Dấu vết của lỗi - GoBack: ← Trở lại - NoScheduledFound: Không có tác vụ đã lên lịch nào được tìm thấy - When: Khi nào - ScheduledJobs: Những Tác Vụ Được Hẹn - idle: Đang chờ - active: Đang hoạt động - Version: Phiên bản - Connections: Các kết nối - MemoryUsage: Lượng bộ nhớ sử dụng - PeakMemoryUsage: Lượng bộ nhớ sử dụng đỉnh điểm - Uptime: Thời gian hệ thống đã online (days) - OneWeek: 1 tuần - OneMonth: 1 tháng - ThreeMonths: 3 tháng - SixMonths: 6 tháng - Failures: Các thất bại - DeadJobs: Những tác vụ đã chết - NoDeadJobsFound: Không có tác vụ đã chết nào được tìm thấy - Dead: Chết - Processes: Tiến trình xử lí - Thread: Luồng xử lí - Threads: Những luồng xử lí - Jobs: Các tác vụ - Paused: Đã tạm dừng - Stop: Dừng Lại - Quiet: Im lặng - StopAll: Dừng lại tất cả - QuietAll: Làm cho tất cả im lặng - PollingInterval: Khoảng thời gian giữa các lần thăm dò - Plugins: Hệ thống đính kèm - NotYetEnqueued: Chưa được bỏ vào hàng đợi - CreatedAt: Được tạo vào lúc - BackToApp: Trở về ứng dụng - Latency: Độ trễ - Pause: Tạm dừng - Unpause: Hủy tạm dừng diff --git a/web/locales/zh-cn.yml b/web/locales/zh-cn.yml deleted file mode 100644 index 09bf4c2a..00000000 --- a/web/locales/zh-cn.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -zh-cn: # <---- change this to your locale code - Dashboard: 信息板 - Status: 状态 - Time: 时间 - Namespace: 命名空间 - Realtime: 实时 - History: 历史记录 - Busy: 执行中 - Processed: 已处理 - Failed: 已失败 - Scheduled: 已计划 - Retries: 重试 - Enqueued: 已进入队列 - Worker: 工人 - LivePoll: 实时轮询 - StopPolling: 停止轮询 - Queue: 队列 - Class: 类别 - Job: 作业 - Arguments: 参数 - Extras: 额外的 - Started: 已开始 - ShowAll: 显示全部 - CurrentMessagesInQueue: 目前在%{queue}的作业 - Delete: 删除 - AddToQueue: 添加至队列 - AreYouSureDeleteJob: 你确定要删除这个作业么? - AreYouSureDeleteQueue: 你确定要删除%{queue}这个队列? - Queues: 队列 - Size: 容量 - Actions: 动作 - NextRetry: 下次重试 - RetryCount: 重试次數 - RetryNow: 现在重试 - LastRetry: 最后一次重试 - OriginallyFailed: 原本已失败 - AreYouSure: 你确定? - DeleteAll: 删除全部 - RetryAll: 重试全部 - NoRetriesFound: 沒有发现可重试 - Error: 错误 - ErrorClass: 错误类别 - ErrorMessage: 错误消息 - ErrorBacktrace: 错误的回调追踪 - GoBack: ← 返回 - NoScheduledFound: 沒有发现计划作业 - When: 当 - ScheduledJobs: 计划作业 - idle: 闲置 - active: 活动中 - Version: 版本 - Connections: 连接 - MemoryUsage: 内存占用 - PeakMemoryUsage: 内存占用峰值 - Uptime: 上线时间 (天数) - OneWeek: 一周 - OneMonth: 一个月 - ThreeMonths: 三个月 - SixMonths: 六个月 - Failures: 失败 - DeadJobs: 已停滞作业 - NoDeadJobsFound: 沒有发现任何已停滞的作业 - Dead: 已停滞 - Processes: 处理中 - Thread: 线程 - Threads: 线程 - Jobs: 作业 diff --git a/web/locales/zh-tw.yml b/web/locales/zh-tw.yml deleted file mode 100644 index d56bfc97..00000000 --- a/web/locales/zh-tw.yml +++ /dev/null @@ -1,68 +0,0 @@ -# elements like %{queue} are variables and should not be translated -zh-tw: # <---- change this to your locale code - Dashboard: 資訊主頁 - Status: 狀態 - Time: 時間 - Namespace: 命名空間 - Realtime: 即時 - History: 歷史資料 - Busy: 忙碌 - Processed: 已處理 - Failed: 已失敗 - Scheduled: 已排程 - Retries: 重試 - Enqueued: 已佇列 - Worker: 工人 - LivePoll: 即時輪詢 - StopPolling: 停止輪詢 - Queue: 佇列 - Class: 類別 - Job: 工作 - Arguments: 參數 - Extras: 額外的 - Started: 已開始 - ShowAll: 顯示全部 - CurrentMessagesInQueue: 目前在%{queue}的工作 - Delete: 刪除 - AddToQueue: 增加至佇列 - AreYouSureDeleteJob: 你確定要刪除這個工作嗎? - AreYouSureDeleteQueue: 你確定要刪除%{queue}這個佇列? - Queues: 佇列 - Size: 容量 - Actions: 動作 - NextRetry: 下次重試 - RetryCount: 重試次數 - RetryNow: 馬上重試 - LastRetry: 最後一次重試 - OriginallyFailed: 原本已失敗 - AreYouSure: 你確定? - DeleteAll: 刪除全部 - RetryAll: 重試全部 - NoRetriesFound: 沒有發現可重試 - Error: 錯誤 - ErrorClass: 錯誤類別 - ErrorMessage: 錯誤訊息 - ErrorBacktrace: 錯誤的回調追踨 - GoBack: ← 返回 - NoScheduledFound: 沒有發現已排程的工作 - When: 當 - ScheduledJobs: 已排程的工作 - idle: 閒置 - active: 活動中 - Version: 版本 - Connections: 連線 - MemoryUsage: 記憶體使用量 - PeakMemoryUsage: 尖峰記憶體使用量 - Uptime: 上線時間 (天數) - OneWeek: 一週 - OneMonth: 一個月 - ThreeMonths: 三個月 - SixMonths: 六個月 - Failures: 失敗 - DeadJobs: 停滯工作 - NoDeadJobsFound: 沒有發現任何停滯的工作 - Dead: 停滯 - Processes: 處理中 - Thread: 執行緒 - Threads: 執行緒 - Jobs: 工作 diff --git a/web/views/_footer.erb b/web/views/_footer.erb deleted file mode 100644 index cb284f3c..00000000 --- a/web/views/_footer.erb +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/web/views/_job_info.erb b/web/views/_job_info.erb deleted file mode 100644 index 176e8cf1..00000000 --- a/web/views/_job_info.erb +++ /dev/null @@ -1,89 +0,0 @@ -
-

<%= t('Job') %>

-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - <% unless retry_extra_items(job).empty? %> - - - - - <% end %> - <% if type == :retry %> - <% if job['retry_count'] && job['retry_count'] > 0 %> - - - - - - - - - <% else %> - - - - - <% end %> - - - - - <% end %> - <% if type == :scheduled %> - - - - - <% end %> - <% if type == :dead %> - - - - - <% end %> - -
<%= t('Queue') %> - <%= job.queue %> -
<%= t('Job') %> - <%= job.display_class %> - <%= display_tags(job) %> -
<%= t('Arguments') %> - - -
<%= display_args(job.display_args, nil) %>
-
-
JID - <%= job.jid %> -
<%= t('CreatedAt') %><%= relative_time(job.created_at) %>
<%= t('Enqueued') %><%= (enqueued_at = job.enqueued_at) ? relative_time(enqueued_at) : t('NotYetEnqueued') %>
<%= t('Extras') %> - - <%= retry_extra_items(job).inspect %> - -
<%= t('RetryCount') %><%= job['retry_count'] %>
<%= t('LastRetry') %><%= relative_time(Time.at(job['retried_at'])) %>
<%= t('OriginallyFailed') %><%= relative_time(Time.at(job['failed_at'])) %>
<%= t('NextRetry') %><%= relative_time(job.at) %>
<%= t('Scheduled') %><%= relative_time(job.at) %>
<%= t('LastRetry') %><%= relative_time(job.at) if job['retry_count'] %>
-
diff --git a/web/views/_nav.erb b/web/views/_nav.erb deleted file mode 100644 index d3b94659..00000000 --- a/web/views/_nav.erb +++ /dev/null @@ -1,52 +0,0 @@ - diff --git a/web/views/_paging.erb b/web/views/_paging.erb deleted file mode 100644 index cee57b7c..00000000 --- a/web/views/_paging.erb +++ /dev/null @@ -1,23 +0,0 @@ -<% if @total_size > @count %> - -<% end %> diff --git a/web/views/_poll_link.erb b/web/views/_poll_link.erb deleted file mode 100644 index 8bc57995..00000000 --- a/web/views/_poll_link.erb +++ /dev/null @@ -1,4 +0,0 @@ -<% if current_path != '' %> - <%= t('LivePoll') %> - <%= t('StopPolling') %> -<% end %> diff --git a/web/views/_status.erb b/web/views/_status.erb deleted file mode 100644 index 93528c9d..00000000 --- a/web/views/_status.erb +++ /dev/null @@ -1,4 +0,0 @@ - - - <%= t(current_status) %> - diff --git a/web/views/_summary.erb b/web/views/_summary.erb deleted file mode 100644 index 802162a7..00000000 --- a/web/views/_summary.erb +++ /dev/null @@ -1,40 +0,0 @@ - diff --git a/web/views/busy.erb b/web/views/busy.erb deleted file mode 100644 index c4f30925..00000000 --- a/web/views/busy.erb +++ /dev/null @@ -1,132 +0,0 @@ -
-
-

<%= t('Status') %>

-
-
- -
-
-
-

<%= s = processes.size; number_with_delimiter(s) %>

-

<%= t('Processes') %>

-
-
-

<%= x = processes.total_concurrency; number_with_delimiter(x) %>

-

<%= t('Threads') %>

-
-
-

<%= ws = workers.size; number_with_delimiter(ws) %>

-

<%= t('Busy') %>

-
-
-

<%= x == 0 ? 0 : ((ws / x.to_f) * 100).round(0) %>%

-

<%= t('Utilization') %>

-
-
-

<%= format_memory(processes.total_rss) %>

-

<%= t('RSS') %>

-
-
-
- -
-
-

<%= t('Processes') %>

-
-
-
- <%= csrf_tag %> -
- - -
-
-
-
-
- - - - - - - - - - <% lead = processes.leader %> - <% processes.each do |process| %> - - - - - - - - - <% end %> -
<%= t('Name') %><%= t('Started') %><%= t('RSS') %>?<%= t('Threads') %><%= t('Busy') %> 
- <%= "#{process['hostname']}:#{process['pid']}" %> - <%= process.tag %> - <% process.labels.each do |label| %> - <%= label %> - <% end %> - <% if process.stopping? %> - quiet - <% end %> - <% if process.identity == lead %> - leader - <% end %> -
- <%= "#{t('Queues')}: " %> - <%= process.queues.join(", ") %> -
<%= relative_time(Time.at(process['started_at'])) %><%= format_memory(process['rss'].to_i) %><%= process['concurrency'] %><%= process['busy'] %> -
- <%= csrf_tag %> - - -
- <% unless process.stopping? %><% end %> - -
-
-
-
- -
-
-

<%= t('Jobs') %>

-
-
- -
- - - - - - - - - - - <% workers.each do |process, thread, msg| %> - <% job = Sidekiq::JobRecord.new(msg['payload']) %> - - - - - - - - - - <% end %> -
<%= t('Process') %><%= t('TID') %><%= t('JID') %><%= t('Queue') %><%= t('Job') %><%= t('Arguments') %><%= t('Started') %>
<%= process %><%= thread %><%= job.jid %> - <%= msg['queue'] %> - - <%= job.display_class %> - <%= display_tags(job, nil) %> - -
<%= display_args(job.display_args) %>
-
<%= relative_time(Time.at(msg['run_at'])) %>
-
diff --git a/web/views/dashboard.erb b/web/views/dashboard.erb deleted file mode 100644 index ae51c9b6..00000000 --- a/web/views/dashboard.erb +++ /dev/null @@ -1,83 +0,0 @@ - -
-

- <%= t('Dashboard') %> - - - - -

-
- <%= t('PollingInterval') %>: - 5 sec -
- -
-
- -
-
-
-
- -
-
-

<%= t('History') %>

-
-
- - -
-
-
-

Redis

-
-
-
-
- <% if @redis_info.fetch("redis_version", nil) %> -
-

<%= @redis_info.fetch("redis_version") %>

-

<%= t('Version') %>

-
- <% end %> - - <% if @redis_info.fetch("uptime_in_days", nil) %> -
-

<%= @redis_info.fetch("uptime_in_days") %>

-

<%= t('Uptime') %>

-
- <% end %> - - <% if @redis_info.fetch("connected_clients", nil) %> -
-

<%= @redis_info.fetch("connected_clients") %>

-

<%= t('Connections') %>

-
- <% end %> - - <% if @redis_info.fetch("used_memory_human", nil) %> -
-

<%= @redis_info.fetch("used_memory_human") %>

-

<%= t('MemoryUsage') %>

-
- <% end %> - - <% if @redis_info.fetch("used_memory_peak_human", nil) %> -
-

<%= @redis_info.fetch("used_memory_peak_human") %>

-

<%= t('PeakMemoryUsage') %>

-
- <% end %> -
-
diff --git a/web/views/dead.erb b/web/views/dead.erb deleted file mode 100644 index 4f35ac93..00000000 --- a/web/views/dead.erb +++ /dev/null @@ -1,34 +0,0 @@ -<%= erb :_job_info, locals: { job: @dead, type: :dead } %> - -

<%= t('Error') %>

-
- - - - - - - - - - - <% if @dead.error_backtrace %> - - - - - <% end %> - -
<%= t('ErrorClass') %> - <%= @dead['error_class'] %> -
<%= t('ErrorMessage') %><%= h(@dead['error_message']) %>
<%= t('ErrorBacktrace') %> - <%= @dead.error_backtrace.join("
") %>
-
-
- -
- <%= csrf_tag %> - <%= t('GoBack') %> - - -
diff --git a/web/views/layout.erb b/web/views/layout.erb deleted file mode 100644 index 3cbe9c2b..00000000 --- a/web/views/layout.erb +++ /dev/null @@ -1,42 +0,0 @@ - - - - <%= environment_title_prefix %><%= Sidekiq::NAME %> - - - - - <% if rtl? %> - - <% end %> - - - - <% if rtl? %> - - <% end %> - - - - - - <%= display_custom_head %> - - - <%= erb :_nav %> -
-
-
-
- <%= erb :_summary %> -
- -
- <%= yield %> -
-
-
-
- <%= erb :_footer %> - - diff --git a/web/views/morgue.erb b/web/views/morgue.erb deleted file mode 100644 index 411e5720..00000000 --- a/web/views/morgue.erb +++ /dev/null @@ -1,78 +0,0 @@ -
-
-

<%= t('DeadJobs') %>

-
- <% if @dead.size > 0 && @total_size > @count %> -
- <%= erb :_paging, locals: { url: "#{root_path}morgue" } %> -
- <% end %> - <%= filtering('dead') %> -
- -<% if @dead.size > 0 %> -
- <%= csrf_tag %> -
- - - - - - - - - - - - <% @dead.each do |entry| %> - - - - - - - - - <% end %> -
- - <%= t('LastRetry') %><%= t('Queue') %><%= t('Job') %><%= t('Arguments') %><%= t('Error') %>
- - - <%= relative_time(entry.at) %> - - <%= entry.queue %> - - <%= entry.display_class %> - <%= display_tags(entry, "dead") %> - -
<%= display_args(entry.display_args) %>
-
- <% if entry.error? %> -
<%= h truncate("#{entry['error_class']}: #{entry['error_message']}", 200) %>
- <% end %> -
-
- - -
- - <% unfiltered? do %> -
- <%= csrf_tag %> - -
-
- <%= csrf_tag %> - -
- <% end %> - -<% else %> -
<%= t('NoDeadJobsFound') %>
-<% end %> diff --git a/web/views/queue.erb b/web/views/queue.erb deleted file mode 100644 index 57585112..00000000 --- a/web/views/queue.erb +++ /dev/null @@ -1,55 +0,0 @@ -
-
-

- <%= t('CurrentMessagesInQueue', :queue => h(@name)) %> - <% if @queue.paused? %> - <%= t('Paused') %> - <% end %> - <%= number_with_delimiter(@total_size) %> -

-
-
- <%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %> -
-
-
- - - - - - - - <% @jobs.each_with_index do |job, index| %> - - <% if params[:direction] == 'asc' %> - - <% else %> - - <% end %> - - - - - <% end %> -
# <%= sort_direction_label %><%= t('Job') %><%= t('Arguments') %>
<%= @count * (@current_page - 1) + index + 1 %><%= @total_size - (@count * (@current_page - 1) + index) %> - <%= h(job.display_class) %> - <%= display_tags(job, nil) %> - - <% a = job.display_args %> - <% if a.inspect.size > 100 %> - <%= h(a.inspect[0..100]) + "... " %> - -
<%= display_args(a) %>
- <% else %> - <%= display_args(job.display_args) %> - <% end %> -
-
- <%= csrf_tag %> - - -
-
-
-<%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %> diff --git a/web/views/queues.erb b/web/views/queues.erb deleted file mode 100644 index 9d090739..00000000 --- a/web/views/queues.erb +++ /dev/null @@ -1,38 +0,0 @@ -

<%= t('Queues') %>

- -
- - - - - - - - <% @queues.each do |queue| %> - - - - - - - <% end %> -
<%= t('Queue') %><%= t('Size') %><%= t('Latency') %><%= t('Actions') %>
- <%= h queue.name %> - <% if queue.paused? %> - <%= t('Paused') %> - <% end %> - <%= number_with_delimiter(queue.size) %> <% queue_latency = queue.latency %><%= number_with_delimiter(queue_latency.round(2)) %><%= (queue_latency < 60) ? '' : " (#{relative_time(Time.at(Time.now.to_f - queue_latency))})" %> -
- <%= csrf_tag %> - - - <% if Sidekiq.pro? %> - <% if queue.paused? %> - - <% else %> - - <% end %> - <% end %> -
-
-
diff --git a/web/views/retries.erb b/web/views/retries.erb deleted file mode 100644 index a46ed1e8..00000000 --- a/web/views/retries.erb +++ /dev/null @@ -1,83 +0,0 @@ -
-
-

<%= t('Retries') %>

-
- <% if @retries.size > 0 && @total_size > @count %> -
- <%= erb :_paging, locals: { url: "#{root_path}retries" } %> -
- <% end %> - <%= filtering('retries') %> -
- -<% if @retries.size > 0 %> -
- <%= csrf_tag %> -
- - - - - - - - - - - - - <% @retries.each do |entry| %> - - - - - - - - - - <% end %> -
- - <%= t('NextRetry') %><%= t('RetryCount') %><%= t('Queue') %><%= t('Job') %><%= t('Arguments') %><%= t('Error') %>
- - - <%= relative_time(entry.at) %> - <%= entry['retry_count'] %> - <%= entry.queue %> - - <%= entry.display_class %> - <%= display_tags(entry, "retries") %> - -
<%= display_args(entry.display_args) %>
-
-
<%= h truncate("#{entry['error_class']}: #{entry['error_message']}", 200) %>
-
-
- - - -
- - <% unfiltered? do %> -
- <%= csrf_tag %> - -
-
- <%= csrf_tag %> - -
-
- <%= csrf_tag %> - -
- <% end %> - -<% else %> -
<%= t('NoRetriesFound') %>
-<% end %> diff --git a/web/views/retry.erb b/web/views/retry.erb deleted file mode 100644 index 52ad9ac4..00000000 --- a/web/views/retry.erb +++ /dev/null @@ -1,34 +0,0 @@ -<%= erb :_job_info, locals: { job: @retry, type: :retry } %> - -

<%= t('Error') %>

-
- - - - - - - - - - - <% if @retry.error_backtrace %> - - - - - <% end %> - -
<%= t('ErrorClass') %> - <%= h @retry['error_class'] %> -
<%= t('ErrorMessage') %><%= h(@retry['error_message']) %>
<%= t('ErrorBacktrace') %> - <%= @retry.error_backtrace.join("
") %>
-
-
- -
- <%= csrf_tag %> - <%= t('GoBack') %> - - -
diff --git a/web/views/scheduled.erb b/web/views/scheduled.erb deleted file mode 100644 index 0ee8aee3..00000000 --- a/web/views/scheduled.erb +++ /dev/null @@ -1,57 +0,0 @@ -
-
-

<%= t('ScheduledJobs') %>

-
- <% if @scheduled.size > 0 && @total_size > @count %> -
- <%= erb :_paging, locals: { url: "#{root_path}scheduled" } %> -
- <% end %> - <%= filtering('scheduled') %> -
- -<% if @scheduled.size > 0 %> - -
- <%= csrf_tag %> -
- - - - - - - - - - - <% @scheduled.each do |entry| %> - - - - - - - - <% end %> -
- - <%= t('When') %><%= t('Queue') %><%= t('Job') %><%= t('Arguments') %>
- - - <%= relative_time(entry.at) %> - - <%= entry.queue %> - - <%= entry.display_class %> - <%= display_tags(entry, "scheduled") %> - -
<%= display_args(entry.display_args) %>
-
-
- - -
-<% else %> -
<%= t('NoScheduledFound') %>
-<% end %> diff --git a/web/views/scheduled_job_info.erb b/web/views/scheduled_job_info.erb deleted file mode 100644 index 4f532616..00000000 --- a/web/views/scheduled_job_info.erb +++ /dev/null @@ -1,8 +0,0 @@ -<%= erb :_job_info, locals: { job: @job, type: :scheduled } %> - -
- <%= csrf_tag %> - <%= t('GoBack') %> - - -
From 30f094d089c0b3250ea7558d199662f5948162c9 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 8 Feb 2022 12:19:56 -0600 Subject: [PATCH 2/3] Basic Sidekiq::DelayedExtension --- .github/ISSUE_TEMPLATE/bug_report.md | 20 + .github/contributing.md | 47 + .github/workflows/ci.yml | 39 + .gitignore | 14 + .standard.yml | 13 + COMM-LICENSE.txt | 97 + Changes.md | 1864 +++++++++++++++++ Gemfile | 28 + LICENSE | 9 + README.md | 98 + Rakefile | 10 + code_of_conduct.md | 50 + lib/sidekiq/delay_extensions.rb | 46 + lib/sidekiq/delay_extensions/action_mailer.rb | 48 + lib/sidekiq/delay_extensions/active_record.rb | 43 + lib/sidekiq/delay_extensions/api.rb | 39 + lib/sidekiq/delay_extensions/class_methods.rb | 43 + lib/sidekiq/delay_extensions/generic_proxy.rb | 33 + lib/sidekiq/delay_extensions/testing.rb | 8 + lib/sidekiq/delay_extensions/version.rb | 7 + myapp/.gitignore | 30 + myapp/Gemfile | 11 + myapp/Rakefile | 6 + myapp/app/assets/config/manifest.js | 0 .../app/controllers/application_controller.rb | 2 + myapp/app/controllers/work_controller.rb | 44 + myapp/app/helpers/application_helper.rb | 2 + myapp/app/jobs/application_job.rb | 2 + myapp/app/jobs/exit_job.rb | 11 + myapp/app/jobs/some_job.rb | 8 + myapp/app/mailers/.gitkeep | 0 myapp/app/mailers/user_mailer.rb | 9 + myapp/app/models/.gitkeep | 0 myapp/app/models/exiter.rb | 9 + myapp/app/models/post.rb | 9 + myapp/app/views/layouts/application.html.erb | 15 + .../app/views/user_mailer/greetings.html.erb | 3 + myapp/app/views/work/index.html.erb | 1 + myapp/app/workers/exit_worker.rb | 11 + myapp/app/workers/hard_worker.rb | 10 + myapp/app/workers/lazy_worker.rb | 6 + myapp/bin/bundle | 3 + myapp/bin/rails | 9 + myapp/bin/rake | 9 + myapp/bin/setup | 36 + myapp/config.ru | 5 + myapp/config/application.rb | 34 + myapp/config/boot.rb | 3 + myapp/config/database.yml | 25 + myapp/config/environment.rb | 5 + myapp/config/environments/development.rb | 61 + myapp/config/environments/production.rb | 94 + myapp/config/environments/test.rb | 46 + myapp/config/initializers/assets.rb | 11 + .../initializers/backtrace_silencers.rb | 7 + .../config/initializers/cookies_serializer.rb | 5 + .../initializers/filter_parameter_logging.rb | 4 + myapp/config/initializers/inflections.rb | 16 + myapp/config/initializers/mime_types.rb | 4 + myapp/config/initializers/secret_token.rb | 7 + myapp/config/initializers/session_store.rb | 3 + myapp/config/initializers/sidekiq.rb | 44 + myapp/config/initializers/wrap_parameters.rb | 14 + myapp/config/locales/en.yml | 23 + myapp/config/routes.rb | 15 + myapp/config/secrets.yml | 22 + myapp/config/sidekiq.yml | 3 + .../db/migrate/20120123214055_create_posts.rb | 10 + myapp/db/schema.rb | 22 + myapp/db/seeds.rb | 7 + myapp/lib/assets/.gitkeep | 0 myapp/lib/tasks/.gitkeep | 0 myapp/log/.gitkeep | 0 myapp/script/rails | 6 + myapp/simple.ru | 19 + sidekiq-delay_extensions.gemspec | 32 + test/config.yml | 7 + test/config__FILE__and__dir__.yml | 5 + test/config_empty.yml | 1 + test/config_environment.yml | 10 + test/config_queues_without_weights.yml | 4 + test/config_string.yml | 8 + test/config_with_alias.yml | 12 + test/config_with_internal_options.yml | 11 + test/dummy/config/application.rb | 19 + test/dummy/config/database.yml | 11 + test/dummy/config/environment.rb | 3 + test/dummy/config/sidekiq.yml | 3 + test/dummy/tmp/.keep | 0 test/fake_env.rb | 1 + test/fixtures/en.yml | 2 + test/helper.rb | 46 + test/test_api.rb | 680 ++++++ test/test_extensions.rb | 112 + test/test_testing_fake.rb | 365 ++++ test/test_testing_inline.rb | 92 + 96 files changed, 4741 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/contributing.md create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .standard.yml create mode 100644 COMM-LICENSE.txt create mode 100644 Changes.md create mode 100644 Gemfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 code_of_conduct.md create mode 100644 lib/sidekiq/delay_extensions.rb create mode 100644 lib/sidekiq/delay_extensions/action_mailer.rb create mode 100644 lib/sidekiq/delay_extensions/active_record.rb create mode 100644 lib/sidekiq/delay_extensions/api.rb create mode 100644 lib/sidekiq/delay_extensions/class_methods.rb create mode 100644 lib/sidekiq/delay_extensions/generic_proxy.rb create mode 100644 lib/sidekiq/delay_extensions/testing.rb create mode 100644 lib/sidekiq/delay_extensions/version.rb create mode 100644 myapp/.gitignore create mode 100644 myapp/Gemfile create mode 100644 myapp/Rakefile create mode 100644 myapp/app/assets/config/manifest.js create mode 100644 myapp/app/controllers/application_controller.rb create mode 100644 myapp/app/controllers/work_controller.rb create mode 100644 myapp/app/helpers/application_helper.rb create mode 100644 myapp/app/jobs/application_job.rb create mode 100644 myapp/app/jobs/exit_job.rb create mode 100644 myapp/app/jobs/some_job.rb create mode 100644 myapp/app/mailers/.gitkeep create mode 100644 myapp/app/mailers/user_mailer.rb create mode 100644 myapp/app/models/.gitkeep create mode 100644 myapp/app/models/exiter.rb create mode 100644 myapp/app/models/post.rb create mode 100644 myapp/app/views/layouts/application.html.erb create mode 100644 myapp/app/views/user_mailer/greetings.html.erb create mode 100644 myapp/app/views/work/index.html.erb create mode 100644 myapp/app/workers/exit_worker.rb create mode 100644 myapp/app/workers/hard_worker.rb create mode 100644 myapp/app/workers/lazy_worker.rb create mode 100755 myapp/bin/bundle create mode 100755 myapp/bin/rails create mode 100755 myapp/bin/rake create mode 100755 myapp/bin/setup create mode 100644 myapp/config.ru create mode 100644 myapp/config/application.rb create mode 100644 myapp/config/boot.rb create mode 100644 myapp/config/database.yml create mode 100644 myapp/config/environment.rb create mode 100644 myapp/config/environments/development.rb create mode 100644 myapp/config/environments/production.rb create mode 100644 myapp/config/environments/test.rb create mode 100644 myapp/config/initializers/assets.rb create mode 100644 myapp/config/initializers/backtrace_silencers.rb create mode 100644 myapp/config/initializers/cookies_serializer.rb create mode 100644 myapp/config/initializers/filter_parameter_logging.rb create mode 100644 myapp/config/initializers/inflections.rb create mode 100644 myapp/config/initializers/mime_types.rb create mode 100644 myapp/config/initializers/secret_token.rb create mode 100644 myapp/config/initializers/session_store.rb create mode 100644 myapp/config/initializers/sidekiq.rb create mode 100644 myapp/config/initializers/wrap_parameters.rb create mode 100644 myapp/config/locales/en.yml create mode 100644 myapp/config/routes.rb create mode 100644 myapp/config/secrets.yml create mode 100644 myapp/config/sidekiq.yml create mode 100644 myapp/db/migrate/20120123214055_create_posts.rb create mode 100644 myapp/db/schema.rb create mode 100644 myapp/db/seeds.rb create mode 100644 myapp/lib/assets/.gitkeep create mode 100644 myapp/lib/tasks/.gitkeep create mode 100644 myapp/log/.gitkeep create mode 100755 myapp/script/rails create mode 100644 myapp/simple.ru create mode 100644 sidekiq-delay_extensions.gemspec create mode 100644 test/config.yml create mode 100644 test/config__FILE__and__dir__.yml create mode 100644 test/config_empty.yml create mode 100644 test/config_environment.yml create mode 100644 test/config_queues_without_weights.yml create mode 100644 test/config_string.yml create mode 100644 test/config_with_alias.yml create mode 100644 test/config_with_internal_options.yml create mode 100644 test/dummy/config/application.rb create mode 100644 test/dummy/config/database.yml create mode 100644 test/dummy/config/environment.rb create mode 100644 test/dummy/config/sidekiq.yml create mode 100644 test/dummy/tmp/.keep create mode 100644 test/fake_env.rb create mode 100644 test/fixtures/en.yml create mode 100644 test/helper.rb create mode 100644 test/test_api.rb create mode 100644 test/test_extensions.rb create mode 100644 test/test_testing_fake.rb create mode 100644 test/test_testing_inline.rb diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..95c2601a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +Ruby version: +Rails version: +Sidekiq / Pro / Enterprise version(s): + +Please include your initializer, sidekiq.yml, and any error message with the full backtrace. + +If you are using an old version, have you checked the changelogs to see if your issue has been fixed in a later version? + +https://github.com/mperham/sidekiq/blob/main/Changes.md +https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md +https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 00000000..dffd847a --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,47 @@ +# Contributing + +## Issues + +When opening an issue: + +* include the full **backtrace** with your error +* include your Sidekiq initializer +* list versions you are using: Ruby, Rails, Sidekiq, OS, etc. + +It's always better to include more info rather than less. + +## Code + +It's always best to open an issue before investing a lot of time into a +fix or new functionality. Functionality must meet my design goals and +vision for the project to be accepted; I would be happy to discuss how +your idea can best fit into Sidekiq. + +### Local development setup + +You need Redis installed and a Ruby version that fulfills the requirements in +`sidekiq.gemspec`. Then: + +``` +bundle install +``` + +And in order to run the tests and linter checks: + +``` +bundle exec rake +``` + +## Legal + +By submitting a Pull Request, you disavow any rights or claims to any changes +submitted to the Sidekiq project and assign the copyright of +those changes to Contributed Systems LLC. + +If you cannot or do not want to reassign those rights (your employment +contract for your employer may not allow this), you should not submit a PR. +Open an issue and someone else can do the work. + +This is a legal way of saying "If you submit a PR to us, that code becomes ours". +99.9% of the time that's what you intend anyways; we hope it doesn't scare you +away from contributing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..0a462adc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby: ["2.6", "2.7", "3.0", "3.1"] + services: + redis: + image: redis + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 6379:6379 + + steps: + - uses: actions/checkout@v2 + - name: Set up Ruby + # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, + # change this to (see https://github.com/ruby/setup-ruby#versioning): + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true # 'bundle install' and cache gems + ruby-version: ${{ matrix.ruby }} + - name: Run tests + run: bundle exec rake diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d2af119d --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.rvmrc +.ruby-version +tags +*.swp +dump.rdb +.rbx +coverage/ +vendor/ +.bundle/ +.sass-cache/ +tmp/ +pkg/*.gem +.byebug_history +Gemfile.lock diff --git a/.standard.yml b/.standard.yml new file mode 100644 index 00000000..3abc0644 --- /dev/null +++ b/.standard.yml @@ -0,0 +1,13 @@ +ruby_version: 2.5.0 +fix: true +parallel: true +ignore: + - test/**/* + - examples/**/* + - myapp/**/* + - 'lib/sidekiq/delay_extensions/**/*': + - Style/MissingRespondToMissing + - 'lib/**/*': + - Lint/RescueException + - Security/YAMLLoad + - Style/GlobalVars diff --git a/COMM-LICENSE.txt b/COMM-LICENSE.txt new file mode 100644 index 00000000..880a1d37 --- /dev/null +++ b/COMM-LICENSE.txt @@ -0,0 +1,97 @@ +END-USER LICENSE AGREEMENT + +------------------------------------------------------------------------------ + +IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT ("EULA") IS A LEGAL AGREEMENT (“Agreement”) BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND CONTRIBUTED SYSTEMS. READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION PROCESS AND USING SIDEKIQ PRO AND RELATED SOFTWARE COMPONENTS (“SOFTWARE”). IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE CONFIRMING YOUR ACCEPTANCE OF THE SOFTWARE AND AGREEING TO BECOME BOUND BY THE TERMS OF THIS AGREEMENT. + +------------------------------------------------------------------------------ + +In order to use the Software under this Agreement, you must receive a “Source URL” at the time of purchase, in accordance with the scope of use and other terms specified for each type of Software and as set forth in this Section 1 of this Agreement. + +1. License Grant + +1.1 General Use. This Agreement grants you a non-exclusive, non-transferable, limited license to the use rights for the Software, without the right to grant sublicenses, subject to the terms and conditions in this Agreement. The Software is licensed, not sold. + +1.2 Unlimited Organization License. If you purchased an Organization License (included with the Sidekiq Pro Software), you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. You may also run an unlimited number of Workers. “Worker” means a thread within a Sidekiq server process which executes jobs. You may concurrently run the software on an unlimited number of Hosts, with each host running an unlimited number of Workers. + +1.3 Limited Enterprise License. If you purchased a Limited License for the Sidekiq Enterprise Software, you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. The aggregate number of Workers run by the hosts must not exceed the maximum number of Workers authorized at the time of purchase. “Worker” means a thread within a Sidekiq server process which executes jobs. In order to run additional Workers, you must purchase an additional allowance from Contributed Systems. + +1.4 Enterprise Site License. If you purchased a Site License for the Sidekiq Enterprise Software, you may install the Software on an unlimited number of Hosts. “Host” means any physical or virtual machine which is controlled by you. You may also run an unlimited number of Workers. “Worker” means a thread within a Sidekiq server process which executes jobs. You may concurrently run the software on an unlimited number of Hosts, with each host running an unlimited number of Workers. + +1.5 Appliance License. If you purchased an Appliance License, you may distribute the Software in any applications, frameworks, or elements (collectively referred to as an “Application” or “Applications”) that you develop using the Software in accordance with this EULA, provided that such distribution does not violate the restrictions set forth in section 3 of this EULA. You must not remove, obscure or interfere with any copyright, acknowledgment, attribution, trademark, warning or disclaimer statement affixed to, incorporated in or otherwise applied in connection with the Software. You are required to ensure that the Software is not reused by or with any applications other than those with which you distribute it as permitted herein. For example, if You install the Software on a customer's server, that customer is not permitted to use the Software independently of your Application. You must inform Contributed Systems of your knowledge of any infringing use of the Software by any of your customers. You are liable for compliance by those third parties with the terms and conditions of this EULA. You will not owe Contributed Systems any royalties for your distribution of the Software in accordance with this EULA. + +1.6 Archive Copies. You are entitled to make a reasonable amount of copies of the Software for archival purposes. Each copy must reproduce all copyright and other proprietary rights notices on or in the Software Product. + +1.7 Electronic Delivery. All Software and license documentation shall be delivered by electronic means unless otherwise specified on the applicable invoice or at the time of purchase. Software shall be deemed delivered when it is made available for download by you (“Delivery”). + +2. Modifications. Contributed Systems shall provide you with source code so that you can create Modifications of the original software. “Modification” means: (a) any addition to or deletion from the contents of a file included in the original Software or previous Modifications created by You, or (b) any new file that contains any part of the original Software or previous Modifications. While you retain all rights to any original work authored by you as part of the Modifications, We continue to own all copyright and other intellectual property rights in the Software. + +3. Restricted Uses. + +3.1 You shall not (and shall not allow any third party to): (a) decompile, disassemble, or otherwise reverse engineer the Software or attempt to reconstruct or discover any source code, underlying ideas, algorithms, file formats or programming interfaces of the Software by any means whatsoever (except and only to the extent that applicable law prohibits or restricts reverse engineering restrictions); (b) distribute, sell, sublicense, rent, lease or use the Software for time sharing, hosting, service provider or like purposes, except as expressly permitted under this Agreement; (c) redistribute the Software or Modifications other than by including the Software or a portion thereof within your own product, which must have substantially different functionality than the Software or Modifications and must not allow any third party to use the Software or Modifications, or any portions thereof, for software development or application development purposes; (d) redistribute the Software as part of a product, "appliance" or "virtual server"; (e) redistribute the Software on any server which is not directly under your control; (f) remove any product identification, proprietary, copyright or other notices contained in the Software; (g) modify any part of the Software, create a derivative work of any part of the Software (except as permitted in Section 4), or incorporate the Software, except to the extent expressly authorized in writing by Contributed Systems; (h) publicly disseminate performance information or analysis (including, without limitation, benchmarks) from any source relating to the Software; (i) utilize any equipment, device, software, or other means designed to circumvent or remove any form of Source URL or copy protection used by Contributed Systems in connection with the Software, or use the Software together with any authorization code, Source URL, serial number, or other copy protection device not supplied by Contributed Systems; (j) use the Software to develop a product which is competitive with any Contributed Systems product offerings; or (k) use unauthorized Source URLS or keycode(s) or distribute or publish Source URLs or keycode(s), except as may be expressly permitted by Contributed Systems in writing. If your unique Source URL is ever published, Contributed Systems reserves the right to terminate your access without notice. + +3.2 UNDER NO CIRCUMSTANCES MAY YOU USE THE SOFTWARE AS PART OF A PRODUCT OR SERVICE THAT PROVIDES SIMILAR FUNCTIONALITY TO THE SOFTWARE ITSELF. + +The Open Source version of the Software (“LGPL Version”) is licensed +under the terms of the GNU Lesser General Public License version 3.0 +(“LGPL”) and not under this EULA. + +4. Ownership. Notwithstanding anything to the contrary contained herein, except for the limited license rights expressly provided herein, Contributed Systems and its suppliers have and will retain all rights, title and interest (including, without limitation, all patent, copyright, trademark, trade secret and other intellectual property rights) in and to the Software and all copies, modifications and derivative works thereof (including any changes which incorporate any of your ideas, feedback or suggestions). You acknowledge that you are obtaining only a limited license right to the Software, and that irrespective of any use of the words “purchase”, “sale” or like terms hereunder no ownership rights are being conveyed to you under this Agreement or otherwise. + +5. Fees and Payment. The Software license fees will be due and payable in full as set forth in the applicable invoice or at the time of purchase. If the Software does not function properly within two weeks of purchase, please contact us within those two weeks for a refund. You shall be responsible for all taxes, withholdings, duties and levies arising from the order (excluding taxes based on the net income of Contributed Systems). + +6. Support, Maintenance and Services. Subject to the terms and conditions of this Agreement, as set forth in your invoice, and as set forth on the Sidekiq Pro support page (https://github.com/mperham/sidekiq/wiki/Commercial-Support), support and maintenance services may be included with the purchase of your license subscription. + +7. Term of Agreement. + +7.1 Term. This Agreement is effective as of the Delivery of the Software and expires at such time as all license and service subscriptions hereunder have expired in accordance with their own terms (the “Term”). For clarification, the term of your license under this Agreement may be perpetual, limited for Evaluation Version, or designated as a fixed-term license in the Invoice, and shall be specified at your time of purchase. Either party may terminate this Agreement (including all related Invoices) if the other party: (a) fails to cure any material breach of this Agreement within thirty (30) days after written notice of such breach, provided that Contributed Systems may terminate this Agreement immediately upon any breach of Section 3 or if you exceed any other restrictions contained in Section 1, unless otherwise specified in this agreement; (b) ceases operation without a successor; or (c) seeks protection under any bankruptcy, receivership, trust deed, creditors arrangement, composition or comparable proceeding, or if any such proceeding is instituted against such party (and not dismissed within sixty (60) days)). Termination is not an exclusive remedy and the exercise by either party of any remedy under this Agreement will be without prejudice to any other remedies it may have under this Agreement, by law, or otherwise. + +7.2 Termination. Upon any termination of this Agreement, you shall cease any and all use of any Software and destroy all copies thereof. + +7.3 Expiration of License. Upon the expiration of any term under this Agreement, (a) all Software updates and services pursuant to the license shall cease, (b) you may only continue to run existing installations of the Software, (c) you may not install the Software on any additional Hosts, and (d) any new installation of the Software shall require the purchase of a new license subscription from Contributed Systems. + +8. Disclaimer of Warranties. The Software is provided "as is," with all faults, defects and errors, and without warranty of any kind. Contributed Systems does not warrant that the Software will be free of bugs, errors, viruses or other defects, and Contributed Systems shall have no liability of any kind for the use of or inability to use the Software, the Software content or any associated service, and you acknowledge that it is not technically practicable for Contributed Systems to do so. +To the maximum extent permitted by applicable law, Contributed Systems disclaims all warranties, express, implied, arising by law or otherwise, regarding the Software, the Software content and their respective performance or suitability for your intended use, including without limitation any implied warranty of merchantability, fitness for a particular purpose. + +9. Limitation of Liability. + +In no event will Contributed Systems be liable for any direct, indirect, consequential, incidental, special, exemplary, or punitive damages or liabilities whatsoever arising from or relating to the Software, the Software content or this Agreement, whether based on contract, tort (including negligence), strict liability or other theory, even if Contributed Systems has been advised of the possibility of such damages. + +In no event will Contributed Systems' liability exceed the Software license price as indicated in the invoice. The existence of more than one claim will not enlarge or extend this limit. + +10. Remedies. Your exclusive remedy and Contributed Systems' entire liability for breach of this Agreement shall be limited, at Contributed Systems' sole and exclusive discretion, to (a) replacement of any defective software or documentation; or (b) refund of the license fee paid to Contributed Systems, payable in accordance with Contributed Systems' refund policy. + +11. Acknowledgements. + +11.1 Consent to the Use of Data. You agree that Contributed Systems and its affiliates may collect and use technical information gathered as part of the product support services. Contributed Systems may use this information solely to improve products and services and will not disclose this information in a form that personally identifies you. + +11.2 Verification. We or a certified auditor acting on our behalf, may, upon its reasonable request and at its expense, audit you with respect to the use of the Software. Such audit may be conducted by mail, electronic means or through an in-person visit to your place of business. Any such in-person audit shall be conducted during regular business hours at your facilities and shall not unreasonably interfere with your business activities. We shall not remove, copy, or redistribute any electronic material during the course of an audit. If an audit reveals that you are using the Software in a way that is in material violation of the terms of the EULA, then you shall pay our reasonable costs of conducting the audit. In the case of a material violation, you agree to pay Us any amounts owing that are attributable to the unauthorized use. In the alternative, We reserve the right, at our sole option, to terminate the licenses for the Software. + +11.3 Government End Users. If the Software and related documentation are supplied to or purchased by or on behalf of the United States Government, then the Software is deemed to be "commercial software" as that term is used in the Federal Acquisition Regulation system. Rights of the United States shall not exceed the minimum rights set forth in FAR 52.227-19 for "restricted computer software". All other terms and conditions of this Agreement apply. + +12. Third Party Software. Examples included in Software may provide links to third party libraries or code (collectively “Third Party Software”) to implement various functions. Third Party Software does not comprise part of the Software. In some cases, access to Third Party Software may be included along with the Software delivery as a convenience for demonstration purposes. Such source code and libraries may be included in the “…/examples” source tree delivered with the Software and do not comprise the Software. Licensee acknowledges (1) that some part of Third Party Software may require additional licensing of copyright and patents from the owners of such, and (2) that distribution of any of the Software referencing or including any portion of a Third Party Software may require appropriate licensing from such third parties. + + +13. Miscellaneous + +13.1 Entire Agreement. This Agreement sets forth our entire agreement with respect to the Software and the subject matter hereof and supersedes all prior and contemporaneous understandings and agreements whether written or oral. + +13.2 Amendment. Contributed Systems reserves the right, in its sole discretion, to amend this Agreement from time. Amendments to this Agreement can be located at: https://github.com/mperham/sidekiq/blob/main/COMM-LICENSE. + +13.3 Assignment. You may not assign this Agreement or any of its rights under this Agreement without the prior written consent of Contributed Systems and any attempted assignment without such consent shall be void. + +13.4 Export Compliance. You agree to comply with all applicable laws and regulations, including laws, regulations, orders or other restrictions on export, re-export or redistribution of software. + +13.5 Indemnification. You agree to defend, indemnify, and hold harmless Contributed Systems from and against any lawsuits, claims, losses, damages, fines and expenses (including attorneys' fees and costs) arising out of your use of the Software or breach of this Agreement. + +13.6 Governing Law. This Agreement is governed by the laws of the State of Oregon and the United States without regard to conflicts of laws provisions thereof, and without regard to the United Nations Convention on the International Sale of Goods or the Uniform Computer Information Transactions Act, as currently enacted by any jurisdiction or as may be codified or amended from time to time by any jurisdiction. The jurisdiction and venue for actions related to the subject matter hereof shall be the state of Oregon and United States federal courts located in Portland, Oregon, and both parties hereby submit to the personal jurisdiction of such courts. + +13.7 Attorneys' Fees and Costs. The prevailing party in any action to enforce this Agreement will be entitled to recover its attorneys' fees and costs in connection with such action. + +13.8 Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the remainder of this Agreement will remain in full force and effect. + +13.9 Waiver. Failure or neglect by either party to enforce at any time any of the provisions of this licence Agreement shall not be construed or deemed to be a waiver of that party's rights under this Agreement. + +13.10 Headings. The headings of sections and paragraphs of this Agreement are for convenience of reference only and are not intended to restrict, affect or be of any weight in the interpretation or construction of the provisions of such sections or paragraphs. + +14. Contact Information. If you have any questions about this EULA, or if you want to contact Contributed Systems for any reason, please direct correspondence to info@contribsys.com. diff --git a/Changes.md b/Changes.md new file mode 100644 index 00000000..1891785b --- /dev/null +++ b/Changes.md @@ -0,0 +1,1864 @@ +# Sidekiq Changes + +[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md) + +HEAD +--------- + +- Fix sidekiq.yml YAML load errors on Ruby 3.1 [#5141] +- Sharding support for `perform_bulk` [#5129] +- Refactor job logger for SPEEEEEEED + +6.4.0 +--------- + +- **SECURITY**: Validate input to avoid possible DoS in Web UI. +- Add **strict argument checking** [#5071] + Sidekiq will now log a warning if JSON-unsafe arguments are passed to `perform_async`. + Add `Sidekiq.strict_args!(false)` to your initializer to disable this warning. + This warning will switch to an exception in Sidekiq 7.0. +- Note that Delayed Extensions will be removed in Sidekiq 7.0 [#5076] +- Add `perform_{inline,sync}` in Sidekiq::Job to run a job synchronously [#5061, hasan-ally] +```ruby +SomeJob.perform_async(args...) +SomeJob.perform_sync(args...) +SomeJob.perform_inline(args...) +``` + You can also dynamically redirect a job to run synchronously: +```ruby +SomeJob.set("sync": true).perform_async(args...) # will run via perform_inline +``` +- Replace Sidekiq::Worker `app/workers` generator with Sidekiq::Job `app/sidekiq` generator [#5055] +``` +bin/rails generate sidekiq:job ProcessOrderJob +``` +- Fix job retries losing CurrentAttributes [#5090] +- Tweak shutdown to give long-running threads time to cleanup [#5095] + +6.3.1 +--------- + +- Fix keyword arguments error with CurrentAttributes on Ruby 3.0 [#5048] + +6.3.0 +--------- + +- **BREAK**: The Web UI has been refactored to remove jQuery. Any UI extensions + which use jQuery will break. +- **FEATURE**: Sidekiq.logger has been enhanced so any `Rails.logger` + output in jobs now shows up in the Sidekiq console. Remove any logger + hacks in your initializer and see if it Just Works™ now. [#5021] +- **FEATURE**: Add `Sidekiq::Job` alias for `Sidekiq::Worker`, to better + reflect industry standard terminology. You can now do this: +```ruby +class MyJob + include Sidekiq::Job + sidekiq_options ... + def perform(args) + end +end +``` +- **FEATURE**: Support for serializing ActiveSupport::CurrentAttributes into each job. [#4982] +```ruby +# config/initializers/sidekiq.rb +require "sidekiq/middleware/current_attributes" +Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton +``` +- **FEATURE**: Add `Sidekiq::Worker.perform_bulk` for enqueuing jobs in bulk, + similar to `Sidekiq::Client.push_bulk` [#5042] +```ruby +MyJob.perform_bulk([[1], [2], [3]]) +``` +- Implement `queue_as`, `wait` and `wait_until` for ActiveJob compatibility [#5003] +- Scheduler now uses Lua to reduce Redis load and network roundtrips [#5044] +- Retry Redis operation if we get an `UNBLOCKED` Redis error [#4985] +- Run existing signal traps, if any, before running Sidekiq's trap [#4991] +- Fix fetch bug when using weighted queues which caused Sidekiq to stop + processing queues randomly [#5031] + +6.2.2 +--------- + +- Reduce retry jitter, add jitter to `sidekiq_retry_in` values [#4957] +- Minimize scheduler load on Redis at scale [#4882] +- Improve logging of delay jobs [#4904, BuonOno] +- Minor CSS improvements for buttons and tables, design PRs always welcome! +- Tweak Web UI `Cache-Control` header [#4966] +- Rename internal API class `Sidekiq::Job` to `Sidekiq::JobRecord` [#4955] + +6.2.1 +--------- + +- Update RTT warning logic to handle transient RTT spikes [#4851] +- Fix very low priority CVE on unescaped queue name [#4852] +- Add note about sessions and Rails apps in API mode + +6.2.0 +--------- + +- Store Redis RTT and log if poor [#4824] +- Add process/thread stats to Busy page [#4806] +- Improve Web UI on mobile devices [#4840] +- **Refactor Web UI session usage** [#4804] + Numerous people have hit "Forbidden" errors and struggled with Sidekiq's + Web UI session requirement. If you have code in your initializer for + Web sessions, it's quite possible it will need to be removed. Here's + an overview: +``` +Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app, +make sure you mount Sidekiq::Web *inside* your routes in `config/routes.rb` so +Sidekiq can reuse the Rails session: + + Rails.application.routes.draw do + mount Sidekiq::Web => "/sidekiq" + .... + end + +If this is a bare Rack app, use a session middleware before Sidekiq::Web: + + # first, use IRB to create a shared secret key for sessions and commit it + require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) } + + # now, update your Rack app to include the secret with a session cookie middleware + use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400 + run Sidekiq::Web + +If this is a Rails app in API mode, you need to enable sessions. + + https://guides.rubyonrails.org/api_app.html#using-session-middlewares +``` + +6.1.3 +--------- + +- Warn if Redis is configured to evict data under memory pressure [#4752] +- Add process RSS on the Busy page [#4717] + +6.1.2 +--------- + +- Improve readability in dark mode Web UI [#4674] +- Fix Web UI crash with corrupt session [#4672] +- Allow middleware to yield arguments [#4673, @eugeneius] +- Migrate CI from CircleCI to GitHub Actions [#4677] + +6.1.1 +--------- + +- Jobs are now sorted by age in the Busy Workers table. [#4641] +- Fix "check all" JS logic in Web UI [#4619] + +6.1.0 +--------- + +- Web UI - Dark Mode fixes [#4543, natematykiewicz] +- Ensure `Rack::ContentLength` is loaded as middleware for correct Web UI responses [#4541] +- Avoid exception dumping SSL store in Redis connection logging [#4532] +- Better error messages in Sidekiq::Client [#4549] +- Remove rack-protection, reimplement CSRF protection [#4588] +- Require redis-rb 4.2 [#4591] +- Update to jquery 1.12.4 [#4593] +- Refactor internal fetch logic and API [#4602] + +6.0.7 +--------- + +- Refactor systemd integration to work better with custom binaries [#4511] +- Don't connect to Redis at process exit if not needed [#4502] +- Remove Redis connection naming [#4479] +- Fix Redis Sentinel password redaction [#4499] +- Add Vietnamese locale (vi) [#4528] + +6.0.6 +--------- + +- **Integrate with systemd's watchdog and notification features** [#4488] + Set `Type=notify` in [sidekiq.service](https://github.com/mperham/sidekiq/blob/4b8a8bd3ae42f6e48ae1fdaf95ed7d7af18ed8bb/examples/systemd/sidekiq.service#L30-L39). The integration works automatically. +- Use `setTimeout` rather than `setInterval` to avoid thundering herd [#4480] +- Fix edge case where a job can be pushed without a queue. +- Flush job stats at exit [#4498] +- Check RAILS_ENV before RACK_ENV [#4493] +- Add Lithuanian locale [#4476] + +6.0.5 +--------- + +- Fix broken Web UI response when using NewRelic and Rack 2.1.2+. [#4440] +- Update APIs to use `UNLINK`, not `DEL`. [#4449] +- Fix Ruby 2.7 warnings [#4412] +- Add support for `APP_ENV` [[95fa5d9]](https://github.com/mperham/sidekiq/commit/95fa5d90192148026e52ca2902f1b83c70858ce8) + +6.0.4 +--------- + +- Fix ActiveJob's `sidekiq_options` integration [#4404] +- Sidekiq Pro users will now see a Pause button next to each queue in + the Web UI, allowing them to pause queues manually [#4374, shayonj] +- Fix Sidekiq::Workers API unintentional change in 6.0.2 [#4387] + + +6.0.3 +--------- + +- Fix `Sidekiq::Client.push_bulk` API which was erroneously putting + invalid `at` values in the job payloads [#4321] + +6.0.2 +--------- + +- Fix Sidekiq Enterprise's rolling restart functionality, broken by refactoring in 6.0.0. [#4334] +- More internal refactoring and performance tuning [fatkodima] + +6.0.1 +--------- + +- **Performance tuning**, Sidekiq should be 10-15% faster now [#4303, 4299, + 4269, fatkodima] +- **Dark Mode support in Web UI** (further design polish welcome!) [#4227, mperham, + fatkodima, silent-e] +- **Job-specific log levels**, allowing you to turn on debugging for + problematic workers. [fatkodima, #4287] +```ruby +MyWorker.set(log_level: :debug).perform_async(...) +``` +- **Ad-hoc job tags**. You can tag your jobs with, e.g, subdomain, tenant, country, + locale, application, version, user/client, "alpha/beta/pro/ent", types of jobs, + teams/people responsible for jobs, additional metadata, etc. + Tags are shown on different pages with job listings. Sidekiq Pro users + can filter based on them [fatkodima, #4280] +```ruby +class MyWorker + include Sidekiq::Worker + sidekiq_options tags: ['bank-ops', 'alpha'] + ... +end +``` +- Fetch scheduled jobs in batches before pushing into specific queues. + This will decrease enqueueing time of scheduled jobs by a third. [fatkodima, #4273] +``` +ScheduledSet with 10,000 jobs +Before: 56.6 seconds +After: 39.2 seconds +``` +- Compress error backtraces before pushing into Redis, if you are + storing error backtraces, this will halve the size of your RetrySet + in Redis [fatkodima, #4272] +``` +RetrySet with 100,000 jobs +Before: 261 MB +After: 129 MB +``` +- Support display of ActiveJob 6.0 payloads in the Web UI [#4263] +- Add `SortedSet#scan` for pattern based scanning. For large sets this API will be **MUCH** faster + than standard iteration using each. [fatkodima, #4262] +```ruby + Sidekiq::DeadSet.new.scan("UnreliableApi") do |job| + job.retry + end +``` +- Dramatically speed up SortedSet#find\_job(jid) by using Redis's ZSCAN + support, approx 10x faster. [fatkodima, #4259] +``` +zscan 0.179366 0.047727 0.227093 ( 1.161376) +enum 8.522311 0.419826 8.942137 ( 9.785079) +``` +- Respect rails' generators `test_framework` option and gracefully handle extra `worker` suffix on generator [fatkodima, #4256] +- Add ability to sort 'Enqueued' page on Web UI by position in the queue [fatkodima, #4248] +- Support `Client.push_bulk` with different delays [fatkodima, #4243] +```ruby +Sidekiq::Client.push_bulk("class" => FooJob, "args" => [[1], [2]], "at" => [1.minute.from_now.to_f, 5.minutes.from_now.to_f]) +``` +- Easier way to test enqueuing specific ActionMailer and ActiveRecord delayed jobs. Instead of manually + parsing embedded class, you can now test by fetching jobs for specific classes. [fatkodima, #4292] +```ruby +assert_equal 1, Sidekiq::DelayExtensions::DelayedMailer.jobs_for(FooMailer).size +``` +- Add `sidekiqmon` to gemspec executables [#4242] +- Gracefully handle `Sidekiq.logger = nil` [#4240] +- Inject Sidekiq::LogContext module if user-supplied logger does not include it [#4239] + +6.0 +--------- + +This release has major breaking changes. Read and test carefully in production. + +- With Rails 6.0.2+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq + features/internals like the retry subsystem. [#4213, pirj] +```ruby +class MyJob < ActiveJob::Base + queue_as :myqueue + sidekiq_options retry: 10, backtrace: 20 + def perform(...) + end +end +``` +- Logging has been redesigned to allow for pluggable log formatters: +```ruby +Sidekiq.configure_server do |config| + config.log_formatter = Sidekiq::Logger::Formatters::JSON.new +end +``` +See the [Logging wiki page](https://github.com/mperham/sidekiq/wiki/Logging) for more details. +- **BREAKING CHANGE** Validate proper usage of the `REDIS_PROVIDER` + variable. This variable is meant to hold the name of the environment + variable which contains your Redis URL, so that you can switch Redis + providers quickly and easily with a single variable change. It is not + meant to hold the actual Redis URL itself. If you want to manually set + the Redis URL (not recommended as it implies you have no failover), + then you may set `REDIS_URL` directly. [#3969] +- **BREAKING CHANGE** Increase default shutdown timeout from 8 seconds + to 25 seconds. Both Heroku and ECS now use 30 second shutdown timeout + by default and we want Sidekiq to take advantage of this time. If you + have deployment scripts which depend on the old default timeout, use `-t 8` to + get the old behavior. [#3968] +- **BREAKING CHANGE** Remove the daemonization, logfile and pidfile + arguments to Sidekiq. Use a proper process supervisor (e.g. systemd or + foreman) to manage Sidekiq. See the Deployment wiki page for links to + more resources. +- Integrate the StandardRB code formatter to ensure consistent code + styling. [#4114, gearnode] + +5.2.9 +--------- + +- Release Rack lock due to a cascade of CVEs. [#4566] + Pro-tip: don't lock Rack. + +5.2.8 +--------- + +- Lock to Rack 2.0.x to prevent future incompatibilities +- Fix invalid reference in `sidekiqctl` + +5.2.7 +--------- + +- Fix stale `enqueued_at` when retrying [#4149] +- Move build to [Circle CI](https://circleci.com/gh/mperham/sidekiq) [#4120] + +5.2.6 +--------- + +- Fix edge case where a job failure during Redis outage could result in a lost job [#4141] +- Better handling of malformed job arguments in payload [#4095] +- Restore bootstap's dropdown css component [#4099, urkle] +- Display human-friendly time diff for longer queue latencies [#4111, interlinked] +- Allow `Sidekiq::Worker#set` to be chained + +5.2.5 +--------- + +- Fix default usage of `config/sidekiq.yml` [#4077, Tensho] + +5.2.4 +--------- + +- Add warnings for various deprecations and changes coming in Sidekiq 6.0. + See the 6-0 branch. [#4056] +- Various improvements to the Sidekiq test suite and coverage [#4026, #4039, Tensho] + +5.2.3 +--------- + +- Warning message on invalid REDIS\_PROVIDER [#3970] +- Add `sidekiqctl status` command [#4003, dzunk] +- Update elapsed time calculatons to use monotonic clock [#3999] +- Fix a few issues with mobile Web UI styling [#3973, navied] +- Jobs with `retry: false` now go through the global `death_handlers`, + meaning you can take action on failed ephemeral jobs. [#3980, Benjamin-Dobell] +- Fix race condition in defining Workers. [#3997, mattbooks] + +5.2.2 +--------- + +- Raise error for duplicate queue names in config to avoid unexpected fetch algorithm change [#3911] +- Fix concurrency bug on JRuby [#3958, mattbooks] +- Add "Kill All" button to the retries page [#3938] + +5.2.1 +----------- + +- Fix concurrent modification error during heartbeat [#3921] + +5.2.0 +----------- + +- **Decrease default concurrency from 25 to 10** [#3892] +- Verify connection pool sizing upon startup [#3917] +- Smoother scheduling for large Sidekiq clusters [#3889] +- Switch Sidekiq::Testing impl from alias\_method to Module#prepend, for resiliency [#3852] +- Update Sidekiq APIs to use SCAN for scalability [#3848, ffiller] +- Remove concurrent-ruby gem dependency [#3830] +- Optimize Web UI's bootstrap.css [#3914] + +5.1.3 +----------- + +- Fix version comparison so Ruby 2.2.10 works. [#3808, nateberkopec] + +5.1.2 +----------- + +- Add link to docs in Web UI footer +- Fix crash on Ctrl-C in Windows [#3775, Bernica] +- Remove `freeze` calls on String constants. This is superfluous with Ruby + 2.3+ and `frozen_string_literal: true`. [#3759] +- Fix use of AR middleware outside of Rails [#3787] +- Sidekiq::Worker `sidekiq_retry_in` block can now return nil or 0 to use + the default backoff delay [#3796, dsalahutdinov] + +5.1.1 +----------- + +- Fix Web UI incompatibility with Redis 3.x gem [#3749] + +5.1.0 +----------- + +- **NEW** Global death handlers - called when your job exhausts all + retries and dies. Now you can take action when a job fails permanently. [#3721] +- **NEW** Enable ActiveRecord query cache within jobs by default [#3718, sobrinho] + This will prevent duplicate SELECTS; cache is cleared upon any UPDATE/INSERT/DELETE. + See the issue for how to bypass the cache or disable it completely. +- Scheduler timing is now more accurate, 15 -> 5 seconds [#3734] +- Exceptions during the :startup event will now kill the process [#3717] +- Make `Sidekiq::Client.via` reentrant [#3715] +- Fix use of Sidekiq logger outside of the server process [#3714] +- Tweak `constantize` to better match Rails class lookup. [#3701, caffeinated-tech] + +5.0.5 +----------- + +- Update gemspec to allow newer versions of the Redis gem [#3617] +- Refactor Worker.set so it can be memoized [#3602] +- Fix display of Redis URL in web footer, broken in 5.0.3 [#3560] +- Update `Sidekiq::Job#display_args` to avoid mutation [#3621] + +5.0.4 +----------- + +- Fix "slow startup" performance regression from 5.0.2. [#3525] +- Allow users to disable ID generation since some redis providers disable the CLIENT command. [#3521] + +5.0.3 +----------- + +- Fix overriding `class_attribute` core extension from ActiveSupport with Sidekiq one [PikachuEXE, #3499] +- Allow job logger to be overridden [AlfonsoUceda, #3502] +- Set a default Redis client identifier for debugging [#3516] +- Fix "Uninitialized constant" errors on startup with the delayed extensions [#3509] + +5.0.2 +----------- + +- fix broken release, thanks @nateberkopec + +5.0.1 +----------- + +- Fix incorrect server identity when daemonizing [jwilm, #3496] +- Work around error running Web UI against Redis Cluster [#3492] +- Remove core extensions, Sidekiq is now monkeypatch-free! [#3474] +- Reimplement Web UI's HTTP\_ACCEPT\_LANGUAGE parsing because the spec is utterly + incomprehensible for various edge cases. [johanlunds, natematykiewicz, #3449] +- Update `class_attribute` core extension to avoid warnings +- Expose `job_hash_context` from `Sidekiq::Logging` to support log customization + +5.0.0 +----------- + +- **BREAKING CHANGE** Job dispatch was refactored for safer integration with + Rails 5. The **Logging** and **RetryJobs** server middleware were removed and + functionality integrated directly into Sidekiq::Processor. These aren't + commonly used public APIs so this shouldn't impact most users. +``` +Sidekiq::Middleware::Server::RetryJobs -> Sidekiq::JobRetry +Sidekiq::Middleware::Server::Logging -> Sidekiq::JobLogger +``` +- Quieting Sidekiq is now done via the TSTP signal, the USR1 signal is deprecated. +- The `delay` extension APIs are no longer available by default, you + must opt into them. +- The Web UI is now BiDi and can render RTL languages like Arabic, Farsi and Hebrew. +- Rails 3.2 and Ruby 2.0 and 2.1 are no longer supported. +- The `SomeWorker.set(options)` API was re-written to avoid thread-local state. [#2152] +- Sidekiq Enterprise's encrypted jobs now display "[encrypted data]" in the Web UI instead + of random hex bytes. +- Please see the [5.0 Upgrade notes](5.0-Upgrade.md) for more detail. + +4.2.10 +----------- + +- Scheduled jobs can now be moved directly to the Dead queue via API [#3390] +- Fix edge case leading to job duplication when using Sidekiq Pro's + reliability feature [#3388] +- Fix error class name display on retry page [#3348] +- More robust latency calculation [#3340] + +4.2.9 +----------- + +- Rollback [#3303] which broke Heroku Redis users [#3311] +- Add support for TSTP signal, for Sidekiq 5.0 forward compatibility. [#3302] + +4.2.8 +----------- + +- Fix rare edge case with Redis driver that can create duplicate jobs [#3303] +- Fix Rails 5 loading issue [#3275] +- Restore missing tooltips to timestamps in Web UI [#3310] +- Work on **Sidekiq 5.0** is now active! [#3301] + +4.2.7 +----------- + +- Add new integration testing to verify code loading and job execution + in development and production modes with Rails 4 and 5 [#3241] +- Fix delayed extensions in development mode [#3227, DarthSim] +- Use Worker's `retry` default if job payload does not have a retry + attribute [#3234, mlarraz] + +4.2.6 +----------- + +- Run Rails Executor when in production [#3221, eugeneius] + +4.2.5 +----------- + +- Re-enable eager loading of all code when running non-development Rails 5. [#3203] +- Better root URL handling for zany web servers [#3207] + +4.2.4 +----------- + +- Log errors coming from the Rails 5 reloader. [#3212, eugeneius] +- Clone job data so middleware changes don't appear in Busy tab + +4.2.3 +----------- + +- Disable use of Rails 5's Reloader API in non-development modes, it has proven + to be unstable under load [#3154] +- Allow disabling of Sidekiq::Web's cookie session to handle the + case where the app provides a session already [#3180, inkstak] +```ruby +Sidekiq::Web.set :sessions, false +``` +- Fix Web UI sharding support broken in 4.2.2. [#3169] +- Fix timestamps not updating during UI polling [#3193, shaneog] +- Relax rack-protection version to >= 1.5.0 +- Provide consistent interface to exception handlers, changing the structure of the context hash. [#3161] + +4.2.2 +----------- + +- Fix ever-increasing cookie size with nginx [#3146, cconstantine] +- Fix so Web UI works without trailing slash [#3158, timdorr] + +4.2.1 +----------- + +- Ensure browser does not cache JSON/AJAX responses. [#3136] +- Support old Sinatra syntax for setting config [#3139] + +4.2.0 +----------- + +- Enable development-mode code reloading. **With Rails 5.0+, you don't need + to restart Sidekiq to pick up your Sidekiq::Worker changes anymore!** [#2457] +- **Remove Sinatra dependency**. Sidekiq's Web UI now uses Rack directly. + Thank you to Sidekiq's newest committer, **badosu**, for writing the code + and doing a lot of testing to ensure compatibility with many different + 3rd party plugins. If your Web UI works with 4.1.4 but fails with + 4.2.0, please open an issue. [#3075] +- Allow tuning of concurrency with the `RAILS_MAX_THREADS` env var. [#2985] + This is the same var used by Puma so you can tune all of your systems + the same way: +```sh +web: RAILS_MAX_THREADS=5 bundle exec puma ... +worker: RAILS_MAX_THREADS=10 bundle exec sidekiq ... +``` +Using `-c` or `config/sidekiq.yml` overrides this setting. I recommend +adjusting your `config/database.yml` to use it too so connections are +auto-scaled: +```yaml + pool: <%= ENV['RAILS_MAX_THREADS'] || 5 %> +``` + +4.1.4 +----------- + +- Unlock Sinatra so a Rails 5.0 compatible version may be used [#3048] +- Fix race condition on startup with JRuby [#3043] + + +4.1.3 +----------- + +- Please note the Redis 3.3.0 gem has a [memory leak](https://github.com/redis/redis-rb/issues/612), + Redis 3.2.2 is recommended until that issue is fixed. +- Sinatra 1.4.x is now a required dependency, avoiding cryptic errors + and old bugs due to people not upgrading Sinatra for years. [#3042] +- Fixed race condition in heartbeat which could rarely lead to lingering + processes on the Busy tab. [#2982] +```ruby +# To clean up lingering processes, modify this as necessary to connect to your Redis. +# After 60 seconds, lingering processes should disappear from the Busy page. + +require 'redis' +r = Redis.new(url: "redis://localhost:6379/0") +# uncomment if you need a namespace +#require 'redis-namespace' +#r = Redis::Namespace.new("foo", r) +r.smembers("processes").each do |pro| + r.expire(pro, 60) + r.expire("#{pro}:workers", 60) +end +``` + + +4.1.2 +----------- + +- Fix Redis data leak with worker data when a busy Sidekiq process + crashes. You can find and expire leaked data in Redis with this +script: +```bash +$ redis-cli keys "*:workers" | while read LINE ; do TTL=`redis-cli expire "$LINE" 60`; echo "$LINE"; done; +``` + Please note that `keys` can be dangerous to run on a large, busy Redis. Caveat runner. +- Freeze all string literals with Ruby 2.3. [#2741] +- Client middleware can now stop bulk job push. [#2887] + +4.1.1 +----------- + +- Much better behavior when Redis disappears and comes back. [#2866] +- Update FR locale [dbachet] +- Don't fill logfile in case of Redis downtime [#2860] +- Allow definition of a global retries_exhausted handler. [#2807] +```ruby +Sidekiq.configure_server do |config| + config.default_retries_exhausted = -> (job, ex) do + Sidekiq.logger.info "#{job['class']} job is now dead" + end +end +``` + +4.1.0 +----------- + +- Tag quiet processes in the Web UI [#2757, jcarlson] +- Pass last exception to sidekiq\_retries\_exhausted block [#2787, Nowaker] +```ruby +class MyWorker + include Sidekiq::Worker + sidekiq_retries_exhausted do |job, exception| + end +end +``` +- Add native support for ActiveJob's `set(options)` method allowing +you to override worker options dynamically. This should make it +even easier to switch between ActiveJob and Sidekiq's native APIs [#2780] +```ruby +class MyWorker + include Sidekiq::Worker + sidekiq_options queue: 'default', retry: true + + def perform(*args) + # do something + end +end + +MyWorker.set(queue: 'high', retry: false).perform_async(1) +``` + +4.0.2 +----------- + +- Better Japanese translations +- Remove `json` gem dependency from gemspec. [#2743] +- There's a new testing API based off the `Sidekiq::Queues` namespace. All + assertions made against the Worker class still work as expected. + [#2676, brandonhilkert] +```ruby +assert_equal 0, Sidekiq::Queues["default"].size +HardWorker.perform_async("log") +assert_equal 1, Sidekiq::Queues["default"].size +assert_equal "log", Sidekiq::Queues["default"].first['args'][0] +Sidekiq::Queues.clear_all +``` + +4.0.1 +----------- + +- Yank new queue-based testing API [#2663] +- Fix invalid constant reference in heartbeat + +4.0.0 +----------- + +- Sidekiq's internals have been completely overhauled for performance + and to remove dependencies. This has resulted in major speedups, as + [detailed on my blog](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). +- See the [4.0 upgrade notes](4.0-Upgrade.md) for more detail. + +3.5.4 +----------- + +- Ensure exception message is a string [#2707] +- Revert racy Process.kill usage in sidekiqctl + +3.5.3 +----------- + +- Adjust shutdown event to run in parallel with the rest of system shutdown. [#2635] + +3.5.2 +----------- + +- **Sidekiq 3 is now in maintenance mode**, only major bugs will be fixed. +- The exception triggering a retry is now passed into `sidekiq_retry_in`, + allowing you to retry more frequently for certain types of errors. + [#2619, kreynolds] +```ruby + sidekiq_retry_in do |count, ex| + case ex + when RuntimeError + 5 * count + else + 10 * count + end + end +``` + +3.5.1 +----------- + +- **FIX MEMORY LEAK** Under rare conditions, threads may leak [#2598, gazay] +- Add Ukrainian locale [#2561, elrakita] +- Disconnect and retry Redis operations if we see a READONLY error [#2550] +- Add server middleware testing harness; see [wiki](https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middleware) [#2534, ryansch] + +3.5.0 +----------- + +- Polished new banner! [#2522, firedev] +- Upgrade to Celluloid 0.17. [#2420, digitalextremist] +- Activate sessions in Sinatra for CSRF protection, requires Rails + monkeypatch due to rails/rails#15843. [#2460, jc00ke] + +3.4.2 +----------- + +- Don't allow `Sidekiq::Worker` in ActiveJob::Base classes. [#2424] +- Safer display of job data in Web UI [#2405] +- Fix CSRF vulnerability in Web UI, thanks to Egor Homakov for + reporting. [#2422] If you are running the Web UI as a standalone Rack app, + ensure you have a [session middleware +configured](https://github.com/mperham/sidekiq/wiki/Monitoring#standalone): +```ruby +use Rack::Session::Cookie, :secret => "some unique secret string here" +``` + +3.4.1 +----------- + +- Lock to Celluloid 0.16 + + +3.4.0 +----------- + +- Set a `created_at` attribute when jobs are created, set `enqueued_at` only + when they go into a queue. Fixes invalid latency calculations with scheduled jobs. + [#2373, mrsimo] +- Don't log timestamp on Heroku [#2343] +- Run `shutdown` event handlers in reverse order of definition [#2374] +- Rename and rework `poll_interval` to be simpler, more predictable [#2317, cainlevy] + The new setting is `average_scheduled_poll_interval`. To configure + Sidekiq to look for scheduled jobs every 5 seconds, just set it to 5. +```ruby +Sidekiq.configure_server do |config| + config.average_scheduled_poll_interval = 5 +end +``` + +3.3.4 +----------- + +- **Improved ActiveJob integration** - Web UI now shows ActiveJobs in a + nicer format and job logging shows the actual class name, requires + Rails 4.2.2+ [#2248, #2259] +- Add Sidekiq::Process#dump\_threads API to trigger TTIN output [#2247] +- Web UI polling now uses Ajax to avoid page reload [#2266, davydovanton] +- Several Web UI styling improvements [davydovanton] +- Add Tamil, Hindi translations for Web UI [ferdinandrosario, tejasbubane] +- Fix Web UI to work with country-specific locales [#2243] +- Handle circular error causes [#2285, eugenk] + +3.3.3 +----------- + +- Fix crash on exit when Redis is down [#2235] +- Fix duplicate logging on startup +- Undeprecate delay extension for ActionMailer 4.2+ . [#2186] + +3.3.2 +----------- + +- Add Sidekiq::Stats#queues back +- Allows configuration of dead job set size and timeout [#2173, jonhyman] +- Refactor scheduler enqueuing so Sidekiq Pro can override it. [#2159] + +3.3.1 +----------- + +- Dumb down ActionMailer integration so it tries to deliver if possible [#2149] +- Stringify Sidekiq.default\_worker\_options's keys [#2126] +- Add random integer to process identity [#2113, michaeldiscala] +- Log Sidekiq Pro's Batch ID if available [#2076] +- Refactor Processor Redis usage to avoid redis/redis-rb#490 [#2094] +- Move /dashboard/stats to /stats. Add /stats/queues. [moserke, #2099] +- Add processes count to /stats [ismaelga, #2141] +- Greatly improve speed of Sidekiq::Stats [ismaelga, #2142] +- Add better usage text for `sidekiqctl`. +- `Sidekiq::Logging.with_context` is now a stack so you can set your + own job context for logging purposes [grosser, #2110] +- Remove usage of Google Fonts in Web UI so it loads in China [#2144] + +3.3.0 +----------- + +- Upgrade to Celluloid 0.16 [#2056] +- Fix typo for generator test file name [dlackty, #2016] +- Add Sidekiq::Middleware::Chain#prepend [seuros, #2029] + +3.2.6 +----------- + +- Deprecate delay extension for ActionMailer 4.2+ . [seuros, #1933] +- Poll interval tuning now accounts for dead processes [epchris, #1984] +- Add non-production environment to Web UI page titles [JacobEvelyn, #2004] + +3.2.5 +----------- + +- Lock Celluloid to 0.15.2 due to bugs in 0.16.0. This prevents the + "hang on shutdown" problem with Celluloid 0.16.0. + +3.2.4 +----------- + +- Fix issue preventing ActionMailer sends working in some cases with + Rails 4. [pbhogan, #1923] + +3.2.3 +----------- + +- Clean invalid bytes from error message before converting to JSON (requires Ruby 2.1+) [#1705] +- Add queues list for each process to the Busy page. [davetoxa, #1897] +- Fix for crash caused by empty config file. [jordan0day, #1901] +- Add Rails Worker generator, `rails g sidekiq:worker User` will create `app/workers/user_worker.rb`. [seuros, #1909] +- Fix Web UI rendering with huge job arguments [jhass, #1918] +- Minor refactoring of Sidekiq::Client internals, for Sidekiq Pro. [#1919] + +3.2.2 +----------- + +- **This version of Sidekiq will no longer start on Ruby 1.9.** Sidekiq + 3 does not support MRI 1.9 but we've allowed it to run before now. +- Fix issue which could cause Sidekiq workers to disappear from the Busy + tab while still being active [#1884] +- Add "Back to App" button in Web UI. You can set the button link via + `Sidekiq::Web.app_url = 'http://www.mysite.com'` [#1875, seuros] +- Add process tag (`-g tag`) to the Busy page so you can differentiate processes at a glance. [seuros, #1878] +- Add "Kill" button to move retries directly to the DJQ so they don't retry. [seuros, #1867] + +3.2.1 +----------- + +- Revert eager loading change for Rails 3.x apps, as it broke a few edge + cases. + +3.2.0 +----------- + +- **Fix issue which caused duplicate job execution in Rails 3.x** + This issue is caused by [improper exception handling in ActiveRecord](https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L281) which changes Sidekiq's Shutdown exception into a database + error, making Sidekiq think the job needs to be retried. **The fix requires Ruby 2.1**. [#1805] +- Update how Sidekiq eager loads Rails application code [#1791, jonleighton] +- Change logging timestamp to show milliseconds. +- Reverse sorting of Dead tab so newer jobs are listed first [#1802] + +3.1.4 +----------- + +- Happy π release! +- Self-tuning Scheduler polling, we use heartbeat info to better tune poll\_interval [#1630] +- Remove all table column width rules, hopefully get better column formatting [#1747] +- Handle edge case where YAML can't be decoded in dev mode [#1761] +- Fix lingering jobs in Busy page on Heroku [#1764] + +3.1.3 +----------- + +- Use ENV['DYNO'] on Heroku for hostname display, rather than an ugly UUID. [#1742] +- Show per-process labels on the Busy page, for feature tagging [#1673] + + +3.1.2 +----------- + +- Suitably chastised, @mperham reverts the Bundler change. + + +3.1.1 +----------- + +- Sidekiq::CLI now runs `Bundler.require(:default, environment)` to boot all gems + before loading any app code. +- Sort queues by name in Web UI [#1734] + + +3.1.0 +----------- + +- New **remote control** feature: you can remotely trigger Sidekiq to quiet + or terminate via API, without signals. This is most useful on JRuby + or Heroku which does not support the USR1 'quiet' signal. Now you can + run a rake task like this at the start of your deploy to quiet your + set of Sidekiq processes. [#1703] +```ruby +namespace :sidekiq do + task :quiet => :environment do + Sidekiq::ProcessSet.new.each(&:quiet!) + end +end +``` +- The Web UI can use the API to quiet or stop all processes via the Busy page. +- The Web UI understands and hides the `Sidekiq::DelayExtensions::Delay*` + classes, instead showing `Class.method` as the Job. [#1718] +- Polish the Dashboard graphs a bit, update Rickshaw [brandonhilkert, #1725] +- The poll interval is now configurable in the Web UI [madebydna, #1713] +- Delay extensions can be removed so they don't conflict with + DelayedJob: put `Sidekiq.remove_delay!` in your initializer. [devaroop, #1674] + + +3.0.2 +----------- + +- Revert gemfile requirement of Ruby 2.0. JRuby 1.7 calls itself Ruby + 1.9.3 and broke with this requirement. + +3.0.1 +----------- + +- Revert pidfile behavior from 2.17.5: Sidekiq will no longer remove its own pidfile + as this is a race condition when restarting. [#1470, #1677] +- Show warning on the Queues page if a queue is paused [#1672] +- Only activate the ActiveRecord middleware if ActiveRecord::Base is defined on boot. [#1666] +- Add ability to disable jobs going to the DJQ with the `dead` option. +```ruby +sidekiq_options :dead => false, :retry => 5 +``` +- Minor fixes + + +3.0.0 +----------- + +Please see [3.0-Upgrade.md](3.0-Upgrade.md) for more comprehensive upgrade notes. + +- **Dead Job Queue** - jobs which run out of retries are now moved to a dead + job queue. These jobs must be retried manually or they will expire + after 6 months or 10,000 jobs. The Web UI contains a "Dead" tab + exposing these jobs. Use `sidekiq_options :retry => false` if you +don't wish jobs to be retried or put in the DJQ. Use +`sidekiq_options :retry => 0` if you don't want jobs to retry but go +straight to the DJQ. +- **Process Lifecycle Events** - you can now register blocks to run at + certain points during the Sidekiq process lifecycle: startup, quiet and + shutdown. +```ruby +Sidekiq.configure_server do |config| + config.on(:startup) do + # do something + end +end +``` +- **Global Error Handlers** - blocks of code which handle errors that + occur anywhere within Sidekiq, not just within middleware. +```ruby +Sidekiq.configure_server do |config| + config.error_handlers << proc {|ex,ctx| ... } +end +``` +- **Process Heartbeat** - each Sidekiq process will ping Redis every 5 + seconds to give a summary of the Sidekiq population at work. +- The Workers tab is now renamed to Busy and contains a list of live + Sidekiq processes and jobs in progress based on the heartbeat. +- **Shardable Client** - Sidekiq::Client instances can use a custom + Redis connection pool, allowing very large Sidekiq installations to scale by + sharding: sending different jobs to different Redis instances. +```ruby +client = Sidekiq::Client.new(ConnectionPool.new { Redis.new }) +client.push(...) +``` +```ruby +Sidekiq::Client.via(ConnectionPool.new { Redis.new }) do + FooWorker.perform_async + BarWorker.perform_async +end +``` + **Sharding support does require a breaking change to client-side +middleware, see 3.0-Upgrade.md.** +- New Chinese, Greek, Swedish and Czech translations for the Web UI. +- Updated most languages translations for the new UI features. +- **Remove official Capistrano integration** - this integration has been + moved into the [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem. +- **Remove official support for MRI 1.9** - Things still might work but + I no longer actively test on it. +- **Remove built-in support for Redis-to-Go**. + Heroku users: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL` +- **Remove built-in error integration for Airbrake, Honeybadger, ExceptionNotifier and Exceptional**. + Each error gem should provide its own Sidekiq integration. Update your error gem to the latest + version to pick up Sidekiq support. +- Upgrade to connection\_pool 2.0 which now creates connections lazily. +- Remove deprecated Sidekiq::Client.registered\_\* APIs +- Remove deprecated support for the old Sidekiq::Worker#retries\_exhausted method. +- Removed 'sidekiq/yaml\_patch', this was never documented or recommended. +- Removed --profile option, #1592 +- Remove usage of the term 'Worker' in the UI for clarity. Users would call both threads and + processes 'workers'. Instead, use "Thread", "Process" or "Job". + +2.17.7 +----------- + +- Auto-prune jobs older than one hour from the Workers page [#1508] +- Add Sidekiq::Workers#prune which can perform the auto-pruning. +- Fix issue where a job could be lost when an exception occurs updating + Redis stats before the job executes [#1511] + +2.17.6 +----------- + +- Fix capistrano integration due to missing pidfile. [#1490] + +2.17.5 +----------- + +- Automatically use the config file found at `config/sidekiq.yml`, if not passed `-C`. [#1481] +- Store 'retried\_at' and 'failed\_at' timestamps as Floats, not Strings. [#1473] +- A `USR2` signal will now reopen _all_ logs, using IO#reopen. Thus, instead of creating a new Logger object, + Sidekiq will now just update the existing Logger's file descriptor [#1163]. +- Remove pidfile when shutting down if started with `-P` [#1470] + +2.17.4 +----------- + +- Fix JID support in inline testing, #1454 +- Polish worker arguments display in UI, #1453 +- Marshal arguments fully to avoid worker mutation, #1452 +- Support reverse paging sorted sets, #1098 + + +2.17.3 +----------- + +- Synchronously terminates the poller and fetcher to fix a race condition in bulk requeue during shutdown [#1406] + +2.17.2 +----------- + +- Fix bug where strictly prioritized queues might be processed out of + order [#1408]. A side effect of this change is that it breaks a queue + declaration syntax that worked, although only because of a bug—it was + never intended to work and never supported. If you were declaring your + queues as a comma-separated list, e.g. `sidekiq -q critical,default,low`, + you must now use the `-q` flag before each queue, e.g. + `sidekiq -q critical -q default -q low`. + +2.17.1 +----------- + +- Expose `delay` extension as `sidekiq_delay` also. This allows you to + run Delayed::Job and Sidekiq in the same process, selectively porting + `delay` calls to `sidekiq_delay`. You just need to ensure that + Sidekiq is required **before** Delayed::Job in your Gemfile. [#1393] +- Bump redis client required version to 3.0.6 +- Minor CSS fixes for Web UI + +2.17.0 +----------- + +- Change `Sidekiq::Client#push_bulk` to return an array of pushed `jid`s. [#1315, barelyknown] +- Web UI refactoring to use more API internally (yummy dogfood!) +- Much faster Sidekiq::Job#delete performance for larger queue sizes +- Further capistrano 3 fixes +- Many misc minor fixes + +2.16.1 +----------- + +- Revert usage of `resolv-replace`. MRI's native DNS lookup releases the GIL. +- Fix several Capistrano 3 issues +- Escaping dynamic data like job args and error messages in Sidekiq Web UI. [#1299, lian] + +2.16.0 +----------- + +- Deprecate `Sidekiq::Client.registered_workers` and `Sidekiq::Client.registered_queues` +- Refactor Sidekiq::Client to be instance-based [#1279] +- Pass all Redis options to the Redis driver so Unix sockets + can be fully configured. [#1270, salimane] +- Allow sidekiq-web extensions to add locale paths so extensions + can be localized. [#1261, ondrejbartas] +- Capistrano 3 support [#1254, phallstrom] +- Use Ruby's `resolv-replace` to enable pure Ruby DNS lookups. + This ensures that any DNS resolution that takes place in worker + threads won't lock up the entire VM on MRI. [#1258] + +2.15.2 +----------- + +- Iterating over Sidekiq::Queue and Sidekiq::SortedSet will now work as + intended when jobs are deleted [#866, aackerman] +- A few more minor Web UI fixes [#1247] + +2.15.1 +----------- + +- Fix several Web UI issues with the Bootstrap 3 upgrade. + +2.15.0 +----------- + +- The Core Sidekiq actors are now monitored. If any crash, the + Sidekiq process logs the error and exits immediately. This is to + help prevent "stuck" Sidekiq processes which are running but don't + appear to be doing any work. [#1194] +- Sidekiq's testing behavior is now dynamic. You can choose between + `inline` and `fake` behavior in your tests. See +[Testing](https://github.com/mperham/sidekiq/wiki/Testing) for detail. [#1193] +- The Retries table has a new column for the error message. +- The Web UI topbar now contains the status and live poll button. +- Orphaned worker records are now auto-vacuumed when you visit the + Workers page in the Web UI. +- Sidekiq.default\_worker\_options allows you to configure default + options for all Sidekiq worker types. + +```ruby +Sidekiq.default_worker_options = { 'queue' => 'default', 'backtrace' => true } +``` +- Added two Sidekiq::Client class methods for compatibility with resque-scheduler: + `enqueue_to_in` and `enqueue_in` [#1212] +- Upgrade Web UI to Bootstrap 3.0. [#1211, jeffboek] + +2.14.1 +----------- + +- Fix misc Web UI issues due to ERB conversion. +- Bump redis-namespace version due to security issue. + +2.14.0 +----------- + +- Removed slim gem dependency, Web UI now uses ERB [Locke23rus, #1120] +- Fix more race conditions in Web UI actions +- Don't reset Job enqueued\_at when retrying +- Timestamp tooltips in the Web UI should use UTC +- Fix invalid usage of handle\_exception causing issues in Airbrake + [#1134] + + +2.13.1 +----------- + +- Make Sidekiq::Middleware::Chain Enumerable +- Make summary bar and graphs responsive [manishval, #1025] +- Adds a job status page for scheduled jobs [jonhyman] +- Handle race condition in retrying and deleting jobs in the Web UI +- The Web UI relative times are now i18n. [MadRabbit, #1088] +- Allow for default number of retry attempts to be set for + `Sidekiq::Middleware::Server::RetryJobs` middleware. [czarneckid] [#1091] + +```ruby +Sidekiq.configure_server do |config| + config.server_middleware do |chain| + chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 10 + end +end +``` + + +2.13.0 +----------- + +- Adding button to move scheduled job to main queue [guiceolin, #1020] +- fix i18n support resetting saved locale when job is retried [#1011] +- log rotation via USR2 now closes the old logger [#1008] +- Add ability to customize retry schedule, like so [jmazzi, #1027] + +```ruby +class MyWorker + include Sidekiq::Worker + sidekiq_retry_in { |count| count * 2 } +end +``` +- Redesign Worker#retries\_exhausted callback to use same form as above [jmazzi, #1030] + +```ruby +class MyWorker + include Sidekiq::Worker + sidekiq_retries_exhausted do |msg| + Rails.logger.error "Failed to process #{msg['class']} with args: #{msg['args']}" + end +end +``` + +2.12.4 +----------- + +- Fix error in previous release which crashed the Manager when a + Processor died. + +2.12.3 +----------- + +- Revert back to Celluloid's TaskFiber for job processing which has proven to be more + stable than TaskThread. [#985] +- Avoid possible lockup during hard shutdown [#997] + +At this point, if you are experiencing stability issues with Sidekiq in +Ruby 1.9, please try Ruby 2.0. It seems to be more stable. + +2.12.2 +----------- + +- Relax slim version requirement to >= 1.1.0 +- Refactor historical stats to use TTL, not explicit cleanup. [grosser, #971] + +2.12.1 +----------- + +- Force Celluloid 0.14.1 as 0.14.0 has a serious bug. [#954] +- Scheduled and Retry jobs now use Sidekiq::Client to push + jobs onto the queue, so they use client middleware. [dimko, #948] +- Record the timestamp when jobs are enqueued. Add + Sidekiq::Job#enqueued\_at to query the time. [mariovisic, #944] +- Add Sidekiq::Queue#latency - calculates diff between now and + enqueued\_at for the oldest job in the queue. +- Add testing method `perform_one` that dequeues and performs a single job. + This is mainly to aid testing jobs that spawn other jobs. [fumin, #963] + +2.12.0 +----------- + +- Upgrade to Celluloid 0.14, remove the use of Celluloid's thread + pool. This should halve the number of threads in each Sidekiq + process, thus requiring less resources. [#919] +- Abstract Celluloid usage to Sidekiq::Actor for testing purposes. +- Better handling for Redis downtime when fetching jobs and shutting + down, don't print exceptions every second and print success message + when Redis is back. +- Fix unclean shutdown leading to duplicate jobs [#897] +- Add Korean locale [#890] +- Upgrade test suite to Minitest 5 +- Remove usage of `multi_json` as `json` is now robust on all platforms. + +2.11.2 +----------- + +- Fix Web UI when used without Rails [#886] +- Add Sidekiq::Stats#reset [#349] +- Add Norwegian locale. +- Updates for the JA locale. + +2.11.1 +----------- + +- Fix timeout warning. +- Add Dutch web UI locale. + +2.11.0 +----------- + +- Upgrade to Celluloid 0.13. [#834] +- Remove **timeout** support from `sidekiq_options`. Ruby's timeout + is inherently unsafe in a multi-threaded application and was causing + stability problems for many. See http://bit.ly/OtYpK +- Add Japanese locale for Web UI [#868] +- Fix a few issues with Web UI i18n. + +2.10.1 +----------- + +- Remove need for the i18n gem. (brandonhilkert) +- Improve redis connection info logging on startup for debugging +purposes [#858] +- Revert sinatra/slim as runtime dependencies +- Add `find_job` method to sidekiq/api + + +2.10.0 +----------- + +- Refactor algorithm for putting scheduled jobs onto the queue [#843] +- Fix scheduler thread dying due to incorrect error handling [#839] +- Fix issue which left stale workers if Sidekiq wasn't shutdown while +quiet. [#840] +- I18n for web UI. Please submit translations of `web/locales/en.yml` for +your own language. [#811] +- 'sinatra', 'slim' and 'i18n' are now gem dependencies for Sidekiq. + + +2.9.0 +----------- + +- Update 'sidekiq/testing' to work with any Sidekiq::Client call. It + also serializes the arguments as using Redis would. [#713] +- Raise a Sidekiq::Shutdown error within workers which don't finish within the hard + timeout. This is to prevent unwanted database transaction commits. [#377] +- Lazy load Redis connection pool, you no longer need to specify + anything in Passenger or Unicorn's after_fork callback [#794] +- Add optional Worker#retries_exhausted hook after max retries failed. [jkassemi, #780] +- Fix bug in pagination link to last page [pitr, #774] +- Upstart scripts for multiple Sidekiq instances [dariocravero, #763] +- Use select via pipes instead of poll to catch signals [mrnugget, #761] + +2.8.0 +----------- + +- I18n support! Sidekiq can optionally save and restore the Rails locale + so it will be properly set when your jobs execute. Just include + `require 'sidekiq/middleware/i18n'` in your sidekiq initializer. [#750] +- Fix bug which could lose messages when using namespaces and the message +needs to be requeued in Redis. [#744] +- Refactor Redis namespace support [#747]. The redis namespace can no longer be + passed via the config file, the only supported way is via Ruby in your + initializer: + +```ruby +sidekiq_redis = { :url => 'redis://localhost:3679', :namespace => 'foo' } +Sidekiq.configure_server { |config| config.redis = sidekiq_redis } +Sidekiq.configure_client { |config| config.redis = sidekiq_redis } +``` + +A warning is printed out to the log if a namespace is found in your sidekiq.yml. + + +2.7.5 +----------- + +- Capistrano no longer uses daemonization in order to work with JRuby [#719] +- Refactor signal handling to work on Ruby 2.0 [#728, #730] +- Fix dashboard refresh URL [#732] + +2.7.4 +----------- + +- Fixed daemonization, was broken by some internal refactoring in 2.7.3 [#727] + +2.7.3 +----------- + +- Real-time dashboard is now the default web page +- Make config file optional for capistrano +- Fix Retry All button in the Web UI + +2.7.2 +----------- + +- Remove gem signing infrastructure. It was causing Sidekiq to break +when used via git in Bundler. This is why we can't have nice things. [#688] + + +2.7.1 +----------- + +- Fix issue with hard shutdown [#680] + + +2.7.0 +----------- + +- Add -d daemonize flag, capistrano recipe has been updated to use it [#662] +- Support profiling via `ruby-prof` with -p. When Sidekiq is stopped + via Ctrl-C, it will output `profile.html`. You must add `gem 'ruby-prof'` to your Gemfile for it to work. +- Dynamically update Redis stats on dashboard [brandonhilkert] +- Add Sidekiq::Workers API giving programmatic access to the current + set of active workers. + +``` +workers = Sidekiq::Workers.new +workers.size => 2 +workers.each do |name, work| + # name is a unique identifier per Processor instance + # work is a Hash which looks like: + # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg } +end +``` + +- Allow environment-specific sections within the config file which +override the global values [dtaniwaki, #630] + +``` +--- +:concurrency: 50 +:verbose: false +staging: + :verbose: true + :concurrency: 5 +``` + + +2.6.5 +----------- + +- Several reliability fixes for job requeueing upon termination [apinstein, #622, #624] +- Fix typo in capistrano recipe +- Add `retry_queue` option so retries can be given lower priority [ryanlower, #620] + +```ruby +sidekiq_options queue: 'high', retry_queue: 'low' +``` + +2.6.4 +----------- + +- Fix crash upon empty queue [#612] + +2.6.3 +----------- + +- sidekiqctl exits with non-zero exit code upon error [jmazzi] +- better argument validation in Sidekiq::Client [karlfreeman] + +2.6.2 +----------- + +- Add Dashboard beacon indicating when stats are updated. [brandonhilkert, #606] +- Revert issue with capistrano restart. [#598] + +2.6.1 +----------- + +- Dashboard now live updates summary stats also. [brandonhilkert, #605] +- Add middleware chain APIs `insert_before` and `insert_after` for fine + tuning the order of middleware. [jackrg, #595] + +2.6.0 +----------- + +- Web UI much more mobile friendly now [brandonhilkert, #573] +- Enable live polling for every section in Web UI [brandonhilkert, #567] +- Add Stats API [brandonhilkert, #565] +- Add Stats::History API [brandonhilkert, #570] +- Add Dashboard to Web UI with live and historical stat graphs [brandonhilkert, #580] +- Add option to log output to a file, reopen log file on USR2 signal [mrnugget, #581] + +2.5.4 +----------- + +- `Sidekiq::Client.push` now accepts the worker class as a string so the + Sidekiq client does not have to load your worker classes at all. [#524] +- `Sidekiq::Client.push_bulk` now works with inline testing. +- **Really** fix status icon in Web UI this time. +- Add "Delete All" and "Retry All" buttons to Retries in Web UI + + +2.5.3 +----------- + +- Small Web UI fixes +- Add `delay_until` so you can delay jobs until a specific timestamp: + +```ruby +Auction.delay_until(@auction.ends_at).close(@auction.id) +``` + +This is identical to the existing Sidekiq::Worker method, `perform_at`. + +2.5.2 +----------- + +- Remove asset pipeline from Web UI for much faster, simpler runtime. [#499, #490, #481] +- Add -g option so the procline better identifies a Sidekiq process, defaults to File.basename(Rails.root). [#486] + + sidekiq 2.5.1 myapp [0 of 25 busy] + +- Add splay to retry time so groups of failed jobs don't fire all at once. [#483] + +2.5.1 +----------- + +- Fix issues with core\_ext + +2.5.0 +----------- + +- REDESIGNED WEB UI! [unity, cavneb] +- Support Honeybadger for error delivery +- Inline testing runs the client middleware before executing jobs [#465] +- Web UI can now remove jobs from queue. [#466, dleung] +- Web UI can now show the full message, not just 100 chars [#464, dleung] +- Add APIs for manipulating the retry and job queues. See sidekiq/api. [#457] + + +2.4.0 +----------- + +- ActionMailer.delay.method now only tries to deliver if method returns a valid message. +- Logging now uses "MSG-#{Job ID}", not a random msg ID +- Allow generic Redis provider as environment variable. [#443] +- Add ability to customize sidekiq\_options with delay calls [#450] + +```ruby +Foo.delay(:retry => false).bar +Foo.delay(:retry => 10).bar +Foo.delay(:timeout => 10.seconds).bar +Foo.delay_for(5.minutes, :timeout => 10.seconds).bar +``` + +2.3.3 +----------- + +- Remove option to disable Rails hooks. [#401] +- Allow delay of any module class method + +2.3.2 +----------- + +- Fix retry. 2.3.1 accidentally disabled it. + +2.3.1 +----------- + +- Add Sidekiq::Client.push\_bulk for bulk adding of jobs to Redis. + My own simple test case shows pushing 10,000 jobs goes from 5 sec to 1.5 sec. +- Add support for multiple processes per host to Capistrano recipe +- Re-enable Celluloid::Actor#defer to fix stack overflow issues [#398] + +2.3.0 +----------- + +- Upgrade Celluloid to 0.12 +- Upgrade Twitter Bootstrap to 2.1.0 +- Rescue more Exceptions +- Change Job ID to be Hex, rather than Base64, for HTTP safety +- Use `Airbrake#notify_or_ignore` + +2.2.1 +----------- + +- Add support for custom tabs to Sidekiq::Web [#346] +- Change capistrano recipe to run 'quiet' before deploy:update\_code so + it is run upon both 'deploy' and 'deploy:migrations'. [#352] +- Rescue Exception rather than StandardError to catch and log any sort + of Processor death. + +2.2.0 +----------- + +- Roll back Celluloid optimizations in 2.1.0 which caused instability. +- Add extension to delay any arbitrary class method to Sidekiq. + Previously this was limited to ActiveRecord classes. + +```ruby +SomeClass.delay.class_method(1, 'mike', Date.today) +``` + +- Sidekiq::Client now generates and returns a random, 128-bit Job ID 'jid' which + can be used to track the processing of a Job, e.g. for calling back to a webhook + when a job is finished. + +2.1.1 +----------- + +- Handle networking errors causing the scheduler thread to die [#309] +- Rework exception handling to log all Processor and actor death (#325, subelsky) +- Clone arguments when calling worker so modifications are discarded. (#265, hakanensari) + +2.1.0 +----------- + +- Tune Celluloid to no longer run message processing within a Fiber. + This gives us a full Thread stack and also lowers Sidekiq's memory + usage. +- Add pagination within the Web UI [#253] +- Specify which Redis driver to use: *hiredis* or *ruby* (default) +- Remove FailureJobs and UniqueJobs, which were optional middleware + that I don't want to support in core. [#302] + +2.0.3 +----------- +- Fix sidekiq-web's navbar on mobile devices and windows under 980px (ezkl) +- Fix Capistrano task for first deploys [#259] +- Worker subclasses now properly inherit sidekiq\_options set in + their superclass [#221] +- Add random jitter to scheduler to spread polls across POLL\_INTERVAL + window. [#247] +- Sidekiq has a new mailing list: sidekiq@librelist.org See README. + +2.0.2 +----------- + +- Fix "Retry Now" button on individual retry page. (ezkl) + +2.0.1 +----------- + +- Add "Clear Workers" button to UI. If you kill -9 Sidekiq, the workers + set can fill up with stale entries. +- Update sidekiq/testing to support new scheduled jobs API: + + ```ruby + require 'sidekiq/testing' + DirectWorker.perform_in(10.seconds, 1, 2) + assert_equal 1, DirectWorker.jobs.size + assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01 + ``` + +2.0.0 +----------- + +- **SCHEDULED JOBS**! + +You can now use `perform_at` and `perform_in` to schedule jobs +to run at arbitrary points in the future, like so: + +```ruby + SomeWorker.perform_in(5.days, 'bob', 13) + SomeWorker.perform_at(5.days.from_now, 'bob', 13) +``` + +It also works with the delay extensions: + +```ruby + UserMailer.delay_for(5.days).send_welcome_email(user.id) +``` + +The time is approximately when the job will be placed on the queue; +it is not guaranteed to run at precisely at that moment in time. + +This functionality is meant for one-off, arbitrary jobs. I still +recommend `whenever` or `clockwork` if you want cron-like, +recurring jobs. See `examples/scheduling.rb` + +I want to specially thank @yabawock for his work on sidekiq-scheduler. +His extension for Sidekiq 1.x filled an obvious functional gap that I now think is +useful enough to implement in Sidekiq proper. + +- Fixed issues due to Redis 3.x API changes. Sidekiq now requires + the Redis 3.x client. +- Inline testing now round trips arguments through JSON to catch + serialization issues (betelgeuse) + +1.2.1 +----------- + +- Sidekiq::Worker now has access to Sidekiq's standard logger +- Fix issue with non-StandardErrors leading to Processor exhaustion +- Fix issue with Fetcher slowing Sidekiq shutdown +- Print backtraces for all threads upon TTIN signal [#183] +- Overhaul retries Web UI with new index page and bulk operations [#184] + +1.2.0 +----------- + +- Full or partial error backtraces can optionally be stored as part of the retry + for display in the web UI if you aren't using an error service. [#155] + +```ruby +class Worker + include Sidekiq::Worker + sidekiq_options :backtrace => [true || 10] +end +``` +- Add timeout option to kill a worker after N seconds (blackgold9) + +```ruby +class HangingWorker + include Sidekiq::Worker + sidekiq_options :timeout => 600 + def perform + # will be killed if it takes longer than 10 minutes + end +end +``` + +- Fix delayed extensions not available in workers [#152] +- In test environments add the `#drain` class method to workers. This method + executes all previously queued jobs. (panthomakos) +- Sidekiq workers can be run inline during tests, just `require 'sidekiq/testing/inline'` (panthomakos) +- Queues can now be deleted from the Sidekiq web UI [#154] +- Fix unnecessary shutdown delay due to Retry Poller [#174] + +1.1.4 +----------- + +- Add 24 hr expiry for basic keys set in Redis, to avoid any possible leaking. +- Only register workers in Redis while working, to avoid lingering + workers [#156] +- Speed up shutdown significantly. + +1.1.3 +----------- + +- Better network error handling when fetching jobs from Redis. + Sidekiq will retry once per second until it can re-establish + a connection. (ryanlecompte) +- capistrano recipe now uses `bundle_cmd` if set [#147] +- handle multi\_json API changes (sferik) + +1.1.2 +----------- + +- Fix double restart with cap deploy [#137] + +1.1.1 +----------- + +- Set procline for easy monitoring of Sidekiq status via "ps aux" +- Fix race condition on shutdown [#134] +- Fix hang with cap sidekiq:start [#131] + +1.1.0 +----------- + +- The Sidekiq license has switched from GPLv3 to LGPLv3! +- Sidekiq::Client.push now returns whether the actual Redis + operation succeeded or not. [#123] +- Remove UniqueJobs from the default middleware chain. Its + functionality, while useful, is unexpected for new Sidekiq + users. You can re-enable it with the following config. + Read #119 for more discussion. + +```ruby +Sidekiq.configure_client do |config| + require 'sidekiq/middleware/client/unique_jobs' + config.client_middleware do |chain| + chain.add Sidekiq::Middleware::Client::UniqueJobs + end +end +Sidekiq.configure_server do |config| + require 'sidekiq/middleware/server/unique_jobs' + config.server_middleware do |chain| + chain.add Sidekiq::Middleware::Server::UniqueJobs + end +end +``` + +1.0.0 +----------- + +Thanks to all Sidekiq users and contributors for helping me +get to this big milestone! + +- Default concurrency on client-side to 5, not 25 so we don't + create as many unused Redis connections, same as ActiveRecord's + default pool size. +- Ensure redis= is given a Hash or ConnectionPool. + +0.11.2 +----------- + +- Implement "safe shutdown". The messages for any workers that + are still busy when we hit the TERM timeout will be requeued in + Redis so the messages are not lost when the Sidekiq process exits. + [#110] +- Work around Celluloid's small 4kb stack limit [#115] +- Add support for a custom Capistrano role to limit Sidekiq to + a set of machines. [#113] + +0.11.1 +----------- + +- Fix fetch breaking retry when used with Redis namespaces. [#109] +- Redis connection now just a plain ConnectionPool, not CP::Wrapper. +- Capistrano initial deploy fix [#106] +- Re-implemented weighted queues support (ryanlecompte) + +0.11.0 +----------- + +- Client-side API changes, added sidekiq\_options for Sidekiq::Worker. + As a side effect of this change, the client API works on Ruby 1.8. + It's not officially supported but should work [#103] +- NO POLL! Sidekiq no longer polls Redis, leading to lower network + utilization and lower latency for message processing. +- Add --version CLI option + +0.10.1 +----------- + +- Add details page for jobs in retry queue (jcoene) +- Display relative timestamps in web interface (jcoene) +- Capistrano fixes (hinrik, bensie) + +0.10.0 +----------- + +- Reworked capistrano recipe to make it more fault-tolerant [#94]. +- Automatic failure retry! Sidekiq will now save failed messages + and retry them, with an exponential backoff, over about 20 days. + Did a message fail to process? Just deploy a bug fix in the next + few days and Sidekiq will retry the message eventually. + +0.9.1 +----------- + +- Fix missed deprecations, poor method name in web UI + +0.9.0 +----------- + +- Add -t option to configure the TERM shutdown timeout +- TERM shutdown timeout is now configurable, defaults to 5 seconds. +- USR1 signal now stops Sidekiq from accepting new work, + capistrano sends USR1 at start of deploy and TERM at end of deploy + giving workers the maximum amount of time to finish. +- New Sidekiq::Web rack application available +- Updated Sidekiq.redis API + +0.8.0 +----------- + +- Remove :namespace and :server CLI options (mperham) +- Add ExceptionNotifier support (masterkain) +- Add capistrano support (mperham) +- Workers now log upon start and finish (mperham) +- Messages for terminated workers are now automatically requeued (mperham) +- Add support for Exceptional error reporting (bensie) + +0.7.0 +----------- + +- Example chef recipe and monitrc script (jc00ke) +- Refactor global configuration into Sidekiq.configure\_server and + Sidekiq.configure\_client blocks. (mperham) +- Add optional middleware FailureJobs which saves failed jobs to a + 'failed' queue (fbjork) +- Upon shutdown, workers are now terminated after 5 seconds. This is to + meet Heroku's hard limit of 10 seconds for a process to shutdown. (mperham) +- Refactor middleware API for simplicity, see sidekiq/middleware/chain. (mperham) +- Add `delay` extensions for ActionMailer and ActiveRecord. (mperham) +- Added config file support. See test/config.yml for an example file. (jc00ke) +- Added pidfile for tools like monit (jc00ke) + +0.6.0 +----------- + +- Resque-compatible processing stats in redis (mperham) +- Simple client testing support in sidekiq/testing (mperham) +- Plain old Ruby support via the -r cli flag (mperham) +- Refactored middleware support, introducing ability to add client-side middleware (ryanlecompte) +- Added middleware for ignoring duplicate jobs (ryanlecompte) +- Added middleware for displaying jobs in resque-web dashboard (maxjustus) +- Added redis namespacing support (maxjustus) + +0.5.1 +----------- + +- Initial release! diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..e1fdc703 --- /dev/null +++ b/Gemfile @@ -0,0 +1,28 @@ +source "https://rubygems.org" + +gemspec + +gem "sidekiq", ">= 6.4.1" +gem "rake" +gem "redis-namespace" +gem "rails", ">= 6.0.2" +gem "sqlite3", platforms: :ruby +gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby + +# mail dependencies +gem "net-smtp", platforms: :mri, require: false + +group :test do + gem "minitest" + gem "simplecov" + gem "codecov", require: false +end + +group :development, :test do + gem "standard", require: false +end + +group :load_test do + gem "hiredis" + gem "toxiproxy" +end diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d92e06b7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Contributed Systems LLC + +Sidekiq is an Open Source project licensed under the terms of +the LGPLv3 license. Please see +for license text. + +Sidekiq Pro and Sidekiq Enterprise have a commercial-friendly license. +You can find the commercial license in COMM-LICENSE.txt. +Please see https://sidekiq.org for purchasing options. diff --git a/README.md b/README.md new file mode 100644 index 00000000..0acf8f59 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +Sidekiq +============== + +[![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq) +![Build](https://github.com/mperham/sidekiq/workflows/CI/badge.svg) + +Simple, efficient background processing for Ruby. + +Sidekiq uses threads to handle many jobs at the same time in the +same process. It does not require Rails but will integrate tightly with +Rails to make background processing dead simple. + +Performance +--------------- + +Version | Latency | Garbage created for 10k jobs | Time to process 100k jobs | Throughput | Ruby +-----------------|------|---------|---------|------------------------|----- +Sidekiq 6.0.2 | 3 ms | 156 MB | 14.0 sec| **7100 jobs/sec** | MRI 2.6.3 +Sidekiq 6.0.0 | 3 ms | 156 MB | 19 sec | 5200 jobs/sec | MRI 2.6.3 +Sidekiq 4.0.0 | 10 ms | 151 MB | 22 sec | 4500 jobs/sec | +Sidekiq 3.5.1 | 22 ms | 1257 MB | 125 sec | 800 jobs/sec | +Resque 1.25.2 | - | - | 420 sec | 240 jobs/sec | +DelayedJob 4.1.1 | - | - | 465 sec | 215 jobs/sec | + +This benchmark can be found in `bin/sidekiqload` and assumes a Redis network latency of 1ms. + +Requirements +----------------- + +- Redis: 4.0+ +- Ruby: MRI 2.5+ or JRuby 9.2+. + +Sidekiq 6.0 supports Rails 5.0+ but does not require it. + + +Installation +----------------- + + gem install sidekiq + + +Getting Started +----------------- + +See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Getting-Started) and follow the simple setup process. +You can watch [this YouTube playlist](https://www.youtube.com/playlist?list=PLjeHh2LSCFrWGT5uVjUuFKAcrcj5kSai1) to learn all about +Sidekiq and see its features in action. Here's the Web UI: + +![Web UI](https://github.com/mperham/sidekiq/raw/main/examples/web-ui.png) + + +Want to Upgrade? +------------------- + +I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more +features, a commercial-friendly license and allow you to support high +quality open source development all at the same time. Please see the +[Sidekiq](https://sidekiq.org/) homepage for more detail. + +Subscribe to the **[quarterly newsletter](https://tinyletter.com/sidekiq)** to stay informed about the latest +features and changes to Sidekiq and its bigger siblings. + + +Problems? +----------------- + +**Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public. + +If you have a problem, please review the [FAQ](https://github.com/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages. +Searching the [issues](https://github.com/mperham/sidekiq/issues) for your problem is also a good idea. + +Sidekiq Pro and Sidekiq Enterprise customers get private email support. You can purchase at https://sidekiq.org; email support@contribsys.com for help. + +Useful resources: + +* Product documentation is in the [wiki](https://github.com/mperham/sidekiq/wiki). +* Occasional announcements are made to the [@sidekiq](https://twitter.com/sidekiq) Twitter account. +* The [Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow has lots of useful Q & A. + +Every Friday morning is Sidekiq happy hour: I video chat and answer questions. +See the [Sidekiq support page](https://sidekiq.org/support.html) for details. + +Contributing +----------------- + +Please see [the contributing guidelines](https://github.com/mperham/sidekiq/blob/main/.github/contributing.md). + + +License +----------------- + +Please see [LICENSE](https://github.com/mperham/sidekiq/blob/main/LICENSE) for licensing details. + + +Author +----------------- + +Mike Perham, [@getajobmike](https://twitter.com/getajobmike) / [@sidekiq](https://twitter.com/sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com) diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..a5dd6f6d --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require "bundler/gem_tasks" +require "rake/testtask" +require "standard/rake" + +Rake::TestTask.new(:test) do |test| + test.warning = true + test.pattern = "test/**/test_*.rb" +end + +task default: [:standard, :test] diff --git a/code_of_conduct.md b/code_of_conduct.md new file mode 100644 index 00000000..e3c8d3dd --- /dev/null +++ b/code_of_conduct.md @@ -0,0 +1,50 @@ +# Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, such as physical or electronic + addresses, without explicit permission +* Other unethical or unprofessional conduct + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +By adopting this Code of Conduct, project maintainers commit themselves to +fairly and consistently applying these principles to every aspect of managing +this project. Project maintainers who do not follow or enforce the Code of +Conduct may be permanently removed from the project team. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project maintainer at mperham AT gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. Maintainers are +obligated to maintain confidentiality with regard to the reporter of an +incident. + + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.3.0, available at +[http://contributor-covenant.org/version/1/3/0/][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/3/0/ diff --git a/lib/sidekiq/delay_extensions.rb b/lib/sidekiq/delay_extensions.rb new file mode 100644 index 00000000..07010b24 --- /dev/null +++ b/lib/sidekiq/delay_extensions.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "sidekiq" + +module Sidekiq + module DelayExtensions + def self.enable_delay! + if defined?(::ActiveSupport) + require "sidekiq/delay_extensions/active_record" + require "sidekiq/delay_extensions/action_mailer" + + # Need to patch Psych so it can autoload classes whose names are serialized + # in the delayed YAML. + Psych::Visitors::ToRuby.prepend(Sidekiq::DelayExtensions::PsychAutoload) + + ActiveSupport.on_load(:active_record) do + include Sidekiq::DelayExtensions::ActiveRecord + end + ActiveSupport.on_load(:action_mailer) do + extend Sidekiq::DelayExtensions::ActionMailer + end + end + + require "sidekiq/delay_extensions/class_methods" + Module.__send__(:include, Sidekiq::DelayExtensions::Klass) + + require "sidekiq/delay_extensions/api" + Sidekiq::JobRecord.prepend(Sidekiq::DelayExtensions::JobRecord) + end + + module PsychAutoload + def resolve_class(klass_name) + return nil if !klass_name || klass_name.empty? + # constantize + names = klass_name.split("::") + names.shift if names.empty? || names.first.empty? + + names.inject(Object) do |constant, name| + constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) + end + rescue NameError + super + end + end + end +end diff --git a/lib/sidekiq/delay_extensions/action_mailer.rb b/lib/sidekiq/delay_extensions/action_mailer.rb new file mode 100644 index 00000000..30761d0e --- /dev/null +++ b/lib/sidekiq/delay_extensions/action_mailer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "sidekiq/delay_extensions/generic_proxy" + +module Sidekiq + module DelayExtensions + ## + # Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email + # delivery to Sidekiq. + # + # @example + # UserMailer.delay.send_welcome_email(new_user) + # UserMailer.delay_for(5.days).send_welcome_email(new_user) + # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user) + class DelayedMailer + include Sidekiq::Worker + + def perform(yml) + (target, method_name, args) = YAML.load(yml) + msg = target.public_send(method_name, *args) + # The email method can return nil, which causes ActionMailer to return + # an undeliverable empty message. + if msg + msg.deliver_now + else + raise "#{target.name}##{method_name} returned an undeliverable mail object" + end + end + end + + module ActionMailer + def sidekiq_delay(options = {}) + Proxy.new(DelayedMailer, self, options) + end + + def sidekiq_delay_for(interval, options = {}) + Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f)) + end + + def sidekiq_delay_until(timestamp, options = {}) + Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f)) + end + alias_method :delay, :sidekiq_delay + alias_method :delay_for, :sidekiq_delay_for + alias_method :delay_until, :sidekiq_delay_until + end + end +end diff --git a/lib/sidekiq/delay_extensions/active_record.rb b/lib/sidekiq/delay_extensions/active_record.rb new file mode 100644 index 00000000..94bf03de --- /dev/null +++ b/lib/sidekiq/delay_extensions/active_record.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "sidekiq/delay_extensions/generic_proxy" + +module Sidekiq + module DelayExtensions + ## + # Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method + # execution to Sidekiq. + # + # @example + # User.recent_signups.each { |user| user.delay.mark_as_awesome } + # + # Please note, this is not recommended as this will serialize the entire + # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances. + # This is here for backwards compatibility with Delayed::Job only. + class DelayedModel + include Sidekiq::Worker + + def perform(yml) + (target, method_name, args) = YAML.load(yml) + target.__send__(method_name, *args) + end + end + + module ActiveRecord + def sidekiq_delay(options = {}) + Proxy.new(DelayedModel, self, options) + end + + def sidekiq_delay_for(interval, options = {}) + Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f)) + end + + def sidekiq_delay_until(timestamp, options = {}) + Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f)) + end + alias_method :delay, :sidekiq_delay + alias_method :delay_for, :sidekiq_delay_for + alias_method :delay_until, :sidekiq_delay_until + end + end +end diff --git a/lib/sidekiq/delay_extensions/api.rb b/lib/sidekiq/delay_extensions/api.rb new file mode 100644 index 00000000..0237df8d --- /dev/null +++ b/lib/sidekiq/delay_extensions/api.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "sidekiq/api" + +module Sidekiq + module DelayExtensions + module JobRecord + def display_class + # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI + @klass ||= self["display_class"] || begin + case klass + when /\ASidekiq::DelayExtensions::Delayed/ + safe_load(args[0], klass) do |target, method, _| + "#{target}.#{method}" + end + else + super + end + end + end + + def display_args + # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI + @display_args ||= case klass + when /\ASidekiq::DelayExtensions::Delayed/ + safe_load(args[0], args) do |_, _, arg, kwarg| + if !kwarg || kwarg.empty? + arg + else + [arg, kwarg] + end + end + else + super + end + end + end + end +end diff --git a/lib/sidekiq/delay_extensions/class_methods.rb b/lib/sidekiq/delay_extensions/class_methods.rb new file mode 100644 index 00000000..f40b7f57 --- /dev/null +++ b/lib/sidekiq/delay_extensions/class_methods.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "sidekiq/delay_extensions/generic_proxy" + +module Sidekiq + module DelayExtensions + ## + # Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method + # execution to Sidekiq. + # + # @example + # User.delay.delete_inactive + # Wikipedia.delay.download_changes_for(Date.today) + # + class DelayedClass + include Sidekiq::Worker + + def perform(yml) + (target, method_name, args) = YAML.load(yml) + target.__send__(method_name, *args) + end + end + + module Klass + def sidekiq_delay(options = {}) + Proxy.new(DelayedClass, self, options) + end + + def sidekiq_delay_for(interval, options = {}) + Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f)) + end + + def sidekiq_delay_until(timestamp, options = {}) + Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f)) + end + alias_method :delay, :sidekiq_delay + alias_method :delay_for, :sidekiq_delay_for + alias_method :delay_until, :sidekiq_delay_until + end + end +end + +Module.__send__(:include, Sidekiq::DelayExtensions::Klass) unless defined?(::Rails) diff --git a/lib/sidekiq/delay_extensions/generic_proxy.rb b/lib/sidekiq/delay_extensions/generic_proxy.rb new file mode 100644 index 00000000..47d6c4ae --- /dev/null +++ b/lib/sidekiq/delay_extensions/generic_proxy.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "yaml" + +module Sidekiq + module DelayExtensions + SIZE_LIMIT = 8_192 + + class Proxy < BasicObject + def initialize(performable, target, options = {}) + @performable = performable + @target = target + @opts = options + end + + def method_missing(name, *args) + # Sidekiq has a limitation in that its message must be JSON. + # JSON can't round trip real Ruby objects so we use YAML to + # serialize the objects to a String. The YAML will be converted + # to JSON and then deserialized on the other side back into a + # Ruby object. + obj = [@target, name, args] + marshalled = ::YAML.dump(obj) + if marshalled.size > SIZE_LIMIT + ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" } + end + @performable.client_push({"class" => @performable, + "args" => [marshalled], + "display_class" => "#{@target}.#{name}"}.merge(@opts)) + end + end + end +end diff --git a/lib/sidekiq/delay_extensions/testing.rb b/lib/sidekiq/delay_extensions/testing.rb new file mode 100644 index 00000000..088a2c6f --- /dev/null +++ b/lib/sidekiq/delay_extensions/testing.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "sidekiq/testing" + +module Sidekiq + Sidekiq::DelayExtensions::DelayedMailer.extend(TestingExtensions) if defined?(Sidekiq::DelayExtensions::DelayedMailer) + Sidekiq::DelayExtensions::DelayedModel.extend(TestingExtensions) if defined?(Sidekiq::DelayExtensions::DelayedModel) +end diff --git a/lib/sidekiq/delay_extensions/version.rb b/lib/sidekiq/delay_extensions/version.rb new file mode 100644 index 00000000..2a136464 --- /dev/null +++ b/lib/sidekiq/delay_extensions/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Sidekiq + module DelayExtensions + VERSION = "6.4.1" + end +end diff --git a/myapp/.gitignore b/myapp/.gitignore new file mode 100644 index 00000000..ec0b1960 --- /dev/null +++ b/myapp/.gitignore @@ -0,0 +1,30 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development +/storage/* + +/node_modules +/yarn-error.log + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key diff --git a/myapp/Gemfile b/myapp/Gemfile new file mode 100644 index 00000000..1b6cc51e --- /dev/null +++ b/myapp/Gemfile @@ -0,0 +1,11 @@ +source 'https://rubygems.org' + +gem 'sidekiq-delay_extensions', :path => '..' +gem 'rails' +gem 'puma' + +# Ubuntu required packages to install rails +# apt-get install build-essential patch ruby-dev zlib1g-dev liblzma-dev libsqlite3-dev +platforms :ruby do + gem 'sqlite3' +end diff --git a/myapp/Rakefile b/myapp/Rakefile new file mode 100644 index 00000000..e85f9139 --- /dev/null +++ b/myapp/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/myapp/app/assets/config/manifest.js b/myapp/app/assets/config/manifest.js new file mode 100644 index 00000000..e69de29b diff --git a/myapp/app/controllers/application_controller.rb b/myapp/app/controllers/application_controller.rb new file mode 100644 index 00000000..09705d12 --- /dev/null +++ b/myapp/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/myapp/app/controllers/work_controller.rb b/myapp/app/controllers/work_controller.rb new file mode 100644 index 00000000..fcf1b4b3 --- /dev/null +++ b/myapp/app/controllers/work_controller.rb @@ -0,0 +1,44 @@ +class WorkController < ApplicationController + def index + @count = rand(100) + puts "Adding #{@count} jobs" + @count.times do |x| + HardWorker.perform_async('bubba', 0.01, x) + end + end + + def email + UserMailer.delay_for(30.seconds).greetings(Time.now) + render :plain => 'enqueued' + end + + def bulk + Sidekiq::Client.push_bulk('class' => HardWorker, + 'args' => [['bob', 1, 1], ['mike', 1, 2]]) + render :plain => 'enbulked' + end + + def long + 50.times do |x| + HardWorker.perform_async('bob', 15, x) + end + render :plain => 'enqueued' + end + + def crash + HardWorker.perform_async('crash', 1, Time.now.to_f) + render :plain => 'enqueued' + end + + def delayed_post + p = Post.first + unless p + p = Post.create!(:title => "Title!", :body => 'Body!') + p2 = Post.create!(:title => "Other!", :body => 'Second Body!') + else + p2 = Post.second + end + p.delay.long_method(p2) + render :plain => 'enqueued' + end +end diff --git a/myapp/app/helpers/application_helper.rb b/myapp/app/helpers/application_helper.rb new file mode 100644 index 00000000..de6be794 --- /dev/null +++ b/myapp/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/myapp/app/jobs/application_job.rb b/myapp/app/jobs/application_job.rb new file mode 100644 index 00000000..a009ace5 --- /dev/null +++ b/myapp/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/myapp/app/jobs/exit_job.rb b/myapp/app/jobs/exit_job.rb new file mode 100644 index 00000000..a9d78de1 --- /dev/null +++ b/myapp/app/jobs/exit_job.rb @@ -0,0 +1,11 @@ +class ExitJob < ApplicationJob + queue_as :default + + def perform(*args) + Sidekiq.logger.warn "Success" + Thread.new do + sleep 0.1 + exit(0) + end + end +end diff --git a/myapp/app/jobs/some_job.rb b/myapp/app/jobs/some_job.rb new file mode 100644 index 00000000..ec2c210c --- /dev/null +++ b/myapp/app/jobs/some_job.rb @@ -0,0 +1,8 @@ +class SomeJob < ApplicationJob + queue_as :default + + def perform(*args) + puts "What's up?!?!" + # Do something later + end +end diff --git a/myapp/app/mailers/.gitkeep b/myapp/app/mailers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/myapp/app/mailers/user_mailer.rb b/myapp/app/mailers/user_mailer.rb new file mode 100644 index 00000000..becd58e9 --- /dev/null +++ b/myapp/app/mailers/user_mailer.rb @@ -0,0 +1,9 @@ +class UserMailer < ActionMailer::Base + default from: "sidekiq@example.com" + + def greetings(now) + @now = now + @hostname = `hostname`.strip + mail(:to => 'mperham@gmail.com', :subject => 'Ahoy Matey!') + end +end diff --git a/myapp/app/models/.gitkeep b/myapp/app/models/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/myapp/app/models/exiter.rb b/myapp/app/models/exiter.rb new file mode 100644 index 00000000..61ef5147 --- /dev/null +++ b/myapp/app/models/exiter.rb @@ -0,0 +1,9 @@ +class Exiter + def self.run + Sidekiq.logger.warn "Success" + Thread.new do + sleep 0.1 + exit(0) + end + end +end diff --git a/myapp/app/models/post.rb b/myapp/app/models/post.rb new file mode 100644 index 00000000..490d0dee --- /dev/null +++ b/myapp/app/models/post.rb @@ -0,0 +1,9 @@ +class Post < ActiveRecord::Base + def long_method(other_post) + puts "Running long method with #{self.id} and #{other_post.id}" + end + + def self.testing + Sidekiq.logger.info "Test" + end +end diff --git a/myapp/app/views/layouts/application.html.erb b/myapp/app/views/layouts/application.html.erb new file mode 100644 index 00000000..0f2f3601 --- /dev/null +++ b/myapp/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + + Myapp + <%= csrf_meta_tags %> + <%= csp_meta_tag %> + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + + <%= yield %> + + diff --git a/myapp/app/views/user_mailer/greetings.html.erb b/myapp/app/views/user_mailer/greetings.html.erb new file mode 100644 index 00000000..bdc17008 --- /dev/null +++ b/myapp/app/views/user_mailer/greetings.html.erb @@ -0,0 +1,3 @@ +

+ Hi Mike, it's <%= @now %> and I'm at <%= @hostname %>. +

diff --git a/myapp/app/views/work/index.html.erb b/myapp/app/views/work/index.html.erb new file mode 100644 index 00000000..bcbe068d --- /dev/null +++ b/myapp/app/views/work/index.html.erb @@ -0,0 +1 @@ +Added <%= @count %> jobs! diff --git a/myapp/app/workers/exit_worker.rb b/myapp/app/workers/exit_worker.rb new file mode 100644 index 00000000..41f95cd1 --- /dev/null +++ b/myapp/app/workers/exit_worker.rb @@ -0,0 +1,11 @@ +class ExitWorker + include Sidekiq::Worker + + def perform + logger.warn "Success" + Thread.new do + sleep 0.1 + exit(0) + end + end +end diff --git a/myapp/app/workers/hard_worker.rb b/myapp/app/workers/hard_worker.rb new file mode 100644 index 00000000..83409cf9 --- /dev/null +++ b/myapp/app/workers/hard_worker.rb @@ -0,0 +1,10 @@ +class HardWorker + include Sidekiq::Worker + sidekiq_options :backtrace => 5 + + def perform(name, count, salt) + raise name if name == 'crash' + logger.info Time.now + sleep count + end +end diff --git a/myapp/app/workers/lazy_worker.rb b/myapp/app/workers/lazy_worker.rb new file mode 100644 index 00000000..157cf72c --- /dev/null +++ b/myapp/app/workers/lazy_worker.rb @@ -0,0 +1,6 @@ +class LazyWorker + include Sidekiq::Worker + + def perform + end +end diff --git a/myapp/bin/bundle b/myapp/bin/bundle new file mode 100755 index 00000000..f19acf5b --- /dev/null +++ b/myapp/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +load Gem.bin_path('bundler', 'bundle') diff --git a/myapp/bin/rails b/myapp/bin/rails new file mode 100755 index 00000000..5badb2fd --- /dev/null +++ b/myapp/bin/rails @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +APP_PATH = File.expand_path('../config/application', __dir__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/myapp/bin/rake b/myapp/bin/rake new file mode 100755 index 00000000..d87d5f57 --- /dev/null +++ b/myapp/bin/rake @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/myapp/bin/setup b/myapp/bin/setup new file mode 100755 index 00000000..94fd4d79 --- /dev/null +++ b/myapp/bin/setup @@ -0,0 +1,36 @@ +#!/usr/bin/env ruby +require 'fileutils' +include FileUtils + +# path to your application root. +APP_ROOT = File.expand_path('..', __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file. + + puts '== Installing dependencies ==' + system! 'gem install bundler --conservative' + system('bundle check') || system!('bundle install') + + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + + # puts "\n== Copying sample files ==" + # unless File.exist?('config/database.yml') + # cp 'config/database.yml.sample', 'config/database.yml' + # end + + puts "\n== Preparing database ==" + system! 'bin/rails db:setup' + + puts "\n== Removing old logs and tempfiles ==" + system! 'bin/rails log:clear tmp:clear' + + puts "\n== Restarting application server ==" + system! 'bin/rails restart' +end diff --git a/myapp/config.ru b/myapp/config.ru new file mode 100644 index 00000000..f7ba0b52 --- /dev/null +++ b/myapp/config.ru @@ -0,0 +1,5 @@ +# This file is used by Rack-based servers to start the application. + +require_relative 'config/environment' + +run Rails.application diff --git a/myapp/config/application.rb b/myapp/config/application.rb new file mode 100644 index 00000000..d2e6429b --- /dev/null +++ b/myapp/config/application.rb @@ -0,0 +1,34 @@ +require_relative 'boot' + +require 'rails' +%w( + active_record/railtie + action_controller/railtie + action_view/railtie + action_mailer/railtie + active_job/railtie + sprockets/railtie +).each do |railtie| + begin + require railtie + rescue LoadError + end +end + +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) + +module Myapp + class Application < Rails::Application + # Load the defaults for whichever Rails version we are using, we need to + # ensure we work with all defaults. + config.load_defaults "#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}" + + # Settings in config/environments/* take precedence over those specified here. + # Application configuration can go into files in config/initializers + # -- all .rb files in that directory are automatically loaded after loading + # the framework and any gems in your application. + config.active_job.queue_adapter = :sidekiq + end +end diff --git a/myapp/config/boot.rb b/myapp/config/boot.rb new file mode 100644 index 00000000..30f5120d --- /dev/null +++ b/myapp/config/boot.rb @@ -0,0 +1,3 @@ +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) + +require 'bundler/setup' # Set up gems listed in the Gemfile. diff --git a/myapp/config/database.yml b/myapp/config/database.yml new file mode 100644 index 00000000..0d02f249 --- /dev/null +++ b/myapp/config/database.yml @@ -0,0 +1,25 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: sqlite3 + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + timeout: 5000 + +development: + <<: *default + database: db/development.sqlite3 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + <<: *default + database: db/test.sqlite3 + +production: + <<: *default + database: db/production.sqlite3 diff --git a/myapp/config/environment.rb b/myapp/config/environment.rb new file mode 100644 index 00000000..426333bb --- /dev/null +++ b/myapp/config/environment.rb @@ -0,0 +1,5 @@ +# Load the Rails application. +require_relative 'application' + +# Initialize the Rails application. +Rails.application.initialize! diff --git a/myapp/config/environments/development.rb b/myapp/config/environments/development.rb new file mode 100644 index 00000000..9a842b56 --- /dev/null +++ b/myapp/config/environments/development.rb @@ -0,0 +1,61 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Do not eager load code on boot. + config.eager_load = false + + # Show full error reports. + config.consider_all_requests_local = true + + # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? + config.action_controller.perform_caching = true + + config.cache_store = :memory_store + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{2.days.to_i}" + } + else + config.action_controller.perform_caching = false + + config.cache_store = :null_store + end + + # Store uploaded files on the local file system (see config/storage.yml for options) + #config.active_storage.service = :local + + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false + + config.action_mailer.perform_caching = false + + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log + + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load + + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. + # config.assets.debug = true + + # Suppress logger output for asset requests. + # config.assets.quiet = true + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true + + # Use an evented file watcher to asynchronously detect changes in source code, + # routes, locales, etc. This feature depends on the listen gem. + #config.file_watcher = ActiveSupport::EventedFileUpdateChecker +end diff --git a/myapp/config/environments/production.rb b/myapp/config/environments/production.rb new file mode 100644 index 00000000..2a20538b --- /dev/null +++ b/myapp/config/environments/production.rb @@ -0,0 +1,94 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = false + + # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + # config.action_controller.asset_host = 'http://assets.example.com' + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Store uploaded files on the local file system (see config/storage.yml for options) + #config.active_storage.service = :local + + # Mount Action Cable outside main process or domain + # config.action_cable.mount_path = nil + # config.action_cable.url = 'wss://example.com/cable' + # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [ :request_id ] + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Use a real queuing backend for Active Job (and separate queues per environment) + # config.active_job.queue_adapter = :resque + # config.active_job.queue_name_prefix = "myapp_#{Rails.env}" + + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + + # Use a different logger for distributed setups. + # require 'syslog/logger' + # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') + + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false +end diff --git a/myapp/config/environments/test.rb b/myapp/config/environments/test.rb new file mode 100644 index 00000000..ccf4335d --- /dev/null +++ b/myapp/config/environments/test.rb @@ -0,0 +1,46 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure public file server for tests with Cache-Control for performance. + config.public_file_server.enabled = true + config.public_file_server.headers = { + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" + } + + # Show full error reports and disable caching. + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates. + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + #config.active_storage.service = :test + + config.action_mailer.perform_caching = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + # Print deprecation notices to the stderr. + config.active_support.deprecation = :stderr + + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true +end diff --git a/myapp/config/initializers/assets.rb b/myapp/config/initializers/assets.rb new file mode 100644 index 00000000..ff2d2299 --- /dev/null +++ b/myapp/config/initializers/assets.rb @@ -0,0 +1,11 @@ +# Be sure to restart your server when you modify this file. + +# Version of your assets, change this if you want to expire all your assets. +# Rails.application.config.assets.version = '1.0' + +# Add additional assets to the asset load path +# Rails.application.config.assets.paths << Emoji.images_path + +# Precompile additional assets. +# application.js, application.css, and all non-JS/CSS in app/assets folder are already added. +# Rails.application.config.assets.precompile += %w( search.js ) diff --git a/myapp/config/initializers/backtrace_silencers.rb b/myapp/config/initializers/backtrace_silencers.rb new file mode 100644 index 00000000..59385cdf --- /dev/null +++ b/myapp/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/myapp/config/initializers/cookies_serializer.rb b/myapp/config/initializers/cookies_serializer.rb new file mode 100644 index 00000000..5a6a32d3 --- /dev/null +++ b/myapp/config/initializers/cookies_serializer.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Specify a serializer for the signed and encrypted cookie jars. +# Valid options are :json, :marshal, and :hybrid. +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/myapp/config/initializers/filter_parameter_logging.rb b/myapp/config/initializers/filter_parameter_logging.rb new file mode 100644 index 00000000..4a994e1e --- /dev/null +++ b/myapp/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password] diff --git a/myapp/config/initializers/inflections.rb b/myapp/config/initializers/inflections.rb new file mode 100644 index 00000000..ac033bf9 --- /dev/null +++ b/myapp/config/initializers/inflections.rb @@ -0,0 +1,16 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections(:en) do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/myapp/config/initializers/mime_types.rb b/myapp/config/initializers/mime_types.rb new file mode 100644 index 00000000..dc189968 --- /dev/null +++ b/myapp/config/initializers/mime_types.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf diff --git a/myapp/config/initializers/secret_token.rb b/myapp/config/initializers/secret_token.rb new file mode 100644 index 00000000..007d2f86 --- /dev/null +++ b/myapp/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Myapp::Application.config.secret_token = 'bdd335500c81ba1f279f9dd8198d1f334445d0193168ed73c1282502180dca27e3e102ec345e99b2acac6a63f7fe29da69c60cc9e76e8da34fb5d4989db24cd8' diff --git a/myapp/config/initializers/session_store.rb b/myapp/config/initializers/session_store.rb new file mode 100644 index 00000000..bf64a296 --- /dev/null +++ b/myapp/config/initializers/session_store.rb @@ -0,0 +1,3 @@ +# Be sure to restart your server when you modify this file. + +Rails.application.config.session_store :cookie_store, key: '_myapp_session' diff --git a/myapp/config/initializers/sidekiq.rb b/myapp/config/initializers/sidekiq.rb new file mode 100644 index 00000000..8053418e --- /dev/null +++ b/myapp/config/initializers/sidekiq.rb @@ -0,0 +1,44 @@ +Sidekiq.configure_client do |config| + config.redis = { :size => 2 } +end +Sidekiq.configure_server do |config| + config.on(:startup) { } + config.on(:quiet) { } + config.on(:shutdown) do + #result = RubyProf.stop + + ## Write the results to a file + ## Requires railsexpress patched MRI build + # brew install qcachegrind + #File.open("callgrind.profile", "w") do |f| + #RubyProf::CallTreePrinter.new(result).print(f, :min_percent => 1) + #end + end +end + +class EmptyWorker + include Sidekiq::Worker + + def perform + end +end + +class TimedWorker + include Sidekiq::Worker + + def perform(start) + now = Time.now.to_f + puts "Latency: #{now - start} sec" + end +end + +Sidekiq::DelayExtensions.enable_delay! + +module Myapp + class Current < ActiveSupport::CurrentAttributes + attribute :tenant_id + end +end + +require "sidekiq/middleware/current_attributes" +Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton \ No newline at end of file diff --git a/myapp/config/initializers/wrap_parameters.rb b/myapp/config/initializers/wrap_parameters.rb new file mode 100644 index 00000000..bbfc3961 --- /dev/null +++ b/myapp/config/initializers/wrap_parameters.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end diff --git a/myapp/config/locales/en.yml b/myapp/config/locales/en.yml new file mode 100644 index 00000000..06539571 --- /dev/null +++ b/myapp/config/locales/en.yml @@ -0,0 +1,23 @@ +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. +# +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. + +en: + hello: "Hello world" diff --git a/myapp/config/routes.rb b/myapp/config/routes.rb new file mode 100644 index 00000000..c32e3f26 --- /dev/null +++ b/myapp/config/routes.rb @@ -0,0 +1,15 @@ +# turns off browser asset caching so we can test CSS changes quickly +ENV['SIDEKIQ_WEB_TESTING'] = '1' + +require 'sidekiq/web' +Sidekiq::Web.app_url = '/' + +Rails.application.routes.draw do + mount Sidekiq::Web => '/sidekiq' + get "work" => "work#index" + get "work/email" => "work#email" + get "work/post" => "work#delayed_post" + get "work/long" => "work#long" + get "work/crash" => "work#crash" + get "work/bulk" => "work#bulk" +end diff --git a/myapp/config/secrets.yml b/myapp/config/secrets.yml new file mode 100644 index 00000000..0093078d --- /dev/null +++ b/myapp/config/secrets.yml @@ -0,0 +1,22 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key is used for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! + +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. + +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. + +development: + secret_key_base: c96477a6cbbc1304f41d7b26d5acb28187a8e8a16308a6d0d0310433e86054fe0bbc6d23a5c9269bd21b1a45650dd0ace354db80b9647bffb1c8bb600a00a1ae + +test: + secret_key_base: ff0824c8b17ce583c8402dbe9968fec3a169dd6a294d4768fdef22cb083a048a2b4bababc5f37ca3a5d3c1b034e13fd703490671cb32db2983c4abb65e67fb09 + +# Do not keep production secrets in the repository, +# instead read values from the environment. +production: + secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> diff --git a/myapp/config/sidekiq.yml b/myapp/config/sidekiq.yml new file mode 100644 index 00000000..b56158ff --- /dev/null +++ b/myapp/config/sidekiq.yml @@ -0,0 +1,3 @@ +--- +:labels: + - some_label diff --git a/myapp/db/migrate/20120123214055_create_posts.rb b/myapp/db/migrate/20120123214055_create_posts.rb new file mode 100644 index 00000000..ca423815 --- /dev/null +++ b/myapp/db/migrate/20120123214055_create_posts.rb @@ -0,0 +1,10 @@ +class CreatePosts < ActiveRecord::Migration[6.0] + def change + create_table :posts do |t| + t.string :title + t.string :body + + t.timestamps + end + end +end diff --git a/myapp/db/schema.rb b/myapp/db/schema.rb new file mode 100644 index 00000000..5c5c5ced --- /dev/null +++ b/myapp/db/schema.rb @@ -0,0 +1,22 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2012_01_23_214055) do + + create_table "posts", force: :cascade do |t| + t.string "title" + t.string "body" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + +end diff --git a/myapp/db/seeds.rb b/myapp/db/seeds.rb new file mode 100644 index 00000000..4edb1e85 --- /dev/null +++ b/myapp/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) +# Mayor.create(name: 'Emanuel', city: cities.first) diff --git a/myapp/lib/assets/.gitkeep b/myapp/lib/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/myapp/lib/tasks/.gitkeep b/myapp/lib/tasks/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/myapp/log/.gitkeep b/myapp/log/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/myapp/script/rails b/myapp/script/rails new file mode 100755 index 00000000..f8da2cff --- /dev/null +++ b/myapp/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/myapp/simple.ru b/myapp/simple.ru new file mode 100644 index 00000000..8e007471 --- /dev/null +++ b/myapp/simple.ru @@ -0,0 +1,19 @@ +# Easiest way to run Sidekiq::Web. +# Run with "bundle exec rackup simple.ru" + +require 'sidekiq/web' + +# A Web process always runs as client, no need to configure server +Sidekiq.configure_client do |config| + config.redis = { url: 'redis://localhost:6379/0', size: 1 } +end + +Sidekiq::Client.push('class' => "HardWorker", 'args' => []) + +# In a multi-process deployment, all Web UI instances should share +# this secret key so they can all decode the encrypted browser cookies +# and provide a working session. +# Rails does this in /config/initializers/secret_token.rb +secret_key = SecureRandom.hex(32) +use Rack::Session::Cookie, secret: secret_key, same_site: true, max_age: 86400 +run Sidekiq::Web diff --git a/sidekiq-delay_extensions.gemspec b/sidekiq-delay_extensions.gemspec new file mode 100644 index 00000000..1e6013d2 --- /dev/null +++ b/sidekiq-delay_extensions.gemspec @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require_relative "lib/sidekiq/delay_extensions/version" + +Gem::Specification.new do |spec| + spec.name = "sidekiq-delay_extensions" + spec.version = Sidekiq::DelayExtensions::VERSION + spec.authors = ["Mike Perham", "Benjamin Fleischer"] + spec.email = ["mperham@gmail.com", "github@benjaminfleischer.com"] + + spec.summary = "Sidekiq Delay Extensions" + spec.description = "Extracted from Sidekiq 6.0" + spec.homepage = "https://github.com/gemhome/sidekiq-delay_extensions/wiki/Delayed-extensions" + spec.license = "LGPL-3.0" + + spec.files = Dir.glob("{bin,lib,config}/**/*") + %w[Gemfile sidekiq-delay_extensions.gemspec README.md Changes.md LICENSE] + + spec.bindir = "exe" + spec.executables = [] + spec.require_paths = ["lib"] + spec.required_ruby_version = ">= 2.5.0" + + spec.metadata = { + "homepage_uri" => "https://github.com/gemhome/sidekiq-delay_extensions/wiki/Delayed-extensions", + "bug_tracker_uri" => "https://github.com/gemhome/sidekiq-delay_extensions/issues", + "documentation_uri" => "https://github.com/gemhome/sidekiq-delay_extensions/wiki", + "changelog_uri" => "https://github.com/gemhome/sidekiq-delay_extensions/blob/main/Changes.md", + "source_code_uri" => "https://github.com/gemhome/sidekiq-delay_extensions" + } + + spec.add_dependency "sidekiq", ">= 6.4.1" +end diff --git a/test/config.yml b/test/config.yml new file mode 100644 index 00000000..d1270add --- /dev/null +++ b/test/config.yml @@ -0,0 +1,7 @@ +--- +:verbose: false +:require: ./test/fake_env.rb +:concurrency: 50 +:queues: + - [<%="very_"%>often, 2] + - [seldom, 1] diff --git a/test/config__FILE__and__dir__.yml b/test/config__FILE__and__dir__.yml new file mode 100644 index 00000000..27bbe354 --- /dev/null +++ b/test/config__FILE__and__dir__.yml @@ -0,0 +1,5 @@ +--- +:require: ./test/fake_env.rb + +:__FILE__: <%= __FILE__ %> +:__dir__: <%= __dir__ %> diff --git a/test/config_empty.yml b/test/config_empty.yml new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/test/config_empty.yml @@ -0,0 +1 @@ +--- diff --git a/test/config_environment.yml b/test/config_environment.yml new file mode 100644 index 00000000..b53ab451 --- /dev/null +++ b/test/config_environment.yml @@ -0,0 +1,10 @@ +--- +:concurrency: 50 + +staging: + :verbose: false + :require: ./test/fake_env.rb + :concurrency: 50 + :queues: + - [<%="very_"%>often, 2] + - [seldom, 1] diff --git a/test/config_queues_without_weights.yml b/test/config_queues_without_weights.yml new file mode 100644 index 00000000..4d5370dc --- /dev/null +++ b/test/config_queues_without_weights.yml @@ -0,0 +1,4 @@ +--- +:queues: + - [queue_1] + - [queue_2] diff --git a/test/config_string.yml b/test/config_string.yml new file mode 100644 index 00000000..9eb50306 --- /dev/null +++ b/test/config_string.yml @@ -0,0 +1,8 @@ +--- +verbose: false +require: ./test/fake_env.rb +concurrency: 50 +queues: + - [<%="very_"%>often, 2] + - [seldom, 1] + diff --git a/test/config_with_alias.yml b/test/config_with_alias.yml new file mode 100644 index 00000000..7f9f8bb0 --- /dev/null +++ b/test/config_with_alias.yml @@ -0,0 +1,12 @@ +--- +:production: &production + :verbose: true + :require: ./test/fake_env.rb + :concurrency: 50 + :queues: + - [<%="very_"%>often, 2] + - [seldom, 1] + +:staging: + <<: *production + :verbose: false \ No newline at end of file diff --git a/test/config_with_internal_options.yml b/test/config_with_internal_options.yml new file mode 100644 index 00000000..fe5da7b6 --- /dev/null +++ b/test/config_with_internal_options.yml @@ -0,0 +1,11 @@ +--- +:verbose: false +:timeout: 10 +:require: ./test/fake_env.rb +:concurrency: 50 +:tag: tag +:queues: + - [often] + - [seldom] + +:strict: false diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb new file mode 100644 index 00000000..f7fb6414 --- /dev/null +++ b/test/dummy/config/application.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails" +require "active_model/railtie" +require "active_job/railtie" +require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "action_view/railtie" +require "rails/test_unit/railtie" + +module Dummy + class Application < Rails::Application + config.root = File.expand_path("../..", __FILE__) + config.eager_load = false + config.logger = Logger.new('/dev/null') + config.load_defaults "6.0" + end +end diff --git a/test/dummy/config/database.yml b/test/dummy/config/database.yml new file mode 100644 index 00000000..29cc2b2f --- /dev/null +++ b/test/dummy/config/database.yml @@ -0,0 +1,11 @@ +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +test: + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb new file mode 100644 index 00000000..875ab0f2 --- /dev/null +++ b/test/dummy/config/environment.rb @@ -0,0 +1,3 @@ +require_relative 'application' + +Rails.application.initialize! diff --git a/test/dummy/config/sidekiq.yml b/test/dummy/config/sidekiq.yml new file mode 100644 index 00000000..b9e5211b --- /dev/null +++ b/test/dummy/config/sidekiq.yml @@ -0,0 +1,3 @@ +--- +:require: ./test/fake_env.rb +:concurrency: 25 diff --git a/test/dummy/tmp/.keep b/test/dummy/tmp/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/fake_env.rb b/test/fake_env.rb new file mode 100644 index 00000000..8e9b8f90 --- /dev/null +++ b/test/fake_env.rb @@ -0,0 +1 @@ +# frozen_string_literal: true diff --git a/test/fixtures/en.yml b/test/fixtures/en.yml new file mode 100644 index 00000000..26500216 --- /dev/null +++ b/test/fixtures/en.yml @@ -0,0 +1,2 @@ +en: + translated_text: 'Changed text from add locals' diff --git a/test/helper.rb b/test/helper.rb new file mode 100644 index 00000000..f283c6a9 --- /dev/null +++ b/test/helper.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'bundler/setup' +Bundler.require(:default, :test) + +require 'minitest/pride' +require 'minitest/autorun' + +$TESTING = true +# disable minitest/parallel threads +ENV["MT_CPU"] = "0" +ENV["N"] = "0" +# Disable any stupid backtrace cleansers +ENV["BACKTRACE"] = "1" + +if ENV["COVERAGE"] + require 'simplecov' + SimpleCov.start do + enable_coverage :branch + add_filter "/test/" + add_filter "/myapp/" + end + if ENV['CI'] + require 'codecov' + SimpleCov.formatter = SimpleCov::Formatter::Codecov + end +end + +ENV['REDIS_URL'] ||= 'redis://localhost/15' + +Sidekiq.logger = ::Logger.new(STDOUT) +Sidekiq.logger.level = Logger::ERROR + +def capture_logging(lvl=Logger::INFO) + old = Sidekiq.logger + begin + out = StringIO.new + logger = ::Logger.new(out) + logger.level = lvl + Sidekiq.logger = logger + yield + out.string + ensure + Sidekiq.logger = old + end +end diff --git a/test/test_api.rb b/test/test_api.rb new file mode 100644 index 00000000..101986df --- /dev/null +++ b/test/test_api.rb @@ -0,0 +1,680 @@ +# frozen_string_literal: true +require_relative 'helper' +require 'sidekiq/api' +require 'active_job' +require 'action_mailer' + +describe 'API' do + before do + Sidekiq.redis {|c| c.flushdb } + end + + describe "stats" do + it "is initially zero" do + s = Sidekiq::Stats.new + assert_equal 0, s.processed + assert_equal 0, s.failed + assert_equal 0, s.enqueued + assert_equal 0, s.default_queue_latency + assert_equal 0, s.workers_size + end + + describe "processed" do + it "returns number of processed jobs" do + Sidekiq.redis { |conn| conn.set("stat:processed", 5) } + s = Sidekiq::Stats.new + assert_equal 5, s.processed + end + end + + describe "failed" do + it "returns number of failed jobs" do + Sidekiq.redis { |conn| conn.set("stat:failed", 5) } + s = Sidekiq::Stats.new + assert_equal 5, s.failed + end + end + + describe "reset" do + before do + Sidekiq.redis do |conn| + conn.set('stat:processed', 5) + conn.set('stat:failed', 10) + end + end + + it 'will reset all stats by default' do + Sidekiq::Stats.new.reset + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 0, s.processed + end + + it 'can reset individual stats' do + Sidekiq::Stats.new.reset('failed') + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 5, s.processed + end + + it 'can accept anything that responds to #to_s' do + Sidekiq::Stats.new.reset(:failed) + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 5, s.processed + end + + it 'ignores anything other than "failed" or "processed"' do + Sidekiq::Stats.new.reset((1..10).to_a, ['failed']) + s = Sidekiq::Stats.new + assert_equal 0, s.failed + assert_equal 5, s.processed + end + end + + describe "workers_size" do + it 'retrieves the number of busy workers' do + Sidekiq.redis do |c| + c.sadd("processes", "process_1") + c.sadd("processes", "process_2") + c.hset("process_1", "busy", 1) + c.hset("process_2", "busy", 2) + end + s = Sidekiq::Stats.new + assert_equal 3, s.workers_size + end + end + + describe "queues" do + it "is initially empty" do + s = Sidekiq::Stats::Queues.new + assert_equal 0, s.lengths.size + end + + it "returns a hash of queue and size in order" do + Sidekiq.redis do |conn| + conn.rpush 'queue:foo', '{}' + conn.sadd 'queues', 'foo' + + 3.times { conn.rpush 'queue:bar', '{}' } + conn.sadd 'queues', 'bar' + end + + s = Sidekiq::Stats::Queues.new + assert_equal ({ "foo" => 1, "bar" => 3 }), s.lengths + assert_equal "bar", s.lengths.first.first + + assert_equal Sidekiq::Stats.new.queues, Sidekiq::Stats::Queues.new.lengths + end + end + + describe "enqueued" do + it 'handles latency for good jobs' do + Sidekiq.redis do |conn| + conn.rpush 'queue:default', "{\"enqueued_at\": #{Time.now.to_f}}" + conn.sadd 'queues', 'default' + end + s = Sidekiq::Stats.new + assert s.default_queue_latency > 0 + q = Sidekiq::Queue.new + assert q.latency > 0 + end + + it 'handles latency for incomplete jobs' do + Sidekiq.redis do |conn| + conn.rpush 'queue:default', '{}' + conn.sadd 'queues', 'default' + end + s = Sidekiq::Stats.new + assert_equal 0, s.default_queue_latency + q = Sidekiq::Queue.new + assert_equal 0, q.latency + end + + it "returns total enqueued jobs" do + Sidekiq.redis do |conn| + conn.rpush 'queue:foo', '{}' + conn.sadd 'queues', 'foo' + + 3.times { conn.rpush 'queue:bar', '{}' } + conn.sadd 'queues', 'bar' + end + + s = Sidekiq::Stats.new + assert_equal 4, s.enqueued + end + end + + describe "over time" do + before do + require 'active_support/core_ext/time/conversions' + @before = Time::DATE_FORMATS[:default] + Time::DATE_FORMATS[:default] = "%d/%m/%Y %H:%M:%S" + end + + after do + Time::DATE_FORMATS[:default] = @before + end + + describe "history" do + it "does not allow invalid input" do + assert_raises(ArgumentError) { Sidekiq::Stats::History.new(-1) } + assert_raises(ArgumentError) { Sidekiq::Stats::History.new(0) } + assert_raises(ArgumentError) { Sidekiq::Stats::History.new(2000) } + assert Sidekiq::Stats::History.new(200) + end + end + + describe "processed" do + it 'retrieves hash of dates' do + Sidekiq.redis do |c| + c.incrby("stat:processed:2012-12-24", 4) + c.incrby("stat:processed:2012-12-25", 1) + c.incrby("stat:processed:2012-12-26", 6) + c.incrby("stat:processed:2012-12-27", 2) + end + Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do + s = Sidekiq::Stats::History.new(2) + assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1 }, s.processed) + + s = Sidekiq::Stats::History.new(3) + assert_equal({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) + + s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) + assert_equal({ "2012-12-25" => 1, "2012-12-24" => 4 }, s.processed) + end + end + end + + describe "failed" do + it 'retrieves hash of dates' do + Sidekiq.redis do |c| + c.incrby("stat:failed:2012-12-24", 4) + c.incrby("stat:failed:2012-12-25", 1) + c.incrby("stat:failed:2012-12-26", 6) + c.incrby("stat:failed:2012-12-27", 2) + end + Time.stub(:now, Time.parse("2012-12-26 1:00:00 -0500")) do + s = Sidekiq::Stats::History.new(2) + assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1 }), s.failed + + s = Sidekiq::Stats::History.new(3) + assert_equal ({ "2012-12-26" => 6, "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed + + s = Sidekiq::Stats::History.new(2, Date.parse("2012-12-25")) + assert_equal ({ "2012-12-25" => 1, "2012-12-24" => 4 }), s.failed + end + end + end + end + end + + describe 'with an empty database' do + it 'shows queue as empty' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + assert_equal 0, q.latency + end + + before do + ActiveJob::Base.queue_adapter = :sidekiq + ActiveJob::Base.logger = nil + end + + class ApiMailer < ActionMailer::Base + def test_email(*) + end + end + + class ApiJob < ActiveJob::Base + def perform(*) + end + end + + class ApiWorker + include Sidekiq::Worker + end + + class WorkerWithTags + include Sidekiq::Worker + sidekiq_options tags: ['foo'] + end + + it 'can enumerate jobs' do + q = Sidekiq::Queue.new + Time.stub(:now, Time.new(2012, 12, 26)) do + ApiWorker.perform_async(1, 'mike') + assert_equal [ApiWorker.name], q.map(&:klass) + + job = q.first + assert_equal 24, job.jid.size + assert_equal [1, 'mike'], job.args + assert_equal Time.new(2012, 12, 26), job.enqueued_at + end + assert q.latency > 10_000_000 + + q = Sidekiq::Queue.new('other') + assert_equal 0, q.size + end + + it 'enumerates jobs in descending score order' do + # We need to enqueue more than 50 items, which is the page size when retrieving + # from Redis to ensure everything is sorted: the pages and the items withing them. + 51.times { ApiWorker.perform_in(100, 1, 'foo') } + + set = Sidekiq::ScheduledSet.new.to_a + + assert_equal set.sort_by { |job| -job.score }, set + end + + it 'has no enqueued_at time for jobs enqueued in the future' do + job_id = ApiWorker.perform_in(100, 1, 'foo') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + assert_nil job.enqueued_at + end + + it 'unwraps delayed jobs' do + Sidekiq::DelayExtensions.enable_delay! + Sidekiq::Queue.delay.foo(1,2,3) + q = Sidekiq::Queue.new + x = q.first + assert_equal "Sidekiq::Queue.foo", x.display_class + assert_equal [1,2,3], x.display_args + end + + it 'handles previous (raw Array) error_backtrace format' do + add_retry + job = Sidekiq::RetrySet.new.first + assert_equal ['line1', 'line2'], job.error_backtrace + end + + it 'handles previous (marshalled Array) error_backtrace format' do + backtrace = ['line1', 'line2'] + serialized = Marshal.dump(backtrace) + compressed = Zlib::Deflate.deflate(serialized) + encoded = Base64.encode64(compressed) + + payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1], 'queue' => 'default', 'jid' => 'jid', 'error_backtrace' => encoded) + Sidekiq.redis do |conn| + conn.zadd('retry', Time.now.to_f.to_s, payload) + end + + job = Sidekiq::RetrySet.new.first + assert_equal backtrace, job.error_backtrace + end + + describe "Rails unwrapping" do + SERIALIZED_JOBS = { + "5.x" => [ + '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ApiJob","queue":"default","args":[{"job_class":"ApiJob","job_id":"f1bde53f-3852-4ae4-a879-c12eacebbbb0","provider_job_id":null,"queue_name":"default","priority":null,"arguments":[1,2,3],"executions":0,"locale":"en"}],"retry":true,"jid":"099eee72911085a511d0e312","created_at":1568305542.339916,"enqueued_at":1568305542.339947}', + '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::DeliveryJob","queue":"mailers","args":[{"job_class":"ActionMailer::DeliveryJob","job_id":"19cc0115-3d1c-4bbe-a51e-bfa1385895d1","provider_job_id":null,"queue_name":"mailers","priority":null,"arguments":["ApiMailer","test_email","deliver_now",1,2,3],"executions":0,"locale":"en"}],"retry":true,"jid":"37436e5504936400e8cf98db","created_at":1568305542.370133,"enqueued_at":1568305542.370241}', + ], + "6.x" => [ + '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ApiJob","queue":"default","args":[{"job_class":"ApiJob","job_id":"ff2b48d4-bdce-4825-af6b-ef8c11ab651e","provider_job_id":null,"queue_name":"default","priority":null,"arguments":[1,2,3],"executions":0,"exception_executions":{},"locale":"en","timezone":"UTC","enqueued_at":"2019-09-12T16:28:37Z"}],"retry":true,"jid":"ce121bf77b37ae81fe61b6dc","created_at":1568305717.9469702,"enqueued_at":1568305717.947005}', + '{"class":"ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper","wrapped":"ActionMailer::MailDeliveryJob","queue":"mailers","args":[{"job_class":"ActionMailer::MailDeliveryJob","job_id":"2f967da1-a389-479c-9a4e-5cc059e6d65c","provider_job_id":null,"queue_name":"mailers","priority":null,"arguments":["ApiMailer","test_email","deliver_now",{"args":[1,2,3],"_aj_symbol_keys":["args"]}],"executions":0,"exception_executions":{},"locale":"en","timezone":"UTC","enqueued_at":"2019-09-12T16:28:37Z"}],"retry":true,"jid":"469979df52bb9ef9f48b49e1","created_at":1568305717.9457421,"enqueued_at":1568305717.9457731}', + ], + }.each_pair do |ver,jobs| + it "unwraps ActiveJob #{ver} jobs" do + #ApiJob.perform_later(1,2,3) + #puts Sidekiq::Queue.new.first.value + x = Sidekiq::JobRecord.new(jobs[0], "default") + assert_equal ApiJob.name, x.display_class + assert_equal [1,2,3], x.display_args + end + + it "unwraps ActionMailer #{ver} jobs" do + #ApiMailer.test_email(1,2,3).deliver_later + #puts Sidekiq::Queue.new("mailers").first.value + x = Sidekiq::JobRecord.new(jobs[1], "mailers") + assert_equal "#{ApiMailer.name}#test_email", x.display_class + assert_equal [1,2,3], x.display_args + end + end + end + + it 'has no enqueued_at time for jobs enqueued in the future' do + job_id = ApiWorker.perform_in(100, 1, 'foo') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + assert_nil job.enqueued_at + end + + it 'returns tags field for jobs' do + job_id = ApiWorker.perform_async + assert_equal [], Sidekiq::Queue.new.find_job(job_id).tags + + job_id = WorkerWithTags.perform_async + assert_equal ['foo'], Sidekiq::Queue.new.find_job(job_id).tags + end + + it 'can delete jobs' do + q = Sidekiq::Queue.new + ApiWorker.perform_async(1, 'mike') + assert_equal 1, q.size + + x = q.first + assert_equal ApiWorker.name, x.display_class + assert_equal [1,'mike'], x.display_args + + assert_equal [true], q.map(&:delete) + assert_equal 0, q.size + end + + it "can move scheduled job to queue" do + remain_id = ApiWorker.perform_in(100, 1, 'jason') + job_id = ApiWorker.perform_in(100, 1, 'jason') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + q = Sidekiq::Queue.new + job.add_to_queue + queued_job = q.find_job(job_id) + refute_nil queued_job + assert_equal queued_job.jid, job_id + assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) + refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) + end + + it "handles multiple scheduled jobs when moving to queue" do + jids = Sidekiq::Client.push_bulk('class' => ApiWorker, + 'args' => [[1, 'jason'], [2, 'jason']], + 'at' => Time.now.to_f) + assert_equal 2, jids.size + (remain_id, job_id) = jids + job = Sidekiq::ScheduledSet.new.find_job(job_id) + q = Sidekiq::Queue.new + job.add_to_queue + queued_job = q.find_job(job_id) + refute_nil queued_job + assert_equal queued_job.jid, job_id + assert_nil Sidekiq::ScheduledSet.new.find_job(job_id) + refute_nil Sidekiq::ScheduledSet.new.find_job(remain_id) + end + + it 'can kill a scheduled job' do + job_id = ApiWorker.perform_in(100, 1, '{"foo":123}') + job = Sidekiq::ScheduledSet.new.find_job(job_id) + ds = Sidekiq::DeadSet.new + assert_equal 0, ds.size + job.kill + assert_equal 1, ds.size + end + + it 'can find a scheduled job by jid' do + 10.times do |idx| + ApiWorker.perform_in(idx, 1) + end + + job_id = ApiWorker.perform_in(5, 1) + job = Sidekiq::ScheduledSet.new.find_job(job_id) + assert_equal job_id, job.jid + + ApiWorker.perform_in(100, 1, 'jid' => 'jid_in_args') + assert_nil Sidekiq::ScheduledSet.new.find_job('jid_in_args') + end + + it 'can remove jobs when iterating over a sorted set' do + # scheduled jobs must be greater than SortedSet#each underlying page size + 51.times do + ApiWorker.perform_in(100, 'aaron') + end + set = Sidekiq::ScheduledSet.new + set.map(&:delete) + assert_equal set.size, 0 + end + + it 'can remove jobs when iterating over a queue' do + # initial queue size must be greater than Queue#each underlying page size + 51.times do + ApiWorker.perform_async(1, 'aaron') + end + q = Sidekiq::Queue.new + q.map(&:delete) + assert_equal q.size, 0 + end + + it 'can find job by id in queues' do + q = Sidekiq::Queue.new + job_id = ApiWorker.perform_async(1, 'jason') + job = q.find_job(job_id) + refute_nil job + assert_equal job_id, job.jid + end + + it 'can clear a queue' do + q = Sidekiq::Queue.new + 2.times { ApiWorker.perform_async(1, 'mike') } + q.clear + + Sidekiq.redis do |conn| + refute conn.smembers('queues').include?('foo') + refute conn.exists?('queue:foo') + end + end + + it 'can fetch by score' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 2, r.fetch(same_time).size + end + + it 'can fetch by score and jid' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 1, r.fetch(same_time, 'bob1').size + end + + it 'can fetch by score range' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time + 1) + add_retry('bob3', same_time + 2) + r = Sidekiq::RetrySet.new + range = (same_time..(same_time + 1)) + assert_equal 2, r.fetch(range).size + end + + it 'can fetch by score range and jid' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time + 1) + add_retry('bob3', same_time + 2) + r = Sidekiq::RetrySet.new + range = (same_time..(same_time + 1)) + jobs = r.fetch(range, 'bob2') + assert_equal 1, jobs.size + assert_equal jobs[0].jid, 'bob2' + end + + it 'shows empty retries' do + r = Sidekiq::RetrySet.new + assert_equal 0, r.size + end + + it 'can enumerate retries' do + add_retry + + r = Sidekiq::RetrySet.new + assert_equal 1, r.size + array = r.to_a + assert_equal 1, array.size + + retri = array.first + assert_equal 'ApiWorker', retri.klass + assert_equal 'default', retri.queue + assert_equal 'bob', retri.jid + assert_in_delta Time.now.to_f, retri.at.to_f, 0.02 + end + + it 'requires a jid to delete an entry' do + start_time = Time.now.to_f + add_retry('bob2', Time.now.to_f) + assert_raises(ArgumentError) do + Sidekiq::RetrySet.new.delete(start_time) + end + end + + it 'can delete a single retry from score and jid' do + same_time = Time.now.to_f + add_retry('bob1', same_time) + add_retry('bob2', same_time) + r = Sidekiq::RetrySet.new + assert_equal 2, r.size + Sidekiq::RetrySet.new.delete(same_time, 'bob1') + assert_equal 1, r.size + end + + it 'can retry a retry' do + add_retry + r = Sidekiq::RetrySet.new + assert_equal 1, r.size + r.first.retry + assert_equal 0, r.size + assert_equal 1, Sidekiq::Queue.new('default').size + job = Sidekiq::Queue.new('default').first + assert_equal 'bob', job.jid + assert_equal 1, job['retry_count'] + end + + it 'can clear retries' do + add_retry + add_retry('test') + r = Sidekiq::RetrySet.new + assert_equal 2, r.size + r.clear + assert_equal 0, r.size + end + + it 'can scan retries' do + add_retry + add_retry('test') + r = Sidekiq::RetrySet.new + assert_instance_of Enumerator, r.scan('Worker') + assert_equal 2, r.scan('ApiWorker').to_a.size + assert_equal 1, r.scan('*test*').to_a.size + end + + it 'can enumerate processes' do + identity_string = "identity_string" + odata = { + 'pid' => 123, + 'hostname' => Socket.gethostname, + 'key' => identity_string, + 'identity' => identity_string, + 'started_at' => Time.now.to_f - 15, + 'queues' => ['foo', 'bar'] + } + + time = Time.now.to_f + Sidekiq.redis do |conn| + conn.multi do |transaction| + transaction.sadd('processes', odata['key']) + transaction.hmset(odata['key'], 'info', Sidekiq.dump_json(odata), 'busy', 10, 'beat', time) + transaction.sadd('processes', 'fake:pid') + end + end + + ps = Sidekiq::ProcessSet.new.to_a + assert_equal 1, ps.size + data = ps.first + assert_equal 10, data['busy'] + assert_equal time, data['beat'] + assert_equal 123, data['pid'] + assert_equal ['foo', 'bar'], data.queues + data.quiet! + data.stop! + signals_string = "#{odata['key']}-signals" + assert_equal "TERM", Sidekiq.redis{|c| c.lpop(signals_string) } + assert_equal "TSTP", Sidekiq.redis{|c| c.lpop(signals_string) } + end + + it 'can enumerate workers' do + w = Sidekiq::Workers.new + assert_equal 0, w.size + w.each do + assert false + end + + hn = Socket.gethostname + key = "#{hn}:#{$$}" + pdata = { 'pid' => $$, 'hostname' => hn, 'started_at' => Time.now.to_i } + Sidekiq.redis do |conn| + conn.sadd('processes', key) + conn.hmset(key, 'info', Sidekiq.dump_json(pdata), 'busy', 0, 'beat', Time.now.to_f) + end + + s = "#{key}:workers" + data = Sidekiq.dump_json({ 'payload' => "{}", 'queue' => 'default', 'run_at' => Time.now.to_i }) + Sidekiq.redis do |c| + c.hmset(s, '1234', data) + end + + w.each do |p, x, y| + assert_equal key, p + assert_equal "1234", x + assert_equal 'default', y['queue'] + assert_equal({}, y['payload']) + assert_equal Time.now.year, Time.at(y['run_at']).year + end + + s = "#{key}:workers" + data = Sidekiq.dump_json({ 'payload' => {}, 'queue' => 'default', 'run_at' => (Time.now.to_i - 2*60*60) }) + Sidekiq.redis do |c| + c.multi do |transaction| + transaction.hmset(s, '5678', data) + transaction.hmset("b#{s}", '5678', data) + end + end + + assert_equal ['5678', '1234'], w.map { |_, tid, _| tid } + end + + it 'can reschedule jobs' do + add_retry('foo1') + add_retry('foo2') + + retries = Sidekiq::RetrySet.new + assert_equal 2, retries.size + refute(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) + + retries.each do |retri| + retri.reschedule(Time.now + 15) if retri.jid == 'foo1' + retri.reschedule(Time.now.to_f + 10) if retri.jid == 'foo2' + end + + assert_equal 2, retries.size + assert(retries.map { |r| r.score > (Time.now.to_f + 9) }.any?) + assert(retries.map { |r| r.score > (Time.now.to_f + 14) }.any?) + end + + it 'prunes processes which have died' do + data = { 'pid' => rand(10_000), 'hostname' => "app#{rand(1_000)}", 'started_at' => Time.now.to_f } + key = "#{data['hostname']}:#{data['pid']}" + Sidekiq.redis do |conn| + conn.sadd('processes', key) + conn.hmset(key, 'info', Sidekiq.dump_json(data), 'busy', 0, 'beat', Time.now.to_f) + end + + ps = Sidekiq::ProcessSet.new + assert_equal 1, ps.size + assert_equal 1, ps.to_a.size + + Sidekiq.redis do |conn| + conn.sadd('processes', "bar:987") + conn.sadd('processes', "bar:986") + end + + ps = Sidekiq::ProcessSet.new + assert_equal 1, ps.size + assert_equal 1, ps.to_a.size + end + + def add_retry(jid = 'bob', at = Time.now.to_f) + payload = Sidekiq.dump_json('class' => 'ApiWorker', 'args' => [1, 'mike'], 'queue' => 'default', 'jid' => jid, 'retry_count' => 2, 'failed_at' => Time.now.to_f, 'error_backtrace' => ['line1', 'line2']) + Sidekiq.redis do |conn| + conn.zadd('retry', at.to_s, payload) + end + end + end +end diff --git a/test/test_extensions.rb b/test/test_extensions.rb new file mode 100644 index 00000000..4cec62df --- /dev/null +++ b/test/test_extensions.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true +require_relative 'helper' +require 'sidekiq/api' +require 'active_record' +require 'action_mailer' +Sidekiq::DelayExtensions.enable_delay! + +describe Sidekiq::DelayExtensions do + before do + Sidekiq.redis {|c| c.flushdb } + end + + class MyModel < ActiveRecord::Base + def self.long_class_method + raise "Should not be called!" + end + end + + it 'allows delayed execution of ActiveRecord class methods' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + MyModel.delay.long_class_method + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + end + + it 'uses and stringifies specified options' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new('notdefault') + assert_equal 0, q.size + MyModel.delay(queue: :notdefault).long_class_method + assert_equal ['notdefault'], Sidekiq::Queue.all.map(&:name) + assert_equal ['MyModel.long_class_method'], q.map(&:display_class) + assert_equal 1, q.size + end + + it 'allows delayed scheduling of AR class methods' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + MyModel.delay_for(5.days).long_class_method + assert_equal 1, ss.size + end + + it 'allows until delayed scheduling of AR class methods' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + MyModel.delay_until(1.day.from_now).long_class_method + assert_equal 1, ss.size + end + + class UserMailer < ActionMailer::Base + def greetings(a, b) + raise "Should not be called!" + end + end + + it 'allows delayed delivery of ActionMailer mails' do + assert_equal [], Sidekiq::Queue.all.map(&:name) + q = Sidekiq::Queue.new + assert_equal 0, q.size + UserMailer.delay.greetings(1, 2) + assert_equal ['default'], Sidekiq::Queue.all.map(&:name) + assert_equal 1, q.size + end + + it 'allows delayed scheduling of AM mails' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + UserMailer.delay_for(5.days).greetings(1, 2) + assert_equal 1, ss.size + end + + it 'allows until delay scheduling of AM mails' do + ss = Sidekiq::ScheduledSet.new + assert_equal 0, ss.size + UserMailer.delay_until(5.days.from_now).greetings(1, 2) + assert_equal 1, ss.size + end + + class SomeClass + def self.doit(arg) + end + end + + it 'allows delay of any ole class method' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeClass.delay.doit(Date.today) + assert_equal 1, q.size + end + + module SomeModule + def self.doit(arg) + end + end + + it 'logs large payloads' do + output = capture_logging(Logger::WARN) do + SomeClass.delay.doit('a' * 8192) + end + assert_match(/#{SomeClass}.doit job argument is/, output) + end + + it 'allows delay of any module class method' do + q = Sidekiq::Queue.new + assert_equal 0, q.size + SomeModule.delay.doit(Date.today) + assert_equal 1, q.size + end + +end diff --git a/test/test_testing_fake.rb b/test/test_testing_fake.rb new file mode 100644 index 00000000..3d3baaa7 --- /dev/null +++ b/test/test_testing_fake.rb @@ -0,0 +1,365 @@ +# frozen_string_literal: true +require_relative 'helper' + +describe 'Sidekiq::Testing.fake' do + class PerformError < RuntimeError; end + + class DirectWorker + include Sidekiq::Worker + def perform(a, b) + a + b + end + end + + class EnqueuedWorker + include Sidekiq::Worker + def perform(a, b) + a + b + end + end + + class StoredWorker + include Sidekiq::Worker + def perform(error) + raise PerformError if error + end + end + + before do + require 'sidekiq/delay_extensions/testing' + Sidekiq::Testing.fake! + EnqueuedWorker.jobs.clear + DirectWorker.jobs.clear + end + + after do + Sidekiq::Testing.disable! + Sidekiq::Queues.clear_all + end + + it 'stubs the async call' do + assert_equal 0, DirectWorker.jobs.size + assert DirectWorker.perform_async(1, 2) + assert_in_delta Time.now.to_f, DirectWorker.jobs.last['enqueued_at'], 0.1 + assert_equal 1, DirectWorker.jobs.size + assert DirectWorker.perform_in(10, 1, 2) + refute DirectWorker.jobs.last['enqueued_at'] + assert_equal 2, DirectWorker.jobs.size + assert DirectWorker.perform_at(10, 1, 2) + assert_equal 3, DirectWorker.jobs.size + assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.1 + end + + describe 'delayed' do + require 'action_mailer' + class FooMailer < ActionMailer::Base + def bar(str) + str + end + end + + before do + Sidekiq::DelayExtensions.enable_delay! + end + + it 'stubs the delay call on mailers' do + assert_equal 0, Sidekiq::DelayExtensions::DelayedMailer.jobs.size + FooMailer.delay.bar('hello!') + assert_equal 1, Sidekiq::DelayExtensions::DelayedMailer.jobs.size + end + + class Something + def self.foo(x) + end + end + + it 'stubs the delay call on classes' do + assert_equal 0, Sidekiq::DelayExtensions::DelayedClass.jobs.size + Something.delay.foo(Date.today) + assert_equal 1, Sidekiq::DelayExtensions::DelayedClass.jobs.size + end + + class BarMailer < ActionMailer::Base + def foo(str) + str + end + end + + it 'returns enqueued jobs for specific classes' do + assert_equal 0, Sidekiq::DelayExtensions::DelayedClass.jobs.size + FooMailer.delay.bar('hello!') + BarMailer.delay.foo('hello!') + assert_equal 2, Sidekiq::DelayExtensions::DelayedMailer.jobs.size + assert_equal 1, Sidekiq::DelayExtensions::DelayedMailer.jobs_for(FooMailer).size + assert_equal 1, Sidekiq::DelayExtensions::DelayedMailer.jobs_for(BarMailer).size + end + end + + it 'stubs the enqueue call' do + assert_equal 0, EnqueuedWorker.jobs.size + assert Sidekiq::Client.enqueue(EnqueuedWorker, 1, 2) + assert_equal 1, EnqueuedWorker.jobs.size + end + + it 'stubs the enqueue_to call' do + assert_equal 0, EnqueuedWorker.jobs.size + assert Sidekiq::Client.enqueue_to('someq', EnqueuedWorker, 1, 2) + assert_equal 1, Sidekiq::Queues['someq'].size + end + + it 'executes all stored jobs' do + assert StoredWorker.perform_async(false) + assert StoredWorker.perform_async(true) + + assert_equal 2, StoredWorker.jobs.size + assert_raises PerformError do + StoredWorker.drain + end + assert_equal 0, StoredWorker.jobs.size + end + + class SpecificJidWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform(worker_jid) + return unless worker_jid == self.jid + self.class.count += 1 + end + end + + it 'execute only jobs with assigned JID' do + 4.times do |i| + jid = SpecificJidWorker.perform_async(nil) + if i % 2 == 0 + SpecificJidWorker.jobs[-1]["args"] = ["wrong_jid"] + else + SpecificJidWorker.jobs[-1]["args"] = [jid] + end + end + + SpecificJidWorker.perform_one + assert_equal 0, SpecificJidWorker.count + + SpecificJidWorker.perform_one + assert_equal 1, SpecificJidWorker.count + + SpecificJidWorker.drain + assert_equal 2, SpecificJidWorker.count + end + + it 'round trip serializes the job arguments' do + assert StoredWorker.perform_async(:mike) + job = StoredWorker.jobs.first + assert_equal "mike", job['args'].first + StoredWorker.clear + end + + it 'perform_one runs only one job' do + DirectWorker.perform_async(1, 2) + DirectWorker.perform_async(3, 4) + assert_equal 2, DirectWorker.jobs.size + + DirectWorker.perform_one + assert_equal 1, DirectWorker.jobs.size + + DirectWorker.clear + end + + it 'perform_one raise error upon empty queue' do + DirectWorker.clear + assert_raises Sidekiq::EmptyQueueError do + DirectWorker.perform_one + end + end + + class FirstWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform + self.class.count += 1 + end + end + + class SecondWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + self.count = 0 + def perform + self.class.count += 1 + end + end + + class ThirdWorker + include Sidekiq::Worker + sidekiq_class_attribute :count + def perform + FirstWorker.perform_async + SecondWorker.perform_async + end + end + + it 'clears jobs across all workers' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 + + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size + + FirstWorker.perform_async + SecondWorker.perform_async + + assert_equal 1, FirstWorker.jobs.size + assert_equal 1, SecondWorker.jobs.size + + Sidekiq::Worker.clear_all + + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size + + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count + end + + it 'drains jobs across all workers' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 + + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size + + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count + + FirstWorker.perform_async + SecondWorker.perform_async + + assert_equal 1, FirstWorker.jobs.size + assert_equal 1, SecondWorker.jobs.size + + Sidekiq::Worker.drain_all + + assert_equal 0, FirstWorker.jobs.size + assert_equal 0, SecondWorker.jobs.size + + assert_equal 1, FirstWorker.count + assert_equal 1, SecondWorker.count + end + + it 'drains jobs across all workers even when workers create new jobs' do + Sidekiq::Worker.jobs.clear + FirstWorker.count = 0 + SecondWorker.count = 0 + + assert_equal 0, ThirdWorker.jobs.size + + assert_equal 0, FirstWorker.count + assert_equal 0, SecondWorker.count + + ThirdWorker.perform_async + + assert_equal 1, ThirdWorker.jobs.size + + Sidekiq::Worker.drain_all + + assert_equal 0, ThirdWorker.jobs.size + + assert_equal 1, FirstWorker.count + assert_equal 1, SecondWorker.count + end + + it 'drains jobs of workers with symbolized queue names' do + Sidekiq::Worker.jobs.clear + + AltQueueWorker.perform_async(5,6) + assert_equal 1, AltQueueWorker.jobs.size + + Sidekiq::Worker.drain_all + assert_equal 0, AltQueueWorker.jobs.size + end + + it 'can execute a job' do + DirectWorker.execute_job(DirectWorker.new, [2, 3]) + end + + describe 'queue testing' do + before do + require 'sidekiq/delay_extensions/testing' + Sidekiq::Testing.fake! + end + + after do + Sidekiq::Testing.disable! + Sidekiq::Queues.clear_all + end + + class QueueWorker + include Sidekiq::Worker + def perform(a, b) + a + b + end + end + + class AltQueueWorker + include Sidekiq::Worker + sidekiq_options queue: :alt + def perform(a, b) + a + b + end + end + + it 'finds enqueued jobs' do + assert_equal 0, Sidekiq::Queues["default"].size + + QueueWorker.perform_async(1, 2) + QueueWorker.perform_async(1, 2) + AltQueueWorker.perform_async(1, 2) + + assert_equal 2, Sidekiq::Queues["default"].size + assert_equal [1, 2], Sidekiq::Queues["default"].first["args"] + + assert_equal 1, Sidekiq::Queues["alt"].size + end + + it 'clears out all queues' do + assert_equal 0, Sidekiq::Queues["default"].size + + QueueWorker.perform_async(1, 2) + QueueWorker.perform_async(1, 2) + AltQueueWorker.perform_async(1, 2) + + Sidekiq::Queues.clear_all + + assert_equal 0, Sidekiq::Queues["default"].size + assert_equal 0, QueueWorker.jobs.size + assert_equal 0, Sidekiq::Queues["alt"].size + assert_equal 0, AltQueueWorker.jobs.size + end + + it 'finds jobs enqueued by client' do + Sidekiq::Client.push( + 'class' => 'NonExistentWorker', + 'queue' => 'missing', + 'args' => [1] + ) + + assert_equal 1, Sidekiq::Queues["missing"].size + end + + it 'respects underlying array changes' do + # Rspec expect change() syntax saves a reference to + # an underlying array. When the array containing jobs is + # derived, Rspec test using `change(QueueWorker.jobs, :size).by(1)` + # won't pass. This attempts to recreate that scenario + # by saving a reference to the jobs array and ensuring + # it changes properly on enqueueing + jobs = QueueWorker.jobs + assert_equal 0, jobs.size + QueueWorker.perform_async(1, 2) + assert_equal 1, jobs.size + end + end +end diff --git a/test/test_testing_inline.rb b/test/test_testing_inline.rb new file mode 100644 index 00000000..e356fb30 --- /dev/null +++ b/test/test_testing_inline.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +require_relative 'helper' + +describe 'Sidekiq::Testing.inline' do + class InlineError < RuntimeError; end + class ParameterIsNotString < RuntimeError; end + + class InlineWorker + include Sidekiq::Worker + def perform(pass) + raise ArgumentError, "no jid" unless jid + raise InlineError unless pass + end + end + + class InlineWorkerWithTimeParam + include Sidekiq::Worker + def perform(time) + raise ParameterIsNotString unless time.is_a?(String) || time.is_a?(Numeric) + end + end + + before do + require 'sidekiq/delay_extensions/testing' + require 'sidekiq/testing/inline' + Sidekiq::Testing.inline! + end + + after do + Sidekiq::Testing.disable! + end + + it 'stubs the async call when in testing mode' do + assert InlineWorker.perform_async(true) + + assert_raises InlineError do + InlineWorker.perform_async(false) + end + end + + describe 'delay' do + require 'action_mailer' + class InlineFooMailer < ActionMailer::Base + def bar(str) + raise InlineError + end + end + + class InlineFooModel + def self.bar(str) + raise InlineError + end + end + + before do + Sidekiq::DelayExtensions.enable_delay! + end + + it 'stubs the delay call on mailers' do + assert_raises InlineError do + InlineFooMailer.delay.bar('three') + end + end + + it 'stubs the delay call on models' do + assert_raises InlineError do + InlineFooModel.delay.bar('three') + end + end + end + + it 'stubs the enqueue call when in testing mode' do + assert Sidekiq::Client.enqueue(InlineWorker, true) + + assert_raises InlineError do + Sidekiq::Client.enqueue(InlineWorker, false) + end + end + + it 'stubs the push_bulk call when in testing mode' do + assert Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [true]]}) + + assert_raises InlineError do + Sidekiq::Client.push_bulk({'class' => InlineWorker, 'args' => [[true], [false]]}) + end + end + + it 'should relay parameters through json' do + assert Sidekiq::Client.enqueue(InlineWorkerWithTimeParam, Time.now.to_f) + end + +end From a86614cbfc1f6b0cfa28da283b2608370a29e5c8 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 8 Feb 2022 14:25:59 -0600 Subject: [PATCH 3/3] Update docs --- .github/ISSUE_TEMPLATE/bug_report.md | 1 + Changes.md | 1864 +------------------------- README.md | 84 +- 3 files changed, 25 insertions(+), 1924 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 95c2601a..45c84df3 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,6 +15,7 @@ Please include your initializer, sidekiq.yml, and any error message with the ful If you are using an old version, have you checked the changelogs to see if your issue has been fixed in a later version? +https://github.com/gemhome/sidekiq-delay_extensions/blob/main/Changes.md https://github.com/mperham/sidekiq/blob/main/Changes.md https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md diff --git a/Changes.md b/Changes.md index 1891785b..9f9b1f8b 100644 --- a/Changes.md +++ b/Changes.md @@ -1,1864 +1,8 @@ -# Sidekiq Changes +# Sidekiq Delay Extensions Changes -[Sidekiq Changes](https://github.com/mperham/sidekiq/blob/main/Changes.md) | [Sidekiq Pro Changes](https://github.com/mperham/sidekiq/blob/main/Pro-Changes.md) | [Sidekiq Enterprise Changes](https://github.com/mperham/sidekiq/blob/main/Ent-Changes.md) +[Sidekiq Changes](https://github.com/gemhome/sidekiq-delay_extensions/blob/main/Changes.md) -HEAD +6.4.1 --------- -- Fix sidekiq.yml YAML load errors on Ruby 3.1 [#5141] -- Sharding support for `perform_bulk` [#5129] -- Refactor job logger for SPEEEEEEED - -6.4.0 ---------- - -- **SECURITY**: Validate input to avoid possible DoS in Web UI. -- Add **strict argument checking** [#5071] - Sidekiq will now log a warning if JSON-unsafe arguments are passed to `perform_async`. - Add `Sidekiq.strict_args!(false)` to your initializer to disable this warning. - This warning will switch to an exception in Sidekiq 7.0. -- Note that Delayed Extensions will be removed in Sidekiq 7.0 [#5076] -- Add `perform_{inline,sync}` in Sidekiq::Job to run a job synchronously [#5061, hasan-ally] -```ruby -SomeJob.perform_async(args...) -SomeJob.perform_sync(args...) -SomeJob.perform_inline(args...) -``` - You can also dynamically redirect a job to run synchronously: -```ruby -SomeJob.set("sync": true).perform_async(args...) # will run via perform_inline -``` -- Replace Sidekiq::Worker `app/workers` generator with Sidekiq::Job `app/sidekiq` generator [#5055] -``` -bin/rails generate sidekiq:job ProcessOrderJob -``` -- Fix job retries losing CurrentAttributes [#5090] -- Tweak shutdown to give long-running threads time to cleanup [#5095] - -6.3.1 ---------- - -- Fix keyword arguments error with CurrentAttributes on Ruby 3.0 [#5048] - -6.3.0 ---------- - -- **BREAK**: The Web UI has been refactored to remove jQuery. Any UI extensions - which use jQuery will break. -- **FEATURE**: Sidekiq.logger has been enhanced so any `Rails.logger` - output in jobs now shows up in the Sidekiq console. Remove any logger - hacks in your initializer and see if it Just Works™ now. [#5021] -- **FEATURE**: Add `Sidekiq::Job` alias for `Sidekiq::Worker`, to better - reflect industry standard terminology. You can now do this: -```ruby -class MyJob - include Sidekiq::Job - sidekiq_options ... - def perform(args) - end -end -``` -- **FEATURE**: Support for serializing ActiveSupport::CurrentAttributes into each job. [#4982] -```ruby -# config/initializers/sidekiq.rb -require "sidekiq/middleware/current_attributes" -Sidekiq::CurrentAttributes.persist(Myapp::Current) # Your AS::CurrentAttributes singleton -``` -- **FEATURE**: Add `Sidekiq::Worker.perform_bulk` for enqueuing jobs in bulk, - similar to `Sidekiq::Client.push_bulk` [#5042] -```ruby -MyJob.perform_bulk([[1], [2], [3]]) -``` -- Implement `queue_as`, `wait` and `wait_until` for ActiveJob compatibility [#5003] -- Scheduler now uses Lua to reduce Redis load and network roundtrips [#5044] -- Retry Redis operation if we get an `UNBLOCKED` Redis error [#4985] -- Run existing signal traps, if any, before running Sidekiq's trap [#4991] -- Fix fetch bug when using weighted queues which caused Sidekiq to stop - processing queues randomly [#5031] - -6.2.2 ---------- - -- Reduce retry jitter, add jitter to `sidekiq_retry_in` values [#4957] -- Minimize scheduler load on Redis at scale [#4882] -- Improve logging of delay jobs [#4904, BuonOno] -- Minor CSS improvements for buttons and tables, design PRs always welcome! -- Tweak Web UI `Cache-Control` header [#4966] -- Rename internal API class `Sidekiq::Job` to `Sidekiq::JobRecord` [#4955] - -6.2.1 ---------- - -- Update RTT warning logic to handle transient RTT spikes [#4851] -- Fix very low priority CVE on unescaped queue name [#4852] -- Add note about sessions and Rails apps in API mode - -6.2.0 ---------- - -- Store Redis RTT and log if poor [#4824] -- Add process/thread stats to Busy page [#4806] -- Improve Web UI on mobile devices [#4840] -- **Refactor Web UI session usage** [#4804] - Numerous people have hit "Forbidden" errors and struggled with Sidekiq's - Web UI session requirement. If you have code in your initializer for - Web sessions, it's quite possible it will need to be removed. Here's - an overview: -``` -Sidekiq::Web needs a valid Rack session for CSRF protection. If this is a Rails app, -make sure you mount Sidekiq::Web *inside* your routes in `config/routes.rb` so -Sidekiq can reuse the Rails session: - - Rails.application.routes.draw do - mount Sidekiq::Web => "/sidekiq" - .... - end - -If this is a bare Rack app, use a session middleware before Sidekiq::Web: - - # first, use IRB to create a shared secret key for sessions and commit it - require 'securerandom'; File.open(".session.key", "w") {|f| f.write(SecureRandom.hex(32)) } - - # now, update your Rack app to include the secret with a session cookie middleware - use Rack::Session::Cookie, secret: File.read(".session.key"), same_site: true, max_age: 86400 - run Sidekiq::Web - -If this is a Rails app in API mode, you need to enable sessions. - - https://guides.rubyonrails.org/api_app.html#using-session-middlewares -``` - -6.1.3 ---------- - -- Warn if Redis is configured to evict data under memory pressure [#4752] -- Add process RSS on the Busy page [#4717] - -6.1.2 ---------- - -- Improve readability in dark mode Web UI [#4674] -- Fix Web UI crash with corrupt session [#4672] -- Allow middleware to yield arguments [#4673, @eugeneius] -- Migrate CI from CircleCI to GitHub Actions [#4677] - -6.1.1 ---------- - -- Jobs are now sorted by age in the Busy Workers table. [#4641] -- Fix "check all" JS logic in Web UI [#4619] - -6.1.0 ---------- - -- Web UI - Dark Mode fixes [#4543, natematykiewicz] -- Ensure `Rack::ContentLength` is loaded as middleware for correct Web UI responses [#4541] -- Avoid exception dumping SSL store in Redis connection logging [#4532] -- Better error messages in Sidekiq::Client [#4549] -- Remove rack-protection, reimplement CSRF protection [#4588] -- Require redis-rb 4.2 [#4591] -- Update to jquery 1.12.4 [#4593] -- Refactor internal fetch logic and API [#4602] - -6.0.7 ---------- - -- Refactor systemd integration to work better with custom binaries [#4511] -- Don't connect to Redis at process exit if not needed [#4502] -- Remove Redis connection naming [#4479] -- Fix Redis Sentinel password redaction [#4499] -- Add Vietnamese locale (vi) [#4528] - -6.0.6 ---------- - -- **Integrate with systemd's watchdog and notification features** [#4488] - Set `Type=notify` in [sidekiq.service](https://github.com/mperham/sidekiq/blob/4b8a8bd3ae42f6e48ae1fdaf95ed7d7af18ed8bb/examples/systemd/sidekiq.service#L30-L39). The integration works automatically. -- Use `setTimeout` rather than `setInterval` to avoid thundering herd [#4480] -- Fix edge case where a job can be pushed without a queue. -- Flush job stats at exit [#4498] -- Check RAILS_ENV before RACK_ENV [#4493] -- Add Lithuanian locale [#4476] - -6.0.5 ---------- - -- Fix broken Web UI response when using NewRelic and Rack 2.1.2+. [#4440] -- Update APIs to use `UNLINK`, not `DEL`. [#4449] -- Fix Ruby 2.7 warnings [#4412] -- Add support for `APP_ENV` [[95fa5d9]](https://github.com/mperham/sidekiq/commit/95fa5d90192148026e52ca2902f1b83c70858ce8) - -6.0.4 ---------- - -- Fix ActiveJob's `sidekiq_options` integration [#4404] -- Sidekiq Pro users will now see a Pause button next to each queue in - the Web UI, allowing them to pause queues manually [#4374, shayonj] -- Fix Sidekiq::Workers API unintentional change in 6.0.2 [#4387] - - -6.0.3 ---------- - -- Fix `Sidekiq::Client.push_bulk` API which was erroneously putting - invalid `at` values in the job payloads [#4321] - -6.0.2 ---------- - -- Fix Sidekiq Enterprise's rolling restart functionality, broken by refactoring in 6.0.0. [#4334] -- More internal refactoring and performance tuning [fatkodima] - -6.0.1 ---------- - -- **Performance tuning**, Sidekiq should be 10-15% faster now [#4303, 4299, - 4269, fatkodima] -- **Dark Mode support in Web UI** (further design polish welcome!) [#4227, mperham, - fatkodima, silent-e] -- **Job-specific log levels**, allowing you to turn on debugging for - problematic workers. [fatkodima, #4287] -```ruby -MyWorker.set(log_level: :debug).perform_async(...) -``` -- **Ad-hoc job tags**. You can tag your jobs with, e.g, subdomain, tenant, country, - locale, application, version, user/client, "alpha/beta/pro/ent", types of jobs, - teams/people responsible for jobs, additional metadata, etc. - Tags are shown on different pages with job listings. Sidekiq Pro users - can filter based on them [fatkodima, #4280] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_options tags: ['bank-ops', 'alpha'] - ... -end -``` -- Fetch scheduled jobs in batches before pushing into specific queues. - This will decrease enqueueing time of scheduled jobs by a third. [fatkodima, #4273] -``` -ScheduledSet with 10,000 jobs -Before: 56.6 seconds -After: 39.2 seconds -``` -- Compress error backtraces before pushing into Redis, if you are - storing error backtraces, this will halve the size of your RetrySet - in Redis [fatkodima, #4272] -``` -RetrySet with 100,000 jobs -Before: 261 MB -After: 129 MB -``` -- Support display of ActiveJob 6.0 payloads in the Web UI [#4263] -- Add `SortedSet#scan` for pattern based scanning. For large sets this API will be **MUCH** faster - than standard iteration using each. [fatkodima, #4262] -```ruby - Sidekiq::DeadSet.new.scan("UnreliableApi") do |job| - job.retry - end -``` -- Dramatically speed up SortedSet#find\_job(jid) by using Redis's ZSCAN - support, approx 10x faster. [fatkodima, #4259] -``` -zscan 0.179366 0.047727 0.227093 ( 1.161376) -enum 8.522311 0.419826 8.942137 ( 9.785079) -``` -- Respect rails' generators `test_framework` option and gracefully handle extra `worker` suffix on generator [fatkodima, #4256] -- Add ability to sort 'Enqueued' page on Web UI by position in the queue [fatkodima, #4248] -- Support `Client.push_bulk` with different delays [fatkodima, #4243] -```ruby -Sidekiq::Client.push_bulk("class" => FooJob, "args" => [[1], [2]], "at" => [1.minute.from_now.to_f, 5.minutes.from_now.to_f]) -``` -- Easier way to test enqueuing specific ActionMailer and ActiveRecord delayed jobs. Instead of manually - parsing embedded class, you can now test by fetching jobs for specific classes. [fatkodima, #4292] -```ruby -assert_equal 1, Sidekiq::DelayExtensions::DelayedMailer.jobs_for(FooMailer).size -``` -- Add `sidekiqmon` to gemspec executables [#4242] -- Gracefully handle `Sidekiq.logger = nil` [#4240] -- Inject Sidekiq::LogContext module if user-supplied logger does not include it [#4239] - -6.0 ---------- - -This release has major breaking changes. Read and test carefully in production. - -- With Rails 6.0.2+, ActiveJobs can now use `sidekiq_options` directly to configure Sidekiq - features/internals like the retry subsystem. [#4213, pirj] -```ruby -class MyJob < ActiveJob::Base - queue_as :myqueue - sidekiq_options retry: 10, backtrace: 20 - def perform(...) - end -end -``` -- Logging has been redesigned to allow for pluggable log formatters: -```ruby -Sidekiq.configure_server do |config| - config.log_formatter = Sidekiq::Logger::Formatters::JSON.new -end -``` -See the [Logging wiki page](https://github.com/mperham/sidekiq/wiki/Logging) for more details. -- **BREAKING CHANGE** Validate proper usage of the `REDIS_PROVIDER` - variable. This variable is meant to hold the name of the environment - variable which contains your Redis URL, so that you can switch Redis - providers quickly and easily with a single variable change. It is not - meant to hold the actual Redis URL itself. If you want to manually set - the Redis URL (not recommended as it implies you have no failover), - then you may set `REDIS_URL` directly. [#3969] -- **BREAKING CHANGE** Increase default shutdown timeout from 8 seconds - to 25 seconds. Both Heroku and ECS now use 30 second shutdown timeout - by default and we want Sidekiq to take advantage of this time. If you - have deployment scripts which depend on the old default timeout, use `-t 8` to - get the old behavior. [#3968] -- **BREAKING CHANGE** Remove the daemonization, logfile and pidfile - arguments to Sidekiq. Use a proper process supervisor (e.g. systemd or - foreman) to manage Sidekiq. See the Deployment wiki page for links to - more resources. -- Integrate the StandardRB code formatter to ensure consistent code - styling. [#4114, gearnode] - -5.2.9 ---------- - -- Release Rack lock due to a cascade of CVEs. [#4566] - Pro-tip: don't lock Rack. - -5.2.8 ---------- - -- Lock to Rack 2.0.x to prevent future incompatibilities -- Fix invalid reference in `sidekiqctl` - -5.2.7 ---------- - -- Fix stale `enqueued_at` when retrying [#4149] -- Move build to [Circle CI](https://circleci.com/gh/mperham/sidekiq) [#4120] - -5.2.6 ---------- - -- Fix edge case where a job failure during Redis outage could result in a lost job [#4141] -- Better handling of malformed job arguments in payload [#4095] -- Restore bootstap's dropdown css component [#4099, urkle] -- Display human-friendly time diff for longer queue latencies [#4111, interlinked] -- Allow `Sidekiq::Worker#set` to be chained - -5.2.5 ---------- - -- Fix default usage of `config/sidekiq.yml` [#4077, Tensho] - -5.2.4 ---------- - -- Add warnings for various deprecations and changes coming in Sidekiq 6.0. - See the 6-0 branch. [#4056] -- Various improvements to the Sidekiq test suite and coverage [#4026, #4039, Tensho] - -5.2.3 ---------- - -- Warning message on invalid REDIS\_PROVIDER [#3970] -- Add `sidekiqctl status` command [#4003, dzunk] -- Update elapsed time calculatons to use monotonic clock [#3999] -- Fix a few issues with mobile Web UI styling [#3973, navied] -- Jobs with `retry: false` now go through the global `death_handlers`, - meaning you can take action on failed ephemeral jobs. [#3980, Benjamin-Dobell] -- Fix race condition in defining Workers. [#3997, mattbooks] - -5.2.2 ---------- - -- Raise error for duplicate queue names in config to avoid unexpected fetch algorithm change [#3911] -- Fix concurrency bug on JRuby [#3958, mattbooks] -- Add "Kill All" button to the retries page [#3938] - -5.2.1 ------------ - -- Fix concurrent modification error during heartbeat [#3921] - -5.2.0 ------------ - -- **Decrease default concurrency from 25 to 10** [#3892] -- Verify connection pool sizing upon startup [#3917] -- Smoother scheduling for large Sidekiq clusters [#3889] -- Switch Sidekiq::Testing impl from alias\_method to Module#prepend, for resiliency [#3852] -- Update Sidekiq APIs to use SCAN for scalability [#3848, ffiller] -- Remove concurrent-ruby gem dependency [#3830] -- Optimize Web UI's bootstrap.css [#3914] - -5.1.3 ------------ - -- Fix version comparison so Ruby 2.2.10 works. [#3808, nateberkopec] - -5.1.2 ------------ - -- Add link to docs in Web UI footer -- Fix crash on Ctrl-C in Windows [#3775, Bernica] -- Remove `freeze` calls on String constants. This is superfluous with Ruby - 2.3+ and `frozen_string_literal: true`. [#3759] -- Fix use of AR middleware outside of Rails [#3787] -- Sidekiq::Worker `sidekiq_retry_in` block can now return nil or 0 to use - the default backoff delay [#3796, dsalahutdinov] - -5.1.1 ------------ - -- Fix Web UI incompatibility with Redis 3.x gem [#3749] - -5.1.0 ------------ - -- **NEW** Global death handlers - called when your job exhausts all - retries and dies. Now you can take action when a job fails permanently. [#3721] -- **NEW** Enable ActiveRecord query cache within jobs by default [#3718, sobrinho] - This will prevent duplicate SELECTS; cache is cleared upon any UPDATE/INSERT/DELETE. - See the issue for how to bypass the cache or disable it completely. -- Scheduler timing is now more accurate, 15 -> 5 seconds [#3734] -- Exceptions during the :startup event will now kill the process [#3717] -- Make `Sidekiq::Client.via` reentrant [#3715] -- Fix use of Sidekiq logger outside of the server process [#3714] -- Tweak `constantize` to better match Rails class lookup. [#3701, caffeinated-tech] - -5.0.5 ------------ - -- Update gemspec to allow newer versions of the Redis gem [#3617] -- Refactor Worker.set so it can be memoized [#3602] -- Fix display of Redis URL in web footer, broken in 5.0.3 [#3560] -- Update `Sidekiq::Job#display_args` to avoid mutation [#3621] - -5.0.4 ------------ - -- Fix "slow startup" performance regression from 5.0.2. [#3525] -- Allow users to disable ID generation since some redis providers disable the CLIENT command. [#3521] - -5.0.3 ------------ - -- Fix overriding `class_attribute` core extension from ActiveSupport with Sidekiq one [PikachuEXE, #3499] -- Allow job logger to be overridden [AlfonsoUceda, #3502] -- Set a default Redis client identifier for debugging [#3516] -- Fix "Uninitialized constant" errors on startup with the delayed extensions [#3509] - -5.0.2 ------------ - -- fix broken release, thanks @nateberkopec - -5.0.1 ------------ - -- Fix incorrect server identity when daemonizing [jwilm, #3496] -- Work around error running Web UI against Redis Cluster [#3492] -- Remove core extensions, Sidekiq is now monkeypatch-free! [#3474] -- Reimplement Web UI's HTTP\_ACCEPT\_LANGUAGE parsing because the spec is utterly - incomprehensible for various edge cases. [johanlunds, natematykiewicz, #3449] -- Update `class_attribute` core extension to avoid warnings -- Expose `job_hash_context` from `Sidekiq::Logging` to support log customization - -5.0.0 ------------ - -- **BREAKING CHANGE** Job dispatch was refactored for safer integration with - Rails 5. The **Logging** and **RetryJobs** server middleware were removed and - functionality integrated directly into Sidekiq::Processor. These aren't - commonly used public APIs so this shouldn't impact most users. -``` -Sidekiq::Middleware::Server::RetryJobs -> Sidekiq::JobRetry -Sidekiq::Middleware::Server::Logging -> Sidekiq::JobLogger -``` -- Quieting Sidekiq is now done via the TSTP signal, the USR1 signal is deprecated. -- The `delay` extension APIs are no longer available by default, you - must opt into them. -- The Web UI is now BiDi and can render RTL languages like Arabic, Farsi and Hebrew. -- Rails 3.2 and Ruby 2.0 and 2.1 are no longer supported. -- The `SomeWorker.set(options)` API was re-written to avoid thread-local state. [#2152] -- Sidekiq Enterprise's encrypted jobs now display "[encrypted data]" in the Web UI instead - of random hex bytes. -- Please see the [5.0 Upgrade notes](5.0-Upgrade.md) for more detail. - -4.2.10 ------------ - -- Scheduled jobs can now be moved directly to the Dead queue via API [#3390] -- Fix edge case leading to job duplication when using Sidekiq Pro's - reliability feature [#3388] -- Fix error class name display on retry page [#3348] -- More robust latency calculation [#3340] - -4.2.9 ------------ - -- Rollback [#3303] which broke Heroku Redis users [#3311] -- Add support for TSTP signal, for Sidekiq 5.0 forward compatibility. [#3302] - -4.2.8 ------------ - -- Fix rare edge case with Redis driver that can create duplicate jobs [#3303] -- Fix Rails 5 loading issue [#3275] -- Restore missing tooltips to timestamps in Web UI [#3310] -- Work on **Sidekiq 5.0** is now active! [#3301] - -4.2.7 ------------ - -- Add new integration testing to verify code loading and job execution - in development and production modes with Rails 4 and 5 [#3241] -- Fix delayed extensions in development mode [#3227, DarthSim] -- Use Worker's `retry` default if job payload does not have a retry - attribute [#3234, mlarraz] - -4.2.6 ------------ - -- Run Rails Executor when in production [#3221, eugeneius] - -4.2.5 ------------ - -- Re-enable eager loading of all code when running non-development Rails 5. [#3203] -- Better root URL handling for zany web servers [#3207] - -4.2.4 ------------ - -- Log errors coming from the Rails 5 reloader. [#3212, eugeneius] -- Clone job data so middleware changes don't appear in Busy tab - -4.2.3 ------------ - -- Disable use of Rails 5's Reloader API in non-development modes, it has proven - to be unstable under load [#3154] -- Allow disabling of Sidekiq::Web's cookie session to handle the - case where the app provides a session already [#3180, inkstak] -```ruby -Sidekiq::Web.set :sessions, false -``` -- Fix Web UI sharding support broken in 4.2.2. [#3169] -- Fix timestamps not updating during UI polling [#3193, shaneog] -- Relax rack-protection version to >= 1.5.0 -- Provide consistent interface to exception handlers, changing the structure of the context hash. [#3161] - -4.2.2 ------------ - -- Fix ever-increasing cookie size with nginx [#3146, cconstantine] -- Fix so Web UI works without trailing slash [#3158, timdorr] - -4.2.1 ------------ - -- Ensure browser does not cache JSON/AJAX responses. [#3136] -- Support old Sinatra syntax for setting config [#3139] - -4.2.0 ------------ - -- Enable development-mode code reloading. **With Rails 5.0+, you don't need - to restart Sidekiq to pick up your Sidekiq::Worker changes anymore!** [#2457] -- **Remove Sinatra dependency**. Sidekiq's Web UI now uses Rack directly. - Thank you to Sidekiq's newest committer, **badosu**, for writing the code - and doing a lot of testing to ensure compatibility with many different - 3rd party plugins. If your Web UI works with 4.1.4 but fails with - 4.2.0, please open an issue. [#3075] -- Allow tuning of concurrency with the `RAILS_MAX_THREADS` env var. [#2985] - This is the same var used by Puma so you can tune all of your systems - the same way: -```sh -web: RAILS_MAX_THREADS=5 bundle exec puma ... -worker: RAILS_MAX_THREADS=10 bundle exec sidekiq ... -``` -Using `-c` or `config/sidekiq.yml` overrides this setting. I recommend -adjusting your `config/database.yml` to use it too so connections are -auto-scaled: -```yaml - pool: <%= ENV['RAILS_MAX_THREADS'] || 5 %> -``` - -4.1.4 ------------ - -- Unlock Sinatra so a Rails 5.0 compatible version may be used [#3048] -- Fix race condition on startup with JRuby [#3043] - - -4.1.3 ------------ - -- Please note the Redis 3.3.0 gem has a [memory leak](https://github.com/redis/redis-rb/issues/612), - Redis 3.2.2 is recommended until that issue is fixed. -- Sinatra 1.4.x is now a required dependency, avoiding cryptic errors - and old bugs due to people not upgrading Sinatra for years. [#3042] -- Fixed race condition in heartbeat which could rarely lead to lingering - processes on the Busy tab. [#2982] -```ruby -# To clean up lingering processes, modify this as necessary to connect to your Redis. -# After 60 seconds, lingering processes should disappear from the Busy page. - -require 'redis' -r = Redis.new(url: "redis://localhost:6379/0") -# uncomment if you need a namespace -#require 'redis-namespace' -#r = Redis::Namespace.new("foo", r) -r.smembers("processes").each do |pro| - r.expire(pro, 60) - r.expire("#{pro}:workers", 60) -end -``` - - -4.1.2 ------------ - -- Fix Redis data leak with worker data when a busy Sidekiq process - crashes. You can find and expire leaked data in Redis with this -script: -```bash -$ redis-cli keys "*:workers" | while read LINE ; do TTL=`redis-cli expire "$LINE" 60`; echo "$LINE"; done; -``` - Please note that `keys` can be dangerous to run on a large, busy Redis. Caveat runner. -- Freeze all string literals with Ruby 2.3. [#2741] -- Client middleware can now stop bulk job push. [#2887] - -4.1.1 ------------ - -- Much better behavior when Redis disappears and comes back. [#2866] -- Update FR locale [dbachet] -- Don't fill logfile in case of Redis downtime [#2860] -- Allow definition of a global retries_exhausted handler. [#2807] -```ruby -Sidekiq.configure_server do |config| - config.default_retries_exhausted = -> (job, ex) do - Sidekiq.logger.info "#{job['class']} job is now dead" - end -end -``` - -4.1.0 ------------ - -- Tag quiet processes in the Web UI [#2757, jcarlson] -- Pass last exception to sidekiq\_retries\_exhausted block [#2787, Nowaker] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retries_exhausted do |job, exception| - end -end -``` -- Add native support for ActiveJob's `set(options)` method allowing -you to override worker options dynamically. This should make it -even easier to switch between ActiveJob and Sidekiq's native APIs [#2780] -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_options queue: 'default', retry: true - - def perform(*args) - # do something - end -end - -MyWorker.set(queue: 'high', retry: false).perform_async(1) -``` - -4.0.2 ------------ - -- Better Japanese translations -- Remove `json` gem dependency from gemspec. [#2743] -- There's a new testing API based off the `Sidekiq::Queues` namespace. All - assertions made against the Worker class still work as expected. - [#2676, brandonhilkert] -```ruby -assert_equal 0, Sidekiq::Queues["default"].size -HardWorker.perform_async("log") -assert_equal 1, Sidekiq::Queues["default"].size -assert_equal "log", Sidekiq::Queues["default"].first['args'][0] -Sidekiq::Queues.clear_all -``` - -4.0.1 ------------ - -- Yank new queue-based testing API [#2663] -- Fix invalid constant reference in heartbeat - -4.0.0 ------------ - -- Sidekiq's internals have been completely overhauled for performance - and to remove dependencies. This has resulted in major speedups, as - [detailed on my blog](http://www.mikeperham.com/2015/10/14/optimizing-sidekiq/). -- See the [4.0 upgrade notes](4.0-Upgrade.md) for more detail. - -3.5.4 ------------ - -- Ensure exception message is a string [#2707] -- Revert racy Process.kill usage in sidekiqctl - -3.5.3 ------------ - -- Adjust shutdown event to run in parallel with the rest of system shutdown. [#2635] - -3.5.2 ------------ - -- **Sidekiq 3 is now in maintenance mode**, only major bugs will be fixed. -- The exception triggering a retry is now passed into `sidekiq_retry_in`, - allowing you to retry more frequently for certain types of errors. - [#2619, kreynolds] -```ruby - sidekiq_retry_in do |count, ex| - case ex - when RuntimeError - 5 * count - else - 10 * count - end - end -``` - -3.5.1 ------------ - -- **FIX MEMORY LEAK** Under rare conditions, threads may leak [#2598, gazay] -- Add Ukrainian locale [#2561, elrakita] -- Disconnect and retry Redis operations if we see a READONLY error [#2550] -- Add server middleware testing harness; see [wiki](https://github.com/mperham/sidekiq/wiki/Testing#testing-server-middleware) [#2534, ryansch] - -3.5.0 ------------ - -- Polished new banner! [#2522, firedev] -- Upgrade to Celluloid 0.17. [#2420, digitalextremist] -- Activate sessions in Sinatra for CSRF protection, requires Rails - monkeypatch due to rails/rails#15843. [#2460, jc00ke] - -3.4.2 ------------ - -- Don't allow `Sidekiq::Worker` in ActiveJob::Base classes. [#2424] -- Safer display of job data in Web UI [#2405] -- Fix CSRF vulnerability in Web UI, thanks to Egor Homakov for - reporting. [#2422] If you are running the Web UI as a standalone Rack app, - ensure you have a [session middleware -configured](https://github.com/mperham/sidekiq/wiki/Monitoring#standalone): -```ruby -use Rack::Session::Cookie, :secret => "some unique secret string here" -``` - -3.4.1 ------------ - -- Lock to Celluloid 0.16 - - -3.4.0 ------------ - -- Set a `created_at` attribute when jobs are created, set `enqueued_at` only - when they go into a queue. Fixes invalid latency calculations with scheduled jobs. - [#2373, mrsimo] -- Don't log timestamp on Heroku [#2343] -- Run `shutdown` event handlers in reverse order of definition [#2374] -- Rename and rework `poll_interval` to be simpler, more predictable [#2317, cainlevy] - The new setting is `average_scheduled_poll_interval`. To configure - Sidekiq to look for scheduled jobs every 5 seconds, just set it to 5. -```ruby -Sidekiq.configure_server do |config| - config.average_scheduled_poll_interval = 5 -end -``` - -3.3.4 ------------ - -- **Improved ActiveJob integration** - Web UI now shows ActiveJobs in a - nicer format and job logging shows the actual class name, requires - Rails 4.2.2+ [#2248, #2259] -- Add Sidekiq::Process#dump\_threads API to trigger TTIN output [#2247] -- Web UI polling now uses Ajax to avoid page reload [#2266, davydovanton] -- Several Web UI styling improvements [davydovanton] -- Add Tamil, Hindi translations for Web UI [ferdinandrosario, tejasbubane] -- Fix Web UI to work with country-specific locales [#2243] -- Handle circular error causes [#2285, eugenk] - -3.3.3 ------------ - -- Fix crash on exit when Redis is down [#2235] -- Fix duplicate logging on startup -- Undeprecate delay extension for ActionMailer 4.2+ . [#2186] - -3.3.2 ------------ - -- Add Sidekiq::Stats#queues back -- Allows configuration of dead job set size and timeout [#2173, jonhyman] -- Refactor scheduler enqueuing so Sidekiq Pro can override it. [#2159] - -3.3.1 ------------ - -- Dumb down ActionMailer integration so it tries to deliver if possible [#2149] -- Stringify Sidekiq.default\_worker\_options's keys [#2126] -- Add random integer to process identity [#2113, michaeldiscala] -- Log Sidekiq Pro's Batch ID if available [#2076] -- Refactor Processor Redis usage to avoid redis/redis-rb#490 [#2094] -- Move /dashboard/stats to /stats. Add /stats/queues. [moserke, #2099] -- Add processes count to /stats [ismaelga, #2141] -- Greatly improve speed of Sidekiq::Stats [ismaelga, #2142] -- Add better usage text for `sidekiqctl`. -- `Sidekiq::Logging.with_context` is now a stack so you can set your - own job context for logging purposes [grosser, #2110] -- Remove usage of Google Fonts in Web UI so it loads in China [#2144] - -3.3.0 ------------ - -- Upgrade to Celluloid 0.16 [#2056] -- Fix typo for generator test file name [dlackty, #2016] -- Add Sidekiq::Middleware::Chain#prepend [seuros, #2029] - -3.2.6 ------------ - -- Deprecate delay extension for ActionMailer 4.2+ . [seuros, #1933] -- Poll interval tuning now accounts for dead processes [epchris, #1984] -- Add non-production environment to Web UI page titles [JacobEvelyn, #2004] - -3.2.5 ------------ - -- Lock Celluloid to 0.15.2 due to bugs in 0.16.0. This prevents the - "hang on shutdown" problem with Celluloid 0.16.0. - -3.2.4 ------------ - -- Fix issue preventing ActionMailer sends working in some cases with - Rails 4. [pbhogan, #1923] - -3.2.3 ------------ - -- Clean invalid bytes from error message before converting to JSON (requires Ruby 2.1+) [#1705] -- Add queues list for each process to the Busy page. [davetoxa, #1897] -- Fix for crash caused by empty config file. [jordan0day, #1901] -- Add Rails Worker generator, `rails g sidekiq:worker User` will create `app/workers/user_worker.rb`. [seuros, #1909] -- Fix Web UI rendering with huge job arguments [jhass, #1918] -- Minor refactoring of Sidekiq::Client internals, for Sidekiq Pro. [#1919] - -3.2.2 ------------ - -- **This version of Sidekiq will no longer start on Ruby 1.9.** Sidekiq - 3 does not support MRI 1.9 but we've allowed it to run before now. -- Fix issue which could cause Sidekiq workers to disappear from the Busy - tab while still being active [#1884] -- Add "Back to App" button in Web UI. You can set the button link via - `Sidekiq::Web.app_url = 'http://www.mysite.com'` [#1875, seuros] -- Add process tag (`-g tag`) to the Busy page so you can differentiate processes at a glance. [seuros, #1878] -- Add "Kill" button to move retries directly to the DJQ so they don't retry. [seuros, #1867] - -3.2.1 ------------ - -- Revert eager loading change for Rails 3.x apps, as it broke a few edge - cases. - -3.2.0 ------------ - -- **Fix issue which caused duplicate job execution in Rails 3.x** - This issue is caused by [improper exception handling in ActiveRecord](https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb#L281) which changes Sidekiq's Shutdown exception into a database - error, making Sidekiq think the job needs to be retried. **The fix requires Ruby 2.1**. [#1805] -- Update how Sidekiq eager loads Rails application code [#1791, jonleighton] -- Change logging timestamp to show milliseconds. -- Reverse sorting of Dead tab so newer jobs are listed first [#1802] - -3.1.4 ------------ - -- Happy π release! -- Self-tuning Scheduler polling, we use heartbeat info to better tune poll\_interval [#1630] -- Remove all table column width rules, hopefully get better column formatting [#1747] -- Handle edge case where YAML can't be decoded in dev mode [#1761] -- Fix lingering jobs in Busy page on Heroku [#1764] - -3.1.3 ------------ - -- Use ENV['DYNO'] on Heroku for hostname display, rather than an ugly UUID. [#1742] -- Show per-process labels on the Busy page, for feature tagging [#1673] - - -3.1.2 ------------ - -- Suitably chastised, @mperham reverts the Bundler change. - - -3.1.1 ------------ - -- Sidekiq::CLI now runs `Bundler.require(:default, environment)` to boot all gems - before loading any app code. -- Sort queues by name in Web UI [#1734] - - -3.1.0 ------------ - -- New **remote control** feature: you can remotely trigger Sidekiq to quiet - or terminate via API, without signals. This is most useful on JRuby - or Heroku which does not support the USR1 'quiet' signal. Now you can - run a rake task like this at the start of your deploy to quiet your - set of Sidekiq processes. [#1703] -```ruby -namespace :sidekiq do - task :quiet => :environment do - Sidekiq::ProcessSet.new.each(&:quiet!) - end -end -``` -- The Web UI can use the API to quiet or stop all processes via the Busy page. -- The Web UI understands and hides the `Sidekiq::DelayExtensions::Delay*` - classes, instead showing `Class.method` as the Job. [#1718] -- Polish the Dashboard graphs a bit, update Rickshaw [brandonhilkert, #1725] -- The poll interval is now configurable in the Web UI [madebydna, #1713] -- Delay extensions can be removed so they don't conflict with - DelayedJob: put `Sidekiq.remove_delay!` in your initializer. [devaroop, #1674] - - -3.0.2 ------------ - -- Revert gemfile requirement of Ruby 2.0. JRuby 1.7 calls itself Ruby - 1.9.3 and broke with this requirement. - -3.0.1 ------------ - -- Revert pidfile behavior from 2.17.5: Sidekiq will no longer remove its own pidfile - as this is a race condition when restarting. [#1470, #1677] -- Show warning on the Queues page if a queue is paused [#1672] -- Only activate the ActiveRecord middleware if ActiveRecord::Base is defined on boot. [#1666] -- Add ability to disable jobs going to the DJQ with the `dead` option. -```ruby -sidekiq_options :dead => false, :retry => 5 -``` -- Minor fixes - - -3.0.0 ------------ - -Please see [3.0-Upgrade.md](3.0-Upgrade.md) for more comprehensive upgrade notes. - -- **Dead Job Queue** - jobs which run out of retries are now moved to a dead - job queue. These jobs must be retried manually or they will expire - after 6 months or 10,000 jobs. The Web UI contains a "Dead" tab - exposing these jobs. Use `sidekiq_options :retry => false` if you -don't wish jobs to be retried or put in the DJQ. Use -`sidekiq_options :retry => 0` if you don't want jobs to retry but go -straight to the DJQ. -- **Process Lifecycle Events** - you can now register blocks to run at - certain points during the Sidekiq process lifecycle: startup, quiet and - shutdown. -```ruby -Sidekiq.configure_server do |config| - config.on(:startup) do - # do something - end -end -``` -- **Global Error Handlers** - blocks of code which handle errors that - occur anywhere within Sidekiq, not just within middleware. -```ruby -Sidekiq.configure_server do |config| - config.error_handlers << proc {|ex,ctx| ... } -end -``` -- **Process Heartbeat** - each Sidekiq process will ping Redis every 5 - seconds to give a summary of the Sidekiq population at work. -- The Workers tab is now renamed to Busy and contains a list of live - Sidekiq processes and jobs in progress based on the heartbeat. -- **Shardable Client** - Sidekiq::Client instances can use a custom - Redis connection pool, allowing very large Sidekiq installations to scale by - sharding: sending different jobs to different Redis instances. -```ruby -client = Sidekiq::Client.new(ConnectionPool.new { Redis.new }) -client.push(...) -``` -```ruby -Sidekiq::Client.via(ConnectionPool.new { Redis.new }) do - FooWorker.perform_async - BarWorker.perform_async -end -``` - **Sharding support does require a breaking change to client-side -middleware, see 3.0-Upgrade.md.** -- New Chinese, Greek, Swedish and Czech translations for the Web UI. -- Updated most languages translations for the new UI features. -- **Remove official Capistrano integration** - this integration has been - moved into the [capistrano-sidekiq](https://github.com/seuros/capistrano-sidekiq) gem. -- **Remove official support for MRI 1.9** - Things still might work but - I no longer actively test on it. -- **Remove built-in support for Redis-to-Go**. - Heroku users: `heroku config:set REDIS_PROVIDER=REDISTOGO_URL` -- **Remove built-in error integration for Airbrake, Honeybadger, ExceptionNotifier and Exceptional**. - Each error gem should provide its own Sidekiq integration. Update your error gem to the latest - version to pick up Sidekiq support. -- Upgrade to connection\_pool 2.0 which now creates connections lazily. -- Remove deprecated Sidekiq::Client.registered\_\* APIs -- Remove deprecated support for the old Sidekiq::Worker#retries\_exhausted method. -- Removed 'sidekiq/yaml\_patch', this was never documented or recommended. -- Removed --profile option, #1592 -- Remove usage of the term 'Worker' in the UI for clarity. Users would call both threads and - processes 'workers'. Instead, use "Thread", "Process" or "Job". - -2.17.7 ------------ - -- Auto-prune jobs older than one hour from the Workers page [#1508] -- Add Sidekiq::Workers#prune which can perform the auto-pruning. -- Fix issue where a job could be lost when an exception occurs updating - Redis stats before the job executes [#1511] - -2.17.6 ------------ - -- Fix capistrano integration due to missing pidfile. [#1490] - -2.17.5 ------------ - -- Automatically use the config file found at `config/sidekiq.yml`, if not passed `-C`. [#1481] -- Store 'retried\_at' and 'failed\_at' timestamps as Floats, not Strings. [#1473] -- A `USR2` signal will now reopen _all_ logs, using IO#reopen. Thus, instead of creating a new Logger object, - Sidekiq will now just update the existing Logger's file descriptor [#1163]. -- Remove pidfile when shutting down if started with `-P` [#1470] - -2.17.4 ------------ - -- Fix JID support in inline testing, #1454 -- Polish worker arguments display in UI, #1453 -- Marshal arguments fully to avoid worker mutation, #1452 -- Support reverse paging sorted sets, #1098 - - -2.17.3 ------------ - -- Synchronously terminates the poller and fetcher to fix a race condition in bulk requeue during shutdown [#1406] - -2.17.2 ------------ - -- Fix bug where strictly prioritized queues might be processed out of - order [#1408]. A side effect of this change is that it breaks a queue - declaration syntax that worked, although only because of a bug—it was - never intended to work and never supported. If you were declaring your - queues as a comma-separated list, e.g. `sidekiq -q critical,default,low`, - you must now use the `-q` flag before each queue, e.g. - `sidekiq -q critical -q default -q low`. - -2.17.1 ------------ - -- Expose `delay` extension as `sidekiq_delay` also. This allows you to - run Delayed::Job and Sidekiq in the same process, selectively porting - `delay` calls to `sidekiq_delay`. You just need to ensure that - Sidekiq is required **before** Delayed::Job in your Gemfile. [#1393] -- Bump redis client required version to 3.0.6 -- Minor CSS fixes for Web UI - -2.17.0 ------------ - -- Change `Sidekiq::Client#push_bulk` to return an array of pushed `jid`s. [#1315, barelyknown] -- Web UI refactoring to use more API internally (yummy dogfood!) -- Much faster Sidekiq::Job#delete performance for larger queue sizes -- Further capistrano 3 fixes -- Many misc minor fixes - -2.16.1 ------------ - -- Revert usage of `resolv-replace`. MRI's native DNS lookup releases the GIL. -- Fix several Capistrano 3 issues -- Escaping dynamic data like job args and error messages in Sidekiq Web UI. [#1299, lian] - -2.16.0 ------------ - -- Deprecate `Sidekiq::Client.registered_workers` and `Sidekiq::Client.registered_queues` -- Refactor Sidekiq::Client to be instance-based [#1279] -- Pass all Redis options to the Redis driver so Unix sockets - can be fully configured. [#1270, salimane] -- Allow sidekiq-web extensions to add locale paths so extensions - can be localized. [#1261, ondrejbartas] -- Capistrano 3 support [#1254, phallstrom] -- Use Ruby's `resolv-replace` to enable pure Ruby DNS lookups. - This ensures that any DNS resolution that takes place in worker - threads won't lock up the entire VM on MRI. [#1258] - -2.15.2 ------------ - -- Iterating over Sidekiq::Queue and Sidekiq::SortedSet will now work as - intended when jobs are deleted [#866, aackerman] -- A few more minor Web UI fixes [#1247] - -2.15.1 ------------ - -- Fix several Web UI issues with the Bootstrap 3 upgrade. - -2.15.0 ------------ - -- The Core Sidekiq actors are now monitored. If any crash, the - Sidekiq process logs the error and exits immediately. This is to - help prevent "stuck" Sidekiq processes which are running but don't - appear to be doing any work. [#1194] -- Sidekiq's testing behavior is now dynamic. You can choose between - `inline` and `fake` behavior in your tests. See -[Testing](https://github.com/mperham/sidekiq/wiki/Testing) for detail. [#1193] -- The Retries table has a new column for the error message. -- The Web UI topbar now contains the status and live poll button. -- Orphaned worker records are now auto-vacuumed when you visit the - Workers page in the Web UI. -- Sidekiq.default\_worker\_options allows you to configure default - options for all Sidekiq worker types. - -```ruby -Sidekiq.default_worker_options = { 'queue' => 'default', 'backtrace' => true } -``` -- Added two Sidekiq::Client class methods for compatibility with resque-scheduler: - `enqueue_to_in` and `enqueue_in` [#1212] -- Upgrade Web UI to Bootstrap 3.0. [#1211, jeffboek] - -2.14.1 ------------ - -- Fix misc Web UI issues due to ERB conversion. -- Bump redis-namespace version due to security issue. - -2.14.0 ------------ - -- Removed slim gem dependency, Web UI now uses ERB [Locke23rus, #1120] -- Fix more race conditions in Web UI actions -- Don't reset Job enqueued\_at when retrying -- Timestamp tooltips in the Web UI should use UTC -- Fix invalid usage of handle\_exception causing issues in Airbrake - [#1134] - - -2.13.1 ------------ - -- Make Sidekiq::Middleware::Chain Enumerable -- Make summary bar and graphs responsive [manishval, #1025] -- Adds a job status page for scheduled jobs [jonhyman] -- Handle race condition in retrying and deleting jobs in the Web UI -- The Web UI relative times are now i18n. [MadRabbit, #1088] -- Allow for default number of retry attempts to be set for - `Sidekiq::Middleware::Server::RetryJobs` middleware. [czarneckid] [#1091] - -```ruby -Sidekiq.configure_server do |config| - config.server_middleware do |chain| - chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 10 - end -end -``` - - -2.13.0 ------------ - -- Adding button to move scheduled job to main queue [guiceolin, #1020] -- fix i18n support resetting saved locale when job is retried [#1011] -- log rotation via USR2 now closes the old logger [#1008] -- Add ability to customize retry schedule, like so [jmazzi, #1027] - -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retry_in { |count| count * 2 } -end -``` -- Redesign Worker#retries\_exhausted callback to use same form as above [jmazzi, #1030] - -```ruby -class MyWorker - include Sidekiq::Worker - sidekiq_retries_exhausted do |msg| - Rails.logger.error "Failed to process #{msg['class']} with args: #{msg['args']}" - end -end -``` - -2.12.4 ------------ - -- Fix error in previous release which crashed the Manager when a - Processor died. - -2.12.3 ------------ - -- Revert back to Celluloid's TaskFiber for job processing which has proven to be more - stable than TaskThread. [#985] -- Avoid possible lockup during hard shutdown [#997] - -At this point, if you are experiencing stability issues with Sidekiq in -Ruby 1.9, please try Ruby 2.0. It seems to be more stable. - -2.12.2 ------------ - -- Relax slim version requirement to >= 1.1.0 -- Refactor historical stats to use TTL, not explicit cleanup. [grosser, #971] - -2.12.1 ------------ - -- Force Celluloid 0.14.1 as 0.14.0 has a serious bug. [#954] -- Scheduled and Retry jobs now use Sidekiq::Client to push - jobs onto the queue, so they use client middleware. [dimko, #948] -- Record the timestamp when jobs are enqueued. Add - Sidekiq::Job#enqueued\_at to query the time. [mariovisic, #944] -- Add Sidekiq::Queue#latency - calculates diff between now and - enqueued\_at for the oldest job in the queue. -- Add testing method `perform_one` that dequeues and performs a single job. - This is mainly to aid testing jobs that spawn other jobs. [fumin, #963] - -2.12.0 ------------ - -- Upgrade to Celluloid 0.14, remove the use of Celluloid's thread - pool. This should halve the number of threads in each Sidekiq - process, thus requiring less resources. [#919] -- Abstract Celluloid usage to Sidekiq::Actor for testing purposes. -- Better handling for Redis downtime when fetching jobs and shutting - down, don't print exceptions every second and print success message - when Redis is back. -- Fix unclean shutdown leading to duplicate jobs [#897] -- Add Korean locale [#890] -- Upgrade test suite to Minitest 5 -- Remove usage of `multi_json` as `json` is now robust on all platforms. - -2.11.2 ------------ - -- Fix Web UI when used without Rails [#886] -- Add Sidekiq::Stats#reset [#349] -- Add Norwegian locale. -- Updates for the JA locale. - -2.11.1 ------------ - -- Fix timeout warning. -- Add Dutch web UI locale. - -2.11.0 ------------ - -- Upgrade to Celluloid 0.13. [#834] -- Remove **timeout** support from `sidekiq_options`. Ruby's timeout - is inherently unsafe in a multi-threaded application and was causing - stability problems for many. See http://bit.ly/OtYpK -- Add Japanese locale for Web UI [#868] -- Fix a few issues with Web UI i18n. - -2.10.1 ------------ - -- Remove need for the i18n gem. (brandonhilkert) -- Improve redis connection info logging on startup for debugging -purposes [#858] -- Revert sinatra/slim as runtime dependencies -- Add `find_job` method to sidekiq/api - - -2.10.0 ------------ - -- Refactor algorithm for putting scheduled jobs onto the queue [#843] -- Fix scheduler thread dying due to incorrect error handling [#839] -- Fix issue which left stale workers if Sidekiq wasn't shutdown while -quiet. [#840] -- I18n for web UI. Please submit translations of `web/locales/en.yml` for -your own language. [#811] -- 'sinatra', 'slim' and 'i18n' are now gem dependencies for Sidekiq. - - -2.9.0 ------------ - -- Update 'sidekiq/testing' to work with any Sidekiq::Client call. It - also serializes the arguments as using Redis would. [#713] -- Raise a Sidekiq::Shutdown error within workers which don't finish within the hard - timeout. This is to prevent unwanted database transaction commits. [#377] -- Lazy load Redis connection pool, you no longer need to specify - anything in Passenger or Unicorn's after_fork callback [#794] -- Add optional Worker#retries_exhausted hook after max retries failed. [jkassemi, #780] -- Fix bug in pagination link to last page [pitr, #774] -- Upstart scripts for multiple Sidekiq instances [dariocravero, #763] -- Use select via pipes instead of poll to catch signals [mrnugget, #761] - -2.8.0 ------------ - -- I18n support! Sidekiq can optionally save and restore the Rails locale - so it will be properly set when your jobs execute. Just include - `require 'sidekiq/middleware/i18n'` in your sidekiq initializer. [#750] -- Fix bug which could lose messages when using namespaces and the message -needs to be requeued in Redis. [#744] -- Refactor Redis namespace support [#747]. The redis namespace can no longer be - passed via the config file, the only supported way is via Ruby in your - initializer: - -```ruby -sidekiq_redis = { :url => 'redis://localhost:3679', :namespace => 'foo' } -Sidekiq.configure_server { |config| config.redis = sidekiq_redis } -Sidekiq.configure_client { |config| config.redis = sidekiq_redis } -``` - -A warning is printed out to the log if a namespace is found in your sidekiq.yml. - - -2.7.5 ------------ - -- Capistrano no longer uses daemonization in order to work with JRuby [#719] -- Refactor signal handling to work on Ruby 2.0 [#728, #730] -- Fix dashboard refresh URL [#732] - -2.7.4 ------------ - -- Fixed daemonization, was broken by some internal refactoring in 2.7.3 [#727] - -2.7.3 ------------ - -- Real-time dashboard is now the default web page -- Make config file optional for capistrano -- Fix Retry All button in the Web UI - -2.7.2 ------------ - -- Remove gem signing infrastructure. It was causing Sidekiq to break -when used via git in Bundler. This is why we can't have nice things. [#688] - - -2.7.1 ------------ - -- Fix issue with hard shutdown [#680] - - -2.7.0 ------------ - -- Add -d daemonize flag, capistrano recipe has been updated to use it [#662] -- Support profiling via `ruby-prof` with -p. When Sidekiq is stopped - via Ctrl-C, it will output `profile.html`. You must add `gem 'ruby-prof'` to your Gemfile for it to work. -- Dynamically update Redis stats on dashboard [brandonhilkert] -- Add Sidekiq::Workers API giving programmatic access to the current - set of active workers. - -``` -workers = Sidekiq::Workers.new -workers.size => 2 -workers.each do |name, work| - # name is a unique identifier per Processor instance - # work is a Hash which looks like: - # { 'queue' => name, 'run_at' => timestamp, 'payload' => msg } -end -``` - -- Allow environment-specific sections within the config file which -override the global values [dtaniwaki, #630] - -``` ---- -:concurrency: 50 -:verbose: false -staging: - :verbose: true - :concurrency: 5 -``` - - -2.6.5 ------------ - -- Several reliability fixes for job requeueing upon termination [apinstein, #622, #624] -- Fix typo in capistrano recipe -- Add `retry_queue` option so retries can be given lower priority [ryanlower, #620] - -```ruby -sidekiq_options queue: 'high', retry_queue: 'low' -``` - -2.6.4 ------------ - -- Fix crash upon empty queue [#612] - -2.6.3 ------------ - -- sidekiqctl exits with non-zero exit code upon error [jmazzi] -- better argument validation in Sidekiq::Client [karlfreeman] - -2.6.2 ------------ - -- Add Dashboard beacon indicating when stats are updated. [brandonhilkert, #606] -- Revert issue with capistrano restart. [#598] - -2.6.1 ------------ - -- Dashboard now live updates summary stats also. [brandonhilkert, #605] -- Add middleware chain APIs `insert_before` and `insert_after` for fine - tuning the order of middleware. [jackrg, #595] - -2.6.0 ------------ - -- Web UI much more mobile friendly now [brandonhilkert, #573] -- Enable live polling for every section in Web UI [brandonhilkert, #567] -- Add Stats API [brandonhilkert, #565] -- Add Stats::History API [brandonhilkert, #570] -- Add Dashboard to Web UI with live and historical stat graphs [brandonhilkert, #580] -- Add option to log output to a file, reopen log file on USR2 signal [mrnugget, #581] - -2.5.4 ------------ - -- `Sidekiq::Client.push` now accepts the worker class as a string so the - Sidekiq client does not have to load your worker classes at all. [#524] -- `Sidekiq::Client.push_bulk` now works with inline testing. -- **Really** fix status icon in Web UI this time. -- Add "Delete All" and "Retry All" buttons to Retries in Web UI - - -2.5.3 ------------ - -- Small Web UI fixes -- Add `delay_until` so you can delay jobs until a specific timestamp: - -```ruby -Auction.delay_until(@auction.ends_at).close(@auction.id) -``` - -This is identical to the existing Sidekiq::Worker method, `perform_at`. - -2.5.2 ------------ - -- Remove asset pipeline from Web UI for much faster, simpler runtime. [#499, #490, #481] -- Add -g option so the procline better identifies a Sidekiq process, defaults to File.basename(Rails.root). [#486] - - sidekiq 2.5.1 myapp [0 of 25 busy] - -- Add splay to retry time so groups of failed jobs don't fire all at once. [#483] - -2.5.1 ------------ - -- Fix issues with core\_ext - -2.5.0 ------------ - -- REDESIGNED WEB UI! [unity, cavneb] -- Support Honeybadger for error delivery -- Inline testing runs the client middleware before executing jobs [#465] -- Web UI can now remove jobs from queue. [#466, dleung] -- Web UI can now show the full message, not just 100 chars [#464, dleung] -- Add APIs for manipulating the retry and job queues. See sidekiq/api. [#457] - - -2.4.0 ------------ - -- ActionMailer.delay.method now only tries to deliver if method returns a valid message. -- Logging now uses "MSG-#{Job ID}", not a random msg ID -- Allow generic Redis provider as environment variable. [#443] -- Add ability to customize sidekiq\_options with delay calls [#450] - -```ruby -Foo.delay(:retry => false).bar -Foo.delay(:retry => 10).bar -Foo.delay(:timeout => 10.seconds).bar -Foo.delay_for(5.minutes, :timeout => 10.seconds).bar -``` - -2.3.3 ------------ - -- Remove option to disable Rails hooks. [#401] -- Allow delay of any module class method - -2.3.2 ------------ - -- Fix retry. 2.3.1 accidentally disabled it. - -2.3.1 ------------ - -- Add Sidekiq::Client.push\_bulk for bulk adding of jobs to Redis. - My own simple test case shows pushing 10,000 jobs goes from 5 sec to 1.5 sec. -- Add support for multiple processes per host to Capistrano recipe -- Re-enable Celluloid::Actor#defer to fix stack overflow issues [#398] - -2.3.0 ------------ - -- Upgrade Celluloid to 0.12 -- Upgrade Twitter Bootstrap to 2.1.0 -- Rescue more Exceptions -- Change Job ID to be Hex, rather than Base64, for HTTP safety -- Use `Airbrake#notify_or_ignore` - -2.2.1 ------------ - -- Add support for custom tabs to Sidekiq::Web [#346] -- Change capistrano recipe to run 'quiet' before deploy:update\_code so - it is run upon both 'deploy' and 'deploy:migrations'. [#352] -- Rescue Exception rather than StandardError to catch and log any sort - of Processor death. - -2.2.0 ------------ - -- Roll back Celluloid optimizations in 2.1.0 which caused instability. -- Add extension to delay any arbitrary class method to Sidekiq. - Previously this was limited to ActiveRecord classes. - -```ruby -SomeClass.delay.class_method(1, 'mike', Date.today) -``` - -- Sidekiq::Client now generates and returns a random, 128-bit Job ID 'jid' which - can be used to track the processing of a Job, e.g. for calling back to a webhook - when a job is finished. - -2.1.1 ------------ - -- Handle networking errors causing the scheduler thread to die [#309] -- Rework exception handling to log all Processor and actor death (#325, subelsky) -- Clone arguments when calling worker so modifications are discarded. (#265, hakanensari) - -2.1.0 ------------ - -- Tune Celluloid to no longer run message processing within a Fiber. - This gives us a full Thread stack and also lowers Sidekiq's memory - usage. -- Add pagination within the Web UI [#253] -- Specify which Redis driver to use: *hiredis* or *ruby* (default) -- Remove FailureJobs and UniqueJobs, which were optional middleware - that I don't want to support in core. [#302] - -2.0.3 ------------ -- Fix sidekiq-web's navbar on mobile devices and windows under 980px (ezkl) -- Fix Capistrano task for first deploys [#259] -- Worker subclasses now properly inherit sidekiq\_options set in - their superclass [#221] -- Add random jitter to scheduler to spread polls across POLL\_INTERVAL - window. [#247] -- Sidekiq has a new mailing list: sidekiq@librelist.org See README. - -2.0.2 ------------ - -- Fix "Retry Now" button on individual retry page. (ezkl) - -2.0.1 ------------ - -- Add "Clear Workers" button to UI. If you kill -9 Sidekiq, the workers - set can fill up with stale entries. -- Update sidekiq/testing to support new scheduled jobs API: - - ```ruby - require 'sidekiq/testing' - DirectWorker.perform_in(10.seconds, 1, 2) - assert_equal 1, DirectWorker.jobs.size - assert_in_delta 10.seconds.from_now.to_f, DirectWorker.jobs.last['at'], 0.01 - ``` - -2.0.0 ------------ - -- **SCHEDULED JOBS**! - -You can now use `perform_at` and `perform_in` to schedule jobs -to run at arbitrary points in the future, like so: - -```ruby - SomeWorker.perform_in(5.days, 'bob', 13) - SomeWorker.perform_at(5.days.from_now, 'bob', 13) -``` - -It also works with the delay extensions: - -```ruby - UserMailer.delay_for(5.days).send_welcome_email(user.id) -``` - -The time is approximately when the job will be placed on the queue; -it is not guaranteed to run at precisely at that moment in time. - -This functionality is meant for one-off, arbitrary jobs. I still -recommend `whenever` or `clockwork` if you want cron-like, -recurring jobs. See `examples/scheduling.rb` - -I want to specially thank @yabawock for his work on sidekiq-scheduler. -His extension for Sidekiq 1.x filled an obvious functional gap that I now think is -useful enough to implement in Sidekiq proper. - -- Fixed issues due to Redis 3.x API changes. Sidekiq now requires - the Redis 3.x client. -- Inline testing now round trips arguments through JSON to catch - serialization issues (betelgeuse) - -1.2.1 ------------ - -- Sidekiq::Worker now has access to Sidekiq's standard logger -- Fix issue with non-StandardErrors leading to Processor exhaustion -- Fix issue with Fetcher slowing Sidekiq shutdown -- Print backtraces for all threads upon TTIN signal [#183] -- Overhaul retries Web UI with new index page and bulk operations [#184] - -1.2.0 ------------ - -- Full or partial error backtraces can optionally be stored as part of the retry - for display in the web UI if you aren't using an error service. [#155] - -```ruby -class Worker - include Sidekiq::Worker - sidekiq_options :backtrace => [true || 10] -end -``` -- Add timeout option to kill a worker after N seconds (blackgold9) - -```ruby -class HangingWorker - include Sidekiq::Worker - sidekiq_options :timeout => 600 - def perform - # will be killed if it takes longer than 10 minutes - end -end -``` - -- Fix delayed extensions not available in workers [#152] -- In test environments add the `#drain` class method to workers. This method - executes all previously queued jobs. (panthomakos) -- Sidekiq workers can be run inline during tests, just `require 'sidekiq/testing/inline'` (panthomakos) -- Queues can now be deleted from the Sidekiq web UI [#154] -- Fix unnecessary shutdown delay due to Retry Poller [#174] - -1.1.4 ------------ - -- Add 24 hr expiry for basic keys set in Redis, to avoid any possible leaking. -- Only register workers in Redis while working, to avoid lingering - workers [#156] -- Speed up shutdown significantly. - -1.1.3 ------------ - -- Better network error handling when fetching jobs from Redis. - Sidekiq will retry once per second until it can re-establish - a connection. (ryanlecompte) -- capistrano recipe now uses `bundle_cmd` if set [#147] -- handle multi\_json API changes (sferik) - -1.1.2 ------------ - -- Fix double restart with cap deploy [#137] - -1.1.1 ------------ - -- Set procline for easy monitoring of Sidekiq status via "ps aux" -- Fix race condition on shutdown [#134] -- Fix hang with cap sidekiq:start [#131] - -1.1.0 ------------ - -- The Sidekiq license has switched from GPLv3 to LGPLv3! -- Sidekiq::Client.push now returns whether the actual Redis - operation succeeded or not. [#123] -- Remove UniqueJobs from the default middleware chain. Its - functionality, while useful, is unexpected for new Sidekiq - users. You can re-enable it with the following config. - Read #119 for more discussion. - -```ruby -Sidekiq.configure_client do |config| - require 'sidekiq/middleware/client/unique_jobs' - config.client_middleware do |chain| - chain.add Sidekiq::Middleware::Client::UniqueJobs - end -end -Sidekiq.configure_server do |config| - require 'sidekiq/middleware/server/unique_jobs' - config.server_middleware do |chain| - chain.add Sidekiq::Middleware::Server::UniqueJobs - end -end -``` - -1.0.0 ------------ - -Thanks to all Sidekiq users and contributors for helping me -get to this big milestone! - -- Default concurrency on client-side to 5, not 25 so we don't - create as many unused Redis connections, same as ActiveRecord's - default pool size. -- Ensure redis= is given a Hash or ConnectionPool. - -0.11.2 ------------ - -- Implement "safe shutdown". The messages for any workers that - are still busy when we hit the TERM timeout will be requeued in - Redis so the messages are not lost when the Sidekiq process exits. - [#110] -- Work around Celluloid's small 4kb stack limit [#115] -- Add support for a custom Capistrano role to limit Sidekiq to - a set of machines. [#113] - -0.11.1 ------------ - -- Fix fetch breaking retry when used with Redis namespaces. [#109] -- Redis connection now just a plain ConnectionPool, not CP::Wrapper. -- Capistrano initial deploy fix [#106] -- Re-implemented weighted queues support (ryanlecompte) - -0.11.0 ------------ - -- Client-side API changes, added sidekiq\_options for Sidekiq::Worker. - As a side effect of this change, the client API works on Ruby 1.8. - It's not officially supported but should work [#103] -- NO POLL! Sidekiq no longer polls Redis, leading to lower network - utilization and lower latency for message processing. -- Add --version CLI option - -0.10.1 ------------ - -- Add details page for jobs in retry queue (jcoene) -- Display relative timestamps in web interface (jcoene) -- Capistrano fixes (hinrik, bensie) - -0.10.0 ------------ - -- Reworked capistrano recipe to make it more fault-tolerant [#94]. -- Automatic failure retry! Sidekiq will now save failed messages - and retry them, with an exponential backoff, over about 20 days. - Did a message fail to process? Just deploy a bug fix in the next - few days and Sidekiq will retry the message eventually. - -0.9.1 ------------ - -- Fix missed deprecations, poor method name in web UI - -0.9.0 ------------ - -- Add -t option to configure the TERM shutdown timeout -- TERM shutdown timeout is now configurable, defaults to 5 seconds. -- USR1 signal now stops Sidekiq from accepting new work, - capistrano sends USR1 at start of deploy and TERM at end of deploy - giving workers the maximum amount of time to finish. -- New Sidekiq::Web rack application available -- Updated Sidekiq.redis API - -0.8.0 ------------ - -- Remove :namespace and :server CLI options (mperham) -- Add ExceptionNotifier support (masterkain) -- Add capistrano support (mperham) -- Workers now log upon start and finish (mperham) -- Messages for terminated workers are now automatically requeued (mperham) -- Add support for Exceptional error reporting (bensie) - -0.7.0 ------------ - -- Example chef recipe and monitrc script (jc00ke) -- Refactor global configuration into Sidekiq.configure\_server and - Sidekiq.configure\_client blocks. (mperham) -- Add optional middleware FailureJobs which saves failed jobs to a - 'failed' queue (fbjork) -- Upon shutdown, workers are now terminated after 5 seconds. This is to - meet Heroku's hard limit of 10 seconds for a process to shutdown. (mperham) -- Refactor middleware API for simplicity, see sidekiq/middleware/chain. (mperham) -- Add `delay` extensions for ActionMailer and ActiveRecord. (mperham) -- Added config file support. See test/config.yml for an example file. (jc00ke) -- Added pidfile for tools like monit (jc00ke) - -0.6.0 ------------ - -- Resque-compatible processing stats in redis (mperham) -- Simple client testing support in sidekiq/testing (mperham) -- Plain old Ruby support via the -r cli flag (mperham) -- Refactored middleware support, introducing ability to add client-side middleware (ryanlecompte) -- Added middleware for ignoring duplicate jobs (ryanlecompte) -- Added middleware for displaying jobs in resque-web dashboard (maxjustus) -- Added redis namespacing support (maxjustus) - -0.5.1 ------------ - -- Initial release! +- Extracted from https://github.com/mperham/sidekiq/tree/v6.4.1 diff --git a/README.md b/README.md index 0acf8f59..79671640 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,54 @@ -Sidekiq +Sidekiq Delay Extensions ============== -[![Gem Version](https://badge.fury.io/rb/sidekiq.svg)](https://rubygems.org/gems/sidekiq) -![Build](https://github.com/mperham/sidekiq/workflows/CI/badge.svg) +[![Gem Version](https://badge.fury.io/rb/sidekiq-delay_extensions.svg)](https://rubygems.org/gems/sidekiq-delay_extensions) +![Build](https://github.com/gehome/sidekiq-delay_extensions/workflows/CI/badge.svg) -Simple, efficient background processing for Ruby. +The [Sidekiq delay extensions are deprecated in 6.x and will be removed from 7.x](https://github.com/mperham/sidekiq/issues/5076). -Sidekiq uses threads to handle many jobs at the same time in the -same process. It does not require Rails but will integrate tightly with -Rails to make background processing dead simple. +This gem extracts the delay extensions from the latest 6.x release and will match +Sidekiq 6.x version numbers. -Performance ---------------- - -Version | Latency | Garbage created for 10k jobs | Time to process 100k jobs | Throughput | Ruby ------------------|------|---------|---------|------------------------|----- -Sidekiq 6.0.2 | 3 ms | 156 MB | 14.0 sec| **7100 jobs/sec** | MRI 2.6.3 -Sidekiq 6.0.0 | 3 ms | 156 MB | 19 sec | 5200 jobs/sec | MRI 2.6.3 -Sidekiq 4.0.0 | 10 ms | 151 MB | 22 sec | 4500 jobs/sec | -Sidekiq 3.5.1 | 22 ms | 1257 MB | 125 sec | 800 jobs/sec | -Resque 1.25.2 | - | - | 420 sec | 240 jobs/sec | -DelayedJob 4.1.1 | - | - | 465 sec | 215 jobs/sec | - -This benchmark can be found in `bin/sidekiqload` and assumes a Redis network latency of 1ms. +When Sidekiq reaches 7.0, this gem will begin being maintained on its own. Maintainers wanted. Requirements ----------------- -- Redis: 4.0+ -- Ruby: MRI 2.5+ or JRuby 9.2+. - -Sidekiq 6.0 supports Rails 5.0+ but does not require it. - +- See https://github.com/mperham/sidekiq/tree/v6.4.1 + - Redis: 4.0+ + - Ruby: MRI 2.5+ or JRuby 9.2+. + - Sidekiq 6.0 supports Rails 5.0+ but does not require it. Installation ----------------- gem install sidekiq + gem install sidekiq-delay_extensions +In your initializers, include the line: -Getting Started ------------------ - -See the [Getting Started wiki page](https://github.com/mperham/sidekiq/wiki/Getting-Started) and follow the simple setup process. -You can watch [this YouTube playlist](https://www.youtube.com/playlist?list=PLjeHh2LSCFrWGT5uVjUuFKAcrcj5kSai1) to learn all about -Sidekiq and see its features in action. Here's the Web UI: - -![Web UI](https://github.com/mperham/sidekiq/raw/main/examples/web-ui.png) - + Sidekiq::DelayExtensions.enable_delay! -Want to Upgrade? -------------------- - -I also sell Sidekiq Pro and Sidekiq Enterprise, extensions to Sidekiq which provide more -features, a commercial-friendly license and allow you to support high -quality open source development all at the same time. Please see the -[Sidekiq](https://sidekiq.org/) homepage for more detail. - -Subscribe to the **[quarterly newsletter](https://tinyletter.com/sidekiq)** to stay informed about the latest -features and changes to Sidekiq and its bigger siblings. - - -Problems? +Testing ----------------- -**Please do not directly email any Sidekiq committers with questions or problems.** A community is best served when discussions are held in public. - -If you have a problem, please review the [FAQ](https://github.com/mperham/sidekiq/wiki/FAQ) and [Troubleshooting](https://github.com/mperham/sidekiq/wiki/Problems-and-Troubleshooting) wiki pages. -Searching the [issues](https://github.com/mperham/sidekiq/issues) for your problem is also a good idea. - -Sidekiq Pro and Sidekiq Enterprise customers get private email support. You can purchase at https://sidekiq.org; email support@contribsys.com for help. - -Useful resources: - -* Product documentation is in the [wiki](https://github.com/mperham/sidekiq/wiki). -* Occasional announcements are made to the [@sidekiq](https://twitter.com/sidekiq) Twitter account. -* The [Sidekiq tag](https://stackoverflow.com/questions/tagged/sidekiq) on Stack Overflow has lots of useful Q & A. +In your test environment, include the line: -Every Friday morning is Sidekiq happy hour: I video chat and answer questions. -See the [Sidekiq support page](https://sidekiq.org/support.html) for details. + require "sidekiq/delay_extensions/testing" Contributing ----------------- -Please see [the contributing guidelines](https://github.com/mperham/sidekiq/blob/main/.github/contributing.md). +Please see [the contributing guidelines](https://github.com/gemhome/sidekiq-delay_extensions/blob/main/.github/contributing.md). License ----------------- -Please see [LICENSE](https://github.com/mperham/sidekiq/blob/main/LICENSE) for licensing details. +Please see [LICENSE](https://github.com/gemhome/sidekiq-delay_extensions/blob/main/LICENSE) for licensing details. -Author +Original Author ----------------- Mike Perham, [@getajobmike](https://twitter.com/getajobmike) / [@sidekiq](https://twitter.com/sidekiq), [https://www.mikeperham.com](https://www.mikeperham.com) / [https://www.contribsys.com](https://www.contribsys.com)