lex-cognitive-load-balancing 0.1.0
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.
- checksums.yaml +7 -0
- data/lib/legion/extensions/cognitive_load_balancing/client.rb +15 -0
- data/lib/legion/extensions/cognitive_load_balancing/helpers/constants.rb +40 -0
- data/lib/legion/extensions/cognitive_load_balancing/helpers/load_balancer.rb +133 -0
- data/lib/legion/extensions/cognitive_load_balancing/helpers/subsystem.rb +97 -0
- data/lib/legion/extensions/cognitive_load_balancing/runners/cognitive_load_balancing.rb +65 -0
- data/lib/legion/extensions/cognitive_load_balancing/version.rb +9 -0
- data/lib/legion/extensions/cognitive_load_balancing.rb +16 -0
- metadata +68 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c544354dc10debe5f37d9481d770ae4d9ef9d9ff05e2f9463c7ede05ce1243bf
|
|
4
|
+
data.tar.gz: 6d51ca7b3e00f79ab95e6cd401c1499b78af6554dd017ef80600fd67e24d601c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 57cc1ca0b87c2fc8d5be5d5341072cba5f8338a52dbf2c236a54b7b4ba4c27a3b71b85de72fcf2f237bb5401531663f6dc7986d911357fbceab06dcb4d7e1280
|
|
7
|
+
data.tar.gz: 44efcecde4666d5374e1e8abdaa3f8b3fe7ba1ada740112b2ffb0b285fa06eaa71c5a5f53a7b491f54922cde082df6d17631f6dd40837d8acd9e5ec0755ef982
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLoadBalancing
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_SUBSYSTEMS = 50
|
|
9
|
+
MAX_TASKS = 500
|
|
10
|
+
|
|
11
|
+
DEFAULT_CAPACITY = 1.0
|
|
12
|
+
OVERLOAD_THRESHOLD = 0.85
|
|
13
|
+
UNDERLOAD_THRESHOLD = 0.2
|
|
14
|
+
REBALANCE_STEP = 0.1
|
|
15
|
+
|
|
16
|
+
LOAD_LABELS = {
|
|
17
|
+
(0.85..) => :overloaded,
|
|
18
|
+
(0.7...0.85) => :heavy,
|
|
19
|
+
(0.4...0.7) => :balanced,
|
|
20
|
+
(0.2...0.4) => :light,
|
|
21
|
+
(..0.2) => :idle
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
HEALTH_LABELS = {
|
|
25
|
+
(0.8..) => :excellent,
|
|
26
|
+
(0.6...0.8) => :good,
|
|
27
|
+
(0.4...0.6) => :fair,
|
|
28
|
+
(0.2...0.4) => :strained,
|
|
29
|
+
(..0.2) => :failing
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
SUBSYSTEM_TYPES = %i[
|
|
33
|
+
perception reasoning memory attention
|
|
34
|
+
language planning motor emotional
|
|
35
|
+
].freeze
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLoadBalancing
|
|
6
|
+
module Helpers
|
|
7
|
+
class LoadBalancer
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@subsystems = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def register_subsystem(name:, subsystem_type: :general, capacity: DEFAULT_CAPACITY)
|
|
15
|
+
prune_if_needed
|
|
16
|
+
subsystem = Subsystem.new(
|
|
17
|
+
name: name,
|
|
18
|
+
subsystem_type: subsystem_type,
|
|
19
|
+
capacity: capacity
|
|
20
|
+
)
|
|
21
|
+
@subsystems[subsystem.id] = subsystem
|
|
22
|
+
subsystem
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def assign_load(subsystem_id:, amount:)
|
|
26
|
+
subsystem = @subsystems[subsystem_id]
|
|
27
|
+
return nil unless subsystem
|
|
28
|
+
|
|
29
|
+
subsystem.add_load!(amount: amount)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def shed_load(subsystem_id:, amount:)
|
|
33
|
+
subsystem = @subsystems[subsystem_id]
|
|
34
|
+
return nil unless subsystem
|
|
35
|
+
|
|
36
|
+
subsystem.shed_load!(amount: amount)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def auto_assign(amount:, subsystem_type: nil)
|
|
40
|
+
candidates = if subsystem_type
|
|
41
|
+
@subsystems.values.select { |s| s.subsystem_type == subsystem_type.to_sym }
|
|
42
|
+
else
|
|
43
|
+
@subsystems.values
|
|
44
|
+
end
|
|
45
|
+
return nil if candidates.empty?
|
|
46
|
+
|
|
47
|
+
best = candidates.min_by(&:utilization)
|
|
48
|
+
best.add_load!(amount: amount)
|
|
49
|
+
best
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def rebalance
|
|
53
|
+
overloaded = @subsystems.values.select(&:overloaded?)
|
|
54
|
+
underloaded = @subsystems.values.select(&:underloaded?)
|
|
55
|
+
transfers = 0
|
|
56
|
+
|
|
57
|
+
overloaded.each do |over|
|
|
58
|
+
target = underloaded.min_by(&:utilization)
|
|
59
|
+
break unless target
|
|
60
|
+
|
|
61
|
+
transfer_amount = [REBALANCE_STEP, over.current_load * 0.2].min
|
|
62
|
+
shed = over.shed_load!(amount: transfer_amount)
|
|
63
|
+
target.add_load!(amount: shed)
|
|
64
|
+
transfers += 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
transfers
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def overloaded_subsystems
|
|
71
|
+
@subsystems.values.select(&:overloaded?)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def underloaded_subsystems
|
|
75
|
+
@subsystems.values.select(&:underloaded?)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def subsystems_by_type(subsystem_type:)
|
|
79
|
+
st = subsystem_type.to_sym
|
|
80
|
+
@subsystems.values.select { |s| s.subsystem_type == st }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def most_loaded(limit: 5)
|
|
84
|
+
@subsystems.values.sort_by { |s| -s.utilization }.first(limit)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def overall_utilization
|
|
88
|
+
return 0.0 if @subsystems.empty?
|
|
89
|
+
|
|
90
|
+
utils = @subsystems.values.map(&:utilization)
|
|
91
|
+
(utils.sum / utils.size).round(10)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def overall_health
|
|
95
|
+
return 1.0 if @subsystems.empty?
|
|
96
|
+
|
|
97
|
+
healths = @subsystems.values.map(&:health)
|
|
98
|
+
(healths.sum / healths.size).round(10)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def balance_report
|
|
102
|
+
{
|
|
103
|
+
total_subsystems: @subsystems.size,
|
|
104
|
+
overloaded_count: overloaded_subsystems.size,
|
|
105
|
+
underloaded_count: underloaded_subsystems.size,
|
|
106
|
+
overall_utilization: overall_utilization,
|
|
107
|
+
overall_health: overall_health,
|
|
108
|
+
most_loaded: most_loaded(limit: 3).map(&:to_h)
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def to_h
|
|
113
|
+
{
|
|
114
|
+
total_subsystems: @subsystems.size,
|
|
115
|
+
overall_utilization: overall_utilization,
|
|
116
|
+
overall_health: overall_health,
|
|
117
|
+
overloaded_count: overloaded_subsystems.size
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def prune_if_needed
|
|
124
|
+
return if @subsystems.size < MAX_SUBSYSTEMS
|
|
125
|
+
|
|
126
|
+
least_used = @subsystems.values.min_by(&:tasks_processed)
|
|
127
|
+
@subsystems.delete(least_used.id) if least_used
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveLoadBalancing
|
|
8
|
+
module Helpers
|
|
9
|
+
class Subsystem
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :name, :subsystem_type, :capacity, :current_load,
|
|
13
|
+
:tasks_processed, :tasks_shed, :created_at
|
|
14
|
+
|
|
15
|
+
def initialize(name:, subsystem_type: :general, capacity: DEFAULT_CAPACITY)
|
|
16
|
+
@id = SecureRandom.uuid
|
|
17
|
+
@name = name
|
|
18
|
+
@subsystem_type = subsystem_type.to_sym
|
|
19
|
+
@capacity = capacity.to_f.clamp(0.1, 5.0)
|
|
20
|
+
@current_load = 0.0
|
|
21
|
+
@tasks_processed = 0
|
|
22
|
+
@tasks_shed = 0
|
|
23
|
+
@created_at = Time.now.utc
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def utilization
|
|
27
|
+
return 0.0 if @capacity.zero?
|
|
28
|
+
|
|
29
|
+
(@current_load / @capacity).clamp(0.0, 1.5).round(10)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load_label
|
|
33
|
+
match = LOAD_LABELS.find { |range, _| range.cover?(utilization) }
|
|
34
|
+
match ? match.last : :overloaded
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def overloaded?
|
|
38
|
+
utilization >= OVERLOAD_THRESHOLD
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def underloaded?
|
|
42
|
+
utilization <= UNDERLOAD_THRESHOLD
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def health
|
|
46
|
+
if overloaded?
|
|
47
|
+
[1.0 - ((utilization - OVERLOAD_THRESHOLD) * 3), 0.0].max.round(10)
|
|
48
|
+
else
|
|
49
|
+
1.0
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def health_label
|
|
54
|
+
match = HEALTH_LABELS.find { |range, _| range.cover?(health) }
|
|
55
|
+
match ? match.last : :failing
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_load!(amount:)
|
|
59
|
+
@current_load = (@current_load + amount.to_f).clamp(0.0, @capacity * 1.5).round(10)
|
|
60
|
+
@tasks_processed += 1
|
|
61
|
+
self
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def shed_load!(amount:)
|
|
65
|
+
removed = [amount.to_f, @current_load].min
|
|
66
|
+
@current_load = (@current_load - removed).clamp(0.0, @capacity * 1.5).round(10)
|
|
67
|
+
@tasks_shed += 1
|
|
68
|
+
removed
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def available_capacity
|
|
72
|
+
[(@capacity - @current_load), 0.0].max.round(10)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_h
|
|
76
|
+
{
|
|
77
|
+
id: @id,
|
|
78
|
+
name: @name,
|
|
79
|
+
subsystem_type: @subsystem_type,
|
|
80
|
+
capacity: @capacity,
|
|
81
|
+
current_load: @current_load,
|
|
82
|
+
utilization: utilization,
|
|
83
|
+
load_label: load_label,
|
|
84
|
+
overloaded: overloaded?,
|
|
85
|
+
health: health,
|
|
86
|
+
health_label: health_label,
|
|
87
|
+
available_capacity: available_capacity,
|
|
88
|
+
tasks_processed: @tasks_processed,
|
|
89
|
+
tasks_shed: @tasks_shed,
|
|
90
|
+
created_at: @created_at
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveLoadBalancing
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveLoadBalancing
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
9
|
+
|
|
10
|
+
def register_cognitive_subsystem(name:, subsystem_type: :general,
|
|
11
|
+
capacity: nil, **)
|
|
12
|
+
cap = capacity || Helpers::Constants::DEFAULT_CAPACITY
|
|
13
|
+
sub = engine.register_subsystem(name: name, subsystem_type: subsystem_type,
|
|
14
|
+
capacity: cap)
|
|
15
|
+
{ success: true }.merge(sub.to_h)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def assign_cognitive_load(subsystem_id:, amount:, **)
|
|
19
|
+
result = engine.assign_load(subsystem_id: subsystem_id, amount: amount)
|
|
20
|
+
return { success: false, error: 'subsystem not found' } unless result
|
|
21
|
+
|
|
22
|
+
{ success: true }.merge(result.to_h)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def shed_cognitive_load(subsystem_id:, amount:, **)
|
|
26
|
+
result = engine.shed_load(subsystem_id: subsystem_id, amount: amount)
|
|
27
|
+
return { success: false, error: 'subsystem not found' } unless result
|
|
28
|
+
|
|
29
|
+
{ success: true, shed: result }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def auto_assign_load(amount:, subsystem_type: nil, **)
|
|
33
|
+
result = engine.auto_assign(amount: amount, subsystem_type: subsystem_type)
|
|
34
|
+
return { success: false, error: 'no available subsystem' } unless result
|
|
35
|
+
|
|
36
|
+
{ success: true, assigned_to: result.name }.merge(result.to_h)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def rebalance_cognitive_load(**)
|
|
40
|
+
transfers = engine.rebalance
|
|
41
|
+
{ success: true, transfers: transfers, stats: engine.to_h }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def overloaded_subsystems_report(**)
|
|
45
|
+
subs = engine.overloaded_subsystems
|
|
46
|
+
{ success: true, count: subs.size, subsystems: subs.map(&:to_h) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def most_loaded_report(limit: 5, **)
|
|
50
|
+
subs = engine.most_loaded(limit: limit)
|
|
51
|
+
{ success: true, limit: limit, subsystems: subs.map(&:to_h) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def cognitive_load_balance_report(**)
|
|
55
|
+
engine.balance_report
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cognitive_load_balancing_stats(**)
|
|
59
|
+
engine.to_h
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'cognitive_load_balancing/version'
|
|
4
|
+
require_relative 'cognitive_load_balancing/helpers/constants'
|
|
5
|
+
require_relative 'cognitive_load_balancing/helpers/subsystem'
|
|
6
|
+
require_relative 'cognitive_load_balancing/helpers/load_balancer'
|
|
7
|
+
require_relative 'cognitive_load_balancing/runners/cognitive_load_balancing'
|
|
8
|
+
require_relative 'cognitive_load_balancing/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module CognitiveLoadBalancing
|
|
13
|
+
extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-cognitive-load-balancing
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Dynamic distribution of cognitive work across subsystems with overload
|
|
27
|
+
detection, auto-assignment, and rebalancing.
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- lib/legion/extensions/cognitive_load_balancing.rb
|
|
35
|
+
- lib/legion/extensions/cognitive_load_balancing/client.rb
|
|
36
|
+
- lib/legion/extensions/cognitive_load_balancing/helpers/constants.rb
|
|
37
|
+
- lib/legion/extensions/cognitive_load_balancing/helpers/load_balancer.rb
|
|
38
|
+
- lib/legion/extensions/cognitive_load_balancing/helpers/subsystem.rb
|
|
39
|
+
- lib/legion/extensions/cognitive_load_balancing/runners/cognitive_load_balancing.rb
|
|
40
|
+
- lib/legion/extensions/cognitive_load_balancing/version.rb
|
|
41
|
+
homepage: https://github.com/LegionIO/lex-cognitive-load-balancing
|
|
42
|
+
licenses:
|
|
43
|
+
- MIT
|
|
44
|
+
metadata:
|
|
45
|
+
homepage_uri: https://github.com/LegionIO/lex-cognitive-load-balancing
|
|
46
|
+
source_code_uri: https://github.com/LegionIO/lex-cognitive-load-balancing
|
|
47
|
+
documentation_uri: https://github.com/LegionIO/lex-cognitive-load-balancing
|
|
48
|
+
changelog_uri: https://github.com/LegionIO/lex-cognitive-load-balancing/blob/main/CHANGELOG.md
|
|
49
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-cognitive-load-balancing/issues
|
|
50
|
+
rubygems_mfa_required: 'true'
|
|
51
|
+
rdoc_options: []
|
|
52
|
+
require_paths:
|
|
53
|
+
- lib
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '3.4'
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 3.6.9
|
|
66
|
+
specification_version: 4
|
|
67
|
+
summary: Cognitive load balancing across subsystems for LegionIO
|
|
68
|
+
test_files: []
|