rbsim 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|