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
data/lib/qtrix/cli.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'mixlib/cli'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Qtrix
|
5
|
+
module CLI
|
6
|
+
class Base
|
7
|
+
include Mixlib::CLI
|
8
|
+
attr_reader :stdout, :stderr
|
9
|
+
|
10
|
+
def initialize(stdout=STDOUT, stderr=STDERR)
|
11
|
+
super()
|
12
|
+
@stdout = stdout
|
13
|
+
@stderr = stderr
|
14
|
+
end
|
15
|
+
|
16
|
+
def exec
|
17
|
+
Qtrix.connection_config(config)
|
18
|
+
unless exec_behavior
|
19
|
+
msg = "no appropriate combination of options.\n\n"
|
20
|
+
msg += "Type 'bundle exec [command] --help' for usage"
|
21
|
+
error(msg)
|
22
|
+
end
|
23
|
+
rescue StandardError => e
|
24
|
+
error("Failure: #{e}")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def to_args(*args)
|
29
|
+
args.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(msg)
|
33
|
+
stdout.write("#{msg}\n")
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def error(msg)
|
38
|
+
stderr.write("Failure: #{msg}\n")
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Default
|
44
|
+
include Mixlib::CLI
|
45
|
+
|
46
|
+
banner <<-EOS
|
47
|
+
|
48
|
+
Usage: bundle exec qtrix [sub-command] [options]
|
49
|
+
|
50
|
+
Where available sub-commands are:
|
51
|
+
|
52
|
+
config-sets: Perform operations on configuration sets.
|
53
|
+
queues: Perform operations on queues within a
|
54
|
+
configuration set.
|
55
|
+
overrides: Perform operations on overrides within a
|
56
|
+
configuration set.
|
57
|
+
|
58
|
+
For more information about the subcommands, try:
|
59
|
+
|
60
|
+
bundle exec qtrix [subcommand] --help
|
61
|
+
|
62
|
+
EOS
|
63
|
+
end
|
64
|
+
|
65
|
+
require 'qtrix/cli/config_sets'
|
66
|
+
require 'qtrix/cli/queues'
|
67
|
+
require 'qtrix/cli/overrides'
|
68
|
+
|
69
|
+
@commands = {
|
70
|
+
config_sets: ConfigSets,
|
71
|
+
overrides: Overrides,
|
72
|
+
queues: Queues
|
73
|
+
}
|
74
|
+
|
75
|
+
def self.get_command_class(str)
|
76
|
+
@commands.fetch(str.downcase.to_sym) if str
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Qtrix
|
2
|
+
module CLI
|
3
|
+
class ConfigSets < Base
|
4
|
+
include Mixlib::CLI
|
5
|
+
|
6
|
+
banner <<-EOS
|
7
|
+
|
8
|
+
Usage: bundle exec qtrix config_sets [options]
|
9
|
+
|
10
|
+
Allows interaction with the configuration sets in the qtrix system.
|
11
|
+
With this, you can:
|
12
|
+
|
13
|
+
* View all configuration sets.
|
14
|
+
* View the current configuration set.
|
15
|
+
* Add a configuration set.
|
16
|
+
* Specify the current configuration set.
|
17
|
+
* Remove a configuration set.
|
18
|
+
|
19
|
+
Options include:
|
20
|
+
|
21
|
+
EOS
|
22
|
+
|
23
|
+
option :list,
|
24
|
+
short: '-l',
|
25
|
+
long: '--list',
|
26
|
+
description: 'The list of known config sets'
|
27
|
+
|
28
|
+
option :current_configuration_set,
|
29
|
+
short: '-c',
|
30
|
+
long: '--current',
|
31
|
+
description: 'List current config set'
|
32
|
+
|
33
|
+
option :add_configuration_set,
|
34
|
+
long: '--create CONFIG_SET_NAME',
|
35
|
+
description: 'Create a new config set'
|
36
|
+
|
37
|
+
option :activate_configuration_set,
|
38
|
+
short: '-a CONFIG_SET_NAME',
|
39
|
+
long: '--activate CONFIG_SET_NAME',
|
40
|
+
description: 'Activate a configuration set'
|
41
|
+
|
42
|
+
option :remove_configuration_set,
|
43
|
+
short: '-d CONFIG_SET_NAME',
|
44
|
+
long: '--delete CONFIG_SET_NAME',
|
45
|
+
description: 'Delete a non-active config set'
|
46
|
+
|
47
|
+
option :host,
|
48
|
+
short: '-h HOST',
|
49
|
+
description: 'The host where redis is located',
|
50
|
+
default: 'localhost'
|
51
|
+
|
52
|
+
option :port,
|
53
|
+
short: '-p PORT',
|
54
|
+
description: 'The host which redis is listening on',
|
55
|
+
default: 6379
|
56
|
+
|
57
|
+
option :db,
|
58
|
+
short: '-n DB',
|
59
|
+
description: 'The redis DB where the action should occur',
|
60
|
+
default: 0
|
61
|
+
|
62
|
+
def exec_behavior
|
63
|
+
if config[:list]
|
64
|
+
config_sets = Qtrix.configuration_sets
|
65
|
+
msg = "Known configuration sets: #{config_sets.join(", ")}"
|
66
|
+
write(msg)
|
67
|
+
elsif config[:current_configuration_set]
|
68
|
+
current_config_set = Qtrix.current_configuration_set
|
69
|
+
msg = "Current configuration set: #{current_config_set}"
|
70
|
+
write(msg)
|
71
|
+
elsif config[:add_configuration_set]
|
72
|
+
config_set = config[:add_configuration_set]
|
73
|
+
Qtrix.create_configuration_set(config_set)
|
74
|
+
write("Configuration set created successfully: #{config_set}")
|
75
|
+
elsif config[:activate_configuration_set]
|
76
|
+
config_set = config[:activate_configuration_set].to_sym
|
77
|
+
Qtrix.activate_configuration_set!(config_set)
|
78
|
+
write("Activated configuration set successfully: #{config_set}")
|
79
|
+
elsif config[:remove_configuration_set]
|
80
|
+
config_set = config[:remove_configuration_set]
|
81
|
+
Qtrix.remove_configuration_set!(config_set.to_sym)
|
82
|
+
write("Configuration set removed successfully: #{config_set}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Qtrix
|
2
|
+
module CLI
|
3
|
+
class Overrides < Base
|
4
|
+
banner <<-EOS
|
5
|
+
|
6
|
+
Usage: bundle exec qtrix overrides [options]
|
7
|
+
|
8
|
+
Allows interaction with the overrides in a configuration set. With this
|
9
|
+
you can:
|
10
|
+
|
11
|
+
* List all the overrides in the config set.
|
12
|
+
* Add overrides for a list of queues and number of workers.
|
13
|
+
* Remove overrides for a list of queues and number of workers.
|
14
|
+
|
15
|
+
Options include:
|
16
|
+
|
17
|
+
EOS
|
18
|
+
|
19
|
+
option :list_overrides,
|
20
|
+
short: '-l',
|
21
|
+
long: '--list',
|
22
|
+
description: 'List the queue overrides'
|
23
|
+
|
24
|
+
option :add_overrides,
|
25
|
+
short: '-a',
|
26
|
+
long: '--add',
|
27
|
+
description: 'Add a queue list override'
|
28
|
+
|
29
|
+
option :delete_overrides,
|
30
|
+
short: '-d',
|
31
|
+
long: '--delete',
|
32
|
+
description: 'Delete a queue list override, be sure to add -q <queue names>'
|
33
|
+
|
34
|
+
option :queue_list,
|
35
|
+
short: '-q QUEUE_LIST',
|
36
|
+
long: '--queues QUEUE_LIST',
|
37
|
+
description: 'Specify the list of queues for the override'
|
38
|
+
|
39
|
+
option :workers,
|
40
|
+
short: '-w WORKER_COUNT',
|
41
|
+
long: '--workers WORKER_COUNT',
|
42
|
+
description: 'Specify the list of workers for the override',
|
43
|
+
default: '1'
|
44
|
+
|
45
|
+
option :config_set,
|
46
|
+
short: '-c CONFIG_SET_NAME',
|
47
|
+
long: '--config-set CONFIG_SET_NAME',
|
48
|
+
description: 'Specify the config set being operated on'
|
49
|
+
|
50
|
+
option :host,
|
51
|
+
short: '-h HOST',
|
52
|
+
description: 'The host where redis is located',
|
53
|
+
default: 'localhost'
|
54
|
+
|
55
|
+
option :port,
|
56
|
+
short: '-p PORT',
|
57
|
+
description: 'The host which redis is listening on',
|
58
|
+
default: 6379
|
59
|
+
|
60
|
+
option :db,
|
61
|
+
short: '-n DB',
|
62
|
+
description: 'The redis DB where the action should occur',
|
63
|
+
default: 0
|
64
|
+
|
65
|
+
def exec_behavior
|
66
|
+
if config[:list_overrides]
|
67
|
+
msg = "Current Overrides:\n"
|
68
|
+
msg += overrides.map(&stringify).join("\n")
|
69
|
+
write(msg)
|
70
|
+
elsif add_overrides_params_provided?
|
71
|
+
Qtrix.add_override *to_args(config_set, queue_list, workers)
|
72
|
+
write("Added #{workers} overrides for #{queue_list}")
|
73
|
+
elsif delete_overrides_params_provided?
|
74
|
+
Qtrix.remove_override *to_args(config_set, queue_list, workers)
|
75
|
+
write("Deleted #{workers} overrides for #{queue_list}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
def config_set
|
81
|
+
config[:config_set]
|
82
|
+
end
|
83
|
+
|
84
|
+
def stringify
|
85
|
+
lambda {|override| string_value_of(override)}
|
86
|
+
end
|
87
|
+
|
88
|
+
def overrides
|
89
|
+
Qtrix.overrides config[:config_set]
|
90
|
+
end
|
91
|
+
|
92
|
+
def string_value_of(override)
|
93
|
+
" #{hostname_of(override)}: #{override.queues.join(',')}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def hostname_of(override)
|
97
|
+
override.host || 'Unclaimed'
|
98
|
+
end
|
99
|
+
|
100
|
+
def add_overrides_params_provided?
|
101
|
+
config[:add_overrides] && queues_and_workers?
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete_overrides_params_provided?
|
105
|
+
config[:delete_overrides] && queues_and_workers?
|
106
|
+
end
|
107
|
+
|
108
|
+
def queues_and_workers?
|
109
|
+
config[:queue_list] && config[:workers]
|
110
|
+
end
|
111
|
+
|
112
|
+
def queue_list
|
113
|
+
config[:queue_list].split(",").map(&:strip)
|
114
|
+
end
|
115
|
+
|
116
|
+
def workers
|
117
|
+
config[:workers].to_i
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Qtrix
|
2
|
+
module CLI
|
3
|
+
class Queues < Base
|
4
|
+
banner <<-EOS
|
5
|
+
|
6
|
+
Usage: bundle exec qtrix queues [options]
|
7
|
+
|
8
|
+
Allows observance and manipulation of queue priority within a
|
9
|
+
configuration set. With this you can:
|
10
|
+
|
11
|
+
* View the current queue priority in the global worker pool.
|
12
|
+
* Specify the weightings for all queues inline or via yaml
|
13
|
+
|
14
|
+
Options include:
|
15
|
+
|
16
|
+
EOS
|
17
|
+
|
18
|
+
option :queue_weights,
|
19
|
+
short: '-w',
|
20
|
+
long: '--weights QUEUE_WEIGHT_LIST',
|
21
|
+
description: 'Specifies the queue-weight mappings as queue:weight,queue:weight,...'
|
22
|
+
|
23
|
+
option :queue_weights_yaml,
|
24
|
+
short: '-y',
|
25
|
+
long: '--yaml PATH_TO_YAML',
|
26
|
+
description: 'Path to a yaml containing a hash of queue names to weights'
|
27
|
+
|
28
|
+
option :desired_distribution,
|
29
|
+
short: '-l',
|
30
|
+
long: '--list',
|
31
|
+
description: 'Lists the queues by their desired distribution'
|
32
|
+
|
33
|
+
option :config_set,
|
34
|
+
short: '-c CONFIG_SET_NAME',
|
35
|
+
long: '--config-set CONFIG_SET_NAME',
|
36
|
+
description: 'Specify the config set being operated on'
|
37
|
+
|
38
|
+
option :host,
|
39
|
+
short: '-h HOST',
|
40
|
+
description: 'The host where redis is located',
|
41
|
+
default: 'localhost'
|
42
|
+
|
43
|
+
option :port,
|
44
|
+
short: '-p PORT',
|
45
|
+
description: 'The host which redis is listening on',
|
46
|
+
default: 6379
|
47
|
+
|
48
|
+
option :db,
|
49
|
+
short: '-n DB',
|
50
|
+
description: 'The redis DB where the action should occur',
|
51
|
+
default: 0
|
52
|
+
|
53
|
+
def exec_behavior
|
54
|
+
if config[:desired_distribution]
|
55
|
+
msg = "Queues:\n"
|
56
|
+
msg += Qtrix.desired_distribution.map(&stringify).join("\n")
|
57
|
+
write(msg)
|
58
|
+
elsif queue_weights
|
59
|
+
map_queue_weights queue_weights
|
60
|
+
elsif queue_weights_yaml
|
61
|
+
map_queue_weights queue_weights_yaml
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def map_queue_weights(weight_map)
|
67
|
+
Qtrix.map_queue_weights *to_args(config_set, weight_map)
|
68
|
+
write("OK")
|
69
|
+
end
|
70
|
+
|
71
|
+
def stringify
|
72
|
+
lambda {|queue| " #{queue.name} (#{queue.weight})"}
|
73
|
+
end
|
74
|
+
|
75
|
+
def string_value_of(queue)
|
76
|
+
" #{q.name} (#{q.weight})"
|
77
|
+
end
|
78
|
+
|
79
|
+
def queue_weights
|
80
|
+
if config[:queue_weights]
|
81
|
+
tuple_list = config[:queue_weights].split(",").map{|kv| kv.split(":")}
|
82
|
+
Hash[tuple_list]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def queue_weights_yaml
|
87
|
+
if config[:queue_weights_yaml]
|
88
|
+
YAML.load(File.read(config[:queue_weights_yaml]))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def config_set
|
93
|
+
config[:config_set]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/qtrix/matrix.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'qtrix/matrix/common'
|
3
|
+
require 'qtrix/matrix/model'
|
4
|
+
require 'qtrix/matrix/queue_picker'
|
5
|
+
require 'qtrix/matrix/reader'
|
6
|
+
require 'qtrix/matrix/row_builder'
|
7
|
+
require 'qtrix/matrix/queue_prioritizer'
|
8
|
+
require 'qtrix/matrix/analyzer'
|
9
|
+
|
10
|
+
module Qtrix
|
11
|
+
##
|
12
|
+
# Represents the matrix of queues to workers across the global worker pool,
|
13
|
+
# and is used to assign queues to pools based on the following goals:
|
14
|
+
#
|
15
|
+
# 1. Maintain a desired distribution of worker resources to queues based
|
16
|
+
# on their weight within the system as a whole.
|
17
|
+
# 2. Ensure that every queue is at the head of at least 1 workers queue
|
18
|
+
# list.
|
19
|
+
#
|
20
|
+
# Given a queue's weight, we calculate its resource percentage as:
|
21
|
+
#
|
22
|
+
# weight_of(queue) / weight_of(all_queues)
|
23
|
+
#
|
24
|
+
# To generate the list of queues for a worker, we take a list of queues
|
25
|
+
# sorted by current priority. The current priority is calculated in one of
|
26
|
+
# two ways. If the queue has not been at the head of the list, it is
|
27
|
+
# calculated as:
|
28
|
+
#
|
29
|
+
# resource_percentage_of(queue) * 1000
|
30
|
+
#
|
31
|
+
# This inflated value ensures that we will assign all queues to the head of
|
32
|
+
# at least one worker's queue list.
|
33
|
+
#
|
34
|
+
# If a queue has not been assigned to the head of a worker's queue list,
|
35
|
+
# the following algortihm is used. Each entry in the matrix has a value,
|
36
|
+
# which is generally the likelihood that the queue would be reached given
|
37
|
+
# the weight of jobs to its left in a queue list. Mathematically, this is:
|
38
|
+
#
|
39
|
+
# 1.0 - resource_percentage_of(previous_queues_in_list)
|
40
|
+
#
|
41
|
+
# Thus the closer to the head of a list a queue's entry is, the higher a
|
42
|
+
# value it receives for that entry. The priority is then calculated as:
|
43
|
+
#
|
44
|
+
# resource_percentage_of(queue) / (1.0 + value_of(entries_for(queue))
|
45
|
+
#
|
46
|
+
# This ensures that the higher a queue appears in a list, the lower its
|
47
|
+
# priority for the next generated list.
|
48
|
+
|
49
|
+
module Matrix
|
50
|
+
include Qtrix::Namespacing
|
51
|
+
include Common
|
52
|
+
|
53
|
+
class << self
|
54
|
+
##
|
55
|
+
# Obtain lists of queues for a number of worker processes
|
56
|
+
# on a server identified by the hostname. This works
|
57
|
+
# within the current namespace only.
|
58
|
+
def queues_for!(*args)
|
59
|
+
namespace, hostname, workers = extract_args(2, *args)
|
60
|
+
QueuePicker.new(namespace, Reader, hostname, workers).pick!
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Returns all of the queues in the table.
|
65
|
+
def fetch(namespace=:current)
|
66
|
+
Reader.fetch(namespace)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Fetches a matrix of simple queue names for each entry.
|
71
|
+
def to_table(namespace=:current)
|
72
|
+
Reader.to_table(namespace)
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Clears the matrix so its rebuilt again when rows are requested.
|
77
|
+
def clear!(namespace=:current)
|
78
|
+
redis(namespace).del(REDIS_KEY)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|