rbsim 0.0.3

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