rbsim 0.0.3
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/.hgignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +66 -0
- data/LICENSE.txt +674 -0
- data/README.md +960 -0
- data/TODO +28 -0
- data/basic_sim.rb +62 -0
- data/fast-tcpn.rb +3 -0
- data/lib/rbsim.rb +14 -0
- data/lib/rbsim/dsl.rb +30 -0
- data/lib/rbsim/dsl/infrastructure.rb +48 -0
- data/lib/rbsim/dsl/mapping.rb +32 -0
- data/lib/rbsim/dsl/process.rb +129 -0
- data/lib/rbsim/dsl/program.rb +10 -0
- data/lib/rbsim/experiment.rb +110 -0
- data/lib/rbsim/hlmodel.rb +25 -0
- data/lib/rbsim/hlmodel/infrastructure.rb +116 -0
- data/lib/rbsim/hlmodel/mapping.rb +5 -0
- data/lib/rbsim/hlmodel/process.rb +152 -0
- data/lib/rbsim/numeric_units.rb +107 -0
- data/lib/rbsim/simulator.rb +184 -0
- data/lib/rbsim/statistics.rb +77 -0
- data/lib/rbsim/tokens.rb +146 -0
- data/lib/rbsim/version.rb +3 -0
- data/new_process.rb +49 -0
- data/rbsim.gemspec +42 -0
- data/show_readme.rb +15 -0
- data/sim.rb +142 -0
- data/sim_bamboo.rb +251 -0
- data/sim_process.rb +83 -0
- data/sim_process_dsl.rb +58 -0
- data/spec/dsl/infrastructure_nets_spec.rb +39 -0
- data/spec/dsl/infrastructure_nodes_spec.rb +72 -0
- data/spec/dsl/infrastructure_routes_spec.rb +44 -0
- data/spec/dsl/mapping_spec.rb +70 -0
- data/spec/dsl/process_spec.rb +56 -0
- data/spec/dsl/program_spec.rb +36 -0
- data/spec/dsl_and_hlmodel/new_process_spec.rb +235 -0
- data/spec/hlmodel/net_spec.rb +112 -0
- data/spec/hlmodel/process_spec.rb +242 -0
- data/spec/hlmodel/route_spec.rb +47 -0
- data/spec/hlmodel/routes_spec.rb +44 -0
- data/spec/integration/basic_simulation_spec.rb +104 -0
- data/spec/integration/net_spec.rb +44 -0
- data/spec/integration/process_spec.rb +117 -0
- data/spec/integration/rbsim_spec.rb +40 -0
- data/spec/simulator/logger_spec.rb +35 -0
- data/spec/simulator/stats_spec.rb +93 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/statistics_spec.rb +300 -0
- data/spec/tcpn/add_route_spec.rb +55 -0
- data/spec/tcpn/cpu_spec.rb +53 -0
- data/spec/tcpn/map_data_spec.rb +37 -0
- data/spec/tcpn/network_spec.rb +163 -0
- data/spec/tcpn/register_event_spec.rb +48 -0
- data/spec/tcpn/route_to_self_spec.rb +53 -0
- data/spec/tcpn/stats_spec.rb +77 -0
- data/spec/tokens/data_queue_obsolete.rb +121 -0
- data/spec/tokens/data_queue_spec.rb +111 -0
- data/spec/units_spec.rb +48 -0
- data/tcpn/model.rb +6 -0
- data/tcpn/model/add_route.rb +78 -0
- data/tcpn/model/application.rb +250 -0
- data/tcpn/model/cpu.rb +75 -0
- data/tcpn/model/map_data.rb +42 -0
- data/tcpn/model/network.rb +108 -0
- data/tcpn/model/register_event.rb +89 -0
- data/tcpn/model/stats.rb +46 -0
- metadata +221 -0
data/TODO
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
* Error handling:
|
2
|
+
** when there is not route between two processes, packets sent
|
3
|
+
between them are silently ignored -- no error message
|
4
|
+
|
5
|
+
* Add to README.md description of preferred RBSim usage scenario describing
|
6
|
+
usage of RBSim::Experiment. Good usage example can be found in
|
7
|
+
nauka/mrzasa_dynosize/
|
8
|
+
* Maybe it would be useful to add optional `:preemption` parameter to `cpu`
|
9
|
+
statement in process model to enable user to define preemption levels.
|
10
|
+
Currently, it can be done with the following piece of code:
|
11
|
+
|
12
|
+
10.times do # can be preempted 10 times
|
13
|
+
cpu do |cpu|
|
14
|
+
cpu_time / 10 / cpu.performance
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Obviously, it slows down simulation and makes stats swell (every cpu event
|
19
|
+
is logged in stats), but sometimes may be required. The preemption
|
20
|
+
parameter could itself do the above.
|
21
|
+
|
22
|
+
* Data to self imlementation will spoil DATAQ WAIT stats -- no
|
23
|
+
stats_start is called when network is passed over! Neet to add
|
24
|
+
stats_start
|
25
|
+
* To speed-up simulation, we may create a token with one data queue
|
26
|
+
for each net segment in place 'data with route', if multiple data
|
27
|
+
packages are send in one moment, simulator will not choke on large
|
28
|
+
cartesian product in transition 'net'
|
data/basic_sim.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rbsim'
|
2
|
+
|
3
|
+
model = RBSim.model do
|
4
|
+
|
5
|
+
program :wget do |opts|
|
6
|
+
sent = 0
|
7
|
+
on_event :send do
|
8
|
+
cpu do |cpu|
|
9
|
+
(150 / cpu.performance).miliseconds
|
10
|
+
end
|
11
|
+
send_data to: opts[:target], size: 1024.bytes, type: :request, content: sent
|
12
|
+
sent += 1
|
13
|
+
register_event :send, delay: 5.miliseconds if sent < opts[:count]
|
14
|
+
end
|
15
|
+
|
16
|
+
on_event :data_received do |data|
|
17
|
+
log "Got data #{data} in process #{process.name}"
|
18
|
+
stats :request_served, process.name
|
19
|
+
end
|
20
|
+
|
21
|
+
register_event :send
|
22
|
+
end
|
23
|
+
|
24
|
+
program :apache do
|
25
|
+
on_event :data_received do |data|
|
26
|
+
stats_start :apache, process.name
|
27
|
+
cpu do |cpu|
|
28
|
+
(100 * data.size.in_bytes / cpu.performance).miliseconds
|
29
|
+
end
|
30
|
+
send_data to: data.src, size: data.size * 10, type: :response, content: data.content
|
31
|
+
stats_stop :apache, process.name
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
node :desktop do
|
36
|
+
cpu 100
|
37
|
+
end
|
38
|
+
|
39
|
+
node :gandalf do
|
40
|
+
cpu 1400
|
41
|
+
end
|
42
|
+
|
43
|
+
new_process :client1, program: :wget, args: { target: :server, count: 10 }
|
44
|
+
new_process :client2, program: :wget, args: { target: :server, count: 10 }
|
45
|
+
new_process :server, program: :apache, args: 'apache1'
|
46
|
+
|
47
|
+
net :net01, bw: 1024.bps
|
48
|
+
net :net02, bw: 510.bps
|
49
|
+
|
50
|
+
route from: :desktop, to: :gandalf, via: [ :net01, :net02 ], twoway: true
|
51
|
+
|
52
|
+
put :server, on: :gandalf
|
53
|
+
put :client1, on: :desktop
|
54
|
+
put :client2, on: :desktop
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
model.run
|
60
|
+
|
61
|
+
model.stats_print
|
62
|
+
p model.stats_summary
|
data/fast-tcpn.rb
ADDED
data/lib/rbsim.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'fast-tcpn'
|
2
|
+
require 'rbsim/dsl'
|
3
|
+
require 'rbsim/hlmodel'
|
4
|
+
require 'rbsim/simulator'
|
5
|
+
require 'rbsim/statistics'
|
6
|
+
require 'rbsim/numeric_units'
|
7
|
+
require 'rbsim/experiment'
|
8
|
+
require 'rbsim/version'
|
9
|
+
|
10
|
+
module RBSim
|
11
|
+
def self.stats_read(file)
|
12
|
+
Marshal.load File.read(file)
|
13
|
+
end
|
14
|
+
end
|
data/lib/rbsim/dsl.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'docile'
|
2
|
+
require 'rbsim/tokens'
|
3
|
+
require 'rbsim/dsl/infrastructure'
|
4
|
+
require 'rbsim/dsl/program'
|
5
|
+
require 'rbsim/dsl/process'
|
6
|
+
require 'rbsim/dsl/mapping'
|
7
|
+
|
8
|
+
module RBSim
|
9
|
+
|
10
|
+
def self.dsl(&block)
|
11
|
+
hlmodel = RBSim::HLModel::Model.new
|
12
|
+
Docile.dsl_eval(RBSim::DSL.new(hlmodel), &block)
|
13
|
+
hlmodel
|
14
|
+
end
|
15
|
+
|
16
|
+
# TODO: after implementing marking TCPN from the HLModel, update spec/integration/new_process_spec.rb!
|
17
|
+
|
18
|
+
def self.model(params = {}, &block)
|
19
|
+
Simulator.new params, &block
|
20
|
+
end
|
21
|
+
|
22
|
+
# Read model from a +file+.
|
23
|
+
# +params+ will be passes as to the model as 'params' variable
|
24
|
+
def self.read(file, params = {})
|
25
|
+
p = "proc { |params| #{File.read file} }"
|
26
|
+
block = instance_eval p, file
|
27
|
+
model params, &block
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RBSim
|
2
|
+
class DSL
|
3
|
+
def initialize(model)
|
4
|
+
@model = model
|
5
|
+
end
|
6
|
+
|
7
|
+
def node(name, &block)
|
8
|
+
@model.nodes << Docile.dsl_eval(NodeDSL.new(name), &block).build
|
9
|
+
rescue => e
|
10
|
+
puts e
|
11
|
+
raise
|
12
|
+
end
|
13
|
+
|
14
|
+
def net(name, args = {})
|
15
|
+
bw = args[:bw]
|
16
|
+
delay = args[:delay] || 0
|
17
|
+
drop = args[:drop] || 0
|
18
|
+
tags = args[:tags] || {}
|
19
|
+
@model.nets << Tokens::NetToken.new(name, bw, delay, drop, tags)
|
20
|
+
end
|
21
|
+
|
22
|
+
def route(args = {})
|
23
|
+
twoway = if args[:twoway] || args[:twoway] == :true
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
@model.routes << HLModel::Route.new(args[:from], args[:to], args[:via], twoway)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class NodeDSL
|
33
|
+
def initialize(name)
|
34
|
+
@name = name
|
35
|
+
@cpus = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def cpu(performance, args = {})
|
39
|
+
tags = args[:tags] || {}
|
40
|
+
@cpus << Tokens::CPUToken.new(performance, nil, tags)
|
41
|
+
end
|
42
|
+
|
43
|
+
def build
|
44
|
+
HLModel::Node.new(@name, @cpus)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RBSim
|
2
|
+
class DSL
|
3
|
+
IncorrectMapping = Class.new RuntimeError
|
4
|
+
|
5
|
+
def put(process_name, opts = nil)
|
6
|
+
if !process_name.instance_of?(Hash) && opts.nil?
|
7
|
+
raise IncorrectMapping.new("Does not define node for proces #{process_name}")
|
8
|
+
end
|
9
|
+
|
10
|
+
if process_name.instance_of? Hash
|
11
|
+
opts = process_name
|
12
|
+
else
|
13
|
+
opts[:process] = process_name
|
14
|
+
end
|
15
|
+
|
16
|
+
process = opts[:process]
|
17
|
+
node = opts[:on]
|
18
|
+
|
19
|
+
if process.nil?
|
20
|
+
msg = " to put on node #{node}" unless node.nil?
|
21
|
+
raise IncorrectMapping.new("Does not define process#{msg}")
|
22
|
+
end
|
23
|
+
if node.nil?
|
24
|
+
raise IncorrectMapping.new("Does not define node for proces #{process}")
|
25
|
+
end
|
26
|
+
|
27
|
+
@model.mapping[process] = node
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module RBSim
|
2
|
+
class DSL
|
3
|
+
def new_process(name, opts = {}, &block)
|
4
|
+
ProcessDSL.new_process(@model, name, opts, &block)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class ProcessDSL
|
9
|
+
UnknownProgramName = Class.new RuntimeError
|
10
|
+
NeitherBlockNorProgramGiven = Class.new RuntimeError
|
11
|
+
|
12
|
+
attr_reader :process
|
13
|
+
|
14
|
+
# Create new process.
|
15
|
+
# +name+ process name
|
16
|
+
# +opts+ +{ program: name_of_program_to_run, args: args_to_pass_to_program_block }+
|
17
|
+
# +block+ block defining new process (if given +opts+ are ignored)
|
18
|
+
def self.new_process(model, name, opts = {}, &block)
|
19
|
+
args = nil
|
20
|
+
program = nil
|
21
|
+
unless block_given?
|
22
|
+
program = opts[:program]
|
23
|
+
raise NeitherBlockNorProgamGiven.new("for new_process #{name}") if program.nil?
|
24
|
+
args = opts[:args]
|
25
|
+
block = model.programs[program]
|
26
|
+
raise UnknownProgramName.new("#{program} for new_process #{name}") if block.nil?
|
27
|
+
end
|
28
|
+
process = Tokens::ProcessToken.new(name, program, opts[:tags])
|
29
|
+
process = Docile.dsl_eval(ProcessDSL.new(model, name, program, process), args, &block).process
|
30
|
+
model.processes[name] = process
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize(model, name, program, process)
|
34
|
+
@name = name
|
35
|
+
@model = model
|
36
|
+
@program = program
|
37
|
+
@process = process
|
38
|
+
end
|
39
|
+
|
40
|
+
def on_event(event, &block)
|
41
|
+
# Cannot use self as eval context and
|
42
|
+
# must pass process because after clonning in TCPN it will be
|
43
|
+
# completely different process object then it is now!
|
44
|
+
handler = proc do |process, args|
|
45
|
+
process.functions.each do |name, definition|
|
46
|
+
# This is actually a kind of hack... but there is
|
47
|
+
# no other way to hit exactly this class with define_method, since
|
48
|
+
# almost all other methods from this class are removed by docile.
|
49
|
+
# Therefore every attempt to do metaprogramming on objects of this class
|
50
|
+
# hit the objects to whom this class forwards method calls...
|
51
|
+
# But this way we actually can define required methods that will operate
|
52
|
+
# in the context of proper object representing a process and that will
|
53
|
+
# cause side effects (e.g. changes in event queue) in proper objects.
|
54
|
+
Docile::FallbackContextProxy.__send__ :define_method, name, definition
|
55
|
+
end
|
56
|
+
changed_process = Docile.dsl_eval(ProcessDSL.new(@model, @name, @program, process), args, &block).process
|
57
|
+
process.functions.each do |name, definition|
|
58
|
+
# remove newly defined methods not to mess things up in the Docile::FallbackContextProxy
|
59
|
+
# unless removed, these methods would be available for the other processes!
|
60
|
+
Docile::FallbackContextProxy.__send__ :undef_method
|
61
|
+
end
|
62
|
+
changed_process
|
63
|
+
end
|
64
|
+
@process.on_event(event, &handler)
|
65
|
+
end
|
66
|
+
|
67
|
+
def function(name, &block)
|
68
|
+
@process.function name, &block
|
69
|
+
end
|
70
|
+
|
71
|
+
# register_event name, delay: 100, args: { event related args }
|
72
|
+
def register_event(event, opts = {})
|
73
|
+
args = opts[:args]
|
74
|
+
delay = opts[:delay] || 0
|
75
|
+
@process.enqueue_event(:register_event, event: event, delay: delay, event_args: args)
|
76
|
+
end
|
77
|
+
|
78
|
+
def cpu(&block)
|
79
|
+
@process.enqueue_event(:cpu, block: block)
|
80
|
+
end
|
81
|
+
|
82
|
+
def delay_for(args)
|
83
|
+
if args.kind_of? Numeric
|
84
|
+
args = { time: args }
|
85
|
+
end
|
86
|
+
@process.enqueue_event(:delay_for, args)
|
87
|
+
end
|
88
|
+
|
89
|
+
def send_data(args)
|
90
|
+
@process.enqueue_event(:send_data, args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def log(message)
|
94
|
+
@process.enqueue_event(:log, message)
|
95
|
+
end
|
96
|
+
|
97
|
+
def stats_start(tags)
|
98
|
+
@process.enqueue_event(:stats_start, tags)
|
99
|
+
end
|
100
|
+
|
101
|
+
def stats_stop(tags)
|
102
|
+
@process.enqueue_event(:stats_stop, tags)
|
103
|
+
end
|
104
|
+
|
105
|
+
def stats(tags)
|
106
|
+
@process.enqueue_event(:stats, tags)
|
107
|
+
end
|
108
|
+
|
109
|
+
def stats_save(value, tags)
|
110
|
+
params = { tags: tags, value: value }
|
111
|
+
@process.enqueue_event(:stats_save, params)
|
112
|
+
end
|
113
|
+
|
114
|
+
def new_process(name, args = {}, &block)
|
115
|
+
constructor = proc do |args|
|
116
|
+
new_process = self.class.new_process(@model, name, args, &block)
|
117
|
+
new_process.node = @process.node
|
118
|
+
new_process
|
119
|
+
end
|
120
|
+
@process.enqueue_event(:new_process, constructor_args: args, constructor: constructor)
|
121
|
+
end
|
122
|
+
|
123
|
+
# returns time at which the event occured
|
124
|
+
def event_time
|
125
|
+
@model.simulator.clock
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# This is a base class to create RBSim based experiment
|
2
|
+
# that is able to load your model and start simulation.
|
3
|
+
# It encapsulates model, parameters and statistics
|
4
|
+
# and also all your methods used to compute simulation results.
|
5
|
+
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
module RBSim
|
9
|
+
|
10
|
+
class Experiment
|
11
|
+
attr_accessor :file, :params, :time_limit, :data_fragmentation
|
12
|
+
attr_reader :model
|
13
|
+
|
14
|
+
RECORD_SEPARATOR = "__ END OF RECORD __"
|
15
|
+
|
16
|
+
def initialize(params = nil, stats = nil)
|
17
|
+
@params, @stats = params, stats
|
18
|
+
end
|
19
|
+
|
20
|
+
def stats
|
21
|
+
return @stats unless @stats.nil?
|
22
|
+
@stats = model.stats
|
23
|
+
end
|
24
|
+
|
25
|
+
# Run specified model with its params
|
26
|
+
# and collect statistics
|
27
|
+
def run(file, params)
|
28
|
+
read_model(file, params)
|
29
|
+
@model.run
|
30
|
+
end
|
31
|
+
|
32
|
+
# Save statistics to a file
|
33
|
+
def save_stats(file)
|
34
|
+
File.open file, 'a' do |f|
|
35
|
+
f.print Marshal.dump [params, stats]
|
36
|
+
f.print RECORD_SEPARATOR
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Read statistics from a file, return Enumerator of
|
41
|
+
# objects, each opject represents separate experiment
|
42
|
+
def self.read_stats(file, dots = false)
|
43
|
+
size = 0
|
44
|
+
File.open(file) do |file|
|
45
|
+
size = file.each_line(RECORD_SEPARATOR).count
|
46
|
+
end
|
47
|
+
|
48
|
+
e = Enumerator.new size do |y|
|
49
|
+
File.open(file) do |file|
|
50
|
+
begin
|
51
|
+
while !file.eof?
|
52
|
+
file.each_line(RECORD_SEPARATOR) do |line|
|
53
|
+
print "." if dots
|
54
|
+
params, stats = Marshal.restore line
|
55
|
+
y << self.new(params, stats)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue ArgumentError => e
|
59
|
+
raise ArgumentError.new "#{caller.first} got #{e.inspect} after reading #{objects.length} objects!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts if dots
|
65
|
+
e
|
66
|
+
end
|
67
|
+
|
68
|
+
def res_stats
|
69
|
+
stats[:resources]
|
70
|
+
end
|
71
|
+
|
72
|
+
def app_stats
|
73
|
+
stats[:application]
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def read_model(file, params)
|
79
|
+
@file = file
|
80
|
+
@params = params
|
81
|
+
@model = RBSim.read file, params
|
82
|
+
@model.data_fragmentation = data_fragmentation unless data_fragmentation.nil?
|
83
|
+
@model.tcpn.cb_for :clock, :after, &self.method(:timer)
|
84
|
+
@model.tcpn.cb_for :clock, :after, &self.method(:time_limit_observer)
|
85
|
+
end
|
86
|
+
|
87
|
+
def timer(tag, event)
|
88
|
+
@prev_seconds = 0 if @prev_seconds.nil?
|
89
|
+
@timer_length = 0 if @timer_length.nil?
|
90
|
+
|
91
|
+
seconds = event.clock.in_seconds.truncate
|
92
|
+
if seconds > @prev_seconds
|
93
|
+
print "\b"*@timer_length
|
94
|
+
timer = "Time: #{seconds} sec."
|
95
|
+
print timer
|
96
|
+
@timer_length = timer.length
|
97
|
+
end
|
98
|
+
@prev_seconds = seconds
|
99
|
+
end
|
100
|
+
|
101
|
+
def time_limit_observer(tag, event)
|
102
|
+
return if @time_limit.nil?
|
103
|
+
if event.clock > @time_limit
|
104
|
+
@model.stop
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|