├── frontend
├── .gitignore
├── main-web-api
│ ├── app.js
│ ├── index.html
│ └── demo.js
├── src
│ ├── dumb
│ │ ├── details
│ │ │ ├── job.module.css
│ │ │ ├── busy.js
│ │ │ ├── dead.js
│ │ │ ├── enqueued.js
│ │ │ ├── job.js
│ │ │ └── filter.js
│ │ ├── util
│ │ │ ├── format-duration.js
│ │ │ ├── redirect.js
│ │ │ ├── format-number.js
│ │ │ ├── column-name.js
│ │ │ └── breadcrumbs.js
│ │ ├── dashboard
│ │ │ ├── pulse.module.css
│ │ │ ├── pulse.js
│ │ │ ├── total.js
│ │ │ ├── redis-info.js
│ │ │ ├── index.js
│ │ │ ├── chart.js
│ │ │ └── table.js
│ │ ├── routes.js
│ │ ├── app.js
│ │ └── bootstrap.scss
│ ├── main.js
│ └── web-api
│ │ ├── details
│ │ ├── busy.js
│ │ ├── enqueued.js
│ │ └── dead.js
│ │ ├── index.js
│ │ ├── dashboard.js
│ │ └── client.js
├── main-dumb
│ ├── index.html
│ ├── demo.js
│ └── app.js
├── package.json
├── webpack.config.js
├── webpack.config.dumb.js
└── webpack.config.web-api.js
├── .gitignore
├── .rspec
├── lib
├── lowkiq
│ ├── version.rb
│ ├── splitters
│ │ ├── default.rb
│ │ └── by_node.rb
│ ├── option_parser.rb
│ ├── redis_info.rb
│ ├── schedulers
│ │ ├── seq.rb
│ │ └── lag.rb
│ ├── utils.rb
│ ├── web
│ │ ├── action.rb
│ │ └── api.rb
│ ├── worker.rb
│ ├── web.rb
│ ├── queue
│ │ ├── shard_metrics.rb
│ │ ├── fetch.rb
│ │ ├── actions.rb
│ │ ├── keys.rb
│ │ ├── queue_metrics.rb
│ │ ├── queries.rb
│ │ └── queue.rb
│ ├── script.rb
│ ├── server.rb
│ └── shard_handler.rb
└── lowkiq.rb
├── doc
└── dashboard.png
├── deploy.md
├── examples
├── benchmark
│ ├── Gemfile
│ ├── Readme.md
│ ├── Gemfile.lock
│ ├── sidekiq.rb
│ └── lowkiq.rb
└── dummy
│ ├── Gemfile
│ ├── Gemfile.lock
│ └── lib
│ └── app.rb
├── Rakefile
├── bin
├── setup
└── console
├── Gemfile
├── spec
├── option_parser_spec.rb
├── lowkiq_spec.rb
├── redis_info_spec.rb
├── utils_spec.rb
├── spec_helper.rb
├── schedulers
│ ├── lag_spec.rb
│ └── seq_spec.rb
├── splitters
│ ├── default_spec.rb
│ └── by_node_spec.rb
├── queue
│ ├── shard_metrics_spec.rb
│ ├── queue_metrics_spec.rb
│ ├── actions_spec.rb
│ ├── queries_spec.rb
│ └── queue_spec.rb
├── script_spec.rb
├── shard_handler_spec.rb
└── web_spec.rb
├── CHANGELOG.md
├── docker-compose.yml
├── .github
└── workflows
│ └── rspec.yml
├── exe
└── lowkiq
├── lowkiq.gemspec
├── LICENSE.md
├── README.ru.md
└── README.md
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .rspec_status
2 | /assets
3 | /*.gem
4 | /Gemfile.lock
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --format documentation
2 | --color
3 | --require spec_helper
4 |
--------------------------------------------------------------------------------
/lib/lowkiq/version.rb:
--------------------------------------------------------------------------------
1 | module Lowkiq
2 | VERSION = "1.2.2"
3 | end
4 |
--------------------------------------------------------------------------------
/doc/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bia-technologies/lowkiq/HEAD/doc/dashboard.png
--------------------------------------------------------------------------------
/deploy.md:
--------------------------------------------------------------------------------
1 | + bump version in `lib/lowkiq/version.rb`
2 | + build frontend: `npm run build`
3 | + build gem: `gem build lowkiq.gemspec`
4 |
--------------------------------------------------------------------------------
/examples/benchmark/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "lowkiq", path: "../.."
4 | gem 'sidekiq'
5 |
6 | gem "hiredis"
7 |
--------------------------------------------------------------------------------
/frontend/main-web-api/app.js:
--------------------------------------------------------------------------------
1 | import factory from '../src/web-api';
2 | export default factory('http://localhost:8081/api/web', '');
3 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rspec/core/rake_task"
3 |
4 | RSpec::Core::RakeTask.new(:spec)
5 |
6 | task :default => :spec
7 |
--------------------------------------------------------------------------------
/examples/dummy/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem "rack"
4 | gem "lowkiq", path: "../.."
5 | gem "webrick", "~> 1.7" # for ruby 3
6 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/frontend/src/dumb/details/job.module.css:
--------------------------------------------------------------------------------
1 | .table {
2 | table-layout: fixed;
3 | }
4 |
5 | .table .label {
6 | width: 140px;
7 | }
8 |
9 | .table .value {
10 | }
11 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4 |
5 | # Specify your gem's dependencies in lowkiq.gemspec
6 | gemspec
7 |
--------------------------------------------------------------------------------
/frontend/src/dumb/util/format-duration.js:
--------------------------------------------------------------------------------
1 | import {fmt} from 'human-duration';
2 |
3 | export default function formatDuration(seconds) {
4 | return fmt(seconds * 1000).segments(2);
5 | }
6 |
--------------------------------------------------------------------------------
/spec/option_parser_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Lowkiq::OptionParser do
2 | it "defaults" do
3 | args = ["-r" "./lib/app.rb"]
4 | expect( Lowkiq::OptionParser.call args ).to eq({require: "./lib/app.rb"})
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.0
2 |
3 | * Timestamps are float rather than int. #23
4 | * Due to problems with autoloading, you now need to manually assign a list of workers. #22
5 |
6 | ```ruby
7 | Lowkiq.workers = [ ATestWorker, ATest2Worker ]
8 | ```
9 |
--------------------------------------------------------------------------------
/spec/lowkiq_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Lowkiq do
2 | # it "has a version number" do
3 | # expect(Lowkiq::VERSION).not_to be nil
4 | # end
5 |
6 | # it "does something useful" do
7 | # expect(false).to eq(true)
8 | # end
9 | end
10 |
--------------------------------------------------------------------------------
/frontend/main-dumb/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Webpack App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/frontend/main-web-api/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Webpack App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/spec/redis_info_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Lowkiq::RedisInfo do
2 | let(:redis_pool) { ConnectionPool.new(size: 5, timeout: 5) { Redis.new url: ENV['REDIS_URL'] } }
3 |
4 | let(:subject) { described_class.new redis_pool }
5 |
6 | it "call" do
7 | expect( subject.call ).to be
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import factory from './web-api';
5 |
6 | const {lowkiqRoot} = window;
7 | const App = factory(`${lowkiqRoot}/api/web`, lowkiqRoot);
8 |
9 | const root = document.getElementById('root');
10 |
11 | ReactDOM.render( , root );
12 |
--------------------------------------------------------------------------------
/examples/benchmark/Readme.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | + `bundle exec ../../exe/lowkiq -r ./lowkiq.rb`
4 | + `bundle exec sidekiq -r ./sidekiq.rb`
5 |
6 | # Results
7 |
8 | 5 threads, 100_000 jobs
9 |
10 | + lowkiq default: 155 sec
11 | + lowkiq +seq: 146 sec
12 | + lowkiq +hiredis: 80 sec
13 | + lowkiq +seq +hiredis: 65 sec
14 | + sidekiq: 15 sec
15 |
--------------------------------------------------------------------------------
/frontend/src/dumb/util/redirect.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Redirect } from "react-router-dom";
3 |
4 | import { RoutesContext } from '../routes';
5 |
6 | export function RedirectToDashboard() {
7 | return (
8 |
9 | {
10 | routes =>
11 | }
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "lowkiq"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/frontend/src/dumb/dashboard/pulse.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 1rem;
3 | height: 1rem;
4 | }
5 |
6 | .container.pulse {
7 | animation: pulse 1s;
8 | }
9 |
10 | @keyframes pulse {
11 | from {
12 | box-shadow: 0 0 0 #212529;
13 | }
14 | 50% {
15 | box-shadow: 0 0 1rem #212529;
16 | }
17 | to {
18 | box-shadow: 0 0 0 #212529;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lib/lowkiq/splitters/default.rb:
--------------------------------------------------------------------------------
1 | module Lowkiq
2 | module Splitters
3 | class Default
4 | def initialize(threads_per_node)
5 | @threads_per_node = threads_per_node
6 | end
7 |
8 | def call(shard_handlers)
9 | Utils::Array.new(shard_handlers)
10 | .in_transposed_groups(@threads_per_node)
11 | .reject(&:empty?)
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/utils_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Lowkiq::Utils do
2 | describe "Array" do
3 | it "#in_transposed_groups" do
4 | groups = Lowkiq::Utils::Array.new((0...10)).in_transposed_groups(3)
5 |
6 | expect(groups).to eq([
7 | [0,3,6,9],
8 | [1,4,7],
9 | [2,5,8],
10 | ])
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/frontend/src/dumb/util/format-number.js:
--------------------------------------------------------------------------------
1 | export default function formatNumber(number) {
2 | if (number < 1000) return number;
3 |
4 | const abbrev = ['', 'K', 'M', 'B', 'T'];
5 | const unrangifiedOrder = Math.floor(Math.log10(Math.abs(number)) / 3);
6 | const order = Math.max(0, Math.min(unrangifiedOrder, abbrev.length - 1));
7 | const suffix = abbrev[order];
8 |
9 | return (number / Math.pow(10, order * 3)).toPrecision(3) + suffix;
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/src/dumb/details/busy.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Breadcrumbs from '../util/breadcrumbs';
4 | import Filter from './filter';
5 | import Job from './job';
6 |
7 | export default function Enqueued({name, items}) {
8 | return (
9 |
10 |
11 |
13 | {items.map(item => {
14 | return ;
15 | })}
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/examples/dummy/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../..
3 | specs:
4 | lowkiq (1.1.0)
5 | connection_pool (~> 2.2, >= 2.2.2)
6 | rack (>= 1.5.0)
7 | redis (>= 4.0.1, < 5)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | connection_pool (2.2.5)
13 | rack (2.0.5)
14 | redis (4.5.1)
15 | webrick (1.7.0)
16 |
17 | PLATFORMS
18 | ruby
19 |
20 | DEPENDENCIES
21 | lowkiq!
22 | rack
23 | webrick (~> 1.7)
24 |
25 | BUNDLED WITH
26 | 2.2.22
27 |
--------------------------------------------------------------------------------
/frontend/src/dumb/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export class Routes {
4 | constructor(rootUrl) {
5 | this.rootUrl = rootUrl;
6 | }
7 |
8 | dashboard() {
9 | return `${this.rootUrl}/`;
10 | }
11 |
12 | busy(queue) {
13 | return `${this.rootUrl}/${queue}/busy`;
14 | }
15 |
16 | enqueued(queue) {
17 | return `${this.rootUrl}/${queue}/enqueued`;
18 | }
19 |
20 | dead(queue) {
21 | return `${this.rootUrl}/${queue}/dead`;
22 | }
23 | }
24 |
25 | export const RoutesContext = React.createContext();
26 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "bundler/setup"
2 | require "lowkiq"
3 | require "pry-byebug"
4 |
5 | # to test their usage
6 | Lowkiq.dump_error = -> (msg) { msg&.reverse }
7 | Lowkiq.load_error = -> (msg) { msg&.reverse }
8 |
9 | RSpec.configure do |config|
10 | # Enable flags like --only-failures and --next-failure
11 | config.example_status_persistence_file_path = ".rspec_status"
12 |
13 | # Disable RSpec exposing methods globally on `Module` and `main`
14 | config.disable_monkey_patching!
15 |
16 | config.expect_with :rspec do |c|
17 | c.syntax = :expect
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/frontend/src/dumb/util/column-name.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function ColumnName({name, order, onClick}) {
4 | const orderSymble = {
5 | desc: '▼', // по убыванию
6 | asc: '▲' // по возрастанию
7 | };
8 |
9 | let presentedName = name;
10 | if (orderSymble[order]) {
11 | presentedName += ` ${orderSymble[order]}`;
12 | }
13 |
14 | return (
15 |