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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.hgignore +6 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +12 -0
  5. data/Gemfile.lock +66 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +960 -0
  8. data/TODO +28 -0
  9. data/basic_sim.rb +62 -0
  10. data/fast-tcpn.rb +3 -0
  11. data/lib/rbsim.rb +14 -0
  12. data/lib/rbsim/dsl.rb +30 -0
  13. data/lib/rbsim/dsl/infrastructure.rb +48 -0
  14. data/lib/rbsim/dsl/mapping.rb +32 -0
  15. data/lib/rbsim/dsl/process.rb +129 -0
  16. data/lib/rbsim/dsl/program.rb +10 -0
  17. data/lib/rbsim/experiment.rb +110 -0
  18. data/lib/rbsim/hlmodel.rb +25 -0
  19. data/lib/rbsim/hlmodel/infrastructure.rb +116 -0
  20. data/lib/rbsim/hlmodel/mapping.rb +5 -0
  21. data/lib/rbsim/hlmodel/process.rb +152 -0
  22. data/lib/rbsim/numeric_units.rb +107 -0
  23. data/lib/rbsim/simulator.rb +184 -0
  24. data/lib/rbsim/statistics.rb +77 -0
  25. data/lib/rbsim/tokens.rb +146 -0
  26. data/lib/rbsim/version.rb +3 -0
  27. data/new_process.rb +49 -0
  28. data/rbsim.gemspec +42 -0
  29. data/show_readme.rb +15 -0
  30. data/sim.rb +142 -0
  31. data/sim_bamboo.rb +251 -0
  32. data/sim_process.rb +83 -0
  33. data/sim_process_dsl.rb +58 -0
  34. data/spec/dsl/infrastructure_nets_spec.rb +39 -0
  35. data/spec/dsl/infrastructure_nodes_spec.rb +72 -0
  36. data/spec/dsl/infrastructure_routes_spec.rb +44 -0
  37. data/spec/dsl/mapping_spec.rb +70 -0
  38. data/spec/dsl/process_spec.rb +56 -0
  39. data/spec/dsl/program_spec.rb +36 -0
  40. data/spec/dsl_and_hlmodel/new_process_spec.rb +235 -0
  41. data/spec/hlmodel/net_spec.rb +112 -0
  42. data/spec/hlmodel/process_spec.rb +242 -0
  43. data/spec/hlmodel/route_spec.rb +47 -0
  44. data/spec/hlmodel/routes_spec.rb +44 -0
  45. data/spec/integration/basic_simulation_spec.rb +104 -0
  46. data/spec/integration/net_spec.rb +44 -0
  47. data/spec/integration/process_spec.rb +117 -0
  48. data/spec/integration/rbsim_spec.rb +40 -0
  49. data/spec/simulator/logger_spec.rb +35 -0
  50. data/spec/simulator/stats_spec.rb +93 -0
  51. data/spec/spec_helper.rb +26 -0
  52. data/spec/statistics_spec.rb +300 -0
  53. data/spec/tcpn/add_route_spec.rb +55 -0
  54. data/spec/tcpn/cpu_spec.rb +53 -0
  55. data/spec/tcpn/map_data_spec.rb +37 -0
  56. data/spec/tcpn/network_spec.rb +163 -0
  57. data/spec/tcpn/register_event_spec.rb +48 -0
  58. data/spec/tcpn/route_to_self_spec.rb +53 -0
  59. data/spec/tcpn/stats_spec.rb +77 -0
  60. data/spec/tokens/data_queue_obsolete.rb +121 -0
  61. data/spec/tokens/data_queue_spec.rb +111 -0
  62. data/spec/units_spec.rb +48 -0
  63. data/tcpn/model.rb +6 -0
  64. data/tcpn/model/add_route.rb +78 -0
  65. data/tcpn/model/application.rb +250 -0
  66. data/tcpn/model/cpu.rb +75 -0
  67. data/tcpn/model/map_data.rb +42 -0
  68. data/tcpn/model/network.rb +108 -0
  69. data/tcpn/model/register_event.rb +89 -0
  70. data/tcpn/model/stats.rb +46 -0
  71. 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
@@ -0,0 +1,3 @@
1
+ require 'fast-tcpn'
2
+
3
+ FastTCPN.read 'tcpn/model.rb'
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,10 @@
1
+ module RBSim
2
+ class DSL
3
+ # program statement just remembers
4
+ # program's block under specified name,
5
+ # to be used later when defining/starting processes
6
+ def program(name, &block)
7
+ @model.programs[name] = block
8
+ end
9
+ end
10
+ end
@@ -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