sidekiq-6.5.12/000755 001751 001751 00000000000 14551557262 013560 5ustar00pravipravi000000 000000 sidekiq-6.5.12/Changes.md000644 001751 001751 00000201530 14551557262 015453 0ustar00pravipravi000000 000000 # 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) 6.5.11 ---------- - Fix for Rails 7.1 [#6067] 6.5.10 ---------- - Web UI DoS vector [#6045] CVE-2023-26141 - Fix broadcast logger with Rails 7.1 [#6054] 6.5.9 ---------- - Ensure Sidekiq.options[:environment] == RAILS_ENV [#5932] 6.5.8 ---------- - Fail if using a bad version of scout_apm [#5616] - Add pagination to Busy page [#5556] - Speed up WorkSet#each [#5559] - Adjust CurrentAttributes to work with the String class name so we aren't referencing the Class within a Rails initializer [#5536] 6.5.7 ---------- - Updates for JA and ZH locales - Further optimizations for scheduled polling [#5513] 6.5.6 ---------- - Fix deprecation warnings with redis-rb 4.8.0 [#5484] - Lock redis-rb to < 5.0 as we are moving to redis-client in Sidekiq 7.0 6.5.5 ---------- - Fix require issue with job_retry.rb [#5462] - Improve Sidekiq::Web compatibility with Rack 3.x 6.5.4 ---------- - Fix invalid code on Ruby 2.5 [#5460] - Fix further metrics dependency issues [#5457] 6.5.3 ---------- - Don't require metrics code without explicit opt-in [#5456] 6.5.2 ---------- - [Job Metrics are under active development, help wanted!](https://github.com/mperham/sidekiq/wiki/Metrics#contributing) **BETA** - Add `Context` column on queue page which shows any CurrentAttributes [#5450] - `sidekiq_retry_in` may now return `:discard` or `:kill` to dynamically stop job retries [#5406] - Smarter sorting of processes in /busy Web UI [#5398] - Fix broken hamburger menu in mobile UI [#5428] - Require redis-rb 4.5.0. Note that Sidekiq will break if you use the [`Redis.exists_returns_integer = false`](https://github.com/redis/redis-rb/blob/master/CHANGELOG.md#450) flag. [#5394] 6.5.1 ---------- - Fix `push_bulk` breakage [#5387] 6.5.0 --------- - Substantial refactoring of Sidekiq server internals, part of a larger effort to reduce Sidekiq's internal usage of global methods and data, see [docs/global_to_local.md](docs/global_to_local.md) and [docs/middleware.md](docs/middleware.md). - **Add beta support for the `redis-client` gem**. This will become the default Redis driver in Sidekiq 7.0. [#5298] Read more: https://github.com/mperham/sidekiq/wiki/Using-redis-client - **Add beta support for DB transaction-aware client** [#5291] Add this line to your initializer and any jobs created during a transaction will only be pushed to Redis **after the transaction commits**. You will need to add the `after_commit_everywhere` gem to your Gemfile. ```ruby Sidekiq.transactional_push! ``` This feature does not have a lot of production usage yet; please try it out and let us know if you have any issues. It will be fully supported in Sidekiq 7.0 or removed if it proves problematic. - Fix regression with middleware arguments [#5312] 6.4.2 --------- - Strict argument checking now runs after client-side middleware [#5246] - Fix page events with live polling [#5184] - Many under-the-hood changes to remove all usage of the term "worker" from the Sidekiq codebase and APIs. This mostly involved RDoc and local variable names but a few constants and public APIs were changed. The old APIs will be removed in Sidekiq 7.0. ``` Sidekiq::DEFAULT_WORKER_OPTIONS -> Sidekiq.default_job_options Sidekiq.default_worker_options -> Sidekiq.default_job_options Sidekiq::Queues["default"].jobs_by_worker(HardJob) -> Sidekiq::Queues["default"].jobs_by_class(HardJob) ``` 6.4.1 --------- - Fix pipeline/multi deprecations in redis-rb 4.6 - 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.10 --------- - Backport fix for CVE-2022-23837. - Migrate to `exists?` for redis-rb. - Lock redis-rb to <4.6 to avoid deprecations. 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! sidekiq-6.5.12/bin/000755 001751 001751 00000000000 14551557262 014330 5ustar00pravipravi000000 000000 sidekiq-6.5.12/bin/sidekiqmon000755 001751 001751 00000000223 14551557262 016416 0ustar00pravipravi000000 000000 #!/usr/bin/env ruby require "sidekiq/monitor" section = "all" section = ARGV[0] if ARGV.size == 1 Sidekiq::Monitor::Status.new.display(section) sidekiq-6.5.12/bin/sidekiqload000755 001751 001751 00000010040 14551557262 016542 0ustar00pravipravi000000 000000 #!/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" if ENV["SIDEKIQ_REDIS_CLIENT"] Sidekiq::RedisConnection.adapter = :redis_client end 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 # 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 = 10 count = 10_000 iter.times do arr = Array.new(count) { |idx| [idx] } 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 def with_latency(latency, &block) Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis" if latency > 0 Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block) else yield end end begin # RubyProf::exclude_threads = [ Monitoring ] # RubyProf.start events = Sidekiq.options[:lifecycle_events][:startup] events.each(&:call) events.clear with_latency(Integer(ENV.fetch("LATENCY", "1"))) do launcher = Sidekiq::Launcher.new(Sidekiq) 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 sidekiq-6.5.12/bin/sidekiq000755 001751 001751 00000001507 14551557262 015712 0ustar00pravipravi000000 000000 #!/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 sidekiq-6.5.12/sidekiq.gemspec000644 001751 001751 00000002141 14551557262 016554 0ustar00pravipravi000000 000000 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", ["<5", ">= 4.5.0"] gem.add_dependency "connection_pool", ["<3", ">= 2.2.5"] gem.add_dependency "rack", "~> 2.0" end sidekiq-6.5.12/lib/000755 001751 001751 00000000000 14551557262 014326 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/generators/000755 001751 001751 00000000000 14551557262 016477 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/generators/sidekiq/000755 001751 001751 00000000000 14551557262 020130 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/generators/sidekiq/job_generator.rb000644 001751 001751 00000002477 14551557262 023307 0ustar00pravipravi000000 000000 require "rails/generators/named_base" module Sidekiq module Generators # :nodoc: class JobGenerator < ::Rails::Generators::NamedBase # :nodoc: desc "This generator creates a Sidekiq Job in app/sidekiq and a corresponding test" check_class_collision suffix: "Job" def self.default_generator_root File.dirname(__FILE__) end def create_job_file template "job.rb.erb", File.join("app/sidekiq", class_path, "#{file_name}_job.rb") end def create_test_file return unless test_framework if test_framework == :rspec create_job_spec else create_job_test end end private def create_job_spec template_file = File.join( "spec/sidekiq", class_path, "#{file_name}_job_spec.rb" ) template "job_spec.rb.erb", template_file end def create_job_test template_file = File.join( "test/sidekiq", class_path, "#{file_name}_job_test.rb" ) template "job_test.rb.erb", template_file end def file_name @_file_name ||= super.sub(/_?job\z/i, "") end def test_framework ::Rails.application.config.generators.options[:rails][:test_framework] end end end end sidekiq-6.5.12/lib/generators/sidekiq/templates/000755 001751 001751 00000000000 14551557262 022126 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/generators/sidekiq/templates/job_spec.rb.erb000644 001751 001751 00000000257 14551557262 025012 0ustar00pravipravi000000 000000 require 'rails_helper' <% module_namespacing do -%> RSpec.describe <%= class_name %>Job, type: :job do pending "add some examples to (or delete) #{__FILE__}" end <% end -%> sidekiq-6.5.12/lib/generators/sidekiq/templates/job.rb.erb000644 001751 001751 00000000214 14551557262 023771 0ustar00pravipravi000000 000000 <% module_namespacing do -%> class <%= class_name %>Job include Sidekiq::Job def perform(*args) # Do something end end <% end -%>sidekiq-6.5.12/lib/generators/sidekiq/templates/job_test.rb.erb000644 001751 001751 00000000303 14551557262 025027 0ustar00pravipravi000000 000000 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 -%> sidekiq-6.5.12/lib/sidekiq.rb000644 001751 001751 00000022546 14551557262 016315 0ustar00pravipravi000000 000000 # 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/transaction_aware_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: [], # triggers when we fire the first heartbeat on startup OR repairing a network partition heartbeat: [], # triggers on EVERY heartbeat call, every 10 seconds beat: [] }, dead_max_jobs: 10_000, dead_timeout_in_seconds: 180 * 24 * 60 * 60, # 6 months reloader: proc { |&block| block.call } } 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 # config.concurrency = 5 def self.concurrency=(val) self[:concurrency] = Integer(val) end # config.queues = %w( high default low ) # strict # config.queues = %w( high,3 default,2 low,1 ) # weighted # config.queues = %w( feature1,1 feature2,1 feature3,1 ) # random # # With weighted priority, queue will be checked first (weight / total) of the time. # high will be checked first (3/6) or 50% of the time. # I'd recommend setting weights between 1-10. Weights in the hundreds or thousands # are ridiculous and unnecessarily expensive. You can get random queue ordering # by explicitly setting all weights to 1. def self.queues=(val) self[:queues] = Array(val).each_with_object([]) do |qstr, memo| name, weight = qstr.split(",") self[:strict] = false if weight.to_i > 0 [weight.to_i, 1].max.times do memo << name end end end ### Private APIs def self.default_error_handler(ex, ctx) logger.warn(dump_json(ctx)) unless ctx.empty? logger.warn("#{ex.class.name}: #{ex.message}") logger.warn(ex.backtrace.join("\n")) unless ex.backtrace.nil? end # DEFAULT_ERROR_HANDLER is a constant that allows the default error handler to # be referenced. It must be defined here, after the default_error_handler # method is defined. DEFAULT_ERROR_HANDLER = method(:default_error_handler) @config = DEFAULTS.dup def self.options logger.warn "`config.options[:key] = value` is deprecated, use `config[:key] = value`: #{caller(1..2)}" @config end def self.options=(opts) logger.warn "config.options = hash` is deprecated, use `config.merge!(hash)`: #{caller(1..2)}" @config = opts end def self.[](key) @config[key] end def self.[]=(key, val) @config[key] = val end def self.merge!(hash) @config.merge!(hash) end def self.fetch(*args, &block) @config.fetch(*args, &block) end def self.handle_exception(ex, ctx = {}) self[:error_handlers].each do |handler| handler.call(ex, ctx) rescue => ex logger.error "!!! ERROR HANDLER THREW AN ERROR !!!" logger.error ex logger.error ex.backtrace.join("\n") unless ex.backtrace.nil? end end ### ## # Configuration for Sidekiq server, use like: # # Sidekiq.configure_server do |config| # 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 = { 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 RedisConnection.adapter::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 RedisConnection.adapter::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 ||= RedisConnection.create end def self.redis=(hash) @redis = if hash.is_a?(ConnectionPool) hash else RedisConnection.create(hash) end end def self.client_middleware @client_chain ||= Middleware::Chain.new(self) 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(self) end def self.default_worker_options=(hash) # deprecated @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s)) end def self.default_job_options=(hash) @default_job_options = default_job_options.merge(hash.transform_keys(&:to_s)) end def self.default_worker_options # deprecated @default_job_options ||= {"retry" => true, "queue" => "default"} end def self.default_job_options @default_job_options ||= {"retry" => true, "queue" => "default"} 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 self[: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: :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 def self.ent? defined?(Sidekiq::Enterprise) 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) self[: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 self[: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 self[:lifecycle_events].key?(event) self[:lifecycle_events][event] << block end def self.strict_args!(mode = :raise) self[:on_complex_arguments] = mode end # We are shutting down Sidekiq but what about threads that # are working on some long job? This error is # raised in jobs 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 JOBS class Shutdown < Interrupt; end end require "sidekiq/rails" if defined?(::Rails::Engine) sidekiq-6.5.12/lib/sidekiq/000755 001751 001751 00000000000 14551557262 015757 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/redis_client_adapter.rb000644 001751 001751 00000007246 14551557262 022461 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "connection_pool" require "redis_client" require "redis_client/decorator" require "uri" module Sidekiq class RedisClientAdapter BaseError = RedisClient::Error CommandError = RedisClient::CommandError module CompatMethods def info @client.call("INFO") { |i| i.lines(chomp: true).map { |l| l.split(":", 2) }.select { |l| l.size == 2 }.to_h } end def evalsha(sha, keys, argv) @client.call("EVALSHA", sha, keys.size, *keys, *argv) end def brpoplpush(*args) @client.blocking_call(false, "BRPOPLPUSH", *args) end def brpop(*args) @client.blocking_call(false, "BRPOP", *args) end def set(*args) @client.call("SET", *args) { |r| r == "OK" } end ruby2_keywords :set if respond_to?(:ruby2_keywords, true) def sismember(*args) @client.call("SISMEMBER", *args) { |c| c > 0 } end def exists?(key) @client.call("EXISTS", key) { |c| c > 0 } end private def method_missing(*args, &block) @client.call(*args, *block) end ruby2_keywords :method_missing if respond_to?(:ruby2_keywords, true) def respond_to_missing?(name, include_private = false) super # Appease the linter. We can't tell what is a valid command. end end CompatClient = RedisClient::Decorator.create(CompatMethods) class CompatClient %i[scan sscan zscan hscan].each do |method| alias_method :"#{method}_each", method undef_method method end def disconnect! @client.close end def connection {id: @client.id} end def redis self end def _client @client end def message yield nil, @queue.pop end # NB: this method does not return def subscribe(chan) @queue = ::Queue.new pubsub = @client.pubsub pubsub.call("subscribe", chan) loop do evt = pubsub.next_event next if evt.nil? next unless evt[0] == "message" && evt[1] == chan (_, _, msg) = evt @queue << msg yield self end end end def initialize(options) opts = client_opts(options) @config = if opts.key?(:sentinels) RedisClient.sentinel(**opts) else RedisClient.config(**opts) end end def new_client CompatClient.new(@config.new_client) end private def client_opts(options) opts = options.dup if opts[:namespace] Sidekiq.logger.error("Your Redis configuration uses the namespace '#{opts[:namespace]}' but this feature isn't supported by redis-client. " \ "Either use the redis adapter or remove the namespace.") Kernel.exit(-127) end opts.delete(:size) opts.delete(:pool_timeout) if opts[:network_timeout] opts[:timeout] = opts[:network_timeout] opts.delete(:network_timeout) end if opts[:driver] opts[:driver] = opts[:driver].to_sym end opts[:name] = opts.delete(:master_name) if opts.key?(:master_name) opts[:role] = opts[:role].to_sym if opts.key?(:role) opts.delete(:url) if opts.key?(:sentinels) # 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 end end Sidekiq::RedisConnection.adapter = Sidekiq::RedisClientAdapter sidekiq-6.5.12/lib/sidekiq/delay.rb000644 001751 001751 00000002465 14551557262 017411 0ustar00pravipravi000000 000000 # frozen_string_literal: true module Sidekiq # :nodoc: module Extensions def self.enable_delay! warn "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0", uplevel: 1 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 sidekiq-6.5.12/lib/sidekiq/worker.rb000644 001751 001751 00000027534 14551557262 017630 0ustar00pravipravi000000 000000 # 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_job_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 # NB: the internal hash always has stringified keys @opts = opts.transform_keys(&:to_s) # ActiveJob compatibility interval = @opts.delete("wait_until") || @opts.delete("wait") at(interval) if interval end def set(options) hash = options.transform_keys(&:to_s) interval = hash.delete("wait_until") || @opts.delete("wait") @opts.merge!(hash) 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) # 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) client = @klass.build_client result = args.each_slice(batch_size).flat_map do |slice| client.push_bulk(@opts.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 alias_method :perform_sync, :perform_inline ## # 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 retries via JobRetry, *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. # # These options will be saved into the serialized job when enqueued by # the client. def sidekiq_options(opts = {}) super end def client_push(item) # :nodoc: raise ArgumentError, "Job payloads should contain no Symbols: #{item}" if item.any? { |k, v| k.is_a?(::Symbol) } build_client.push(item) end def build_client # :nodoc: pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options["pool"] || Sidekiq.redis_pool client_class = get_sidekiq_options["client_class"] || Sidekiq::Client client_class.new(pool) end end end end sidekiq-6.5.12/lib/sidekiq/extensions/000755 001751 001751 00000000000 14551557262 020156 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/extensions/action_mailer.rb000644 001751 001751 00000002766 14551557262 023324 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/extensions/active_record.rb000644 001751 001751 00000002503 14551557262 023314 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/extensions/class_methods.rb000644 001751 001751 00000002270 14551557262 023334 0ustar00pravipravi000000 000000 # 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) sidekiq-6.5.12/lib/sidekiq/extensions/generic_proxy.rb000644 001751 001751 00000002204 14551557262 023356 0ustar00pravipravi000000 000000 # 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.transform_keys(&:to_s) 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 sidekiq-6.5.12/lib/sidekiq/transaction_aware_client.rb000644 001751 001751 00000002357 14551557262 023355 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "securerandom" require "sidekiq/client" module Sidekiq class TransactionAwareClient def initialize(redis_pool) @redis_client = Client.new(redis_pool) end def push(item) # pre-allocate the JID so we can return it immediately and # save it to the database as part of the transaction. item["jid"] ||= SecureRandom.hex(12) AfterCommitEverywhere.after_commit { @redis_client.push(item) } item["jid"] end ## # We don't provide transactionality for push_bulk because we don't want # to hold potentially hundreds of thousands of job records in memory due to # a long running enqueue process. def push_bulk(items) @redis_client.push_bulk(items) end end end ## # Use `Sidekiq.transactional_push!` in your sidekiq.rb initializer module Sidekiq def self.transactional_push! begin require "after_commit_everywhere" rescue LoadError Sidekiq.logger.error("You need to add after_commit_everywhere to your Gemfile to use Sidekiq's transactional client") raise end default_job_options["client_class"] = Sidekiq::TransactionAwareClient Sidekiq::JobUtil::TRANSIENT_ATTRIBUTES << "client_class" true end end sidekiq-6.5.12/lib/sidekiq/metrics/000755 001751 001751 00000000000 14551557262 017425 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/metrics/query.rb000644 001751 001751 00000010547 14551557262 021126 0ustar00pravipravi000000 000000 require "sidekiq" require "date" require "set" require "sidekiq/metrics/shared" module Sidekiq module Metrics # Allows caller to query for Sidekiq execution metrics within Redis. # Caller sets a set of attributes to act as filters. {#fetch} will call # Redis and return a Hash of results. # # NB: all metrics and times/dates are UTC only. We specifically do not # support timezones. class Query def initialize(pool: Sidekiq.redis_pool, now: Time.now) @time = now.utc @pool = pool @klass = nil end # Get metric data for all jobs from the last hour def top_jobs(minutes: 60) result = Result.new time = @time redis_results = @pool.with do |conn| conn.pipelined do |pipe| minutes.times do |idx| key = "j|#{time.strftime("%Y%m%d")}|#{time.hour}:#{time.min}" pipe.hgetall key result.prepend_bucket time time -= 60 end end end time = @time redis_results.each do |hash| hash.each do |k, v| kls, metric = k.split("|") result.job_results[kls].add_metric metric, time, v.to_i end time -= 60 end result.marks = fetch_marks(result.starts_at..result.ends_at) result end def for_job(klass, minutes: 60) result = Result.new time = @time redis_results = @pool.with do |conn| conn.pipelined do |pipe| minutes.times do |idx| key = "j|#{time.strftime("%Y%m%d")}|#{time.hour}:#{time.min}" pipe.hmget key, "#{klass}|ms", "#{klass}|p", "#{klass}|f" result.prepend_bucket time time -= 60 end end end time = @time @pool.with do |conn| redis_results.each do |(ms, p, f)| result.job_results[klass].add_metric "ms", time, ms.to_i if ms result.job_results[klass].add_metric "p", time, p.to_i if p result.job_results[klass].add_metric "f", time, f.to_i if f result.job_results[klass].add_hist time, Histogram.new(klass).fetch(conn, time) time -= 60 end end result.marks = fetch_marks(result.starts_at..result.ends_at) result end class Result < Struct.new(:starts_at, :ends_at, :size, :buckets, :job_results, :marks) def initialize super self.buckets = [] self.marks = [] self.job_results = Hash.new { |h, k| h[k] = JobResult.new } end def prepend_bucket(time) buckets.unshift time.strftime("%H:%M") self.ends_at ||= time self.starts_at = time end end class JobResult < Struct.new(:series, :hist, :totals) def initialize super self.series = Hash.new { |h, k| h[k] = Hash.new(0) } self.hist = Hash.new { |h, k| h[k] = [] } self.totals = Hash.new(0) end def add_metric(metric, time, value) totals[metric] += value series[metric][time.strftime("%H:%M")] += value # Include timing measurements in seconds for convenience add_metric("s", time, value / 1000.0) if metric == "ms" end def add_hist(time, hist_result) hist[time.strftime("%H:%M")] = hist_result end def total_avg(metric = "ms") completed = totals["p"] - totals["f"] totals[metric].to_f / completed end def series_avg(metric = "ms") series[metric].each_with_object(Hash.new(0)) do |(bucket, value), result| completed = series.dig("p", bucket) - series.dig("f", bucket) result[bucket] = (completed == 0) ? 0 : value.to_f / completed end end end class MarkResult < Struct.new(:time, :label) def bucket time.strftime("%H:%M") end end private def fetch_marks(time_range) [].tap do |result| marks = @pool.with { |c| c.hgetall("#{@time.strftime("%Y%m%d")}-marks") } marks.each do |timestamp, label| time = Time.parse(timestamp) if time_range.cover? time result << MarkResult.new(time, label) end end end end end end end sidekiq-6.5.12/lib/sidekiq/metrics/shared.rb000644 001751 001751 00000006274 14551557262 021231 0ustar00pravipravi000000 000000 require "concurrent" module Sidekiq module Metrics # TODO Support apps without concurrent-ruby Counter = ::Concurrent::AtomicFixnum # Implements space-efficient but statistically useful histogram storage. # A precise time histogram stores every time. Instead we break times into a set of # known buckets and increment counts of the associated time bucket. Even if we call # the histogram a million times, we'll still only store 26 buckets. # NB: needs to be thread-safe or resiliant to races. # # To store this data, we use Redis' BITFIELD command to store unsigned 16-bit counters # per bucket per klass per minute. It's unlikely that most people will be executing more # than 1000 job/sec for a full minute of a specific type. class Histogram include Enumerable # This number represents the maximum milliseconds for this bucket. # 20 means all job executions up to 20ms, e.g. if a job takes # 280ms, it'll increment bucket[7]. Note we can track job executions # up to about 5.5 minutes. After that, it's assumed you're probably # not too concerned with its performance. BUCKET_INTERVALS = [ 20, 30, 45, 65, 100, 150, 225, 335, 500, 750, 1100, 1700, 2500, 3800, 5750, 8500, 13000, 20000, 30000, 45000, 65000, 100000, 150000, 225000, 335000, Float::INFINITY # the "maybe your job is too long" bucket ] LABELS = [ "20ms", "30ms", "45ms", "65ms", "100ms", "150ms", "225ms", "335ms", "500ms", "750ms", "1.1s", "1.7s", "2.5s", "3.8s", "5.75s", "8.5s", "13s", "20s", "30s", "45s", "65s", "100s", "150s", "225s", "335s", "Slow" ] FETCH = "GET u16 #0 GET u16 #1 GET u16 #2 GET u16 #3 \ GET u16 #4 GET u16 #5 GET u16 #6 GET u16 #7 \ GET u16 #8 GET u16 #9 GET u16 #10 GET u16 #11 \ GET u16 #12 GET u16 #13 GET u16 #14 GET u16 #15 \ GET u16 #16 GET u16 #17 GET u16 #18 GET u16 #19 \ GET u16 #20 GET u16 #21 GET u16 #22 GET u16 #23 \ GET u16 #24 GET u16 #25".split def each buckets.each { |counter| yield counter.value } end def label(idx) LABELS[idx] end attr_reader :buckets def initialize(klass) @klass = klass @buckets = Array.new(BUCKET_INTERVALS.size) { Counter.new } end def record_time(ms) index_to_use = BUCKET_INTERVALS.each_index do |idx| break idx if ms < BUCKET_INTERVALS[idx] end @buckets[index_to_use].increment end def fetch(conn, now = Time.now) window = now.utc.strftime("%d-%H:%-M") key = "#{@klass}-#{window}" conn.bitfield(key, *FETCH) end def persist(conn, now = Time.now) buckets, @buckets = @buckets, [] window = now.utc.strftime("%d-%H:%-M") key = "#{@klass}-#{window}" cmd = [key, "OVERFLOW", "SAT"] buckets.each_with_index do |counter, idx| val = counter.value cmd << "INCRBY" << "u16" << "##{idx}" << val.to_s if val > 0 end conn.bitfield(*cmd) if cmd.size > 3 conn.expire(key, 86400) key end end end end sidekiq-6.5.12/lib/sidekiq/metrics/deploy.rb000644 001751 001751 00000003004 14551557262 021243 0ustar00pravipravi000000 000000 require "sidekiq" require "time" # This file is designed to be required within the user's # deployment script; it should need a bare minimum of dependencies. # # require "sidekiq/metrics/deploy" # gitdesc = `git log -1 --format="%h %s"`.strip # d = Sidekiq::Metrics::Deploy.new # d.mark(label: gitdesc) # # Note that you cannot mark more than once per minute. This is a feature, not a bug. module Sidekiq module Metrics class Deploy MARK_TTL = 90 * 24 * 60 * 60 # 90 days def initialize(pool = Sidekiq.redis_pool) @pool = pool end def mark(at: Time.now, label: "") # we need to round the timestamp so that we gracefully # handle an excepted common error in marking deploys: # having every process mark its deploy, leading # to N marks for each deploy. Instead we round the time # to the minute so that multple marks within that minute # will all naturally rollup into one mark per minute. whence = at.utc floor = Time.utc(whence.year, whence.month, whence.mday, whence.hour, whence.min, 0) datecode = floor.strftime("%Y%m%d") key = "#{datecode}-marks" @pool.with do |c| c.pipelined do |pipe| pipe.hsetnx(key, floor.iso8601, label) pipe.expire(key, MARK_TTL) end end end def fetch(date = Time.now.utc.to_date) datecode = date.strftime("%Y%m%d") @pool.with { |c| c.hgetall("#{datecode}-marks") } end end end end sidekiq-6.5.12/lib/sidekiq/metrics/tracking.rb000644 001751 001751 00000007210 14551557262 021554 0ustar00pravipravi000000 000000 require "time" require "sidekiq" require "sidekiq/metrics/shared" # This file contains the components which track execution metrics within Sidekiq. module Sidekiq module Metrics class ExecutionTracker include Sidekiq::Component def initialize(config) @config = config @jobs = Hash.new(0) @totals = Hash.new(0) @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) } @lock = Mutex.new end def track(queue, klass) start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond) time_ms = 0 begin begin yield ensure finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC, :millisecond) time_ms = finish - start end # We don't track time for failed jobs as they can have very unpredictable # execution times. more important to know average time for successful jobs so we # can better recognize when a perf regression is introduced. @lock.synchronize { @grams[klass].record_time(time_ms) @jobs["#{klass}|ms"] += time_ms @totals["ms"] += time_ms } rescue Exception @lock.synchronize { @jobs["#{klass}|f"] += 1 @totals["f"] += 1 } raise ensure @lock.synchronize { @jobs["#{klass}|p"] += 1 @totals["p"] += 1 } end end LONG_TERM = 90 * 24 * 60 * 60 MID_TERM = 7 * 24 * 60 * 60 SHORT_TERM = 8 * 60 * 60 def flush(time = Time.now) totals, jobs, grams = reset procd = totals["p"] fails = totals["f"] return if procd == 0 && fails == 0 now = time.utc nowdate = now.strftime("%Y%m%d") nowhour = now.strftime("%Y%m%d|%-H") nowmin = now.strftime("%Y%m%d|%-H:%-M") count = 0 redis do |conn| if grams.size > 0 conn.pipelined do |pipe| grams.each do |_, gram| gram.persist(pipe, now) end end end [ ["j", jobs, nowdate, LONG_TERM], ["j", jobs, nowhour, MID_TERM], ["j", jobs, nowmin, SHORT_TERM] ].each do |prefix, data, bucket, ttl| # Quietly seed the new 7.0 stats format so migration is painless. conn.pipelined do |xa| stats = "#{prefix}|#{bucket}" # logger.debug "Flushing metrics #{stats}" data.each_pair do |key, value| xa.hincrby stats, key, value count += 1 end xa.expire(stats, ttl) end end logger.info "Flushed #{count} metrics" count end end private def reset @lock.synchronize { array = [@totals, @jobs, @grams] @totals = Hash.new(0) @jobs = Hash.new(0) @grams = Hash.new { |hash, key| hash[key] = Histogram.new(key) } array } end end class Middleware include Sidekiq::ServerMiddleware def initialize(options) @exec = options end def call(_instance, hash, queue, &block) @exec.track(queue, hash["wrapped"] || hash["class"], &block) end end end end if ENV["SIDEKIQ_METRICS_BETA"] == "1" Sidekiq.configure_server do |config| exec = Sidekiq::Metrics::ExecutionTracker.new(config) config.server_middleware do |chain| chain.add Sidekiq::Metrics::Middleware, exec end config.on(:beat) do exec.flush end end end sidekiq-6.5.12/lib/sidekiq/ring_buffer.rb000644 001751 001751 00000000730 14551557262 020574 0ustar00pravipravi000000 000000 require "forwardable" module Sidekiq 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 end sidekiq-6.5.12/lib/sidekiq/job.rb000644 001751 001751 00000001036 14551557262 017056 0ustar00pravipravi000000 000000 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 sidekiq-6.5.12/lib/sidekiq/paginator.rb000644 001751 001751 00000003104 14551557262 020266 0ustar00pravipravi000000 000000 # 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, withscores: true) else transaction.zrange(key, starting, ending, withscores: true) end } [current_page, total_size, items] when "list" total_size, items = conn.multi { |transaction| transaction.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 def page_items(items, pageidx = 1, page_size = 25) current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i pageidx = current_page - 1 starting = pageidx * page_size items = items.to_a [current_page, items.size, items[starting, page_size]] end end end sidekiq-6.5.12/lib/sidekiq/version.rb000644 001751 001751 00000000107 14551557262 017767 0ustar00pravipravi000000 000000 # frozen_string_literal: true module Sidekiq VERSION = "6.5.12" end sidekiq-6.5.12/lib/sidekiq/systemd.rb000644 001751 001751 00000001411 14551557262 017771 0ustar00pravipravi000000 000000 # # 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 sidekiq-6.5.12/lib/sidekiq/middleware/000755 001751 001751 00000000000 14551557262 020074 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/middleware/i18n.rb000644 001751 001751 00000002113 14551557262 021175 0ustar00pravipravi000000 000000 # 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 include Sidekiq::ClientMiddleware def call(_jobclass, job, _queue, _redis) job["locale"] ||= I18n.locale yield end end # Pull the msg locale out and set the current thread to use it. class Server include Sidekiq::ServerMiddleware def call(_jobclass, job, _queue, &block) I18n.with_locale(job.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 sidekiq-6.5.12/lib/sidekiq/middleware/modules.rb000644 001751 001751 00000000630 14551557262 022070 0ustar00pravipravi000000 000000 module Sidekiq # Server-side middleware must import this Module in order # to get access to server resources during `call`. module ServerMiddleware attr_accessor :config def redis_pool config.redis_pool end def logger config.logger end def redis(&block) config.redis(&block) end end # no difference for now ClientMiddleware = ServerMiddleware end sidekiq-6.5.12/lib/sidekiq/middleware/current_attributes.rb000644 001751 001751 00000003057 14551557262 024356 0ustar00pravipravi000000 000000 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 include Sidekiq::ClientMiddleware def initialize(cattr) @strklass = cattr end def call(_, job, _, _) attrs = @strklass.constantize.attributes if attrs.any? if job.has_key?("cattr") job["cattr"].merge!(attrs) else job["cattr"] = attrs end end yield end end class Load include Sidekiq::ServerMiddleware def initialize(cattr) @strklass = cattr end def call(_, job, _, &block) if job.has_key?("cattr") @strklass.constantize.set(job["cattr"], &block) else yield end end end def self.persist(klass) Sidekiq.configure_client do |config| config.client_middleware.add Save, klass.to_s end Sidekiq.configure_server do |config| config.client_middleware.add Save, klass.to_s config.server_middleware.add Load, klass.to_s end end end end sidekiq-6.5.12/lib/sidekiq/middleware/chain.rb000644 001751 001751 00000013443 14551557262 021510 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq/middleware/modules" module Sidekiq # Middleware is code configured to run before/after # a job 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). # # Callers will register middleware Classes and Sidekiq will # create new instances of the middleware for every job. This # is important so that instance state is not shared accidentally # between job executions. # # 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 # include Sidekiq::ServerMiddleware # # def call(job_instance, msg, queue) # logger.info "Before job" # redis {|conn| conn.get("foo") } # do something in Redis # yield # logger.info "After job" # 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 # include Sidekiq::ClientMiddleware # # def call(job_class, msg, queue, redis_pool) # logger.info "Before push" # result = yield # logger.info "After push" # result # end # end # module Middleware class Chain include Enumerable # A unique instance of the middleware chain is created for # each job executed in order to be thread-safe. # @param copy [Sidekiq::Middleware::Chain] New instance of Chain # @returns nil def initialize_copy(copy) copy.instance_variable_set(:@entries, entries.dup) nil end # Iterate through each middleware in the chain def each(&block) entries.each(&block) end # @api private def initialize(config = nil) # :nodoc: @config = config @entries = nil yield self if block_given? end def entries @entries ||= [] end # Remove all middleware matching the given Class # @param klass [Class] def remove(klass) entries.delete_if { |entry| entry.klass == klass } end # Add the given middleware to the end of the chain. # Sidekiq will call `klass.new(*args)` to create a clean # copy of your middleware for every job executed. # # chain.add(Statsd::Metrics, { collector: "localhost:8125" }) # # @param klass [Class] Your middleware class # @param *args [Array] Set of arguments to pass to every instance of your middleware def add(klass, *args) remove(klass) entries << Entry.new(@config, klass, *args) end # Identical to {#add} except the middleware is added to the front of the chain. def prepend(klass, *args) remove(klass) entries.insert(0, Entry.new(@config, klass, *args)) end # Inserts +newklass+ before +oldklass+ in the chain. # Useful if one middleware must run before another middleware. def insert_before(oldklass, newklass, *args) i = entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i) i = entries.index { |entry| entry.klass == oldklass } || 0 entries.insert(i, new_entry) end # Inserts +newklass+ after +oldklass+ in the chain. # Useful if one middleware must run after another middleware. def insert_after(oldklass, newklass, *args) i = entries.index { |entry| entry.klass == newklass } new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i) i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1 entries.insert(i + 1, new_entry) end # @return [Boolean] if the given class is already in the chain def exists?(klass) any? { |entry| entry.klass == klass } end # @return [Boolean] if the chain contains no middleware def empty? @entries.nil? || @entries.empty? end def retrieve map(&:make_new) end def clear entries.clear end # Used by Sidekiq to execute the middleware at runtime # @api private 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 # Represents each link in the middleware chain # @api private class Entry attr_reader :klass def initialize(config, klass, *args) @config = config @klass = klass @args = args end def make_new x = @klass.new(*@args) x.config = @config if @config && x.respond_to?(:config=) x end end end end sidekiq-6.5.12/lib/sidekiq/manager.rb000644 001751 001751 00000007605 14551557262 017726 0ustar00pravipravi000000 000000 # frozen_string_literal: true 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 Sidekiq::Component attr_reader :workers def initialize(options = {}) @config = options logger.debug { options.inspect } @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(@config, &method(:processor_result)) end @plock = Mutex.new end def start @workers.each(&:start) end def quiet return if @done @done = true logger.info { "Terminating quiet threads" } @workers.each(&: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 jobs to finish..." } wait_for(deadline) { @workers.empty? } return if @workers.empty? hard_shutdown end def processor_result(processor, reason = nil) @plock.synchronize do @workers.delete(processor) unless @done p = Processor.new(@config, &method(:processor_result)) @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 threads. # 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 threads" } logger.debug { "Jobs still in progress #{jobs.inspect}" } # Re-enqueue unfinished jobs # NOTE: You may notice that we may push a job back to redis before # the 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 = @config[:fetch] strategy.bulk_requeue(jobs, @config) 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 # 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 end end sidekiq-6.5.12/lib/sidekiq/job_retry.rb000644 001751 001751 00000022200 14551557262 020277 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "zlib" require "base64" require "sidekiq/component" 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' => 'HardJob', 'args' => [1, 2, 'foo'], 'retry' => true } # # The 'retry' option also accepts a number (in place of 'true'): # # { 'class' => 'HardJob', '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 job and send retries to # a low priority queue with: # # class MyJob # include Sidekiq::Job # sidekiq_options retry: 10, retry_queue: 'low' # end # class JobRetry class Handled < ::RuntimeError; end class Skip < Handled; end include Sidekiq::Component DEFAULT_MAX_RETRY_ATTEMPTS = 25 def initialize(options) @config = options @max_retries = @config[: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 job 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"] process_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 job 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(jobinst, 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"] = jobinst.class.get_sidekiq_options["retry"] end raise e unless msg["retry"] process_retry(jobinst, 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 +jobinst+ can be nil here if an error is raised before we can # instantiate the job instance. All access must be guarded and # best effort. def process_retry(jobinst, 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 # Goodbye dear message, you (re)tried your best I'm sure. return retries_exhausted(jobinst, msg, exception) if count >= max_retry_attempts strategy, delay = delay_for(jobinst, count, exception) case strategy when :discard return # poof! when :kill return retries_exhausted(jobinst, msg, exception) end # Logging here can break retries if the logging device raises ENOSPC #3979 # logger.debug { "Failure! Retry #{count} in #{delay} seconds" } jitter = rand(10) * (count + 1) retry_at = Time.now.to_f + delay + jitter payload = Sidekiq.dump_json(msg) redis do |conn| conn.zadd("retry", retry_at.to_s, payload) end end # returns (strategy, seconds) def delay_for(jobinst, count, exception) rv = begin # sidekiq_retry_in can return two different things: # 1. When to retry next, as an integer of seconds # 2. A symbol which re-routes the job elsewhere, e.g. :discard, :kill, :default jobinst&.sidekiq_retry_in_block&.call(count, exception) rescue Exception => e handle_exception(e, {context: "Failure scheduling retry using the defined `sidekiq_retry_in` in #{jobinst.class.name}, falling back to default"}) nil end delay = (count**4) + 15 if Integer === rv && rv > 0 delay = rv elsif rv == :discard return [:discard, nil] # do nothing, job goes poof elsif rv == :kill return [:kill, nil] end [:default, delay] end def retries_exhausted(jobinst, msg, exception) begin block = jobinst&.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 config.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) now = Time.now.to_f config.redis do |conn| conn.multi do |xa| xa.zadd("dead", now.to_s, payload) xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds]) xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs]) end end end def retry_attempts_from(msg_retry, default) if msg_retry.is_a?(Integer) msg_retry else default end 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 sidekiq-6.5.12/lib/sidekiq/scheduled.rb000644 001751 001751 00000020473 14551557262 020252 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq" require "sidekiq/component" 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, argv) rescue RedisConnection.adapter::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 Sidekiq::Component INITIAL_WAIT = 10 def initialize(options) @config = options @enq = (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 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. # count = process_count interval = poll_interval_average(count) if count < 10 # For small clusters, calculate a random interval that is ±50% the desired average. interval * rand + interval.to_f / 2 else # With 10+ processes, we should have enough randomness to get decent polling # across the entire timespan interval * 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(count) @config[:poll_interval_average] || scaled_poll_interval(count) 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) process_count * @config[:average_scheduled_poll_interval] end def process_count pcount = Sidekiq.redis { |conn| conn.scard("processes") } pcount = 1 if pcount == 0 pcount end # A copy of Sidekiq::ProcessSet#cleanup because server # should never depend on sidekiq/api. def cleanup # dont run cleanup more than once per minute return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) } count = 0 Sidekiq.redis do |conn| procs = conn.sscan_each("processes").to_a 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 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 @config[:poll_interval_average] total += (5 * rand) @sleeper.pop(total) rescue Timeout::Error ensure # periodically clean out the `processes` set in Redis which can collect # references to dead processes over time. The process count affects how # often we scan for scheduled jobs. cleanup end end end end sidekiq-6.5.12/lib/sidekiq/cli.rb000644 001751 001751 00000032721 14551557262 017060 0ustar00pravipravi000000 000000 # frozen_string_literal: true $stdout.sync = true require "yaml" require "singleton" require "optparse" require "erb" require "fileutils" require "sidekiq" require "sidekiq/component" require "sidekiq/launcher" # module ScoutApm # VERSION = "5.3.1" # end fail <<~EOM if defined?(ScoutApm::VERSION) && ScoutApm::VERSION < "5.2.0" scout_apm v#{ScoutApm::VERSION} is unsafe with Sidekiq 6.5. Please run `bundle up scout_apm` to upgrade to 5.2.0 or greater. EOM module Sidekiq # :nodoc: class CLI include Sidekiq::Component include Singleton unless $TESTING attr_accessor :launcher attr_accessor :environment attr_accessor :config def parse(args = ARGV.dup) @config = Sidekiq @config[:error_handlers].clear @config[:error_handlers] << @config.method(:default_error_handler) 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? && @config.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 = @config.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 = @config.redis_pool.size needed = @config[: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 @config[:identity] = identity # Touch middleware so it isn't lazy loaded by multiple threads, #3043 @config.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: #{@config.client_middleware.map(&:klass).join(", ")}" } logger.debug { "Server Middleware: #{@config.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(@config) begin launcher.run while self_read.wait_readable signal = self_read.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) { cli.logger.info "Received TSTP, no longer accepting new work" cli.launcher.quiet }, "TTIN" => ->(cli) { Thread.list.each do |thread| cli.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread.name}" if thread.backtrace cli.logger.warn thread.backtrace.join("\n") else cli.logger.warn "" end end } } UNHANDLED_SIGNAL_HANDLER = ->(cli) { cli.logger.info "No signal handler registered, ignoring" } SIGNAL_HANDLERS.default = UNHANDLED_SIGNAL_HANDLER def handle_signal(sig) 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" config[:environment] = @environment 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(@config[: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 @config.merge!(opts) end def boot_application ENV["RACK_ENV"] = ENV["RAILS_ENV"] = environment if File.directory?(@config[: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("#{@config[:require]}/config/environment.rb") end @config[:tag] ||= default_tag else require @config[: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?(@config[:require]) || (File.directory?(@config[:require]) && !File.exist?("#{@config[:require]}/config/application.rb")) logger.info "==================================================================" logger.info " Please point Sidekiq to a Rails application or a Ruby file " logger.info " to load your job classes with -r [DIR|FILE]." logger.info "==================================================================" logger.info @parser die(1) end [:concurrency, :timeout].each do |opt| raise ArgumentError, "#{opt}: #{@config[opt]} is not a valid value" if @config[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 jobs 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 @config.logger.level = ::Logger::DEBUG if @config[: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" require "sidekiq/metrics/tracking" if ENV["SIDEKIQ_METRICS_BETA"] sidekiq-6.5.12/lib/sidekiq/sd_notify.rb000644 001751 001751 00000011332 14551557262 020302 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/web/000755 001751 001751 00000000000 14551557262 016534 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/web/csrf_protection.rb000644 001751 001751 00000013475 14551557262 022276 0ustar00pravipravi000000 000000 # 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.urlsafe_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.urlsafe_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 sidekiq-6.5.12/lib/sidekiq/web/application.rb000644 001751 001751 00000023025 14551557262 021366 0ustar00pravipravi000000 000000 # 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 "/metrics" do q = Sidekiq::Metrics::Query.new @query_result = q.top_jobs erb(:metrics) end get "/metrics/:name" do @name = route_params[:name] q = Sidekiq::Metrics::Query.new @query_result = q.for_job(@name) erb(:metrics_for_job) end get "/busy" do @count = (params["count"] || 100).to_i (@current_page, @total_size, @workset) = page_items(workset, params["page"], @count) 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 sidekiq-6.5.12/lib/sidekiq/web/action.rb000644 001751 001751 00000004015 14551557262 020336 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/web/router.rb000644 001751 001751 00000004301 14551557262 020377 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/web/helpers.rb000644 001751 001751 00000022170 14551557262 020525 0ustar00pravipravi000000 000000 # 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.safe_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 workset @work ||= Sidekiq::WorkSet.new end def processes @processes ||= Sidekiq::ProcessSet.new end # Sorts processes by hostname following the natural sort order def sorted_processes @sorted_processes ||= begin return processes unless processes.all? { |p| p["hostname"] } processes.to_a.sort_by do |process| # Kudos to `shurikk` on StackOverflow # https://stackoverflow.com/a/15170063/575547 process["hostname"].split(/(\d+)/).map { |a| /\d+/.match?(a) ? a.to_i : a } end end 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 (workset.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[: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 sidekiq-6.5.12/lib/sidekiq/job_util.rb000644 001751 001751 00000005731 14551557262 020121 0ustar00pravipravi000000 000000 require "securerandom" require "time" module Sidekiq module JobUtil # These functions encapsulate various job utilities. TRANSIENT_ATTRIBUTES = %w[] 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) end def verify_json(item) job_class = item["wrapped"] || item["class"] if Sidekiq[:on_complex_arguments] == :raise msg = <<~EOM Job arguments to #{job_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[:on_complex_arguments] == :warn Sidekiq.logger.warn <<~EOM unless json_safe?(item) Job arguments to #{job_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"] == "" # remove job attributes which aren't necessary to persist into Redis TRANSIENT_ATTRIBUTES.each { |key| item.delete(key) } item["jid"] ||= SecureRandom.hex(12) item["class"] = item["class"].to_s item["queue"] = item["queue"].to_s 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::Job class, not class name: #{item_class.ancestors.inspect}") unless item_class.respond_to?(:get_sidekiq_options) item_class.get_sidekiq_options else Sidekiq.default_job_options end end private def json_safe?(item) JSON.parse(JSON.dump(item["args"])) == item["args"] end end end sidekiq-6.5.12/lib/sidekiq/testing.rb000644 001751 001751 00000022326 14551557262 017766 0ustar00pravipravi000000 000000 # 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(HardJob.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 job type, so the array for # HardJob.jobs is a straight reference to a real array. # # Queue-based hash: # # { # "default"=>[ # { # "class"=>"TestTesting::HardJob", # "args"=>[1, 2], # "retry"=>true, # "queue"=>"default", # "jid"=>"abc5b065c5c4b27fc1102833", # "created_at"=>1447445554.419934 # } # ] # } # # Job-based hash: # # { # "TestTesting::HardJob"=>[ # { # "class"=>"TestTesting::HardJob", # "args"=>[1, 2], # "retry"=>true, # "queue"=>"default", # "jid"=>"abc5b065c5c4b27fc1102833", # "created_at"=>1447445554.419934 # } # ] # } # # Example: # # require 'sidekiq/testing' # # assert_equal 0, Sidekiq::Queues["default"].size # HardJob.perform_async(:something) # assert_equal 1, Sidekiq::Queues["default"].size # assert_equal :something, Sidekiq::Queues["default"].first['args'][0] # # You can also clear all jobs: # # assert_equal 0, Sidekiq::Queues["default"].size # HardJob.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_class[klass] << job end def jobs_by_queue @jobs_by_queue ||= Hash.new { |hash, key| hash[key] = [] } end def jobs_by_class @jobs_by_class ||= Hash.new { |hash, key| hash[key] = [] } end alias_method :jobs_by_worker, :jobs_by_class def delete_for(jid, queue, klass) jobs_by_queue[queue.to_s].delete_if { |job| job["jid"] == jid } jobs_by_class[klass].delete_if { |job| job["jid"] == jid } end def clear_for(queue, klass) jobs_by_queue[queue.to_s].clear jobs_by_class[klass].clear end def clear_all jobs_by_queue.clear jobs_by_class.clear end end end module Job ## # 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, HardJob.jobs.size # HardJob.perform_async(:something) # assert_equal 1, HardJob.jobs.size # assert_equal :something, HardJob.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 job types: # # 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::Job.clear_all # end # end # # or for acceptance testing, i.e. with cucumber: # # AfterStep do # Sidekiq::Job.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_class[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) inst = new inst.jid = job["jid"] inst.bid = job["bid"] if inst.respond_to?(:bid=) Sidekiq::Testing.server_middleware.invoke(inst, job, job["queue"]) do execute_job(inst, 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 def clear_all Queues.clear_all end # Drain (execute) all queued jobs def drain_all while jobs.any? job_classes = jobs.map { |job| job["class"] }.uniq job_classes.each do |job_class| Sidekiq::Testing.constantize(job_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 sidekiq-6.5.12/lib/sidekiq/web.rb000644 001751 001751 00000007202 14551557262 017062 0ustar00pravipravi000000 000000 # 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" } if ENV["SIDEKIQ_METRICS_BETA"] == "1" DEFAULT_TABS["Metrics"] = "metrics" end 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 sidekiq-6.5.12/lib/sidekiq/component.rb000644 001751 001751 00000002730 14551557262 020310 0ustar00pravipravi000000 000000 module Sidekiq ## # Sidekiq::Component assumes a config instance is available at @config module Component # :nodoc: attr_reader :config 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 config.logger end def redis(&block) config.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 handle_exception(ex, ctx = {}) config.handle_exception(ex, ctx) end def fire_event(event, options = {}) oneshot = options.fetch(:oneshot, true) reverse = options[:reverse] reraise = options[:reraise] arr = config[: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 if oneshot # once we've fired an event, we never fire it again end end end sidekiq-6.5.12/lib/sidekiq/api.rb000644 001751 001751 00000075523 14551557262 017071 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq" require "zlib" require "set" require "base64" if ENV["SIDEKIQ_METRICS_BETA"] require "sidekiq/metrics/deploy" require "sidekiq/metrics/query" end # # Sidekiq's Data API provides a Ruby object model on top # of Sidekiq's runtime data in Redis. This API should never # be used within application code for business logic. # # The Sidekiq server process never uses this API: all data # manipulation is done directly for performance reasons to # ensure we are using Redis as efficiently as possible at # every callsite. # module Sidekiq # Retrieve runtime statistics from Redis regarding # this Sidekiq cluster. # # stat = Sidekiq::Stats.new # stat.processed 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 # @api private 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 # @api private 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 # @api private def fetch_stats! fetch_stats_fast! fetch_stats_slow! end # @api private 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 RedisConnection.adapter::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 ## # Represents a queue within Sidekiq. # Allows enumeration of all jobs within the queue # and deletion of jobs. NB: this queue data is real-time # and is changing within Redis moment by moment. # # 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 ## # Fetch all known queues within Redis. # # @return [Array] def self.all Sidekiq.redis { |c| c.sscan_each("queues").to_a }.sort.map { |q| Sidekiq::Queue.new(q) } end attr_reader :name # @param name [String] the name of the queue def initialize(name = "default") @name = name.to_s @rname = "queue:#{name}" end # The current size of the queue within Redis. # This value is real-time and can change between calls. # # @return [Integer] the size def size Sidekiq.redis { |con| con.llen(@rname) } end # @return [Boolean] if the queue is currently paused def paused? false end ## # Calculates this queue's latency, the difference in seconds since the oldest # job in the queue was enqueued. # # @return [Float] in seconds 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. # # @param jid [String] the job_id to look for # @return [Sidekiq::JobRecord] # @return [nil] if not found def find_job(jid) detect { |j| j.jid == jid } end # delete all jobs within this queue # @return [Boolean] true def clear Sidekiq.redis do |conn| conn.multi do |transaction| transaction.unlink(@rname) transaction.srem("queues", [name]) end end true end alias_method :💣, :clear # :nodoc: # @api private def as_json(options = nil) {name: name} # 5336 end end ## # Represents a pending job within a Sidekiq queue. # # The job should be considered immutable but may be # removed from the queue via JobRecord#delete. class JobRecord # the parsed Hash of job data # @!attribute [r] Item attr_reader :item # the underlying String in Redis # @!attribute [r] Value attr_reader :value # the queue associated with this job # @!attribute [r] Queue attr_reader :queue # :nodoc: # @api private def initialize(item, queue_name = nil) @args = nil @value = item @item = item.is_a?(Hash) ? item : parse(item) @queue = queue_name || @item["queue"] end # :nodoc: # @api private 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 # This is the job class which Sidekiq will execute. If using ActiveJob, # this class will be the ActiveJob adapter class rather than a specific job. 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 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 # Access arbitrary attributes within the job hash 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. # TODO is this still necessary? Zeitwerk reloader should handle? 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 # Represents a job within a Redis sorted set where the score # represents a timestamp associated with the job. This timestamp # could be the scheduled time for it to run (e.g. scheduled set), # or the expiration date after which the entry should be deleted (e.g. dead set). class SortedEntry < JobRecord attr_reader :score attr_reader :parent # :nodoc: # @api private def initialize(parent, score, item) super(item) @score = Float(score) @parent = parent end # The timestamp associated with this entry def at Time.at(score).utc end # remove this entry from the sorted set def delete if @value @parent.delete_by_value(@parent.name, @value) else @parent.delete_by_jid(score, jid) end end # Change the scheduled time for this job. # # @param at [Time] the new timestamp for this job def reschedule(at) Sidekiq.redis do |conn| conn.zincrby(@parent.name, at.to_f - @score, Sidekiq.dump_json(@item)) end end # Enqueue this job from the scheduled or dead set so it will # be executed at some point in the near future. def add_to_queue remove_job do |message| msg = Sidekiq.load_json(message) Sidekiq::Client.push(msg) end end # enqueue this job from the retry set so it will be executed # at some point in the near future. 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 # Move this job from its current set into 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 # Base class for all sorted sets within Sidekiq. class SortedSet include Enumerable # Redis key of the set # @!attribute [r] Name attr_reader :name # :nodoc: # @api private def initialize(name) @name = name @_size = size end # real-time size of the set, will change def size Sidekiq.redis { |c| c.zcard(name) } end # Scan through each element of the sorted set, yielding each to the supplied block. # Please see Redis's SCAN documentation for implementation details. # # @param match [String] a snippet or regexp to filter matches. # @param count [Integer] number of elements to retrieve at a time, default 100 # @yieldparam [Sidekiq::SortedEntry] each entry 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 # @return [Boolean] always true def clear Sidekiq.redis do |conn| conn.unlink(name) end true end alias_method :💣, :clear # :nodoc: # @api private def as_json(options = nil) {name: name} # 5336 end end # Base class for all sorted sets which contain jobs, e.g. scheduled, retry and dead. # Sidekiq Pro and Enterprise add additional sorted sets which do not contain job data, # e.g. Batches. class JobSet < SortedSet # Add a job with the associated timestamp to this set. # @param timestamp [Time] the score for the job # @param job [Hash] the job data def schedule(timestamp, job) Sidekiq.redis do |conn| conn.zadd(name, timestamp.to_f.to_s, Sidekiq.dump_json(job)) 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, withscores: 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. # # @param score [Time,Range] a specific timestamp or range # @param jid [String, optional] find a specific JID within the score # @return [Array] any results found, can be empty 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, withscores: 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 slow O(n) operation*. Do not use for app logic. # # @param jid [String] the job identifier # @return [SortedEntry] the record or nil 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 # :nodoc: # @api private def delete_by_value(name, value) Sidekiq.redis do |conn| ret = conn.zrem(name, value) @_size -= 1 if ret ret end end # :nodoc: # @api private 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 ## # The set of scheduled jobs within Sidekiq. # Based on this, you can search/filter for jobs. Here's an # example where I'm selecting jobs based on some complex logic # and deleting them from the scheduled set. # # 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 ## # The set 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 # Enqueues all jobs pending within the retry set. def retry_all each(&:retry) while size > 0 end # Kills all jobs pending within the retry set. def kill_all each(&:kill) while size > 0 end end ## # The set of dead jobs within Sidekiq. Dead jobs have failed all of # their retries and are helding in this set pending some sort of manual # fix. They will be removed after 6 months (dead_timeout) if not. # class DeadSet < JobSet def initialize super "dead" end # Add the given job to the Dead set. # @param message [String] the job data as JSON 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 # Enqueue all dead jobs def retry_all each(&:retry) while size > 0 end # The maximum size of the Dead set. Older entries will be trimmed # to stay within this limit. Default value is 10,000. def self.max_jobs Sidekiq[:dead_max_jobs] end # The time limit for entries within the Dead set. Older entries will be thrown away. # Default value is six months. def self.timeout Sidekiq[: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. # # @yieldparam [Sidekiq::Process] # class ProcessSet include Enumerable # :nodoc: # @api private def initialize(clean_plz = true) cleanup if clean_plz end # Cleans up dead processes recorded in Redis. # Returns the number of processes cleaned. # :nodoc: # @api private def cleanup # dont run cleanup more than once per minute return 0 unless Sidekiq.redis { |conn| conn.set("process_cleanup", "1", nx: true, ex: 60) } count = 0 Sidekiq.redis do |conn| procs = conn.sscan_each("processes").to_a 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. # @return [Integer] current number of registered Sidekiq processes 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. # @return [Integer] the sum of process concurrency def total_concurrency sum { |x| x["concurrency"].to_i } end # @return [Integer] total amount of RSS memory consumed by Sidekiq processes 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. # @return [String] Identity of cluster leader # @return [String] empty string if no leader 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 # :nodoc: # @api private 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 # Signal this process to stop processing new jobs. # It will continue to execute jobs it has already fetched. # This method is *asynchronous* and it can take 5-10 # seconds for the process to quiet. def quiet! signal("TSTP") end # Signal this process to shutdown. # It will shutdown within its configured :timeout value, default 25 seconds. # This method is *asynchronous* and it can take 5-10 # seconds for the process to start shutting down. def stop! signal("TERM") end # Signal this process to log backtraces for all threads. # Useful if you have a frozen or deadlocked process which is # still sending a heartbeat. # This method is *asynchronous* and it can take 5-10 seconds. def dump_threads signal("TTIN") end # @return [Boolean] true if this process is quiet or shutting down 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 = [] procs = nil all_works = nil Sidekiq.redis do |conn| procs = conn.sscan_each("processes").to_a.sort all_works = conn.pipelined do |pipeline| procs.each do |key| pipeline.hgetall("#{key}:work") end end end procs.zip(all_works).each do |key, workers| workers.each_pair do |tid, json| next if json.empty? 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 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 sidekiq-6.5.12/lib/sidekiq/client.rb000644 001751 001751 00000021031 14551557262 017557 0ustar00pravipravi000000 000000 # 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' => 'SomeJob', '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 job 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 # job options. Otherwise, they will be based on the job class's options. # # Any options valid for a job 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' => MyJob, 'args' => ['foo', 1, :bat => 'bar']) # def push(item) normed = normalize_item(item) payload = middleware.invoke(item["class"], normed, normed["queue"], @redis_pool) do normed end if payload verify_json(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 jid = items.delete("jid") raise ArgumentError, "Explicitly passing 'jid' when pushing more than one job is not supported" if jid && args.size > 1 normed = normalize_item(items) payloads = args.map.with_index { |job_args, index| copy = normed.merge("args" => job_args, "jid" => SecureRandom.hex(12)) copy["at"] = (at.is_a?(Array) ? at[index] : at) if at result = middleware.invoke(items["class"], copy, copy["queue"], @redis_pool) do verify_json(copy) copy end 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 # SomeJob.perform_async(1,2,3) # SomeOtherJob.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 Sidekiq::Job#client_push. # # Example usage: # Sidekiq::Client.enqueue(MyJob, '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, MyJob, '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, MyJob, '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, MyJob, '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 RedisConnection.adapter::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.flat_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 end end sidekiq-6.5.12/lib/sidekiq/launcher.rb000644 001751 001751 00000017206 14551557262 020113 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq/manager" require "sidekiq/fetch" require "sidekiq/scheduled" require "sidekiq/ring_buffer" module Sidekiq # The Launcher starts the Manager and Poller threads and provides the process heartbeat. class Launcher include Sidekiq::Component STATS_TTL = 5 * 365 * 24 * 60 * 60 # 5 years PROCTITLES = [ proc { "sidekiq" }, proc { Sidekiq::VERSION }, proc { |me, data| data["tag"] }, proc { |me, data| "[#{Processor::WORK_STATE.size} of #{data["concurrency"]} busy]" }, proc { |me, data| "stopping" if me.stopping? } ] attr_accessor :manager, :poller, :fetcher def initialize(options) @config = options options[:fetch] ||= BasicFetch.new(options) @manager = Sidekiq::Manager.new(options) @poller = Sidekiq::Scheduled::Poller.new(options) @done = false 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 this Sidekiq instance. Waits up to the deadline for all jobs to complete. def stop deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + @config[:timeout] @done = true @manager.quiet @poller.terminate @manager.stop(deadline) # Requeue everything in case there was a thread which fetched a job while the process was stopped. # This call is a no-op in Sidekiq but necessary for Sidekiq Pro. strategy = @config[:fetch] strategy.bulk_requeue([], @config) clear_heartbeat end def stopping? @done end private unless $TESTING BEAT_PAUSE = 5 def start_heartbeat loop do heartbeat sleep BEAT_PAUSE end logger.info("Heartbeat stopping...") end def clear_heartbeat flush_stats # 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. redis do |conn| conn.pipelined do |pipeline| pipeline.srem("processes", [identity]) pipeline.unlink("#{identity}:work") end end rescue # best effort, ignore network errors end def heartbeat $0 = PROCTITLES.map { |proc| proc.call(self, to_data) }.compact.join(" ") ❤ end def 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 def ❤ key = identity fails = procd = 0 begin fails = Processor::FAILURE.reset procd = Processor::PROCESSED.reset curstate = Processor::WORK_STATE.dup nowdate = Time.now.utc.strftime("%Y-%m-%d") 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) end # work is the current set of executing jobs work_key = "#{key}:work" conn.pipelined do |transaction| transaction.unlink(work_key) curstate.each_pair do |tid, hash| transaction.hset(work_key, tid, Sidekiq.dump_json(hash)) end transaction.expire(work_key, 60) end end rtt = check_rtt fails = procd = 0 kb = memory_usage(::Process.pid) _, exists, _, _, msg = 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.to_s, "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 fire_event(:beat, oneshot: false) 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 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 } 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-saturated; reduce your concurrency and/or 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" => @config[:tag] || "", "concurrency" => @config[:concurrency], "queues" => @config[:queues].uniq, "labels" => @config[: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 sidekiq-6.5.12/lib/sidekiq/rails.rb000644 001751 001751 00000004312 14551557262 017416 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq/job" 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::Job::Options unless respond_to?(:sidekiq_options) end end config.before_configuration do dep = ActiveSupport::Deprecation.new("7.0", "Sidekiq") dep.deprecate_methods(Sidekiq.singleton_class, default_worker_options: :default_job_options, "default_worker_options=": :default_job_options=) 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 |config| config[:reloader] = Sidekiq::Rails::Reloader.new # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`, # it will appear in the Sidekiq console with all of the job context. unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout) if ::Rails::VERSION::STRING < "7.1" ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger)) else ::Rails.logger.broadcast_to(config.logger) end end end end end end sidekiq-6.5.12/lib/sidekiq/fetch.rb000644 001751 001751 00000004660 14551557262 017403 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq" require "sidekiq/component" module Sidekiq # :nodoc: class BasicFetch include Sidekiq::Component # 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, :config) { def acknowledge # nothing to do end def queue_name queue.delete_prefix("queue:") end def requeue config.redis do |conn| conn.rpush(queue, job) end end } def initialize(config) raise ArgumentError, "missing queue list" unless config[:queues] @config = config @strictly_ordered_queues = !!@config[:strict] @queues = @config[:queues].map { |q| "queue:#{q}" } if @strictly_ordered_queues @queues.uniq! @queues << {timeout: 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 queue, job = redis { |conn| conn.brpop(*qs) } UnitOfWork.new(queue, job, config) if queue end def bulk_requeue(inprogress, options) return if inprogress.empty? 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 redis do |conn| conn.pipelined do |pipeline| jobs_to_requeue.each do |queue, jobs| pipeline.rpush(queue, jobs) end end end logger.info("Pushed #{inprogress.size} jobs back to Redis") rescue => ex 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: TIMEOUT} permute end end end end sidekiq-6.5.12/lib/sidekiq/testing/000755 001751 001751 00000000000 14551557262 017434 5ustar00pravipravi000000 000000 sidekiq-6.5.12/lib/sidekiq/testing/inline.rb000644 001751 001751 00000001163 14551557262 021240 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "sidekiq/testing" ## # The Sidekiq inline infrastructure overrides perform_async so that it # actually calls perform instead. This allows jobs 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 ExternalJob # include Sidekiq::Job # # def perform # $external_variable = 1 # end # end # # assert_equal 0, $external_variable # ExternalJob.perform_async # assert_equal 1, $external_variable # Sidekiq::Testing.inline! sidekiq-6.5.12/lib/sidekiq/job_logger.rb000644 001751 001751 00000002351 14551557262 020416 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/lib/sidekiq/logger.rb000644 001751 001751 00000007203 14551557262 017565 0ustar00pravipravi000000 000000 # 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) current[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 LEVELS.each do |level, numeric_level| define_method("#{level}?") do local_level.nil? ? super() : local_level <= numeric_level end 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 sidekiq-6.5.12/lib/sidekiq/redis_connection.rb000644 001751 001751 00000013243 14551557262 021634 0ustar00pravipravi000000 000000 # frozen_string_literal: true require "connection_pool" require "redis" require "uri" module Sidekiq module RedisConnection class RedisAdapter BaseError = Redis::BaseError CommandError = Redis::CommandError def initialize(options) warn("Usage of the 'redis' gem within Sidekiq itself is deprecated, Sidekiq 7.0 will only use the new, simpler 'redis-client' gem", caller) if ENV["SIDEKIQ_REDIS_CLIENT"] == "1" @options = options end def new_client 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 private 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 # 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 end @adapter = RedisAdapter class << self attr_reader :adapter # RedisConnection.adapter = :redis # RedisConnection.adapter = :redis_client def adapter=(adapter) raise "no" if adapter == self result = case adapter when :redis RedisAdapter when Class adapter else require "sidekiq/#{adapter}_adapter" nil end @adapter = result if result end 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[:concurrency] + 5 elsif ENV["RAILS_MAX_THREADS"] Integer(ENV["RAILS_MAX_THREADS"]) else 5 end verify_sizing(size, Sidekiq[:concurrency]) if Sidekiq.server? pool_timeout = symbolized_options[:pool_timeout] || 1 log_info(symbolized_options) redis_config = adapter.new(symbolized_options) ConnectionPool.new(timeout: pool_timeout, size: size) do redis_config.new_client end end private # Sidekiq needs many 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. Your pool has #{size} connections but must have at least #{concurrency + 2}" if size < (concurrency + 2) 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 #{adapter.name} options #{scrubbed_options}") else Sidekiq.logger.debug("#{Sidekiq::NAME} client with #{adapter.name} 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 sidekiq-6.5.12/lib/sidekiq/monitor.rb000644 001751 001751 00000007246 14551557262 020004 0ustar00pravipravi000000 000000 #!/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 abort "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 sidekiq-6.5.12/lib/sidekiq/processor.rb000644 001751 001751 00000017415 14551557262 020333 0ustar00pravipravi000000 000000 # frozen_string_literal: true 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 job class # b. run the middleware chain # c. call #perform # # A Processor can exit due to shutdown or due to # an error during job execution. # # 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 Sidekiq::Component attr_reader :thread attr_reader :job def initialize(options, &block) @callback = block @down = false @done = false @job = nil @thread = nil @config = options @strategy = options[:fetch] @reloader = options[:reloader] || proc { |&block| block.call } @job_logger = (options[:job_logger] || Sidekiq::JobLogger).new @retrier = Sidekiq::JobRetry.new(options) 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 @callback.call(self) rescue Sidekiq::Shutdown @callback.call(self) rescue Exception => ex @callback.call(self, ex) end def process_one(&block) @job = fetch process(@job) if @job @job = nil end def get_one uow = @strategy.retrieve_work if @down logger.info { "Redis is online, #{::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @down} sec downtime" } @down = nil end uow 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"]) inst = klass.new inst.jid = job_hash["jid"] @retrier.local(inst, jobstr, queue) do yield inst end end end end end end end def process(uow) jobstr = uow.job queue = uow.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}) now = Time.now.to_f config.redis do |conn| conn.multi do |xa| xa.zadd("dead", now.to_s, jobstr) xa.zremrangebyscore("dead", "-inf", now - config[:dead_timeout_in_seconds]) xa.zremrangebyrank("dead", 0, - config[:dead_max_jobs]) end end return uow.acknowledge end ack = false begin dispatch(job_hash, queue, jobstr) do |inst| @config.server_middleware.invoke(inst, job_hash, queue) do execute_job(inst, 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}) 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 uow.acknowledge end end end end def execute_job(inst, cloned_args) inst.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 SharedWorkState def initialize @work_state = {} @lock = Mutex.new end def set(tid, hash) @lock.synchronize { @work_state[tid] = hash } end def delete(tid) @lock.synchronize { @work_state.delete(tid) } end def dup @lock.synchronize { @work_state.dup } end def size @lock.synchronize { @work_state.size } end def clear @lock.synchronize { @work_state.clear } end end PROCESSED = Counter.new FAILURE = Counter.new WORK_STATE = SharedWorkState.new def stats(jobstr, queue) WORK_STATE.set(tid, {queue: queue, payload: jobstr, run_at: Time.now.to_i}) begin yield rescue Exception FAILURE.incr raise ensure WORK_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 sidekiq-6.5.12/README.md000644 001751 001751 00000007440 14551557262 015044 0ustar00pravipravi000000 000000 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 ----------------- bundle add 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) sidekiq-6.5.12/web/000755 001751 001751 00000000000 14551557262 014335 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/locales/000755 001751 001751 00000000000 14551557262 015757 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/locales/hi.yml000644 001751 001751 00000006276 14551557262 017115 0ustar00pravipravi000000 000000 # 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: सर्वेक्षण अंतराल sidekiq-6.5.12/web/locales/pt-br.yml000644 001751 001751 00000004351 14551557262 017531 0ustar00pravipravi000000 000000 # 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 Utilization: Utilização 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 fila %{queue}? Isso irá deletar todas as tarefas desta fila. Queues: Filas Size: Tamanho Actions: Ações NextRetry: Próxima Tentativa RetryCount: Número de Tentativas RetryNow: Tentar novamente agora Kill: Matar LastRetry: Última tentativa OriginallyFailed: Falhou originalmente AreYouSure: Tem certeza? DeleteAll: Apagar tudo RetryAll: Tentar tudo novamente KillAll: Matar todas 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 Process: Processo Processes: Processos Name: Nome Thread: Thread Threads: Threads Jobs: Tarefas Paused: Pausado Stop: Parar Quiet: Silenciar StopAll: Parar Todos QuietAll: Silenciar Todos PollingInterval: Intervalo de Polling Plugins: Plug-ins NotYetEnqueued: Ainda não enfileirado CreatedAt: Criado em BackToApp: De volta ao aplicativo Latency: Latência Pause: Pausar Unpause: Despausar sidekiq-6.5.12/web/locales/sv.yml000644 001751 001751 00000003423 14551557262 017134 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/lt.yml000644 001751 001751 00000004370 14551557262 017125 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/vi.yml000644 001751 001751 00000005460 14551557262 017125 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/cs.yml000644 001751 001751 00000004236 14551557262 017114 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/uk.yml000644 001751 001751 00000005436 14551557262 017131 0ustar00pravipravi000000 000000 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: Ще не в черзі sidekiq-6.5.12/web/locales/el.yml000644 001751 001751 00000007421 14551557262 017106 0ustar00pravipravi000000 000000 # 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: Υπό επεξεργασία Utilization: Σε χρήση 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: Επανάληψη Τώρα # Kill: Kill LastRetry: Τελευταία Προσπάθεια OriginallyFailed: Αρχικές Αποτυχίες AreYouSure: Είστε σίγουρος; DeleteAll: Διαγραφή Όλων RetryAll: Επανάληψη Όλων # KillAll: Kill All 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: Αδρανείς Process: Διεργασία Processes: Διεργασίες Name: Όνομα Thread: Νήμα Threads: Νήματα Jobs: Εργασίες Paused: Σε παύση Stop: Διακοπή Quiet: Σίγαση StopAll: Διακοπή Όλων QuietAll: Σίγαση Όλων PollingInterval: Συχνότητα Ανανέωσης Plugins: Πρόσθετα NotYetEnqueued: Δεν προστέθηκε στην στοίβα ακόμη CreatedAt: Δημιουργήθηκε στις BackToApp: Πίσω στην Εφαρμογή Latency: Καθυστέρηση Pause: Παύση Unpause: Κατάργηση Παύσης Metrics: Μετρήσεις NoDataFound: Δεν βρέθηκαν δεδομένα ExecutionTime: Συνολικός Χρόνος Εκτέλεσης AvgExecutionTime: Μέσος Χρόνος Εκτέλεσης # Context: Context sidekiq-6.5.12/web/locales/nl.yml000644 001751 001751 00000003533 14551557262 017117 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/ar.yml000644 001751 001751 00000005452 14551557262 017112 0ustar00pravipravi000000 000000 # 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: متابعةsidekiq-6.5.12/web/locales/da.yml000644 001751 001751 00000003273 14551557262 017073 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/fa.yml000644 001751 001751 00000004754 14551557262 017102 0ustar00pravipravi000000 000000 # 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: برگشت به برنامه sidekiq-6.5.12/web/locales/de.yml000644 001751 001751 00000004226 14551557262 017076 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/ur.yml000644 001751 001751 00000005524 14551557262 017136 0ustar00pravipravi000000 000000 # 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: ﻭاپﺱ صفحۂ اﻭﻝ پرsidekiq-6.5.12/web/locales/en.yml000644 001751 001751 00000004571 14551557262 017113 0ustar00pravipravi000000 000000 # 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 Metrics: Metrics NoDataFound: No data found ExecutionTime: Total Execution Time AvgExecutionTime: Average Execution Time Context: Context Bucket: Bucket NoJobMetricsFound: No recent job metrics were found sidekiq-6.5.12/web/locales/zh-cn.yml000644 001751 001751 00000004553 14551557262 017530 0ustar00pravipravi000000 000000 # 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: 执行中 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: 一周 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: 取消暂停 Metrics: 指标 NoDataFound: 无数据 TotalExecutionTime: 总执行时间 AvgExecutionTime: 平均执行时间 Context: 上下文 Bucket: 桶 NoJobMetricsFound: 无任务相关指标数据 sidekiq-6.5.12/web/locales/ta.yml000644 001751 001751 00000010200 14551557262 017077 0ustar00pravipravi000000 000000 # 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: வாக்குப்பதிவு இடைவெளிsidekiq-6.5.12/web/locales/ru.yml000644 001751 001751 00000005646 14551557262 017143 0ustar00pravipravi000000 000000 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: Возобновить sidekiq-6.5.12/web/locales/pl.yml000644 001751 001751 00000003215 14551557262 017116 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/fr.yml000644 001751 001751 00000004454 14551557262 017120 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/ko.yml000644 001751 001751 00000003434 14551557262 017117 0ustar00pravipravi000000 000000 # 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: 작업 sidekiq-6.5.12/web/locales/zh-tw.yml000644 001751 001751 00000004713 14551557262 017560 0ustar00pravipravi000000 000000 # 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: 忙碌 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: 一週 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: 取消暫停 Metrics: 計量 NoDataFound: 找無資料 TotalExecutionTime: 總執行時間 AvgExecutionTime: 平均執行時間 Context: 上下文 Bucket: 桶 NoJobMetricsFound: 找無工作相關計量資料 sidekiq-6.5.12/web/locales/pt.yml000644 001751 001751 00000003505 14551557262 017130 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/ja.yml000644 001751 001751 00000005406 14551557262 017101 0ustar00pravipravi000000 000000 # 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: 一時停止を解除 Metrics: メトリクス NoDataFound: データが見つかりませんでした ExecutionTime: 合計実行時間 AvgExecutionTime: 平均実行時間 Context: コンテキスト Bucket: バケット NoJobMetricsFound: 直近のジョブメトリクスが見つかりませんでした sidekiq-6.5.12/web/locales/es.yml000644 001751 001751 00000004355 14551557262 017120 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/it.yml000644 001751 001751 00000003500 14551557262 017114 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/nb.yml000644 001751 001751 00000003701 14551557262 017102 0ustar00pravipravi000000 000000 # 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 sidekiq-6.5.12/web/locales/he.yml000644 001751 001751 00000005007 14551557262 017100 0ustar00pravipravi000000 000000 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: חזרה לאפליקציה sidekiq-6.5.12/web/views/000755 001751 001751 00000000000 14551557262 015472 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/views/retry.erb000644 001751 001751 00000002152 14551557262 017331 0ustar00pravipravi000000 000000 <%= 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') %>
sidekiq-6.5.12/web/views/scheduled.erb000644 001751 001751 00000003665 14551557262 020136 0ustar00pravipravi000000 000000

