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/README.md
ADDED
@@ -0,0 +1,960 @@
|
|
1
|
+
RBSim -- software technical reference
|
2
|
+
=====
|
3
|
+
|
4
|
+
This is a technical description of the RBSim software package.
|
5
|
+
|
6
|
+
RBSim is a simulation tool designed for analysis of architecture of
|
7
|
+
concurrent and distributed applications. Application should be described
|
8
|
+
using convenient Ruby-based DSL. Simulation is based on Timed Colored
|
9
|
+
Petri nets designed by K. Jensen, thus ensuring reliable analysis. Basic
|
10
|
+
statistics module is included.
|
11
|
+
|
12
|
+
In order to provide quick overview of the method here you will
|
13
|
+
find example usage of RBSim. Detailed description of model,
|
14
|
+
simulation and processing statistics can be found in the
|
15
|
+
following sections.
|
16
|
+
|
17
|
+
|
18
|
+
# Example
|
19
|
+
|
20
|
+
Below there is a simple but complete example model of two
|
21
|
+
applications: `wget` sending subsequent requests to specified
|
22
|
+
process and `apache` responding to received requests.
|
23
|
+
|
24
|
+
The `wget` program accepts parameters describing its target
|
25
|
+
(destination of requests) -- `opts[:target]` and count of
|
26
|
+
requests to send -- `opts[:count]`. The parameters are defined
|
27
|
+
when new process is defined using this program. The `apache`
|
28
|
+
program takes no additional parameters.
|
29
|
+
|
30
|
+
Two client processes are started using program `wget`: `client1`
|
31
|
+
and `client2`. Using `apache` program one server is started:
|
32
|
+
`server`. Application uses two nodes: `desktop` with one slower
|
33
|
+
processor and `gandalf` with one faster CPU. The nodes are
|
34
|
+
connected by two nets and one two-way route. Both clients are
|
35
|
+
assigned to the `desktop` node while server is run on `gandalf`.
|
36
|
+
|
37
|
+
The clients are mapped to the `desktop` node and the server is
|
38
|
+
assigned to `gandalf`.
|
39
|
+
|
40
|
+
Logs are printed to STDOUT and statistics are collected. Apache
|
41
|
+
`stats` definitions allow to observe time taken by serving
|
42
|
+
requests. Client `stats` count served requests and allow to
|
43
|
+
verify if responses were received for all sent requests.
|
44
|
+
|
45
|
+
The created model is run and its statistics are printed.
|
46
|
+
|
47
|
+
The `model.rb` file contains model of the application and resources
|
48
|
+
described with DSL:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
program :wget do |opts|
|
52
|
+
sent = 0
|
53
|
+
on_event :send do
|
54
|
+
cpu do |cpu|
|
55
|
+
(150 / cpu.performance).miliseconds
|
56
|
+
end
|
57
|
+
send_data to: opts[:target], size: 1024.bytes,
|
58
|
+
type: :request, content: sent
|
59
|
+
sent += 1
|
60
|
+
register_event :send, delay: 5.miliseconds if sent < opts[:count]
|
61
|
+
end
|
62
|
+
|
63
|
+
on_event :data_received do |data|
|
64
|
+
log "Got data #{data} in process #{process.name}"
|
65
|
+
stats event: :request_served, client: process.name
|
66
|
+
end
|
67
|
+
|
68
|
+
register_event :send
|
69
|
+
end
|
70
|
+
|
71
|
+
program :apache do
|
72
|
+
on_event :data_received do |data|
|
73
|
+
stats_start server: :apache, name: process.name
|
74
|
+
cpu do |cpu|
|
75
|
+
(100 * data.size.in_bytes / cpu.performance).miliseconds
|
76
|
+
end
|
77
|
+
send_data to: data.src, size: data.size * 10,
|
78
|
+
type: :response, content: data.content
|
79
|
+
stats_stop server: :apache, name: process.name
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
node :desktop do
|
84
|
+
cpu 100
|
85
|
+
end
|
86
|
+
|
87
|
+
node :gandalf do
|
88
|
+
cpu 1400
|
89
|
+
end
|
90
|
+
|
91
|
+
new_process :client1, program: :wget, args: { target: :server, count: 10 }
|
92
|
+
new_process :client2, program: :wget, args: { target: :server, count: 10 }
|
93
|
+
new_process :server, program: :apache
|
94
|
+
|
95
|
+
net :net01, bw: 1024.bps
|
96
|
+
net :net02, bw: 510.bps
|
97
|
+
|
98
|
+
route from: :desktop, to: :gandalf, via: [ :net01, :net02 ], twoway: true
|
99
|
+
|
100
|
+
put :server, on: :gandalf
|
101
|
+
put :client1, on: :desktop
|
102
|
+
put :client2, on: :desktop
|
103
|
+
```
|
104
|
+
|
105
|
+
Creating empty subclass of `RBsim::Experiment` in `rbsim_example.rb`
|
106
|
+
file is sufficient to start simulation:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class Experiment < RBSim::Experiment
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
The simulation can be started with the following code:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
require './rbsim_example'
|
117
|
+
|
118
|
+
params = { }
|
119
|
+
|
120
|
+
sim = Experiment.new
|
121
|
+
sim.run './model.rb', params
|
122
|
+
sim.save_stats 'simulation.stats'
|
123
|
+
```
|
124
|
+
|
125
|
+
The `save_stats` method appends statistics to the specified file.
|
126
|
+
Statistics saved can be loaded with:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
all_stats = Experiment.read_stats 'simulation.stats'
|
130
|
+
```
|
131
|
+
|
132
|
+
The `all_stats` will be an iterator yielding objects of class `Experiement`,
|
133
|
+
so statistics of first experiment can be accessed using:
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
all_stats.first.app_stats # application statistics
|
137
|
+
all_stats.first.res_stats # resource statistics
|
138
|
+
```
|
139
|
+
|
140
|
+
Processing statistics into required results can be implemented in the
|
141
|
+
`Experiement` class (so far this class was empty) like this:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
class Experiment < RBSim::Experiment
|
145
|
+
|
146
|
+
def print_req_times_for(server)
|
147
|
+
app_stats.durations(server: server) do |tags, start, stop|
|
148
|
+
puts "Request time #{(stop - start).in_miliseconds} ms. "
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def mean_req_time_for(server)
|
153
|
+
req_times = app_stats.durations(server: server).to_a
|
154
|
+
sum = req_times.reduce(0) do |acc, data|
|
155
|
+
_, start, stop = *data
|
156
|
+
acc + stop - start
|
157
|
+
end
|
158
|
+
sum / req_times.size
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
Then, the statistics can be conveniently used like this:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
all_stats = Experiment.read_stats 'simulation.stats'
|
168
|
+
first_experiment = all_stats.first
|
169
|
+
first_experiment.print_req_times_for(:apache)
|
170
|
+
puts "Mean request time for apache: "
|
171
|
+
puts "#{first_experiment.mean_req_time_for(:apache).in_seconds} s"
|
172
|
+
```
|
173
|
+
|
174
|
+
You can of course iterate over subsequent experiments to get statistics
|
175
|
+
involving a number of different tests.
|
176
|
+
|
177
|
+
In next sections you will find description of subsequent parts of the
|
178
|
+
RBSim tool.
|
179
|
+
|
180
|
+
## Usage
|
181
|
+
|
182
|
+
There are two ways to create model. Preferred one is to subclass the
|
183
|
+
`Experiment` class. But it is also possible to directly create model
|
184
|
+
with RBSim class.
|
185
|
+
|
186
|
+
### Using `RBsim::Experiment` class
|
187
|
+
|
188
|
+
Create a class that inherits `RBsim::Experiment` class.
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
class MyTests < RBSim::Experiment
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
Thereafter you can use it to load model and perform simulations:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
sim = MyTests.new
|
199
|
+
sim.run 'path/to/model_file.rb'
|
200
|
+
```
|
201
|
+
|
202
|
+
Finally, you can save statistics gathered from simulation to a file:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
sim.save_stats 'file_name.stats'
|
206
|
+
```
|
207
|
+
|
208
|
+
If the file exists, new statistics will be appended at its end.
|
209
|
+
|
210
|
+
The saved statistics can be later loaded and analyzed with the same
|
211
|
+
class inheriting from the `RBSim::Experiment`:
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
stats = MyTests.read_stats 'file_name.stats'
|
215
|
+
```
|
216
|
+
|
217
|
+
The `RBSim.read_stats` method will return an array of `MyTests` objects
|
218
|
+
for each experiment saved in the file. The objects can then be used to
|
219
|
+
process statistics as described further.
|
220
|
+
|
221
|
+
You can limit simulation time by setting `sim.time_limit` before you
|
222
|
+
start simulation. In order to use time units you need to `require
|
223
|
+
'rbsim/numeric_units'` before.
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
sim.time_limit = 10.seconds
|
227
|
+
```
|
228
|
+
|
229
|
+
RBSim uses coarse model of network transmission. If you application
|
230
|
+
requires more precise estimation of network transmission time, you can
|
231
|
+
set:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
sim.data_fragmentation = number_of_fragments
|
235
|
+
```
|
236
|
+
|
237
|
+
This will cause each data package transmitted over the network to be
|
238
|
+
divided into at most `number_of_fragments` (but not smaller then 1500B).
|
239
|
+
If your application efficiency depends significantly on numerous data
|
240
|
+
transmissions influencing each other this may improve accuracy, but it
|
241
|
+
will also increase simulation time. If this is not sufficient and
|
242
|
+
efficiency of your application depends on network transmission time
|
243
|
+
(network bounded), not on application logic and logic of communication
|
244
|
+
in the system, you should probably revert to a dedicated network
|
245
|
+
simulator.
|
246
|
+
|
247
|
+
### Using `RBSim.model`
|
248
|
+
|
249
|
+
You can also define your model using `RBSim.model` method:
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
model = RBSim.model some_params do |params|
|
253
|
+
# define your model here
|
254
|
+
# use params passed to the block
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
Or read the model from a file:
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
model = RBSim.read file_name, some_params_hash
|
262
|
+
```
|
263
|
+
|
264
|
+
`some_params_hash` will be available in the model loaded from the file
|
265
|
+
as `params` variable.
|
266
|
+
|
267
|
+
|
268
|
+
Run simulator:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
model.run
|
272
|
+
```
|
273
|
+
|
274
|
+
When simulation is finished, the statistics can be obtained with
|
275
|
+
`model.stats` method.
|
276
|
+
|
277
|
+
## Model
|
278
|
+
|
279
|
+
Use `RBSim.model` to create model described by DSL in a block or
|
280
|
+
`RBSim.read` to load a model from separate file.
|
281
|
+
|
282
|
+
The model is a set of `process`es that are `put` on `nodes` and
|
283
|
+
communicate over `net`s. Processes can be defined by `program`s or
|
284
|
+
directly by blocks, `route`s define sequence of `net`s that should be
|
285
|
+
traversed by data while communication between `node`s. Application
|
286
|
+
logic implemented in `process`es is described in terms of events.
|
287
|
+
|
288
|
+
So to summarize, the most important parts of the model are:
|
289
|
+
|
290
|
+
- Application described as a set of `processes`
|
291
|
+
- Resources described as
|
292
|
+
- `nodes` (computers)
|
293
|
+
- `nets` (network segemtns)
|
294
|
+
- `routes` (from one node to another, over the `net` segments
|
295
|
+
- Mapping of application `processes` to `nodes`
|
296
|
+
|
297
|
+
The application can be modeled independently, resources can be
|
298
|
+
modeled separately and at the end, the application can be mapped
|
299
|
+
to the resources.
|
300
|
+
|
301
|
+
### Processes
|
302
|
+
|
303
|
+
Processes are defined by `new_process` statement.
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
new_process :sender1 do
|
307
|
+
delay_for time: 100
|
308
|
+
cpu do |cpu|
|
309
|
+
(10000 / cpu.performance).miliseconds
|
310
|
+
end
|
311
|
+
end
|
312
|
+
```
|
313
|
+
|
314
|
+
First parameter of the statement is the process name which must
|
315
|
+
be unique in the whole model. The block defines behavior of the process
|
316
|
+
using statements described below.
|
317
|
+
|
318
|
+
#### Delay and CPU Load
|
319
|
+
|
320
|
+
A process can do nothing for some time. This is specified with
|
321
|
+
`delay_for` statement.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
delay_for 100.seconds
|
325
|
+
```
|
326
|
+
|
327
|
+
or
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
delay_for time: 100.seconds
|
331
|
+
```
|
332
|
+
|
333
|
+
Using `delay_for` causes process to stop for specified time. It
|
334
|
+
will not occupy resources, but it will not serve any incoming
|
335
|
+
event either! If you need to put a delay between recurring
|
336
|
+
events, you should use `delay` option of `register_event`
|
337
|
+
statement.
|
338
|
+
|
339
|
+
It can also load node's CPU for specified time. This is defined
|
340
|
+
with `cpu` statement. CPU load time is defined by results of block
|
341
|
+
passed to the statement. The parameter passed to the block
|
342
|
+
represents CPU to which this work is assigned. Performance of
|
343
|
+
this CPU can be checked using `cpu.performance`.
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
cpu do |cpu|
|
347
|
+
(10000 / cpu.performance).miliseconds
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
Time values defined by `delay_for` and returned by the `cpu` block
|
352
|
+
can be random.
|
353
|
+
|
354
|
+
#### Events and Handlers
|
355
|
+
|
356
|
+
Defining process one can use `on_event` statement, to define
|
357
|
+
process behavior when an event occurs. Process can also register
|
358
|
+
event's occurence using `register_event` statement. This is
|
359
|
+
recommended method of describing processes behavior, also
|
360
|
+
recurring behaviors. The following example will repeat sending
|
361
|
+
data 10 times.
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
new_process :wget do
|
365
|
+
sent = 0
|
366
|
+
on_event :send do
|
367
|
+
cpu do |cpu|
|
368
|
+
(150 / cpu.performance).miliseconds
|
369
|
+
end
|
370
|
+
sent += 1
|
371
|
+
register_event :send, delay: 5.miliseconds if sent < 10
|
372
|
+
end
|
373
|
+
|
374
|
+
register_event :send
|
375
|
+
end
|
376
|
+
```
|
377
|
+
|
378
|
+
The optional `delay:` option of the `register_event` statement
|
379
|
+
will cause the event to be registered with specified delay. Thus
|
380
|
+
the above example will register the `:send` events with 5
|
381
|
+
milisecond gaps. By default events are registered immediatelly,
|
382
|
+
without any delay.
|
383
|
+
|
384
|
+
All statements that can be used to describe processe's behavior
|
385
|
+
can also be used inside `on_event` statement. In fact the event
|
386
|
+
handlers are preferred place to describe behavior of a process.
|
387
|
+
|
388
|
+
#### Reusable functions
|
389
|
+
|
390
|
+
The model allows to define functions that can be called from blocks
|
391
|
+
defining event handlers. The functions can hol reusable code useful
|
392
|
+
in one or more then handlers. Functions are defined using `function`
|
393
|
+
statement like this:
|
394
|
+
|
395
|
+
```ruby
|
396
|
+
function :do_something do
|
397
|
+
# any statements allowed in
|
398
|
+
# event handler blocks can be
|
399
|
+
# put here
|
400
|
+
end
|
401
|
+
```
|
402
|
+
|
403
|
+
Functions take parameters defined as usually for Ruby blocks:
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
function :do_something_with_params do |param1, param2|
|
407
|
+
# any statements allowed in
|
408
|
+
# event handler blocks can be
|
409
|
+
# put here
|
410
|
+
end
|
411
|
+
```
|
412
|
+
|
413
|
+
They return values like Ruby methods: either defined by `return`
|
414
|
+
statement or the last stateent in the function block.
|
415
|
+
|
416
|
+
##### Note on `def`
|
417
|
+
|
418
|
+
You don't have to understand this. You don't even have to read this
|
419
|
+
unless you insist on using Ruby's `def` defined methods instead of above
|
420
|
+
described `function` statement.
|
421
|
+
|
422
|
+
You can use Ruby `def` statement to define reusable code in the model,
|
423
|
+
but it is **not recommended** unless you know exactly what you are
|
424
|
+
doing! The `def` defined methods will be accessible from event handler
|
425
|
+
blocks, but when called, they will be evaluated in the context in which
|
426
|
+
they were defined, not in the context of the block calling the function.
|
427
|
+
Consequently, any side effects caused by the function (like
|
428
|
+
`register_event`, `log`, `stat` or anything the DSL gives you) will be
|
429
|
+
reflected in the wrong context! Using `def` defined methods is safe only
|
430
|
+
if they are strictly functional-like -- i.e. cause no side effects.
|
431
|
+
|
432
|
+
#### Reading simulation clock
|
433
|
+
|
434
|
+
I am not convinced that this is necessary, but for now it is available.
|
435
|
+
Read and understand all this section if you think you need this.
|
436
|
+
|
437
|
+
It is possible to check value of simulation clock at which given event
|
438
|
+
is being handled. This clock has the same value inside the whole block
|
439
|
+
handling the event and it equals to the time at which the event handling
|
440
|
+
started (not the time when the event occured/was registered!). The clock
|
441
|
+
can be read using:
|
442
|
+
|
443
|
+
* `event_time` method that returns clock value
|
444
|
+
|
445
|
+
Value returned by the `event_time` method does not change inside
|
446
|
+
the event handling block even if you used `delay_for` or `cpu`
|
447
|
+
statements in this block before the `event_time` method was
|
448
|
+
called. But if you call `event_time inside the `cpu` block it
|
449
|
+
will return the time at which this block is valueted i.e. time at
|
450
|
+
which the CPU processing starts.
|
451
|
+
|
452
|
+
Concluding:
|
453
|
+
|
454
|
+
* the `event_time` method can be used inside each block inside program
|
455
|
+
definition,
|
456
|
+
* inside the whole block it returns the same value -- time when the event
|
457
|
+
handling (i.e. block evaluation) started,
|
458
|
+
* if called inside a block which is inside a block... it returns time at
|
459
|
+
which the most inner event handling (block evaluation) started.
|
460
|
+
|
461
|
+
If you need to measure time between two occurrences in whatever you
|
462
|
+
simulate, just define these occurences as two separate events in
|
463
|
+
the model. Then you will be able to read the time at which
|
464
|
+
handling of each of these events started.
|
465
|
+
|
466
|
+
The truth is that you should not need this. Not ever. For instance if
|
467
|
+
you need to model timeout waiting for a response, just register a
|
468
|
+
timeout event when you send request and inside this event either mark
|
469
|
+
request as timed out or do nothing it response was receivd before.
|
470
|
+
|
471
|
+
#### Communication
|
472
|
+
|
473
|
+
##### Sending data
|
474
|
+
|
475
|
+
A process can send data to another process using its name as
|
476
|
+
destination address.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
new_process :sender1 do
|
480
|
+
send_data to: :receiver, size: 1024.bytes, type: :request, content: 'anything useful for your model'
|
481
|
+
end
|
482
|
+
```
|
483
|
+
|
484
|
+
Data will be sent to process called `:receiver`, size will be
|
485
|
+
1024 bytes, `type` and `content` of the data can be set to anything
|
486
|
+
considered useful.
|
487
|
+
|
488
|
+
##### Receiving data
|
489
|
+
|
490
|
+
Every process is capable of receiving data, but by default the
|
491
|
+
data will be dropped and a warning issued. To process received
|
492
|
+
data, process must define event handler for `:data_received`
|
493
|
+
event.
|
494
|
+
|
495
|
+
```ruby
|
496
|
+
on_event :data_received do |data|
|
497
|
+
cpu do |cpu|
|
498
|
+
(data.size / cpu.performance).miliseconds
|
499
|
+
end
|
500
|
+
end
|
501
|
+
```
|
502
|
+
|
503
|
+
The parameter passed to the event handler (`data` in the example
|
504
|
+
above) contains a Hash describing received data.
|
505
|
+
|
506
|
+
```ruby
|
507
|
+
{ src: :source_process_name,
|
508
|
+
dst: :destination_process_name,
|
509
|
+
size: data_size,
|
510
|
+
type: 'a value_given_by_sender',
|
511
|
+
content: 'a value given by sender' }
|
512
|
+
```
|
513
|
+
|
514
|
+
The complete example of wget -- sending requests and receiving
|
515
|
+
responses can look like this:
|
516
|
+
|
517
|
+
```ruby
|
518
|
+
new_process :wget do
|
519
|
+
sent = 0
|
520
|
+
on_event :send do
|
521
|
+
cpu do |cpu|
|
522
|
+
(150 / cpu.performance).miliseconds
|
523
|
+
end
|
524
|
+
send_data to: opts[:target], size: 1024.bytes, type: :request, content: sent
|
525
|
+
sent += 1
|
526
|
+
register_event :send, delay: 5.miliseconds if sent < 10
|
527
|
+
end
|
528
|
+
|
529
|
+
on_event :data_received do |data|
|
530
|
+
log "Got data #{data} in process #{process.name}"
|
531
|
+
stats event: :request_served, where: process.name
|
532
|
+
end
|
533
|
+
|
534
|
+
register_event :send
|
535
|
+
end
|
536
|
+
```
|
537
|
+
|
538
|
+
##### Logs and statistics
|
539
|
+
|
540
|
+
The `log` statement can be used in the process description, to
|
541
|
+
send a message to logs (by default to STDOUT).
|
542
|
+
|
543
|
+
The `stats` statements can be used to collect running statistics.
|
544
|
+
|
545
|
+
* `stats_start tags` marks start of an activity described by `tags`
|
546
|
+
* `stats_stop tags` marks start of an activity described by `tags`
|
547
|
+
* `stats tags` marks that a counter marked by `tags` should be
|
548
|
+
incremented
|
549
|
+
* `stats_save value, tags` saves given value and current time
|
550
|
+
|
551
|
+
The `tags` parameter allows one to group
|
552
|
+
statistics by required criteria, e.g. name of specific process
|
553
|
+
(apache1, apache2, apache2, ...) in which they were collected, action
|
554
|
+
performred by the process (begin_request, end_request) etc. The
|
555
|
+
parameter should be a hash and it is your responsibility to design
|
556
|
+
structure of these parameters to be able to conveniently collect
|
557
|
+
required events.
|
558
|
+
|
559
|
+
Simulator automatically collects statistics for resource usage
|
560
|
+
(subsequent CPUs and net segments).
|
561
|
+
|
562
|
+
##### Variables, Conditions, Loops
|
563
|
+
|
564
|
+
As shown in examples above (see model of wget), variables can be
|
565
|
+
used to steer behavior of a process. Their visibility should be
|
566
|
+
intuitive. Don't use Ruby's instance variables -- `@something`
|
567
|
+
-- didn't test it, but no guarantee given!
|
568
|
+
|
569
|
+
You can also get name of current process from `process.name`.
|
570
|
+
|
571
|
+
Using conditional statements is encouraged wherever it is useful.
|
572
|
+
Using loops is definitely NOT encouraged. It can (and most probably
|
573
|
+
will) create long event queues which will slow down simulation.
|
574
|
+
You should rather use recurring events, as in the example with
|
575
|
+
wget model.
|
576
|
+
|
577
|
+
### Programs
|
578
|
+
|
579
|
+
Programs can be used to define the same logic used in a numebr of
|
580
|
+
processes. Their names can be the used to define processes.
|
581
|
+
Behavior of programs ca be described using the same statemets
|
582
|
+
that are used to describe processes.
|
583
|
+
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
program :waiter do |time|
|
587
|
+
delay_for time: time
|
588
|
+
end
|
589
|
+
|
590
|
+
program :worker do |volume|
|
591
|
+
cpu do |cpu|
|
592
|
+
( (volume * volume).in_bytes / cpu.performance ).miliseconds
|
593
|
+
end
|
594
|
+
end
|
595
|
+
```
|
596
|
+
|
597
|
+
These two programs can be used to define processes:
|
598
|
+
|
599
|
+
```ruby
|
600
|
+
new_process program: waiter, args: 100.miliseconds
|
601
|
+
new_process program: worker, args: 2000.bytes
|
602
|
+
```
|
603
|
+
|
604
|
+
`args` passed to the `new_process` statement will be passed to
|
605
|
+
the block defining program. So in the example above `time`
|
606
|
+
parameter of `:waiter` process will be set to 100 and `volume`
|
607
|
+
parameter of the `:worker` process will be set to 2000.
|
608
|
+
|
609
|
+
### Resources
|
610
|
+
|
611
|
+
Resources are described in terms of `node`s equipped with `cpu`s
|
612
|
+
of given performance and `net`s with given `name` bandwidth
|
613
|
+
(`bw`). Routes between `node`s are defined using `route`
|
614
|
+
statement.
|
615
|
+
|
616
|
+
#### Nodes
|
617
|
+
|
618
|
+
Nodes are defined using `node` statement, cpus inside nodes using
|
619
|
+
`cpu` statement with performance as parameter.
|
620
|
+
|
621
|
+
```ruby
|
622
|
+
node :laptop do
|
623
|
+
cpu 1000
|
624
|
+
cpu 1000
|
625
|
+
cpu 1000
|
626
|
+
end
|
627
|
+
```
|
628
|
+
|
629
|
+
The performance defined here, can be used in `cpu` statement in
|
630
|
+
process description.
|
631
|
+
|
632
|
+
|
633
|
+
#### Nets
|
634
|
+
|
635
|
+
Nets used in communication are defined with `net` statement with
|
636
|
+
name as parameter and a Hash definind other parameters of the
|
637
|
+
segment. The most important parameter of each net segmetn is its
|
638
|
+
bandwidth:
|
639
|
+
|
640
|
+
```ruby
|
641
|
+
net :lan, bw: 1024.bps
|
642
|
+
net :subnet1, bw: 20480.bps
|
643
|
+
```
|
644
|
+
|
645
|
+
Additionally it is possible to specify probability that a packet
|
646
|
+
transmitted over this network will be dropped. Currently, each message
|
647
|
+
sent between two processes is treated as a single packet, so this
|
648
|
+
probability will apply to dropping the whole message -- the message will
|
649
|
+
be sent, but nothing will be received. By default drop probability is
|
650
|
+
set to 0 and all sent messages are delivered.
|
651
|
+
|
652
|
+
There are two ways to define probability drop probability. First,
|
653
|
+
specify a `Float` number between 0 and 1. The packets will be dropped
|
654
|
+
with this this probability and uniform distribution:
|
655
|
+
|
656
|
+
```ruby
|
657
|
+
net :lan, bw: 1024.bps, drop: 0.01
|
658
|
+
```
|
659
|
+
|
660
|
+
Second, it is possible to define a block of code. That block will be
|
661
|
+
evaluated for each packet transmitted over this network and should
|
662
|
+
return true if packet should be dropped and false otherwise. This block
|
663
|
+
can use Ruby's `rand` function and any desired logic to produce require
|
664
|
+
distribution of dropped packets. Fo example, to drop packets according
|
665
|
+
to exponential distribution with lambda = 2:
|
666
|
+
|
667
|
+
```ruby
|
668
|
+
net :lan, bw: 1024.bps, drop: ->{ -0.5*Math.log(rand) < 0.1 }
|
669
|
+
```
|
670
|
+
|
671
|
+
Currently, it is not possible to make probability of dropping a packet
|
672
|
+
dependent on dropping previous packets.
|
673
|
+
|
674
|
+
#### Routes
|
675
|
+
|
676
|
+
Routes are used to define which `net` segments should be traversed
|
677
|
+
by data transmitted between two given `node`s. Routes can be
|
678
|
+
one-way (default) or two-way.
|
679
|
+
|
680
|
+
```ruby
|
681
|
+
route from: :laptop, to: :node02, via: [ :net01, :net02 ]
|
682
|
+
route from: :node04, to: :node05, via: [ :net07, :net01 ], twoway: true
|
683
|
+
route from: :node06, to: :node07, via: [ :net07, :net01 ], twoway: :true
|
684
|
+
```
|
685
|
+
|
686
|
+
Communication between processes located on different nodes
|
687
|
+
requires a route defined between the nodes. If there is more then
|
688
|
+
one route between a pair of nodes, random one is selected. A
|
689
|
+
node can communicate with itself without any route defined and
|
690
|
+
without traversing any `net` segemtns
|
691
|
+
|
692
|
+
### Mapping of Application to Resources
|
693
|
+
|
694
|
+
When application is defined as a set of processes and resources
|
695
|
+
are defined as nodes connected with net segments, the application
|
696
|
+
can be mapped to the nodes. Mapping is defined using `put`
|
697
|
+
statement.
|
698
|
+
|
699
|
+
```ruby
|
700
|
+
put :wget, on: :laptop
|
701
|
+
put :server1, on: :gandalf
|
702
|
+
```
|
703
|
+
|
704
|
+
First parameter is process name, second (after `on:`) is node
|
705
|
+
name.
|
706
|
+
|
707
|
+
Thus application logic and topology does not depent in any way on
|
708
|
+
topology of resources. The same application can be mapped to
|
709
|
+
different resources in different ways. The same resource set can
|
710
|
+
be used for different applications.
|
711
|
+
|
712
|
+
### Units
|
713
|
+
|
714
|
+
Simulator operates on three kinds of values:
|
715
|
+
|
716
|
+
* data volume
|
717
|
+
* network bandwidth
|
718
|
+
* time
|
719
|
+
|
720
|
+
For each value one can use specific measurement units:
|
721
|
+
|
722
|
+
* for data volume
|
723
|
+
* bits
|
724
|
+
* bytes
|
725
|
+
* for network bandwidth
|
726
|
+
* bps (bits per second)
|
727
|
+
* Bps (bytes per second)
|
728
|
+
* for time:
|
729
|
+
* microseconds
|
730
|
+
* miliseconds
|
731
|
+
* seconds
|
732
|
+
* minutes
|
733
|
+
* hours
|
734
|
+
* days (24 hours)
|
735
|
+
|
736
|
+
In every place where data volume should be given, it can be
|
737
|
+
defined using expressions like
|
738
|
+
|
739
|
+
```ruby
|
740
|
+
1024.bytes
|
741
|
+
128.bits
|
742
|
+
```
|
743
|
+
|
744
|
+
Similarly network bandwidth can be defined using
|
745
|
+
|
746
|
+
```ruby
|
747
|
+
128.Bps
|
748
|
+
1024.bps
|
749
|
+
```
|
750
|
+
|
751
|
+
Finally, if time should be given, one should use a unit, to
|
752
|
+
ensure correct value, e.g.
|
753
|
+
|
754
|
+
```ruby
|
755
|
+
10.seconds
|
756
|
+
100.microseconds
|
757
|
+
2.hours
|
758
|
+
```
|
759
|
+
|
760
|
+
For values returned from simulator, to ensure value in correct
|
761
|
+
units use `in_*` methods, e.g.
|
762
|
+
|
763
|
+
```ruby
|
764
|
+
data.in_bytes
|
765
|
+
time.in_seconds
|
766
|
+
```
|
767
|
+
|
768
|
+
So to define that CPU load time in milliseconds should be equal to
|
769
|
+
10 * data volume in bytes, use:
|
770
|
+
|
771
|
+
```ruby
|
772
|
+
cpu do |cpu|
|
773
|
+
(data.size.in_bytes * 10).miliseconds
|
774
|
+
end
|
775
|
+
```
|
776
|
+
|
777
|
+
Every measurement unit has its equivalent `in_*` method.
|
778
|
+
|
779
|
+
## Using simulation statistics
|
780
|
+
|
781
|
+
The way of obtaining statistics depends on the method used to create
|
782
|
+
simulation. The most convenient is to use subclass of the
|
783
|
+
`RBSim::Experiment` class, but it can also be done with the simulation
|
784
|
+
performed with `RBSim` class.
|
785
|
+
|
786
|
+
### Statistics with subclass of `RBSim::Experiment`
|
787
|
+
|
788
|
+
The `RBSim::Experiment` class provides convenient method to obtain
|
789
|
+
simulation statistics and its subclass created to run simulation is a
|
790
|
+
natural place to implement own methods that process the statistics into
|
791
|
+
required collective results.
|
792
|
+
|
793
|
+
There are two methods available for every instance method in a subclass
|
794
|
+
of the `RBSim::Experiment` class. The `app_stats` gives access to
|
795
|
+
statistics concerning modeled application, and the `res_stats`
|
796
|
+
contains statistics concerning used resources, the ones automatically
|
797
|
+
collected by the simulator.
|
798
|
+
|
799
|
+
For application stats as well as for resource stats the actual data is
|
800
|
+
available with three iterators, for three types of
|
801
|
+
collected statistics:
|
802
|
+
|
803
|
+
* data from basic counters collected using `stats`
|
804
|
+
statement in the model can be obtained with `counters` iterator
|
805
|
+
* data from duration statistics collected with `stats_start` and
|
806
|
+
`stats_stop` statements can be obtained with `duration` iterator
|
807
|
+
* data from value stats saved with `stats_save` are available via
|
808
|
+
`values` iterator.
|
809
|
+
|
810
|
+
Each iterator accepts optional parameter describing filters, so it is
|
811
|
+
possible to limit amount of data that should be processed. The filters
|
812
|
+
allow to select required values on the basis of parameters passed to the
|
813
|
+
`stats_*` statements in the model. For example, in order to get
|
814
|
+
durations of `operation` called `:update` on can use the following
|
815
|
+
snippet:
|
816
|
+
|
817
|
+
```ruby
|
818
|
+
app_stats.durations(operation: :update)
|
819
|
+
```
|
820
|
+
|
821
|
+
assuming that the data are collected in the model with statements
|
822
|
+
|
823
|
+
```ruby
|
824
|
+
stats_start operation: :update
|
825
|
+
```
|
826
|
+
|
827
|
+
and
|
828
|
+
|
829
|
+
```ruby
|
830
|
+
stats_stop operation: :update
|
831
|
+
```
|
832
|
+
|
833
|
+
Depending on the type of collected data (counters, durations, values)
|
834
|
+
different data are passed by the iterator.
|
835
|
+
|
836
|
+
#### Values of counters
|
837
|
+
|
838
|
+
Counters are grouped according to parameters (tags) passed to the
|
839
|
+
`stats` statement in the model. Every time such statement is reached
|
840
|
+
during simulation, current values of the simulation clock is saved. The
|
841
|
+
`counters` iterator yields two arguments: `tags` and array of timestamps
|
842
|
+
when counters with these tags was triggered.
|
843
|
+
|
844
|
+
```ruby
|
845
|
+
app_stats.counters(event: :finished) do |tags, timestamps|
|
846
|
+
# do something with :finished events
|
847
|
+
# if you need you can check the other
|
848
|
+
# tags saved with these events
|
849
|
+
end
|
850
|
+
```
|
851
|
+
|
852
|
+
#### Duration times
|
853
|
+
|
854
|
+
Duration times are saved from the model using `stats_start` and
|
855
|
+
`stats_stop` statements. They are also grouped according to the tags
|
856
|
+
passed to these statements. Data can be subsequently filtered using
|
857
|
+
these tags and the `durations` iterator yields three values: `tags`,
|
858
|
+
`start_time`, `stop_time`, where start and stop times are timestamps at
|
859
|
+
which simulation reached corresponding statement.
|
860
|
+
|
861
|
+
For example if an operation in a model is braced with statement:
|
862
|
+
|
863
|
+
```ruby
|
864
|
+
stats_start operation: :update
|
865
|
+
```
|
866
|
+
|
867
|
+
and
|
868
|
+
|
869
|
+
```ruby
|
870
|
+
stats_stop operation: :update
|
871
|
+
```
|
872
|
+
|
873
|
+
duration of this operation can be obtained using the following
|
874
|
+
statement:
|
875
|
+
|
876
|
+
```ruby
|
877
|
+
app_stats.durations(operation: :update) do |tags, start, stop|
|
878
|
+
# do something with the single duration
|
879
|
+
# you can use the values of the tags
|
880
|
+
end
|
881
|
+
```
|
882
|
+
|
883
|
+
If there were more then one event with the same tags save, the block
|
884
|
+
will be yielded for each of them. Similarly, if there were additional
|
885
|
+
tags set for the `stats_*` statements, the block will be yielded for
|
886
|
+
each of them.
|
887
|
+
|
888
|
+
#### Values
|
889
|
+
|
890
|
+
Values (e.g. queue length) can be saved while simulation using
|
891
|
+
`stats_save` statement with require tags. The save values are grouped
|
892
|
+
using tags and time at which they were saved. The `values` iterator
|
893
|
+
yields three parameters: `tags`, `timestamp`, `values` where values is a
|
894
|
+
list of values saved for the same tags and the same time.
|
895
|
+
|
896
|
+
#### Statistics of resources
|
897
|
+
|
898
|
+
Statistics of resources are automatically collected by simulator in a
|
899
|
+
predefined way. They are available in the `RBSim::Experiment` subclass
|
900
|
+
with the `res_stats` method. They are grouped by the predefined tags
|
901
|
+
that allow to identify resource that generated specific reading and type
|
902
|
+
of the value.
|
903
|
+
|
904
|
+
* CPU usage can be obtained using duration events tagged with `resource:
|
905
|
+
'CPU'` tag, and also with `node:` with name of the node the CPU
|
906
|
+
belongs to,
|
907
|
+
* network usage for subsequent net segments can be obtained with
|
908
|
+
duration iterator using network `resource: 'NET'` tag; these values
|
909
|
+
are also tagged with and `name:` tag corresponding to the name of the
|
910
|
+
network segment,
|
911
|
+
* number of packages dropped by a network segment is available via
|
912
|
+
`counter` iterator using tags `event: 'NET DROP'` with additional
|
913
|
+
`net:` tag corresponding to the name of the segment,
|
914
|
+
* waiting time for data packages that were transmitted over the network,
|
915
|
+
but not yet handled by a busy processes are tagged with: `resource
|
916
|
+
'DATAQ WAIT` and `process:` tag corresponding to name of the receiving
|
917
|
+
process.
|
918
|
+
* length of the queue that holds data that were transmitted over the
|
919
|
+
network but not yet received by a busy process can be read with
|
920
|
+
`values` counter using `resource: 'DATAQ LEN'` tag with additional tag
|
921
|
+
`process:` corresponding to the process name.
|
922
|
+
|
923
|
+
If for a more complicated model of resources there is a need to
|
924
|
+
additionally group the resources, it is possible to put additional tags
|
925
|
+
to
|
926
|
+
|
927
|
+
* nets
|
928
|
+
* cpus
|
929
|
+
* processes
|
930
|
+
|
931
|
+
and these tags will be saved together with statistics corresponding to
|
932
|
+
these processes. So it is e.g. possible to create a group of processes
|
933
|
+
of the same type:
|
934
|
+
|
935
|
+
```ruby
|
936
|
+
new_process :apache1, program: :webserver, tags { type: :apache }
|
937
|
+
new_process :apache2, program: :webserver, tags { type: :apache }
|
938
|
+
```
|
939
|
+
|
940
|
+
and then obtain queue lengths for all of them with:
|
941
|
+
|
942
|
+
```ruby
|
943
|
+
res_stats.values(resource: 'DATAQ LEN', type: :apache)
|
944
|
+
```
|
945
|
+
|
946
|
+
### Statistics when using `RBSim.model`
|
947
|
+
|
948
|
+
The only difference from `RBSim::Experiment` is the way to obtain
|
949
|
+
objects holding the actual statistics. When model was created using
|
950
|
+
`RBSim.model` or `RBSim.read` and saved in a `model` variable, the
|
951
|
+
statistics can be obtained using `model.stats` method. The method
|
952
|
+
returns hash with two elements. Under `:app_stats` key there is the same
|
953
|
+
object that is available in a subclass of `RBSim::Experiment` using
|
954
|
+
method `app_stats`. Similarly there is `:res_stats` key holding the
|
955
|
+
the resource statistics.
|
956
|
+
|
957
|
+
|
958
|
+
# Copyright
|
959
|
+
|
960
|
+
Copyright (c) 2014-2018 Wojciech Rząsa. See LICENSE.txt for further details. Contact me if interested in different license conditions.
|