qtrix 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +317 -0
- data/Rakefile +1 -0
- data/bin/qtrix +15 -0
- data/lib/qtrix.rb +178 -0
- data/lib/qtrix/cli.rb +80 -0
- data/lib/qtrix/cli/config_sets.rb +87 -0
- data/lib/qtrix/cli/overrides.rb +121 -0
- data/lib/qtrix/cli/queues.rb +97 -0
- data/lib/qtrix/matrix.rb +83 -0
- data/lib/qtrix/matrix/analyzer.rb +46 -0
- data/lib/qtrix/matrix/common.rb +22 -0
- data/lib/qtrix/matrix/model.rb +16 -0
- data/lib/qtrix/matrix/queue_picker.rb +52 -0
- data/lib/qtrix/matrix/queue_prioritizer.rb +70 -0
- data/lib/qtrix/matrix/reader.rb +25 -0
- data/lib/qtrix/matrix/row_builder.rb +72 -0
- data/lib/qtrix/namespacing.rb +198 -0
- data/lib/qtrix/override.rb +77 -0
- data/lib/qtrix/queue.rb +77 -0
- data/lib/qtrix/version.rb +3 -0
- data/qtrix.gemspec +24 -0
- data/spec/qtrix/cli/config_sets_spec.rb +94 -0
- data/spec/qtrix/cli/overrides_spec.rb +101 -0
- data/spec/qtrix/cli/queues_spec.rb +70 -0
- data/spec/qtrix/cli/spec_helper.rb +18 -0
- data/spec/qtrix/matrix/analyzer_spec.rb +38 -0
- data/spec/qtrix/matrix/queue_picker_spec.rb +73 -0
- data/spec/qtrix/matrix_profile_spec.rb +72 -0
- data/spec/qtrix/matrix_spec.rb +71 -0
- data/spec/qtrix/namespacing_spec.rb +207 -0
- data/spec/qtrix/override_spec.rb +155 -0
- data/spec/qtrix/queue_spec.rb +183 -0
- data/spec/qtrix_spec.rb +204 -0
- data/spec/spec_helper.rb +48 -0
- metadata +178 -0
@@ -0,0 +1,77 @@
|
|
1
|
+
module Qtrix
|
2
|
+
class Override
|
3
|
+
extend Qtrix::Namespacing
|
4
|
+
REDIS_KEY = :overrides
|
5
|
+
REDIS_CLAIMS_KEY = :override_claims
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def add(*args)
|
9
|
+
namespace, queues, processes = extract_args(2, *args)
|
10
|
+
validate!(processes)
|
11
|
+
processes.times do
|
12
|
+
redis(namespace).rpush(REDIS_KEY, queues.join(","))
|
13
|
+
end
|
14
|
+
Qtrix::Matrix.clear!(namespace)
|
15
|
+
end
|
16
|
+
|
17
|
+
def all(ns=:current)
|
18
|
+
[].tap do |result|
|
19
|
+
raw_list(ns).each_with_index do |queues, index|
|
20
|
+
host = redis(ns).lindex(REDIS_CLAIMS_KEY, index)
|
21
|
+
result << self.new(queues.split(",").map(&:to_sym), host)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def remove(*args)
|
27
|
+
namespace, queues, processes = extract_args(2, *args)
|
28
|
+
redis(namespace).lrem(REDIS_KEY, processes, queues.join(","))
|
29
|
+
Qtrix::Matrix.clear!(namespace)
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear!(namespace=:current)
|
33
|
+
redis(namespace).del REDIS_KEY
|
34
|
+
Qtrix::Matrix.clear!(namespace)
|
35
|
+
end
|
36
|
+
|
37
|
+
def overrides_for(*args)
|
38
|
+
namespace, hostname, workers = extract_args(2, *args)
|
39
|
+
workers.times {redis(namespace).rpush(REDIS_CLAIMS_KEY, hostname)}
|
40
|
+
claimed_by(namespace, hostname).map{|override| override.queues}
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def validate!(processes)
|
45
|
+
raise "processes must be positive integer" unless processes > 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def claimed_by(*args)
|
49
|
+
namespace, hostname = extract_args(1, *args)
|
50
|
+
all(namespace).select{|override| override.host == hostname}
|
51
|
+
end
|
52
|
+
|
53
|
+
def raw_list(ns=:current)
|
54
|
+
redis(ns).lrange(REDIS_KEY, 0, -1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :queues, :host
|
59
|
+
|
60
|
+
def hash
|
61
|
+
@queues.hash ^ @processes.hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def eql?(other)
|
65
|
+
self.class.equal?(other.class) &&
|
66
|
+
@processes == other.processes &&
|
67
|
+
@queues == other.queues
|
68
|
+
end
|
69
|
+
alias == eql?
|
70
|
+
|
71
|
+
private
|
72
|
+
def initialize(queues, host=nil)
|
73
|
+
@queues = queues
|
74
|
+
@host = host
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/qtrix/queue.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Qtrix
|
5
|
+
class Queue
|
6
|
+
include Qtrix::Namespacing
|
7
|
+
REDIS_KEY = :queue_weights
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def map_queue_weights(*args)
|
11
|
+
namespace, map = extract_args(1, *args)
|
12
|
+
map.each {|queue, weight| validate(queue.to_s, weight.to_f)}
|
13
|
+
self.clear!(namespace)
|
14
|
+
map.each {|queue, weight| redis(namespace).zadd(REDIS_KEY, weight.to_f, queue.to_s)}
|
15
|
+
Qtrix::Matrix.clear!(namespace)
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def all_queues(namespace=:current)
|
20
|
+
raw = redis(namespace).zrevrange(REDIS_KEY, 0, -1, withscores: true)
|
21
|
+
[].tap {|result|
|
22
|
+
raw.each_slice(2) do |tuple|
|
23
|
+
result << self.new(namespace, tuple[0], tuple[1].to_f)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def count(namespace=:current)
|
29
|
+
redis(namespace).zcard(REDIS_KEY)
|
30
|
+
end
|
31
|
+
|
32
|
+
def total_weight(ns=:current)
|
33
|
+
all_queues(ns).inject(0) {|memo, queue| memo += queue.weight}
|
34
|
+
end
|
35
|
+
|
36
|
+
def clear!(namespace=:current)
|
37
|
+
redis(namespace).del REDIS_KEY
|
38
|
+
Qtrix::Matrix.clear! namespace
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def validate(name, weight)
|
43
|
+
raise "nil name" if name.nil?
|
44
|
+
raise "empty name" if name.empty?
|
45
|
+
raise "nil weight" if weight.nil?
|
46
|
+
raise "weight of 0 or less" if weight <= 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def high_low
|
50
|
+
lambda{|i,j| j.weight <=> i.weight}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
attr_reader :name, :namespace
|
54
|
+
|
55
|
+
def initialize(ns, name, weight)
|
56
|
+
@namespace = ns
|
57
|
+
@name = name.to_sym
|
58
|
+
@weight = weight.to_f
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(other)
|
62
|
+
name == other.name && weight == other.weight
|
63
|
+
end
|
64
|
+
|
65
|
+
def hash
|
66
|
+
name.hash - weight.hash
|
67
|
+
end
|
68
|
+
|
69
|
+
def resource_percentage
|
70
|
+
@resource_percentage ||= weight.to_f / self.class.total_weight(namespace)
|
71
|
+
end
|
72
|
+
|
73
|
+
def weight
|
74
|
+
@weight ||= redis(namespace).zscore(REDIS_KEY, name).to_f
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/qtrix.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'qtrix/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "qtrix"
|
8
|
+
gem.version = Qtrix::VERSION
|
9
|
+
gem.authors = ["Lance Woodson", "Joshua Flanagan"]
|
10
|
+
gem.email = ["jflanagan@peopleadmin.com"]
|
11
|
+
gem.description = %q{Central worker queues config}
|
12
|
+
gem.summary = gem.description
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
gem.add_dependency "redis-namespace", "~> 1.0.2"
|
20
|
+
gem.add_dependency "mixlib-cli", "1.3.0"
|
21
|
+
gem.add_development_dependency "rspec-core", "2.11.0"
|
22
|
+
gem.add_development_dependency "rspec-prof", "0.0.3"
|
23
|
+
gem.add_development_dependency "pry-debugger"
|
24
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'qtrix/cli/spec_helper'
|
2
|
+
|
3
|
+
describe Qtrix::CLI::ConfigSets do
|
4
|
+
include_context "cli commands"
|
5
|
+
let(:config_sets) {
|
6
|
+
Qtrix::CLI::ConfigSets.new(stdout_stream, stderr_stream)
|
7
|
+
}
|
8
|
+
|
9
|
+
describe "listing configuration sets" do
|
10
|
+
before(:each) {config_sets.parse_options(["-l"])}
|
11
|
+
|
12
|
+
it "should display default when no config sets have been added" do
|
13
|
+
config_sets.exec
|
14
|
+
stdout.should match /default/
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should display all config sets" do
|
18
|
+
Qtrix.create_configuration_set :night
|
19
|
+
config_sets.exec
|
20
|
+
stdout.should match /default/
|
21
|
+
stdout.should match /night/
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "current configuration set" do
|
26
|
+
before(:each) {config_sets.parse_options(["-c"])}
|
27
|
+
|
28
|
+
it "should display the current configuration set" do
|
29
|
+
Qtrix.create_configuration_set :night
|
30
|
+
Qtrix.map_queue_weights :night, A: 10
|
31
|
+
Qtrix.activate_configuration_set! :night
|
32
|
+
config_sets.exec
|
33
|
+
stdout.should match /night/
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "create configuration set" do
|
38
|
+
it "should allow creation of a new configuration set" do
|
39
|
+
config_sets.parse_options "--create day".split
|
40
|
+
config_sets.exec
|
41
|
+
stdout.should match /success/
|
42
|
+
Qtrix.configuration_sets.include?(:day).should == true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not allow creation of duplicate configuration sets" do
|
46
|
+
Qtrix.create_configuration_set 'night'
|
47
|
+
config_sets.parse_options "--create night".split
|
48
|
+
config_sets.exec
|
49
|
+
stderr.should match /failure/i
|
50
|
+
Qtrix.configuration_sets.select{|cs| cs == :default}.size.should == 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "activate configuration set" do
|
55
|
+
it "should allow activation of a configuration set" do
|
56
|
+
Qtrix.create_configuration_set 'night'
|
57
|
+
Qtrix.map_queue_weights 'night', A: 10
|
58
|
+
config_sets.parse_options "-a night".split
|
59
|
+
config_sets.exec
|
60
|
+
stdout.should match /success/i
|
61
|
+
Qtrix.current_configuration_set.should == :night
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "remove configuration set" do
|
66
|
+
before(:each) do
|
67
|
+
Qtrix.create_configuration_set :night
|
68
|
+
Qtrix.map_queue_weights :night, A: 10
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should allow removal of a configuration set" do
|
72
|
+
config_sets.parse_options "-d night".split
|
73
|
+
config_sets.exec
|
74
|
+
stdout.should match /success/
|
75
|
+
Qtrix.configuration_sets.detect{|cs| cs == :night}.should be_nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should not allow removal of the current configuration set" do
|
79
|
+
Qtrix.activate_configuration_set! :night
|
80
|
+
config_sets.parse_options "-d night".split
|
81
|
+
config_sets.exec
|
82
|
+
stderr.should match /failure/i
|
83
|
+
Qtrix.configuration_sets.select{|cs| cs == :night}.size.should == 1
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not allow removal of the default configuration set" do
|
87
|
+
Qtrix.activate_configuration_set! :night
|
88
|
+
config_sets.parse_options "-d default".split
|
89
|
+
config_sets.exec
|
90
|
+
stderr.should match /failure/i
|
91
|
+
Qtrix.configuration_sets.select{|cs| cs == :default}.size.should == 1
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'qtrix/cli/spec_helper'
|
2
|
+
|
3
|
+
describe Qtrix::CLI::Overrides do
|
4
|
+
include_context "cli commands"
|
5
|
+
let(:overrides) {
|
6
|
+
Qtrix::CLI::Overrides.new(stdout_stream, stderr_stream)
|
7
|
+
}
|
8
|
+
|
9
|
+
describe "listing override" do
|
10
|
+
before(:each) {overrides.parse_options(["-l"])}
|
11
|
+
|
12
|
+
it "should list all overrides" do
|
13
|
+
Qtrix.add_override [:A], 1
|
14
|
+
Qtrix.add_override [:B], 1
|
15
|
+
overrides.exec
|
16
|
+
stdout.should match /A/
|
17
|
+
stdout.should match /B/
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "adding overrides" do
|
22
|
+
it "should add a single override" do
|
23
|
+
overrides.parse_options(%w{-a -q A -w 1})
|
24
|
+
overrides.exec
|
25
|
+
stdout.should match /A/
|
26
|
+
Qtrix.overrides.size.should == 1
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add multiple overrides for multiple workers" do
|
30
|
+
overrides.parse_options(%w{-a -q A -w 2})
|
31
|
+
overrides.exec
|
32
|
+
stdout.should match /A.+A/
|
33
|
+
Qtrix.overrides.size.should == 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should default to 1 worker" do
|
37
|
+
overrides.parse_options(%w{-a -q A})
|
38
|
+
overrides.exec
|
39
|
+
stdout.should match /A/
|
40
|
+
Qtrix.overrides.size.should == 1
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should error if we dont specify any queues" do
|
44
|
+
overrides.parse_options(%w{-a -w 2})
|
45
|
+
overrides.exec
|
46
|
+
stderr.should match /failure/i
|
47
|
+
Qtrix.overrides.size.should == 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "deleting overrides" do
|
52
|
+
before(:each) {Qtrix.add_override(['A'], 2)}
|
53
|
+
|
54
|
+
it "should allow deletion of a single override" do
|
55
|
+
overrides.parse_options(%w{-d -q A})
|
56
|
+
overrides.exec
|
57
|
+
stdout.should match /A/
|
58
|
+
Qtrix.overrides.size.should == 1
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should allow deletion of multiple overrides" do
|
62
|
+
overrides.parse_options(%w{-d -q A -w 2})
|
63
|
+
overrides.exec
|
64
|
+
stdout.should match /A/
|
65
|
+
Qtrix.overrides.size.should == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should error if we dont specify any queues" do
|
69
|
+
overrides.parse_options(%w{-d -w 2})
|
70
|
+
overrides.exec
|
71
|
+
stderr.should match /failure/i
|
72
|
+
Qtrix.overrides.size.should == 2
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "targetting to config sets" do
|
77
|
+
before(:each) do
|
78
|
+
Qtrix.create_configuration_set "night"
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be able to create overrides in the config set" do
|
82
|
+
overrides.parse_options(%w{-c night -a -q A})
|
83
|
+
overrides.exec
|
84
|
+
Qtrix.overrides('night').size.should == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should be able to list overrides in the config set" do
|
88
|
+
Qtrix.add_override('night', ['A'], 1)
|
89
|
+
overrides.parse_options(%w{-c night -l})
|
90
|
+
overrides.exec
|
91
|
+
stdout.should match /A/
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be able to delete overrides from the config set" do
|
95
|
+
Qtrix.add_override('night', ['A'], 1)
|
96
|
+
overrides.parse_options(%w{-c night -d -q A})
|
97
|
+
overrides.exec
|
98
|
+
Qtrix.overrides('night').size.should == 0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'qtrix/cli/spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
describe Qtrix::CLI::Queues do
|
5
|
+
include_context "cli commands"
|
6
|
+
let(:queues) {
|
7
|
+
Qtrix::CLI::Queues.new(stdout_stream, stderr_stream)
|
8
|
+
}
|
9
|
+
describe "queue weights" do
|
10
|
+
it "should set the queue weights for the current config set" do
|
11
|
+
queues.parse_options(["-w", "A:40,B:30"])
|
12
|
+
queues.exec
|
13
|
+
stdout.should match /OK/
|
14
|
+
Qtrix.desired_distribution.size.should == 2
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should be targettable to a configuration set" do
|
18
|
+
Qtrix.create_configuration_set "night"
|
19
|
+
queues.parse_options(["-w", "A:100", "-c", "night"])
|
20
|
+
queues.exec
|
21
|
+
stdout.should match /OK/
|
22
|
+
Qtrix.desired_distribution("night").size.should == 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "queue weights by yaml" do
|
27
|
+
around(:each) do |example|
|
28
|
+
yaml = YAML.dump({A: 40, B: 30, C: 20, D: 10})
|
29
|
+
Dir.mktmpdir do |dir|
|
30
|
+
@path = File.join(dir, "queue_weight.yml")
|
31
|
+
File.write(@path, yaml)
|
32
|
+
example.run
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set the queue weights for the current config set" do
|
37
|
+
queues.parse_options(["-y", @path])
|
38
|
+
queues.exec
|
39
|
+
stdout.should match /OK/
|
40
|
+
Qtrix.desired_distribution.size.should == 4
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should set the queue weights for a targetted config set" do
|
44
|
+
Qtrix.create_configuration_set "night"
|
45
|
+
queues.parse_options(["-y", @path, "-c", "night"])
|
46
|
+
queues.exec
|
47
|
+
stdout.should match /OK/
|
48
|
+
Qtrix.desired_distribution("night").size.should == 4
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "list desired distribution" do
|
53
|
+
it "should return the list from the current config set" do
|
54
|
+
Qtrix.map_queue_weights A: 10
|
55
|
+
queues.parse_options(["-l"])
|
56
|
+
queues.exec
|
57
|
+
stdout.should match /A/
|
58
|
+
stdout.should match /10/
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return the list from a specified config set" do
|
62
|
+
Qtrix.create_configuration_set "night"
|
63
|
+
Qtrix.map_queue_weights B: 11
|
64
|
+
queues.parse_options(["-l", "-c", "night"])
|
65
|
+
queues.exec
|
66
|
+
stdout.should match /B/
|
67
|
+
stdout.should match /11/
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|