<%= 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 %> sidekiq-6.5.12/web/views/_paging.erb000644 001751 001751 00000001532 14551557262 017571 0ustar00pravipravi000000 000000 <% if @total_size > @count %> <% end %> sidekiq-6.5.12/web/views/_job_info.erb000644 001751 001751 00000004716 14551557262 020120 0ustar00pravipravi000000 000000

<%= 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'] %>
sidekiq-6.5.12/web/views/_nav.erb000644 001751 001751 00000003671 14551557262 017116 0ustar00pravipravi000000 000000 sidekiq-6.5.12/web/views/retries.erb000644 001751 001751 00000006243 14551557262 017646 0ustar00pravipravi000000 000000

<%= 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 %> sidekiq-6.5.12/web/views/_poll_link.erb000644 001751 001751 00000000323 14551557262 020304 0ustar00pravipravi000000 000000 <% if current_path != '' %> <%= t('LivePoll') %> <%= t('StopPolling') %> <% end %> sidekiq-6.5.12/web/views/_status.erb000644 001751 001751 00000000166 14551557262 017651 0ustar00pravipravi000000 000000 <%= t(current_status) %> sidekiq-6.5.12/web/views/layout.erb000644 001751 001751 00000003207 14551557262 017503 0ustar00pravipravi000000 000000 <%= environment_title_prefix %><%= Sidekiq::NAME %> <% if rtl? %> <% end %> <% if rtl? %> <% end %> <%= display_custom_head %> <%= erb :_nav %>
<%= erb :_summary %>
<%= yield %>
<%= erb :_footer %> sidekiq-6.5.12/web/views/dead.erb000644 001751 001751 00000002136 14551557262 017063 0ustar00pravipravi000000 000000 <%= 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') %>
sidekiq-6.5.12/web/views/_summary.erb000644 001751 001751 00000003050 14551557262 020016 0ustar00pravipravi000000 000000 sidekiq-6.5.12/web/views/morgue.erb000644 001751 001751 00000005421 14551557262 017464 0ustar00pravipravi000000 000000

