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/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.