slotz 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1829fa39dcca433a7c74867bde89d53f70447c4c72c7abd54fb7ef4e68a73c87
4
+ data.tar.gz: acc87fb694a334b2fbaabf99bb4c608ee6fd9fa168f4f143c2087fd4e86e4f2d
5
+ SHA512:
6
+ metadata.gz: cae56d7e4293d7b3152dd353127ef550b089fefb49aea0e3b83cb48e41a67fee0f9a3e7d2f2166872791ceebcabe5066039ecb5969f64063e4a8358fd0ef2a68
7
+ data.tar.gz: 05e01352f292781b59cd23cc4fc0ae665ff718d4c1451c8afefdbcd1c9c3c9149e1bc9ccc6cab42ee97d3a89fbd9a3b62d9f61b67c9de671760a2a9438c62ea1
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rake', '13.0.3'
4
+
5
+ group :docs do
6
+ gem 'yard'
7
+ gem 'redcarpet'
8
+ end
9
+
10
+ group :spec do
11
+ gem 'rspec'
12
+ end
13
+
14
+ group :prof do
15
+ gem 'benchmark-ips'
16
+ gem 'memory_profiler'
17
+ end
18
+
19
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,29 @@
1
+ # License
2
+
3
+ Copyright (C) 2025, Anastasios Laskos <mailto:anastasios.laskos@gmail.com>
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without modification,
7
+ are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice,
10
+ this list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its contributors
17
+ may be used to endorse or promote products derived from this software
18
+ without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Slotz
2
+
3
+ TBD/WiP
@@ -0,0 +1,13 @@
1
+ module Slotz
2
+
3
+ # class Options
4
+ #
5
+ # attr_reader :max_slots
6
+ #
7
+ # def initialize( options = {} )
8
+ # @max_slots = options[:max_slots]
9
+ # end
10
+ #
11
+ # end
12
+
13
+ end
@@ -0,0 +1,43 @@
1
+ module Slotz
2
+
3
+ class Reservation < Module
4
+
5
+ attr_reader :disk
6
+ attr_reader :memory
7
+ attr_reader :cores
8
+
9
+ def initialize( provision )
10
+ @provision = provision
11
+
12
+ ObjectSpace.define_finalizer(self, proc {
13
+ Slotz::RESERVED[:disk] -= self.class.disk
14
+ Slotz::RESERVED[:memory] -= self.class.memory
15
+ Slotz::RESERVED[:cores] -= self.class.cores
16
+ })
17
+ end
18
+
19
+ def included( base )
20
+ provisions = @provision
21
+ base.class_eval do
22
+ @disk = provisions[:disk]
23
+ def self.disk
24
+ @disk
25
+ end
26
+
27
+ @memory = provisions[:memory]
28
+ def self.memory
29
+ @memory
30
+ end
31
+
32
+ @cores = provisions[:cores]
33
+ def self.cores
34
+ @cores
35
+ end
36
+ end
37
+
38
+ Slotz.filter base
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,40 @@
1
+ module Slotz
2
+
3
+ class Application
4
+
5
+ attr_reader :klass
6
+ attr_reader :requirements
7
+
8
+ def initialize( klass, requirements = {} )
9
+ @klass = klass
10
+ @requirements = requirements
11
+
12
+ Slotz.occupy self
13
+ end
14
+
15
+ def disk
16
+ @requirements[:disk]
17
+ end
18
+
19
+ def remaining_disk
20
+
21
+ end
22
+
23
+ def cores
24
+ @requirements[:cores]
25
+ end
26
+
27
+ def remaining_cores
28
+
29
+ end
30
+
31
+ def memory
32
+ @requirements[:memory]
33
+ end
34
+
35
+ def remaining_memory
36
+
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,74 @@
1
+ require 'concurrent'
2
+ require 'tmpdir'
3
+
4
+ module Slotz
5
+
6
+ class System
7
+ module Platforms
8
+
9
+ class Base
10
+ class <<self
11
+
12
+ # @private
13
+ def inherited( platform )
14
+ System.register_platform platform
15
+ end
16
+
17
+ # @return [Bool]
18
+ # `true` if it's the current platform, `false` otherwise.
19
+ #
20
+ # @abstract
21
+ def current?
22
+ raise 'Missing implementation'
23
+ end
24
+ end
25
+
26
+ # @return [Integer]
27
+ # Amount of free RAM in bytes.
28
+ #
29
+ # @abstract
30
+ def memory_free
31
+ raise 'Missing implementation'
32
+ end
33
+
34
+ # @param [Integer] pgid
35
+ # Process group ID.
36
+ #
37
+ # @return [Integer]
38
+ # Amount of RAM in bytes used by the given GPID.
39
+ #
40
+ # @abstract
41
+ def memory_for_process_group( pgid )
42
+ raise 'Missing implementation'
43
+ end
44
+
45
+ # @return [Integer]
46
+ # Amount of free disk in bytes.
47
+ #
48
+ # @abstract
49
+ def disk_space_free
50
+ raise 'Missing implementation'
51
+ end
52
+
53
+ # @return [String
54
+ # Location for temporary file storage.
55
+ def disk_directory
56
+ Dir.tmpdir
57
+ end
58
+
59
+ # @return [Integer]
60
+ # Amount of CPU cores.
61
+ def cpu_count
62
+ Concurrent.processor_count
63
+ end
64
+
65
+ # @private
66
+ def _exec( cmd )
67
+ %x(#{cmd})
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'mixins/unix'
2
+
3
+ module Slotz
4
+
5
+ class System
6
+ module Platforms
7
+ class Linux < Base
8
+ include Mixins::Unix
9
+
10
+ # @return [Integer]
11
+ # Amount of free RAM in bytes.
12
+ def memory_free
13
+ memory.available_bytes
14
+ end
15
+
16
+ class <<self
17
+ def current?
18
+ Slotz::System.linux?
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ require 'vmstat'
2
+
3
+ module Slotz
4
+ class System
5
+ module Platforms
6
+ module Mixins
7
+
8
+ module Unix
9
+
10
+ # @param [Integer] pgid
11
+ # Process group ID.
12
+ #
13
+ # @return [Integer]
14
+ # Amount of RAM in bytes used by the given GPID.
15
+ def memory_for_process_group( pgid )
16
+ rss = 0
17
+
18
+ _exec( "ps -o rss -g #{pgid}" ).split("\n")[1..-1].each do |rss_string|
19
+ rss += rss_string.to_i
20
+ end
21
+
22
+ rss * pagesize
23
+ end
24
+
25
+ # @return [Integer]
26
+ # Amount of free disk in bytes.
27
+ def disk_space_free
28
+ Vmstat.disk( disk_directory ).available_bytes
29
+ end
30
+
31
+ private
32
+
33
+ def pagesize
34
+ @pagesize ||= memory.pagesize
35
+ end
36
+
37
+ def memory
38
+ Vmstat.memory
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ require_relative 'mixins/unix'
2
+
3
+ module Slotz
4
+
5
+ class System
6
+ module Platforms
7
+ class OSX < Base
8
+ include Mixins::Unix
9
+
10
+ # @return [Integer]
11
+ # Amount of free RAM in bytes.
12
+ def memory_free
13
+ pagesize * memory.free
14
+ end
15
+
16
+ class <<self
17
+ def current?
18
+ Slotz::System.mac?
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,81 @@
1
+ module Slotz
2
+
3
+ class System
4
+ module Platforms
5
+
6
+ class Windows < Base
7
+
8
+ class <<self
9
+ def current?
10
+ Slotz::System.windows?
11
+ end
12
+ end
13
+
14
+ # @return [Integer]
15
+ # Amount of free RAM in bytes.
16
+ def memory_free
17
+ result = wmi.ExecQuery(
18
+ 'select AvailableBytes from Win32_PerfFormattedData_PerfOS_Memory'
19
+ )
20
+
21
+ memory = nil
22
+ result.each do |e|
23
+ memory = e.availableBytes.to_i
24
+ e.ole_free
25
+ end
26
+ result.ole_free
27
+
28
+ memory
29
+ end
30
+
31
+ # @param [Integer] pgid
32
+ # Process group ID.
33
+ #
34
+ # @return [Integer]
35
+ # Amount of RAM in bytes used by the given GPID.
36
+ def memory_for_process_group( pgid )
37
+ processes = wmi.ExecQuery(
38
+ "select PrivatePageCount from win32_process where ProcessID='#{pgid}' or ParentProcessID='#{pgid}'"
39
+ )
40
+
41
+ memory = 0
42
+ processes.each do |process|
43
+ # Not actually pages but bytes, no idea why.
44
+ memory += process.privatePageCount.to_i
45
+ process.ole_free
46
+ end
47
+ processes.ole_free
48
+
49
+ memory
50
+ end
51
+
52
+ # @return [Integer]
53
+ # Amount of free disk in bytes.
54
+ def disk_space_free
55
+ device_id = disk_directory.split( '/' ).first
56
+
57
+ drives = wmi.ExecQuery(
58
+ "select FreeSpace from win32_LogicalDisk where DeviceID='#{device_id}'"
59
+ )
60
+
61
+ space = nil
62
+ drives.each do |drive|
63
+ space = drive.freeSpace.to_i
64
+ drive.ole_free
65
+ end
66
+ drives.ole_free
67
+
68
+ space
69
+ end
70
+
71
+ private
72
+
73
+ def wmi
74
+ @wmi ||= WIN32OLE.connect( 'winmgmts://' )
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,12 @@
1
+ module Slotz
2
+
3
+ class System
4
+ module Platforms
5
+
6
+ end
7
+ end
8
+ end
9
+
10
+ Dir.glob( "#{File.dirname(__FILE__)}/**/*.rb" ).each do |platform|
11
+ require platform
12
+ end
@@ -0,0 +1,241 @@
1
+ require_relative 'system/platforms/base'
2
+ require 'singleton'
3
+ require 'set'
4
+
5
+ module Slotz
6
+
7
+ class System
8
+ include Singleton
9
+
10
+ # @return [Bool]
11
+ def self.windows?
12
+ @is_windows ||= Gem.win_platform?
13
+ end
14
+
15
+ # @return [Bool]
16
+ def self.linux?
17
+ @is_linux ||= RbConfig::CONFIG['host_os'] =~ /linux/
18
+ end
19
+
20
+ # @return [Bool]
21
+ def self.mac?
22
+ @is_mac ||= RbConfig::CONFIG['host_os'] =~ /darwin|mac os/i
23
+ end
24
+
25
+ # @return [Array<Platforms::Base>]
26
+ attr_reader :platforms
27
+
28
+ # @return [Slots]
29
+ attr_reader :slots
30
+
31
+ attr_accessor :max_slots
32
+
33
+ def initialize
34
+ @platforms = []
35
+ @pids = Set.new
36
+ end
37
+
38
+ def use( pid )
39
+ @pids << pid
40
+ pid
41
+ end
42
+
43
+ # @return [Integer]
44
+ # Amount of new applications that can be safely run in parallel, currently.
45
+ # User option will override decision based on system resources.
46
+ def available
47
+ # Manual mode, user gave us a value.
48
+ if (max_slots = System.max_slots)
49
+ max_slots - used
50
+
51
+ # Auto-mode, pick the safest restriction, RAM vs CPU.
52
+ else
53
+ available_auto
54
+ end
55
+ end
56
+
57
+ # @return [Integer]
58
+ # Amount of new applications that can be safely run in parallel, currently.
59
+ # The decision is based on the available resources alone.
60
+ def available_auto( requirements )
61
+ [ available_in_memory( requirements[:memory] ),
62
+ available_in_cpu( requirements[:cores] ),
63
+ available_in_disk( requirements[:disk] ) ].min
64
+ end
65
+
66
+ # @return [Integer]
67
+ # Amount of instances that are currently alive.
68
+ def used
69
+ Slotz.applications.size
70
+ end
71
+
72
+ # @return [Integer]
73
+ # Amount of scans that can be safely run in parallel, in total.
74
+ def total
75
+ used + available
76
+ end
77
+
78
+ def fits?( requirements )
79
+ available_auto( requirements )
80
+ end
81
+
82
+ # @return [Integer]
83
+ # Amount of processes we can fit into the available memory.
84
+ #
85
+ # Works based on slots, available memory isn't currently available OS
86
+ # memory but memory that is unallocated.
87
+ def available_in_memory( memory )
88
+ return Float::INFINITY if memory.to_i == 0
89
+ (unallocated_memory / memory).to_i
90
+ end
91
+
92
+ # @return [Integer]
93
+ # Amount of CPU cores that are available.
94
+ #
95
+ # Well, they may not be really available, other stuff on the machine could
96
+ # be using them to a considerable extent, but we can only do so much.
97
+ def available_in_cpu( cores )
98
+ @system.cpu_count - used
99
+ end
100
+
101
+ # @param [Integer] pid
102
+ #
103
+ # @return [Integer]
104
+ # Remaining memory for the scan, in bytes.
105
+ def remaining_memory_for( pid )
106
+ [memory_size - @system.memory_for_process_group( pid ), 0].max
107
+ end
108
+
109
+ # @return [Integer]
110
+ # Amount of memory (in bytes) available for future scans.
111
+ def unallocated_memory
112
+ # Available memory right now.
113
+ available_mem = @system.memory_free
114
+
115
+ # Remove allocated memory to figure out how much we can really spare.
116
+ @pids.each do |pid|
117
+ # Mark the remaining allocated memory as unavailable.
118
+ available_mem -= remaining_memory_for( pid )
119
+ end
120
+
121
+ available_mem
122
+ end
123
+
124
+ # @param [Integer] pid
125
+ #
126
+ # @return [Integer]
127
+ # Remaining disk space for the scan, in bytes.
128
+ def remaining_disk_space_for( pid )
129
+ [disk_space - @system.disk_space_for_process( pid ), 0].max
130
+ end
131
+
132
+ # @return [Integer]
133
+ # Amount of disk space (in bytes) available for future scans.
134
+ def unallocated_disk_space
135
+ # Available space right now.
136
+ available_space = @system.disk_space_free
137
+
138
+ # # Remove allocated space to figure out how much we can really spare.
139
+ # @pids.each do |pid|
140
+ # # Mark the remaining allocated space as unavailable.
141
+ # available_space -= remaining_disk_space_for( pid )
142
+ # end
143
+
144
+ available_space
145
+ end
146
+
147
+ def disk_space_requirement( application )
148
+ application.disk.to_i
149
+ end
150
+
151
+ # @return [Fixnum]
152
+ # Amount of memory (in bytes) to allocate.
153
+ def memory_requirement( application )
154
+ application.memory.to_i
155
+ end
156
+
157
+ # @return [Integer]
158
+ # Amount of free RAM in bytes.
159
+ def memory_free
160
+ platform.memory_free
161
+ end
162
+
163
+ # @param [Integer] pgid
164
+ # Process group ID.
165
+ #
166
+ # @return [Integer]
167
+ # Amount of RAM in bytes used by the given GPID.
168
+ def memory_for_process_group( pgid )
169
+ platform.memory_for_process_group( pgid )
170
+ end
171
+
172
+ # @return [Integer]
173
+ # Amount of free disk space in bytes.
174
+ def disk_space_free
175
+ platform.disk_space_free
176
+ end
177
+
178
+ # @return [String
179
+ # Location for temporary file storage.
180
+ def disk_directory
181
+ platform.disk_directory
182
+ end
183
+
184
+ # @param [Integer] pid
185
+ # Process ID.
186
+ #
187
+ # @return [Integer]
188
+ # Amount of disk space in bytes used by the given PID.
189
+ def disk_space_for_process( pid )
190
+ platform.disk_space_for_process( pid )
191
+ end
192
+
193
+ # @return [Integer]
194
+ # Amount of CPU cores.
195
+ def cpu_count
196
+ @cpu_count ||= platform.cpu_count
197
+ end
198
+
199
+ # @return [Platforms::Base]
200
+ def platform
201
+ return @platform if @platform
202
+
203
+ platforms.each do |klass|
204
+ next if !klass.current?
205
+
206
+ return @platform = klass.new
207
+ end
208
+
209
+ raise "Unsupported platform: #{RUBY_PLATFORM}"
210
+ end
211
+
212
+ # @private
213
+ def register_platform( platform )
214
+ platforms << platform
215
+ end
216
+
217
+ # @private
218
+ def reset
219
+ @pids.clear
220
+ @cpu_count = nil
221
+ @platform = nil
222
+ end
223
+
224
+ class <<self
225
+ def method_missing( sym, *args, &block )
226
+ if instance.respond_to?( sym )
227
+ instance.send( sym, *args, &block )
228
+ else
229
+ super( sym, *args, &block )
230
+ end
231
+ end
232
+
233
+ def respond_to?( *args )
234
+ super || instance.respond_to?( *args )
235
+ end
236
+ end
237
+
238
+ end
239
+ end
240
+
241
+ require_relative 'system/platforms'
@@ -0,0 +1,3 @@
1
+ module Slotz
2
+ VERSION = '0.1'
3
+ end
data/lib/slotz.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Slotz
2
+ require_relative 'slotz/version'
3
+ require_relative 'slotz/options'
4
+ require_relative 'slotz/system'
5
+ require_relative 'slotz/reservation'
6
+
7
+ RESERVED = {
8
+ disk: 0,
9
+ memory: 0,
10
+ cores: 0
11
+ }
12
+
13
+ def self.filter( reservation )
14
+ # fail 'Max utilization.' if Slotz::System.max_utilization?
15
+
16
+ if RESERVED[:disk] + reservation.disk <= System.disk_space_free
17
+ RESERVED[:disk] += reservation.disk
18
+ else
19
+ fail 'Not enough disk resources.'
20
+ end
21
+
22
+ if RESERVED[:memory] + reservation.memory <= System.memory_free
23
+ RESERVED[:memory] += reservation.memory
24
+ else
25
+ fail 'Not enough memory resources.'
26
+ end
27
+
28
+ # if RESERVED[:cores] + requirements[:cores] <= System.cores
29
+ # RESERVED[:cores] += requirements[:cores]
30
+ # else
31
+ # fail 'Not enough CPU resources.'
32
+ # end
33
+ end
34
+
35
+ end
data/slotz.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |s|
4
+ require_relative File.expand_path( File.dirname( __FILE__ ) ) + '/lib/slotz/version'
5
+
6
+ s.name = 'slotz'
7
+ s.version = Slotz::VERSION
8
+ s.date = Time.now.strftime( '%Y-%m-%d' )
9
+ s.summary = 'An application-centric, decentralised and distributed computing solution. '
10
+
11
+ s.homepage = 'https://github.com/qadron/cuboid'
12
+ s.email = 'tasos.laskos@gmail.com'
13
+ s.authors = [ 'Tasos Laskos' ]
14
+ s.licenses = ['MIT']
15
+
16
+ s.files += Dir.glob( 'config/**/**' )
17
+ s.files += Dir.glob( 'lib/**/**' )
18
+ s.files += Dir.glob( 'logs/**/**' )
19
+ s.files += Dir.glob( 'components/**/**' )
20
+ s.files += Dir.glob( 'spec/**/**' )
21
+ s.files += %w(Gemfile slotz.gemspec)
22
+ s.test_files = Dir.glob( 'spec/**/**' )
23
+
24
+ s.extra_rdoc_files = %w(README.md LICENSE.md)
25
+
26
+ s.rdoc_options = [ '--charset=UTF-8' ]
27
+
28
+ s.add_dependency 'awesome_print', '1.9.2'
29
+
30
+ # Don't specify version, messes with the packages since they always grab the
31
+ # latest one.
32
+ s.add_dependency 'bundler'
33
+
34
+ s.add_dependency 'concurrent-ruby'
35
+ s.add_dependency 'vmstat', '~> 2.3.1'
36
+ s.add_dependency 'sys-proctable', '~> 1.3.0'
37
+
38
+ s.description = <<DESCRIPTION
39
+ DESCRIPTION
40
+
41
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slotz::System::Platforms::Linux do
4
+ it_should_behave_like 'Slotz::System::Platforms::Mixins::Unix'
5
+
6
+ describe '#memory_free' do
7
+ it 'returns the amount of free memory' do
8
+ o = Object.new
9
+ expect(o).to receive(:available_bytes).and_return(1000)
10
+ expect(subject).to receive(:memory).at_least(:once).and_return(o)
11
+
12
+ expect(subject.memory_free).to eq 1000
13
+ end
14
+ end
15
+
16
+ describe '.current?' do
17
+ context 'when running on Linux' do
18
+ it 'returns true' do
19
+ expect(Slotz::System).to receive(:linux?).and_return( true )
20
+ expect(described_class).to be_current
21
+ end
22
+ end
23
+
24
+ context 'when not running on Linux' do
25
+ it 'returns false' do
26
+ expect(Slotz::System).to receive(:linux?).and_return( false )
27
+ expect(described_class).to_not be_current
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slotz::System::Platforms::OSX do
4
+ it_should_behave_like 'Slotz::System::Platforms::Mixins::Unix'
5
+
6
+ describe '#memory_free' do
7
+ it 'returns the amount of free memory' do
8
+ o = Object.new
9
+ expect(o).to receive(:free).and_return(1000)
10
+ expect(o).to receive(:pagesize).and_return(4096)
11
+ expect(subject).to receive(:memory).at_least(:once).and_return(o)
12
+
13
+ expect(subject.memory_free).to eq 4096000
14
+ end
15
+ end
16
+
17
+ describe '.current?' do
18
+ context 'when running on OSX' do
19
+ it 'returns true' do
20
+ expect(Slotz::System).to receive(:mac?).and_return( true )
21
+ expect(described_class).to be_current
22
+ end
23
+ end
24
+
25
+ context 'when not running on OSX' do
26
+ it 'returns false' do
27
+ expect(Slotz::System).to receive(:mac?).and_return( false )
28
+ expect(described_class).to_not be_current
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slotz::System::Platforms::Windows, if: Slotz::System.windows? do
4
+ it_should_behave_like 'Slotz::System::Platforms::Base'
5
+
6
+ subject { described_class.new }
7
+
8
+ describe '#memory_free' do
9
+ it 'returns the amount of free memory' do
10
+ expect(subject.memory_free).to be > 0
11
+ end
12
+ end
13
+
14
+ describe '#disk_space_free' do
15
+ it 'returns the amount of free disk space' do
16
+ expect(subject.disk_space_free).to be > 0
17
+ end
18
+ end
19
+
20
+ describe '#memory_for_process_group' do
21
+ it 'returns bytes of memory used by the group' do
22
+ expect(subject.memory_for_process_group( Process.pid )).to be > 0
23
+ end
24
+ end
25
+
26
+ describe '.current?' do
27
+ context 'when running on Windows' do
28
+ it 'returns true'do
29
+ expect(Slotz).to receive(:windows?).and_return( true )
30
+ expect(described_class).to be_current
31
+ end
32
+ end
33
+
34
+ context 'when not running on Windows' do
35
+ it 'returns false' do
36
+ expect(Slotz).to receive(:windows?).and_return( false )
37
+ expect(described_class).to_not be_current
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slotz do
4
+ subject { system }
5
+ let(:system) { Slotz::System.instance }
6
+
7
+ before :each do
8
+ subject.reset
9
+ end
10
+
11
+ describe '#available' do
12
+ context 'when OptionGroups::system#max_slots is set' do
13
+ before do
14
+ Slotz::System.max_slots = 5
15
+ end
16
+
17
+ it 'uses it to calculate available slots' do
18
+ expect(subject.available).to eq 5
19
+ end
20
+
21
+ context 'when some slots have been used' do
22
+ it 'subtracts them' do
23
+ allow(subject).to receive(:used).and_return( 2 )
24
+ expect(subject.available).to eq 3
25
+ end
26
+ end
27
+ end
28
+
29
+ context 'when OptionGroups::system#max_slots is not set' do
30
+ before do
31
+ Slotz::System.max_slots = nil
32
+ end
33
+
34
+ it 'uses #available_auto' do
35
+ pending
36
+ expect(subject).to receive(:available_auto).and_return( 25 )
37
+ expect(subject.available).to eq 25
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#available_auto' do
43
+ before do
44
+ Slotz::System.max_slots = nil
45
+ end
46
+
47
+ it 'calculates slots based on previously reserved resources' do
48
+ pending
49
+ end
50
+
51
+ context 'when restricted by memory' do
52
+ it 'bases the calculation on memory slots' do
53
+ pending
54
+ end
55
+ end
56
+
57
+ context 'when restricted by CPUs' do
58
+ it 'bases the calculation on CPU slots' do
59
+ pending
60
+ end
61
+ end
62
+
63
+ context 'when restricted by disk space' do
64
+ it 'bases the calculation on disk space' do
65
+ pending
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#used' do
71
+ it 'returns the amount of active instances' do
72
+ pending
73
+ end
74
+
75
+ context 'when a process dies' do
76
+ it 'gets removed from the count'
77
+ end
78
+ end
79
+
80
+ describe '#total' do
81
+ it 'sums up free and used slots' do
82
+ expect(subject).to receive(:available).and_return( 3 )
83
+ expect(subject).to receive(:used).and_return( 5 )
84
+
85
+ expect(subject.total).to eq 8
86
+ end
87
+ end
88
+
89
+ describe '#available_in_memory' do
90
+ it 'returns amount of free memory slots' do
91
+ pending
92
+ end
93
+ end
94
+
95
+ describe '#available_in_cpu' do
96
+ it 'returns amount of free CPUs splots' do
97
+ pending
98
+ end
99
+ end
100
+
101
+ describe '#unallocated_memory' do
102
+ context 'when there are no scans running' do
103
+ it 'returns the amount of free memory' do
104
+ pending
105
+ end
106
+ end
107
+
108
+ context 'when there are scans running' do
109
+ context 'using part of their allocation' do
110
+ it 'removes their allocated slots' do
111
+ pending
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ describe '#remaining_memory_for' do
118
+ it 'returns the amount of allocated memory available to the scan' do
119
+ pending
120
+ end
121
+ end
122
+
123
+ describe '#unallocated_disk_space' do
124
+ context 'when there are no scans running' do
125
+ it 'returns the amount of free disk space' do
126
+ pending
127
+ end
128
+ end
129
+
130
+ context 'when there are scans running' do
131
+ context 'using part of their allocation' do
132
+ it 'removes their allocated slots' do
133
+ pending
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ describe '#remaining_disk_space_for' do
140
+ it 'returns the amount of allocated disk space available to the scan' do
141
+ pending
142
+ end
143
+ end
144
+
145
+ describe '#memory_size' do
146
+ before do
147
+ # Slotz::Options.reset
148
+ end
149
+ let(:memory_size) { subject.memory_size }
150
+
151
+ it 'is approx 0.2GB with default options' do
152
+ pending
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slotz::System do
4
+ subject { described_class.instance }
5
+
6
+ describe '#memory_free' do
7
+ it 'delegates to #platform' do
8
+ expect(subject.platform).to receive(:memory_free).and_return(10)
9
+ expect(subject.memory_free).to eq 10
10
+ end
11
+ end
12
+
13
+ describe '#memory_for_process_group' do
14
+ it 'delegates to #platform' do
15
+ expect(subject.platform).to receive(:memory_for_process_group).with(123).and_return(10)
16
+ expect(subject.memory_for_process_group(123)).to eq 10
17
+ end
18
+ end
19
+
20
+ describe '#disk_space_free' do
21
+ it 'delegates to #platform' do
22
+ expect(subject.platform).to receive(:disk_space_free).and_return(10)
23
+ expect(subject.disk_space_free).to eq 10
24
+ end
25
+ end
26
+
27
+ describe '#disk_space_for_process' do
28
+ it 'delegates to #platform' do
29
+ expect(subject.platform).to receive(:disk_space_for_process).with(123).and_return(10)
30
+ expect(subject.disk_space_for_process(123)).to eq 10
31
+ end
32
+ end
33
+
34
+ describe '#disk_directory' do
35
+ it "delegates to #platform" do
36
+ expect(subject.platform).to receive(:disk_directory).and_return('10')
37
+ expect(subject.disk_directory).to eq '10'
38
+ end
39
+ end
40
+
41
+ describe '#cpu_count' do
42
+ it 'delegates to #platform' do
43
+ expect(subject.platform).to receive(:cpu_count).and_return(10)
44
+ expect(subject.cpu_count).to eq 10
45
+ end
46
+ end
47
+
48
+ describe '#platform' do
49
+ it 'returns the current platform' do
50
+ pending
51
+ platform_stub = Class.new do
52
+ def self.current?
53
+ true
54
+ end
55
+ end
56
+
57
+ subject.platforms.unshift platform_stub
58
+
59
+ expect(subject.platform).to be_instance_of platform_stub
60
+
61
+ subject.platforms.delete platform_stub
62
+ end
63
+
64
+ context 'when the platform could not be identified' do
65
+ it 'raises error' do
66
+ pending
67
+ subject.platforms.each do |platform|
68
+ expect(platform).to receive(:current?).and_return(false)
69
+ end
70
+
71
+ expect do
72
+ subject.platform
73
+ end.to raise_error "Unsupported platform: #{RUBY_PLATFORM}"
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#platforms' do
79
+ it 'returns all supported platforms' do
80
+ pending
81
+ expect(subject.platforms.map(&:to_s).sort).to eq [
82
+ described_class::Platforms::Linux,
83
+ described_class::Platforms::OSX,
84
+ described_class::Platforms::Windows
85
+ ].map(&:to_s).sort
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'support/helpers/paths'
2
+
3
+ Dir.glob( "#{support_path}/{lib,helpers,shared,factories}/**/*.rb" ).each { |f| require f }
4
+
5
+ RSpec::Core::MemoizedHelpers.module_eval do
6
+ alias to should
7
+ alias to_not should_not
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.run_all_when_everything_filtered = true
12
+ config.color = true
13
+ config.add_formatter :documentation
14
+ config.alias_example_to :expect_it
15
+ config.filter_run_when_matching focus: true
16
+
17
+ config.mock_with :rspec do |mocks|
18
+ mocks.yield_receiver_to_any_instance_implementation_blocks = true
19
+ end
20
+
21
+ config.before( :each ) do
22
+ # reset_all
23
+ end
24
+
25
+ config.after( :each ) do
26
+ # cleanup_instances
27
+ # processes_killall
28
+ end
29
+ config.after( :all ) do
30
+ # killall
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ def name_from_filename
2
+ File.basename( caller.first.split( ':' ).first, '_spec.rb' )
3
+ end
4
+
5
+ def spec_path
6
+ File.expand_path( File.dirname( File.absolute_path( __FILE__ ) ) + '/../../' ) + '/'
7
+ end
8
+
9
+ def support_path
10
+ "#{spec_path}support/"
11
+ end
12
+
13
+ def fixtures_path
14
+ "#{support_path}fixtures/"
15
+ end
@@ -0,0 +1,19 @@
1
+ require 'tmpdir'
2
+
3
+ shared_examples_for 'Slotz::System::Platforms::Base' do
4
+ subject { described_class.new }
5
+
6
+ describe '#disk_directory' do
7
+ it "delegates to #{Dir.tmpdir}" do
8
+ expect(subject.disk_directory).to eq Dir.tmpdir
9
+ end
10
+ end
11
+
12
+ describe '#cpu_count' do
13
+ it 'returns the amount of CPUs' do
14
+ expect(Concurrent).to receive(:processor_count).and_return(99)
15
+ expect(subject.cpu_count).to eq 99
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,37 @@
1
+ shared_examples_for 'Slotz::System::Platforms::Mixins::Unix' do
2
+ it_should_behave_like 'Slotz::System::Platforms::Base'
3
+
4
+ subject { described_class.new }
5
+
6
+ describe '#memory_for_process_group' do
7
+ let(:ps) do
8
+ <<EOTXT
9
+ RSS
10
+ 109744
11
+ 63732
12
+ 62236
13
+ 63876
14
+ 62772
15
+ 62856
16
+ 64504
17
+ EOTXT
18
+ end
19
+
20
+ it 'returns bytes of memory used by the group' do
21
+ expect(subject).to receive(:pagesize).and_return(4096)
22
+ expect(subject).to receive(:_exec).with('ps -o rss -g 123').and_return(ps)
23
+ expect(subject.memory_for_process_group( 123 )).to eq 2005893120
24
+ end
25
+ end
26
+
27
+ describe '#disk_space_free' do
28
+ it 'returns the amount of free disk space' do
29
+ o = Object.new
30
+ expect(o).to receive(:available_bytes).and_return(1000)
31
+ expect(Vmstat).to receive(:disk).with(Slotz::System.disk_directory).and_return(o)
32
+
33
+ expect(subject.disk_space_free).to eq 1000
34
+ end
35
+ end
36
+
37
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slotz
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Tasos Laskos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: awesome_print
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: concurrent-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: vmstat
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.3.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: sys-proctable
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.0
83
+ description: ''
84
+ email: tasos.laskos@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files:
88
+ - README.md
89
+ - LICENSE.md
90
+ files:
91
+ - Gemfile
92
+ - LICENSE.md
93
+ - README.md
94
+ - lib/slotz.rb
95
+ - lib/slotz/options.rb
96
+ - lib/slotz/reservation.rb
97
+ - lib/slotz/system.rb
98
+ - lib/slotz/system/application.rb
99
+ - lib/slotz/system/platforms.rb
100
+ - lib/slotz/system/platforms/base.rb
101
+ - lib/slotz/system/platforms/linux.rb
102
+ - lib/slotz/system/platforms/mixins/unix.rb
103
+ - lib/slotz/system/platforms/osx.rb
104
+ - lib/slotz/system/platforms/windows.rb
105
+ - lib/slotz/version.rb
106
+ - slotz.gemspec
107
+ - spec/slotz/system/platforms/linux_spec.rb
108
+ - spec/slotz/system/platforms/osx_spec.rb
109
+ - spec/slotz/system/platforms/windows_spec.rb
110
+ - spec/slotz/system/slotz_spec.rb
111
+ - spec/slotz/system_spec.rb
112
+ - spec/spec_helper.rb
113
+ - spec/support/helpers/paths.rb
114
+ - spec/support/shared/system/platforms/base.rb
115
+ - spec/support/shared/system/platforms/mixins/unix.rb
116
+ homepage: https://github.com/qadron/cuboid
117
+ licenses:
118
+ - MIT
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options:
122
+ - "--charset=UTF-8"
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubygems_version: 3.4.22
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: An application-centric, decentralised and distributed computing solution.
140
+ test_files:
141
+ - spec/slotz/system/slotz_spec.rb
142
+ - spec/slotz/system/platforms/osx_spec.rb
143
+ - spec/slotz/system/platforms/linux_spec.rb
144
+ - spec/slotz/system/platforms/windows_spec.rb
145
+ - spec/slotz/system_spec.rb
146
+ - spec/support/shared/system/platforms/base.rb
147
+ - spec/support/shared/system/platforms/mixins/unix.rb
148
+ - spec/support/helpers/paths.rb
149
+ - spec/spec_helper.rb