<%= 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 %> sidekiq-6.5.12/web/views/queue.erb000644 001751 001751 00000004473 14551557262 017320 0ustar00pravipravi000000 000000

<%= 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') %> <%= t('Context') %>
<%= @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 %>
<%= h(job["cattr"].inspect) if job["cattr"]&.any? %>
<%= csrf_tag %>
<%= erb :_paging, locals: { url: "#{root_path}queues/#{CGI.escape(@name)}" } %> sidekiq-6.5.12/web/views/queues.erb000644 001751 001751 00000003262 14551557262 017476 0ustar00pravipravi000000 000000

<%= 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 %>
sidekiq-6.5.12/web/views/metrics_for_job.erb000644 001751 001751 00000005425 14551557262 021340 0ustar00pravipravi000000 000000 <% job_result = @query_result.job_results[@name] hist_totals = job_result.hist.values.first.zip(*job_result.hist.values[1..-1]).map(&:sum) bucket_labels =Sidekiq::Metrics::Histogram::LABELS bucket_intervals =Sidekiq::Metrics::Histogram::BUCKET_INTERVALS.reverse # Replace INFINITY since it can't be represented as JSON bucket_intervals[0] = bucket_intervals[1] * 2 %> <% if job_result.totals["s"] > 0 %>

<%= t(:metrics).to_s.titleize %> / <%= h @name %>

Histogram summary

Performance over time

<% @query_result.buckets.reverse.each do |bucket| %> <% if (total_sec = job_result.series.dig("s", bucket)) > 0 %> <% else %> <% end %> <% end %>
<%= t('Time') %> <%= t('Processed') %> <%= t('Failed') %> <%= t('ExecutionTime') %> <%= t('AvgExecutionTime') %>
<%= bucket %> <%= job_result.series.dig("p", bucket) %> <%= job_result.series.dig("f", bucket) %><%= total_sec.round(2) %> seconds <%= job_result.series_avg("s")[bucket].round(2) %> seconds

Data from <%= @query_result.starts_at %> to <%= @query_result.ends_at %>

<% else %>

<%= t(:metrics).to_s.titleize %> / <%= h @name %>

<%= t('NoJobMetricsFound') %>
<% end %> sidekiq-6.5.12/web/views/busy.erb000644 001751 001751 00000011221 14551557262 017143 0ustar00pravipravi000000 000000

<%= t('Status') %>

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

<%= t('Processes') %>

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

<%= t('Threads') %>

<%= ws = workset.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 %> <% sorted_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') %>

<% if @workset.size > 0 && @total_size > @count %>
<%= erb :_paging, locals: { url: "#{root_path}busy" } %>
<% end %>
<% @workset.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'])) %>
sidekiq-6.5.12/web/views/metrics.erb000644 001751 001751 00000004546 14551557262 017643 0ustar00pravipravi000000 000000

Total execution time

<% table_limit = 20 chart_limit = 5 job_results = @query_result.job_results.sort_by { |(kls, jr)| jr.totals["s"] }.reverse.first(table_limit) visible_kls = job_results.first(chart_limit).map(&:first) %>

Most Time-Consuming Jobs

<% if job_results.any? %> <% job_results.each_with_index do |(kls, jr), i| %> <% end %> <% else %> <% end %>
<%= t('Name') %> <%= t('Processed') %> <%= t('Failed') %> <%= t('ExecutionTime') %> <%= t('AvgExecutionTime') %>
<% id = "metrics-swatch-#{kls}" %> <%= kls %>
<%= jr.dig("totals", "p") %> <%= jr.dig("totals", "f") %> <%= jr.dig("totals", "s").round(2) %> seconds <%= jr.total_avg("s").round(2) %> seconds
<%= t("NoDataFound") %>

Data from <%= @query_result.starts_at %> to <%= @query_result.ends_at %>

sidekiq-6.5.12/web/views/scheduled_job_info.erb000644 001751 001751 00000000742 14551557262 021774 0ustar00pravipravi000000 000000 <%= erb :_job_info, locals: { job: @job, type: :scheduled } %>
<%= csrf_tag %> <%= t('GoBack') %>
sidekiq-6.5.12/web/views/dashboard.erb000644 001751 001751 00000006147 14551557262 020123 0ustar00pravipravi000000 000000

<%= 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 %>
sidekiq-6.5.12/web/views/_footer.erb000644 001751 001751 00000001352 14551557262 017622 0ustar00pravipravi000000 000000 sidekiq-6.5.12/web/assets/000755 001751 001751 00000000000 14551557262 015637 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/assets/images/000755 001751 001751 00000000000 14551557262 017104 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/assets/images/favicon.ico000644 001751 001751 00000012466 14551557262 021236 0ustar00pravipravi000000 000000  h& (  @2222m2222222222!222222222222222222222222'22A22222222K22?22222222C222222_22!222222O2222222222222222222222222222A222222222222222222W22222222222222]2222m22222222I22i222222i222222 2222222222%2212222C222272222222222e22_222222222222[2222222222{22 22( @ 2222#2222222222222222222222222222U22222212222w222222!22u222222522u22222222s22222222s22222222s2222222222s22222222K2222222222y22222222Q222222 22S22222222222222a22222222222222e2222222222O2222222222222222222222222222222222u222222222222222222222222?2222222222222222222222222222222222222222222222222222222222222222222222K22222222222222222222222222222222K22222222222222222222222222222222222222 222222K222222222222222222222222222222222222i22 22222222222222222222'22E2222222222222222222222222222222222222222[2222222222222222+222222222222222222222222-2222222222222222 2222222222222222222222W222222222222222222222222S222222-222222 222222{22222272222S2222G222222222222C2222 22 222222O2222 22 222222+22 222222Y22;2222222222c2222q2222222222%222222922/2222sidekiq-6.5.12/web/assets/images/apple-touch-icon.png000644 001751 001751 00000036352 14551557262 022772 0ustar00pravipravi000000 000000 PNG  IHDRH۱gAMA asRGBFPLTEň===ʂǔUUU𒒒٫դ)))ɹ999###MMM}}}瞞,,,饥 lll⍍hhhxxx[[[rrrttt666BAA ___{{{]]]EEE444111///%%% oooPPP???fffaaaIIIvvvWWWSSSjjj(ZZZ Ö4GGGddd=#KKKcccX Dh -_ Jԛ| OR b ̊swϑxا⽾ߵ!ܮr SW,2ǀimҸ]a9?EK|Q$'W67fhn94:"@^Ҩs xH$C^O(1% o)u,x x(}d3E>T_xJ|R;UD %e%@fTLBW /W6PR V `27pQ /j->̓ ጪ@>DӤ֣-r7*H |gjMd@iKaFF[<H#/ ._}r i\i}'-@= }t ax %.@xB ('.@B@gB3q`l#.@xB ,jC@Ր Q̆V QLj%0@J $>H!\D@B CZAp#h@ WC BN@0A@#@'6@2 ^L < @"!6@2h @ `rKR H !0 i0 GXP@():" z𘭼1q@#n ܷ$/@ p#Q |li sh&,@E_P@~ w !z!)@̋@@3']$@ orD@ y=#)@y@} y-"(@"Y2@-Aގ @$ Hd3B;;f~n%;b3 >W-'8nenҔMREq՘Եp2edGQͨ9B^&O"ӘQͦpL^'V?֙9M `LqDssT@J {wO;=pLeѠ; o He1;E@%";EG̰&{ 'fX$*zYi 4[c$*:Y GlVHTz^ W1DyRNڬh!.y9_0Qw `)tڥxLQ9"h֞|'Wύuq`|YL0|@Yn'F@XFsS7UM=_m;'޿cځ! qPmbK{*tӷ%_) GX{MxK2i,%*mHR9ףo\F뿗rca'|_(\|̹Qen$TԞ5v?8O9Kʼn}0E7B3;&\e>l@e$b9taKܤ'NPg4?&0H)o(\("?sդRKRef$>L ZB$ f~TMZ>F{MPN =+l{ɢ({Lk3moK; ɽrgAEٯPU7@Bmy mloI:ݥ4?\ UWWtPۛ;@ )& O*6Ϋ_p veOv2R^[Rx5I~Guzն@b6^QXJl ~b_AÏa .Qs{>Qcԥoib #Dma;ӯ&,$"@@" E @6" *#Dݠ"(FЎ:3g3ӟӜӟ{l\![u}=-/O+̹`W[8)&6͋[o\J{*-`k"ZfbƮJ.H5"jqzN;[Ttٴ_5qz8Gf-^5MuThc5,2,n϶S.?16Ndz{0:@ 0";ktմYsGN9:,P]AI3>WW0,^۽л~Feq<{%"|/Z^q4v;+i~.=FǛ&+sû*7,~&%U=f~(W`aTxKj ߐjlP7q6VjFsL/ꀶ'G'Qnê!yz }Nw&g6 U4 uªE#M[GVdW.'o~luhssxaȡq;0[e/̒ JֿagT~ӨRm߽3?V>?^793 4/>\_/T'I{(:ͯ>]\XX3Ol[b%Yy2'Nq?i",7 `sy fr R˴5, ԘlYmr#ʧJ.n) ߚ9#e"jE /Xĕ%md7ÿ=_.@TisKI_'ώ[ }kd6C8:q>Uy,\jAV9OI6m#P /i>G't{ʈsMs[Hw bx]dM/Uek4&=x݇tLwܢ>NqIOͺAg] ĨfൾEղJ{T_sfI%D5Z @K1w8y+9"XrʀB$}ͅj}-yk6 d uW  4k7?:nϒPsjc_:ʪ0ͣw&]EyI%y@n[ ic`qQ (vkc dII瞤J.jsKLjGգ`qg1~Z)@|Rvǽ9KvM'ngS`$@@z{jn7U~LoGR  ̀mu@!Q?`S7YU}UZ:gR09&:A+@wXtVļ]Š;͒J B>V0;xo2lAOx3][)P~яwG@)m@(; N"(z&1xW0u GQ*`L]|SP,\R0 MTB$2raJ/4N$rv`'(94aT7.IuE+f9D=0 N*渚BF0A->!O]x$Tž~1?(Z}p 0O*b)&G3yU*bټSrոh 6 YιԼf.oG;)h⛋ E/. k2vx$Vl4T8d^}oy)m+P_D yLsyNe⒉Q.{QMv~#N捲l6\Neb^PUJ;5ch)+͗l?r0fmmu>u:HosoeD񣞅|Sqcark[^:¬z4Ff=q ȽSqpW2xba~9nCbf>瘢 ߛrH=8,mTdc^pg2ﶗ 7O `kc =kn1+D3|N:y2Vj{[3ƭXΦ;[Lȧ=sH"3/C(a}-` k?<3t-?S`>m2/{ l}S?[=w^>eb՗:6g=@'[mu6qJ˟` _5T~#C{o0+*V{#Ye+Cs`=Q?Hye&Aoh?ǵ u}@,eOKn둚Qk)z#u:6_ wZZZ?40(o}/#G O`f9~?P9 BbnoįA`{-`=ĉ/݀S8rI@4  M_jo!_bQ/Jo# a`)I_m0P)&oLـRGGBc0)x'߲rP/+,D13:~2d~6B.pwhK!IHEXj@h? 6I4PL#V2F=dȝYA1  iV7BI)KZЂт(XPPVe ZBޘ9羟/$L2<'=d,S `% )" RD)@@#ȟd*'D)qplis@JB!p$y)@%xP"81X?iH'X7}(` [d P`w).} q{%3AEM"oʄ;,|&@>Z|pG#a}&4yD Ahc8/s/PrTOƃE^YT*5/d>AoI) xl[n C4Kl;nZEV`j֓b}I6U?Gy+$Z U` w&XZ?/P U.h4BWnzl R y$ kw+qWq^K?&aQ)r ߘLo`ND_AO*~S ͘D#7 x TF@_E~Ol@&j#<Y*t (%3 C <<L9j2ND? XecljdXԮ\HZ@6y#j&, ,sL ]~Ddu"i (oT@P@H kRSge@Hb?P!!. P@DFD@Hş tk! >3s9pQy?`Q1!U$ޗoXC2F+gZH]B4@x=!/# @$n8=C#($y@+п Bǒ F+(+z P?Wu & Hy_@K/YP@P6i?e (T@bCHP)q =zp60z:px?Vcc6//֟@cuIRk#@S-2#ׁd(Wyh'CD4?`i.:P6IDAT)\(Ɠ.yxi\yª] %xyE4UU9mk Ϳ}{HJ͛y'&Goy~7ϗ/-86@{ߞ`>|`a'[Hߌ^, nMHʟgE .A`~NlݹN @ Gzɤ{yS77I+=*>~BջUta{ ڸ^5V]Sl  ڸȊ  p=6z$-R i@9a,~,P]:!08_ /bDc~Sxnz #=ĔktHU̴߈?'/bz,@T6FmtEz!01.ɆU,bCzҌRzGM+<>ɒ_2bx2^U4`GZˌs?f,h`ɸ:Z<3kc ' #-pupٗo?'0%6,tA@Z}d`*"Z{m@;G:`Jv e' 82ֵ   - `fܲ=@{3)e ,u5i@rn7yOH( 4nJ?ߑm\ oWd?z;]HZ{K:՝'*8 ,Z'i.qEh{#v7f +fWj05DiBYԽ;+.?i@e- ~8vxa8xN7A Up喝s a[Vٸxl+tT\:D`՞ȃwu <~_qsMTOC׎(s> ZdV8݄`O (7N7amsw"0tCV}#} `oG"0trŵ? iR?Vm^DhVx7ǮmWK7=?9^p|p _> Mpz>cG |ⷫFzw>\_ppM>oT4K7O%4f݇מXY-7O+ftɢ[u@{"27 /?~.M-pGAhi&Mz_>|Bog(G< ;Snȟ= @L?tD/;FS6[]^m%Xl*C}@tؑOnvIޚ vg\x}w86лgjcG 8l-j`W(/> jכ|n]"gz%_:>0Oǎs&iʚ \^ us# TE> @*#0bo 7T"oo 8mo Ȼdh@h $2z{ʾxjXGy5?nq b8iq lr @Xbr  e ϛcGp49Lp֛09߃p@?,9Ƹ&U@ dp㘷 um@]?`DQ a u-w19Ǧ198jr 39G&GA#yYHdml[H$-d8e#y Y#y, #yL`@ 0jr G#G9#|{G-GY@]}kw#uNp38jP *#`@"X`q xdqLV'#,00PW@< r747 dr/4rh dr@]/z@*20Pox`o ]7LHDd6He_:gnn.H%^ $2He+&"0bo ~DZxo57X`4707l`eU`^67˧> uLJA^/XH˸|nm "u]+ P"uqk#l, Pٰl> c#ټF9gdwYnlnld3v56`X36MܗfcXnf$,F lE]'lܲ5ΤGt>@:0lk w3F:5S#l S# |.`|>$P6`~ K# K# ^ wh}@]C`@F#SvFR:{vFRu`@]o08P/a3#9 3#9fFrfFr@R7+#I}>$u}`e `@R7#m~ldnF9##Y#z##Y+x@Vu##YY fd o`ڿ`4FF 8VR+#Io5VF:>$u}<duǁm VF; P@u@K7W!ujFFzyJȪAHku޲1P7䵯ul ^#u p@^Xhb CB6-$hz20ٵb` -3ERkFHmg,/څ "0#Erg] nB^4.ܐ"u 7_7.f4.ݔ m6oQM䷲۲@lPla.X$K-]vE:^XiT茾 e@wwmt>_LtLJ莯<rcE:Y"2@]:GEdaP[2{">ܜ@\$ P>`֜@|L} 59׿5ڿ|`Mc~_>&1_(F5W@X蘅 yk"xϚ@_&1 눿 uYtK__ `@L37* m-TJh9h($(V Xhb1T0"U ƈ H/ĿFd~KAByff2@U{"2E"E"9X5 DjdjS!\w!Y73E9^7;-P7-DE# _I8WV" @ \(ɭ"V<r7A3&M'.M~m:p'l{x@tgGL?#U& d_‰l@k$Jouo4xs!yࢽT`^@*KTJd/ ETI d_3 dR.T^5/#u(3C)@&W诩:&@6^d@ d@o<6G}MQY@@SA@!i|Sld1\I@-!I̗`j@][36h>1X4[oiiv@x+mЌnun]Ӟ;ک.o? M'CD[DZi:!֕eC ծΈ@PMwfDb_!̈oD!io& D@r-tN|&:$:߃@@DݖDXD_Yeg-ijR?$Zݐ@@; SkDBZp̈t_2#ӱM`@T? ]  ;Lu?d@ .к m}Ym?C;K@}տ! Vt[^:O 768 o$vy}Eb>a3 d@׍b@{n@SvDʮ~  %nDZ OxEf0 'Eޞfb@eS!yL ` Ϳe!Ya3 ߊ9b5 -Ƌo68j7 m>vp@+mO $?Np Vh5 '*y !yl*:H彂d. W@:_H+BR /= d6}o} Лy'ƹ+~GzkGƟ΍    u of IENDB`sidekiq-6.5.12/web/assets/images/status.png000755 001751 001751 00000004337 14551557262 021147 0ustar00pravipravi000000 000000 PNG  IHDRn0cIDATxyXSW_6RvM6F*#*NcשkQHA83* @"YD IXB^=91 r=r=ޗb C LmS/iwh~&ӈ=>A$_` =XoYQ#32eO*o+= ,D;& a!qkקzaShDNv_Q2<_F=+lp@J?O0'ۜ?{qB;uUвaQԸTn8y5OҙhH,3GdxV+Z&ZY]F[p!V_}2ǵ'nА;}]qqGJXȬDM)uŅJ}&~H ~HS 3gдQ)ojPqG3>]f e%_ 1NJ_Xu7~pwe2% 2]<7. )^R0T.J=!GjqKmF P %*)Uŏ3Y 8-J-OVW& č('xYp8ϻh+R+QWۿvp'b`2 u5HhP\.e~a)߱&s!Qy' l# $hts乄xޯԖ/*@~xӹ*4o[Ug(`00ijHa\=>j^|D[76jEWl׮xTn`  3W4DB ܴ_;&EF˸=2Xu>3ciN#`{nRx},tB38c2jx2p>K2ީM!ـK0@0U~G=ՅS =j5 524i e P]1G"*]Jj]Ya]gk=V54d[Mu}'@VIK|zkt76sיx{myyJuU <>zmeUsF,H`Fp!ErH}5 10c` 10c` 10LUΖ7br3VL_m,/.ȷӌ)av1"rq{⳸NRb%e&p.-luJt D:EB+*jA)N58h&$JTx<0js,#ˏ%opjX["Ig6{XYq2W3d~[H I Bzĵ%'Qq`myQPd (63"+8Ԡ 1QMzR#eKhWh jPMDqin'U-cTjDhR(v"JW"1աntA蕵XIۍ̗(@nmloNyLPө&{Dȋjd/C lMM*ƺtf54(hEE+T_[=>;3:TG!A(ԻosÄ'x^1k|V _zSù]T-Gv.d͝owƊrxzM-.ȱO/XVICm[c +4q~H:5l~9>xYI=gnǤ8&XT͝X8:#+vǵq;1=^N%p&F#IENDB`sidekiq-6.5.12/web/assets/images/logo.png000755 001751 001751 00000010057 14551557262 020560 0ustar00pravipravi000000 000000 PNG  IHDRNUEtEXtSoftwareAdobe ImageReadyqe<ViTXtXML:com.adobe.xmp K oIDATx]ye٘AMI?B<qCZ%(PA˛R9PlRd<@$pS1&"ݟyuoL3z5====~{uidXLADOp-¦)}.91h Щ@1h5zͪ%q3ZGo{ ؕXUt1{8Ol9 .qbw= gȉn&'.U޴נZE0ۼ-(/6qbʀl7 8oxfU66ME=k>yCQD8I%UI#Q z`q@E3æby<$h>IP$Xy$:<%l2Pu8@C㿣2mA|qD^~D{؏El/gPǍw4߲ޯzBg~8|f$t 6/*er@N645Te0M;ߢA؅`'mR{-I`"٭´@" $~/+sn{/U~x!//ؿ~>`sV].'sѰgu*Ux\:^Um <.ԯ=`w[,>=C"e K +jfS]Bl5]l>H_ưytȯ5|"E-٨-S,/%5NYQThcbk`g *(PCqTdB,GVӈ1e _eyHxH4J"2EߡܷdTY=EM\wd qKf=(>Eu,#c& &u-Tٻ/2RIZGʓg-(`'М! Ncv"` xͫJzGC%&I[fQ'|2Y`)`wvOgbDlm'tH|F~u6sN[;Ը]#)AxK**!{ k]AP۱<5zHXeSJ>W`hJ㍮*UzrH D n7oƥVS$p"* #$[5nBzNz\"1SYCtCo*5юDxV!$zAfZR*9)ﳥB[I@g7GWݽ= :aͻ@*7VOy_zԪBؗ&Ǻi38I;wc r}Zi eySLY}:7sZ帹Y{4qz.'7Pey/ ā\}>o;7LPeگr䭮q+rQS`[.tGU2QQj~%𢟐+hgGͨPjjqFqN"t~_M3FmqN?pꠜg{>N>q[ NZ%( /QA?4Lw{ \]88pkP bGXSN>UM}WtM&+㩘I9ת}j8hvǑ+ 4f(Emf=,O{nk ߰*MPM]),8oIFi`i/j|&IENDB`sidekiq-6.5.12/web/assets/javascripts/000755 001751 001751 00000000000 14551557262 020170 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/assets/javascripts/metrics.js000644 001751 001751 00000014267 14551557262 022206 0ustar00pravipravi000000 000000 if (window.matchMedia('(prefers-color-scheme: dark)').matches) { Chart.defaults.borderColor = "#333" Chart.defaults.color = "#aaa" } class BaseChart { constructor(id, options) { this.ctx = document.getElementById(id); this.options = options this.fallbackColor = "#999"; this.colors = [ // Colors taken from https://www.chartjs.org/docs/latest/samples/utils.html "#537bc4", "#4dc9f6", "#f67019", "#f53794", "#acc236", "#166a8f", "#00a950", "#58595b", "#8549ba", "#991b1b", ]; this.chart = new Chart(this.ctx, { type: this.options.chartType, data: { labels: this.options.labels, datasets: this.datasets }, options: this.chartOptions, }); } addMarksToChart() { this.options.marks.forEach(([bucket, label], i) => { this.chart.options.plugins.annotation.annotations[`deploy-${i}`] = { type: "line", xMin: bucket, xMax: bucket, borderColor: "rgba(220, 38, 38, 0.4)", borderWidth: 2, }; }); } } class JobMetricsOverviewChart extends BaseChart { constructor(id, options) { super(id, { ...options, chartType: "line" }); this.swatches = []; this.addMarksToChart(); this.chart.update(); } registerSwatch(id) { const el = document.getElementById(id); el.onchange = () => this.toggle(el.value, el.checked); this.swatches[el.value] = el; this.updateSwatch(el.value); } updateSwatch(kls) { const el = this.swatches[kls]; const ds = this.chart.data.datasets.find((ds) => ds.label == kls); el.checked = !!ds; el.style.color = ds ? ds.borderColor : null; } toggle(kls, visible) { if (visible) { this.chart.data.datasets.push(this.dataset(kls)); } else { const i = this.chart.data.datasets.findIndex((ds) => ds.label == kls); this.colors.unshift(this.chart.data.datasets[i].borderColor); this.chart.data.datasets.splice(i, 1); } this.updateSwatch(kls); this.chart.update(); } dataset(kls) { const color = this.colors.shift() || this.fallbackColor; return { label: kls, data: this.options.series[kls], borderColor: color, backgroundColor: color, borderWidth: 2, pointRadius: 2, }; } get datasets() { return Object.entries(this.options.series) .filter(([kls, _]) => this.options.visible.includes(kls)) .map(([kls, _]) => this.dataset(kls)); } get chartOptions() { return { aspectRatio: 4, scales: { y: { beginAtZero: true, title: { text: "Total execution time (sec)", display: true, }, }, }, interaction: { mode: "x", }, plugins: { legend: { display: false, }, tooltip: { callbacks: { title: (items) => `${items[0].label} UTC`, label: (item) => `${item.dataset.label}: ${item.parsed.y.toFixed(1)} seconds`, footer: (items) => { const bucket = items[0].label; const marks = this.options.marks.filter(([b, _]) => b == bucket); return marks.map(([b, msg]) => `Deploy: ${msg}`); }, }, }, }, }; } } class HistTotalsChart extends BaseChart { constructor(id, options) { super(id, { ...options, chartType: "bar" }); } get datasets() { return [{ data: this.options.series, backgroundColor: this.colors[0], borderWidth: 0, }]; } get chartOptions() { return { aspectRatio: 6, scales: { y: { beginAtZero: true, title: { text: "Total jobs", display: true, }, }, }, interaction: { mode: "x", }, plugins: { legend: { display: false, }, tooltip: { callbacks: { label: (item) => `${item.parsed.y} jobs`, }, }, }, }; } } class HistBubbleChart extends BaseChart { constructor(id, options) { super(id, { ...options, chartType: "bubble" }); this.addMarksToChart(); this.chart.update(); } get datasets() { const data = []; let maxCount = 0; Object.entries(this.options.hist).forEach(([bucket, hist]) => { hist.forEach((count, histBucket) => { if (count > 0) { data.push({ x: bucket, // histogram data is ordered fastest to slowest, but this.histIntervals is // slowest to fastest (so it displays correctly on the chart). y: this.options.histIntervals[this.options.histIntervals.length - 1 - histBucket] / 1000, count: count, }); if (count > maxCount) maxCount = count; } }); }); // Chart.js will not calculate the bubble size. We have to do that. const maxRadius = this.ctx.offsetWidth / this.options.labels.length; const minRadius = 1 const multiplier = (maxRadius / maxCount) * 1.5; data.forEach((entry) => { entry.r = entry.count * multiplier + minRadius; }); return [{ data: data, backgroundColor: "#537bc4", borderColor: "#537bc4", }]; } get chartOptions() { return { aspectRatio: 3, scales: { x: { type: "category", labels: this.options.labels, }, y: { title: { text: "Execution time (sec)", display: true, }, }, }, interaction: { mode: "x", }, plugins: { legend: { display: false, }, tooltip: { callbacks: { title: (items) => `${items[0].raw.x} UTC`, label: (item) => `${item.parsed.y} seconds: ${item.raw.count} job${ item.raw.count == 1 ? "" : "s" }`, footer: (items) => { const bucket = items[0].raw.x; const marks = this.options.marks.filter(([b, _]) => b == bucket); return marks.map(([b, msg]) => `Deploy: ${msg}`); }, }, }, }, }; } } sidekiq-6.5.12/web/assets/stylesheets/000755 001751 001751 00000000000 14551557262 020213 5ustar00pravipravi000000 000000 sidekiq-6.5.12/web/assets/stylesheets/application-dark.css000644 001751 001751 00000004365 14551557262 024157 0ustar00pravipravi000000 000000 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; } sidekiq-6.5.12/web/assets/stylesheets/bootstrap-rtl.min.css000644 001751 001751 00000060507 14551557262 024333 0ustar00pravipravi000000 000000 /******************************************************************************* * 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}sidekiq-6.5.12/web/assets/stylesheets/application.css000644 001751 001751 00000112537 14551557262 023241 0ustar00pravipravi000000 000000 * { 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; } h1, h2, h3 { font-size: 24px; line-height: 45px; } .header-with-subheader h2 { margin-top: -18px; } .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; } .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; } .metrics-swatch-wrapper { display: flex; align-items: center; gap: 6px; } .metrics-swatch[type=checkbox] { display: inline-block; width: 16px; height: 16px; margin: 0; border-radius: 2px; appearance: none; -webkit-appearance: none; -moz-appearance: none; border: 1px solid #bbb; color: white; background-color: currentColor; } /* We need to add the checkmark since we've taken over the appearance */ .metrics-swatch[type=checkbox]:checked { border-color: currentColor; background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); background-size: 100% 100%; background-position: center; background-repeat: no-repeat; } .metrics-swatch[type=checkbox]:focus { outline: 1px solid #888; outline-offset: 2px; } canvas { margin: 20px 0 30px; } sidekiq-6.5.12/web/assets/stylesheets/application-rtl.css000644 001751 001751 00000010773 14551557262 024037 0ustar00pravipravi000000 000000 .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; } sidekiq-6.5.12/LICENSE000644 001751 001751 00000000573 14551557262 014572 0ustar00pravipravi000000 000000 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.