sisfc 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.projections.json +12 -0
- data/.travis.yml +6 -0
- data/README.md +37 -19
- data/Rakefile +4 -2
- data/TODO +5 -0
- data/bin/sisfc +29 -6
- data/examples/generator.R +3 -1
- data/examples/simulator.conf +80 -29
- data/lib/sisfc.rb +4 -4
- data/lib/sisfc/configuration.rb +73 -6
- data/lib/sisfc/data_center.rb +42 -29
- data/lib/sisfc/evaluation.rb +23 -15
- data/lib/sisfc/event.rb +9 -6
- data/lib/sisfc/generator.rb +14 -21
- data/lib/sisfc/latency_manager.rb +65 -0
- data/lib/sisfc/logger.rb +28 -0
- data/lib/sisfc/request.rb +42 -85
- data/lib/sisfc/service_type.rb +2 -0
- data/lib/sisfc/simulation.rb +234 -47
- data/lib/sisfc/sorted_array.rb +2 -0
- data/lib/sisfc/statistics.rb +37 -3
- data/lib/sisfc/support/dsl_helper.rb +2 -0
- data/lib/sisfc/version.rb +3 -1
- data/lib/sisfc/vm.rb +46 -27
- data/sisfc.gemspec +9 -5
- data/spec/minitest_helper.rb +9 -0
- data/{test/sisfc/configuration_test.rb → spec/sisfc/configuration_spec.rb} +4 -2
- data/spec/sisfc/data_center_spec.rb +19 -0
- data/{test/sisfc/evaluation_test.rb → spec/sisfc/evaluation_spec.rb} +5 -3
- data/{test/sisfc/generator_test.rb → spec/sisfc/generator_spec.rb} +21 -18
- data/spec/sisfc/latency_manager_spec.rb +13 -0
- data/spec/sisfc/reference_configuration.rb +534 -0
- data/spec/sisfc/request_spec.rb +19 -0
- metadata +115 -49
- data/test/sisfc/reference_configuration.rb +0 -191
- data/test/sisfc/request_test.rb +0 -13
- data/test/test_helper.rb +0 -4
data/lib/sisfc/service_type.rb
CHANGED
data/lib/sisfc/simulation.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './data_center'
|
4
|
+
require_relative './event'
|
5
|
+
require_relative './generator'
|
6
|
+
require_relative './sorted_array'
|
7
|
+
require_relative './statistics'
|
8
|
+
require_relative './vm'
|
9
|
+
require_relative './latency_manager'
|
7
10
|
|
8
11
|
|
9
12
|
module SISFC
|
@@ -15,6 +18,9 @@ module SISFC
|
|
15
18
|
def initialize(opts = {})
|
16
19
|
@configuration = opts[:configuration]
|
17
20
|
@evaluator = opts[:evaluator]
|
21
|
+
|
22
|
+
# create latency manager
|
23
|
+
@latency_manager = LatencyManager.new(@configuration.latency_models)
|
18
24
|
end
|
19
25
|
|
20
26
|
|
@@ -33,28 +39,63 @@ module SISFC
|
|
33
39
|
# setup simulation start and current time
|
34
40
|
@current_time = @start_time = @configuration.start_time
|
35
41
|
|
36
|
-
# create data centers
|
37
|
-
|
42
|
+
# create data centers and store them in a repository
|
43
|
+
data_center_repository = Hash[
|
44
|
+
@configuration.data_centers.map do |k,v|
|
45
|
+
[ k, DataCenter.new(id: k, **v) ]
|
46
|
+
end
|
47
|
+
]
|
48
|
+
|
49
|
+
customer_repository = @configuration.customers
|
50
|
+
workflow_type_repository = @configuration.workflow_types
|
38
51
|
|
39
52
|
# initialize statistics
|
40
|
-
stats
|
41
|
-
|
53
|
+
stats = Statistics.new
|
54
|
+
per_workflow_and_customer_stats = Hash[
|
55
|
+
workflow_type_repository.keys.map do |wft_id|
|
56
|
+
[
|
57
|
+
wft_id,
|
58
|
+
Hash[
|
59
|
+
customer_repository.keys.map do |c_id|
|
60
|
+
[ c_id, Statistics.new(@configuration.custom_stats.find{|x| x[:customer_id] == c_id && x[:workflow_type_id] == wft_id } || {}) ]
|
61
|
+
end
|
62
|
+
]
|
63
|
+
]
|
64
|
+
end
|
65
|
+
]
|
66
|
+
reqs_received_per_workflow_and_customer = Hash[
|
67
|
+
workflow_type_repository.keys.map do |wft_id|
|
68
|
+
[ wft_id, Hash[customer_repository.keys.map {|c_id| [ c_id, 0 ]}] ]
|
69
|
+
end
|
70
|
+
]
|
42
71
|
|
43
72
|
# create VMs
|
44
73
|
@vms = []
|
45
74
|
vmid = 0
|
46
75
|
vm_allocation.each do |opts|
|
47
76
|
# setup service_time_distribution
|
48
|
-
|
77
|
+
stdist = @configuration.service_component_types[opts[:component_type]][:service_time_distribution]
|
49
78
|
|
50
79
|
# allocate the VMs
|
51
80
|
opts[:vm_num].times do
|
52
81
|
# create VM ...
|
53
|
-
|
82
|
+
# if [ "Financial Transaction Server", "RDBMS C", "Queue Manager" ].include? opts[:component_type]
|
83
|
+
# vm = VM.new(vmid, opts[:dc_id], opts[:vm_size], stdist, trace: true, notes: "ct #{opts[:component_type]}")
|
84
|
+
# else
|
85
|
+
# vm = VM.new(vmid, opts[:dc_id], opts[:vm_size], stdist)
|
86
|
+
# end
|
87
|
+
vm = VM.new(vmid, opts[:dc_id], opts[:vm_size], stdist)
|
54
88
|
# ... add it to the vm list ...
|
55
89
|
@vms << vm
|
56
90
|
# ... and register it in the corresponding data center
|
57
|
-
|
91
|
+
unless data_center_repository[opts[:dc_id]].add_vm(vm, opts[:component_type])
|
92
|
+
$stderr.puts "====== Unfeasible allocation at data center #{dc_id} ======"
|
93
|
+
$stderr.flush
|
94
|
+
# here we return Float::MAX instead of, e.g., Float::INFINITY,
|
95
|
+
# because the latter would break optimization tools. instead, we
|
96
|
+
# want to have a very high but comparable value.
|
97
|
+
return Float::MAX
|
98
|
+
end
|
58
99
|
# update vm id
|
59
100
|
vmid += 1
|
60
101
|
end
|
@@ -67,8 +108,8 @@ module SISFC
|
|
67
108
|
|
68
109
|
# generate first request
|
69
110
|
rg = RequestGenerator.new(@configuration.request_generation)
|
70
|
-
|
71
|
-
new_event(Event::
|
111
|
+
req_attrs = rg.generate
|
112
|
+
new_event(Event::ET_REQUEST_GENERATION, req_attrs, req_attrs[:generation_time], nil)
|
72
113
|
|
73
114
|
# schedule end of simulation
|
74
115
|
unless @configuration.end_time.nil?
|
@@ -80,54 +121,115 @@ module SISFC
|
|
80
121
|
warmup_threshold = @configuration.start_time + @configuration.warmup_duration.to_i
|
81
122
|
|
82
123
|
requests_being_worked_on = 0
|
83
|
-
|
124
|
+
requests_forwarded_to_other_dcs = 0
|
125
|
+
current_event = 0
|
84
126
|
|
85
127
|
# launch simulation
|
86
128
|
until @event_queue.empty?
|
87
129
|
e = @event_queue.shift
|
88
130
|
|
89
|
-
|
131
|
+
current_event += 1
|
90
132
|
# sanity check on simulation time flow
|
91
133
|
if @current_time > e.time
|
92
|
-
raise "Error: simulation time inconsistency for event #{
|
134
|
+
raise "Error: simulation time inconsistency for event #{current_event} " +
|
93
135
|
"e.type=#{e.type} @current_time=#{@current_time}, e.time=#{e.time}"
|
94
136
|
end
|
95
137
|
|
96
138
|
@current_time = e.time
|
97
139
|
|
98
140
|
case e.type
|
141
|
+
when Event::ET_REQUEST_GENERATION
|
142
|
+
req_attrs = e.data
|
143
|
+
|
144
|
+
# find closest data center
|
145
|
+
customer_location_id = customer_repository.dig(req_attrs[:customer_id], :location_id)
|
146
|
+
dc_at_customer_location = data_center_repository.values.find {|dc| dc.location_id == customer_location_id }
|
147
|
+
|
148
|
+
raise "No data center found at location id #{customer_location_id}!" unless dc_at_customer_location
|
149
|
+
|
150
|
+
# find first component name for requested workflow
|
151
|
+
workflow = workflow_type_repository[req_attrs[:workflow_type_id]]
|
152
|
+
first_component_name = workflow[:component_sequence][0][:name]
|
153
|
+
|
154
|
+
closest_dc = if dc_at_customer_location.has_vms_of_type?(first_component_name)
|
155
|
+
dc_at_customer_location
|
156
|
+
else
|
157
|
+
data_center_repository.values.select{|dc| dc.has_vms_of_type?(first_component_name) }&.sample
|
158
|
+
end
|
159
|
+
|
160
|
+
raise "Invalid configuration! No VMs of type #{first_component_name} found!" unless closest_dc
|
161
|
+
|
162
|
+
arrival_time = @current_time + @latency_manager.sample_latency_between(customer_location_id, closest_dc.location_id)
|
163
|
+
new_req = Request.new(req_attrs.merge!(initial_data_center_id: closest_dc.dcid,
|
164
|
+
arrival_time: arrival_time))
|
165
|
+
|
166
|
+
# schedule arrival of current request
|
167
|
+
new_event(Event::ET_REQUEST_ARRIVAL, new_req, arrival_time, nil)
|
168
|
+
|
169
|
+
# schedule generation of next request
|
170
|
+
req_attrs = rg.generate
|
171
|
+
new_event(Event::ET_REQUEST_GENERATION, req_attrs, req_attrs[:generation_time], nil)
|
172
|
+
|
99
173
|
when Event::ET_REQUEST_ARRIVAL
|
100
174
|
# get request
|
101
175
|
req = e.data
|
102
176
|
|
103
177
|
# find data center
|
104
|
-
data_center =
|
178
|
+
data_center = data_center_repository[req.data_center_id]
|
179
|
+
|
180
|
+
# update reqs_received_per_workflow_and_customer
|
181
|
+
reqs_received_per_workflow_and_customer[req.workflow_type_id][req.customer_id] += 1
|
105
182
|
|
106
183
|
# find next component name
|
107
|
-
workflow =
|
184
|
+
workflow = workflow_type_repository[req.workflow_type_id]
|
108
185
|
next_component_name = workflow[:component_sequence][req.next_step][:name]
|
186
|
+
# if [5,6,9].include? req.workflow_type_id
|
187
|
+
# $stderr.puts "received request for wf #{req.workflow_type_id} at dc #{req.data_center_id}"
|
188
|
+
# $stderr.puts "next component name is #{next_component_name}"
|
189
|
+
# $stderr.flush
|
190
|
+
# end
|
109
191
|
|
110
192
|
# get random vm providing next service component type
|
111
193
|
vm = data_center.get_random_vm(next_component_name)
|
194
|
+
# if [5,6,9].include? req.workflow_type_id
|
195
|
+
# $stderr.puts "next vm is #{vm.__id__}"
|
196
|
+
# $stderr.flush
|
197
|
+
# end
|
112
198
|
|
113
|
-
#
|
114
|
-
|
199
|
+
# schedule request forwarding to vm
|
200
|
+
new_event(Event::ET_REQUEST_FORWARDING, req, e.time, vm)
|
115
201
|
|
116
202
|
# update stats
|
117
203
|
if req.arrival_time > warmup_threshold
|
118
204
|
# increase the number of requests being worked on
|
119
205
|
requests_being_worked_on += 1
|
120
|
-
end
|
121
206
|
|
122
|
-
|
123
|
-
|
124
|
-
|
207
|
+
# increase count of received requests
|
208
|
+
stats.request_received
|
209
|
+
|
210
|
+
# increase count of received requests in per_workflow_and_customer_stats
|
211
|
+
per_workflow_and_customer_stats[req.workflow_type_id][req.customer_id].request_received
|
212
|
+
|
213
|
+
# if stats.received % 10_000 == 0
|
214
|
+
# $stderr.puts "#{Thread.current.__id__} sisfc: Received #{stats.received} requests."
|
215
|
+
# $stderr.puts "#{Thread.current.__id__} sisfc: Working on #{requests_being_worked_on} requests."
|
216
|
+
# $stderr.flush
|
217
|
+
# end
|
218
|
+
end
|
125
219
|
|
126
220
|
|
127
221
|
# Leave these events for when we add VM migration support
|
128
222
|
# when Event::ET_VM_SUSPEND
|
129
223
|
# when Event::ET_VM_RESUME
|
130
224
|
|
225
|
+
when Event::ET_REQUEST_FORWARDING
|
226
|
+
# get request
|
227
|
+
req = e.data
|
228
|
+
time = e.time
|
229
|
+
vm = e.destination
|
230
|
+
|
231
|
+
vm.new_request(self, req, time)
|
232
|
+
|
131
233
|
|
132
234
|
when Event::ET_WORKFLOW_STEP_COMPLETED
|
133
235
|
# retrieve request and vm
|
@@ -138,22 +240,103 @@ module SISFC
|
|
138
240
|
vm.request_finished(self, e.time)
|
139
241
|
|
140
242
|
# find data center and workflow
|
141
|
-
data_center =
|
142
|
-
workflow =
|
243
|
+
data_center = data_center_repository[req.data_center_id]
|
244
|
+
workflow = workflow_type_repository[req.workflow_type_id]
|
143
245
|
|
144
246
|
# check if there are other steps left to complete the workflow
|
145
247
|
if req.next_step < workflow[:component_sequence].size
|
146
248
|
# find next component name
|
147
249
|
next_component_name = workflow[:component_sequence][req.next_step][:name]
|
148
250
|
|
149
|
-
# get random
|
251
|
+
# get random VM providing next service component type
|
150
252
|
new_vm = data_center.get_random_vm(next_component_name)
|
151
253
|
|
152
|
-
#
|
153
|
-
|
254
|
+
# this is the request's time of arrival at the new VM
|
255
|
+
forwarding_time = e.time
|
256
|
+
|
257
|
+
# there might not be a VM of the type we need in the current data
|
258
|
+
# center, so look in the other data centers
|
259
|
+
unless new_vm
|
260
|
+
# get list of other data centers, randomly picked
|
261
|
+
other_dcs = data_center_repository.values.select{|x| x != data_center && x.has_vms_of_type?(next_component_name) }&.shuffle
|
262
|
+
other_dcs.each do |dc|
|
263
|
+
new_vm = dc.get_random_vm(next_component_name)
|
264
|
+
if new_vm
|
265
|
+
# need to update data_center_id of request
|
266
|
+
req.data_center_id = dc.dcid
|
267
|
+
|
268
|
+
# keep track of transmission time
|
269
|
+
transmission_time =
|
270
|
+
@latency_manager.sample_latency_between(data_center.location_id,
|
271
|
+
dc.location_id)
|
272
|
+
|
273
|
+
unless transmission_time >= 0.0
|
274
|
+
raise "Negative transmission time (#{transmission_time})!"
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
# if [5,6,9].include? req.workflow_type_id
|
279
|
+
# $stderr.puts "rerouting request for wf #{req.workflow_type_id} from dc #{data_center.dcid} to dc #{dc.dcid}"
|
280
|
+
# $stderr.puts "next component name is #{next_component_name}"
|
281
|
+
# $stderr.puts "transmission time is #{transmission_time}"
|
282
|
+
# $stderr.flush
|
283
|
+
# end
|
284
|
+
|
285
|
+
req.update_transfer_time(transmission_time)
|
286
|
+
forwarding_time += transmission_time
|
287
|
+
|
288
|
+
# update request's current data_center_id
|
289
|
+
req.data_center_id = dc.dcid
|
290
|
+
|
291
|
+
# keep track of number of requests forwarded to other data centers
|
292
|
+
requests_forwarded_to_other_dcs += 1
|
293
|
+
|
294
|
+
# we're done here
|
295
|
+
break
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# make sure we actually found a VM
|
301
|
+
raise "Cannot find VM running a component of type " +
|
302
|
+
"#{next_component_name} in any data center!" unless new_vm
|
303
|
+
|
304
|
+
# if [5,6,9].include? req.workflow_type_id
|
305
|
+
# $stderr.puts "received request for wf #{req.workflow_type_id} at dc #{req.data_center_id}"
|
306
|
+
# $stderr.puts "next component name is #{next_component_name}"
|
307
|
+
# $stderr.puts "next vm is #{new_vm.__id__}"
|
308
|
+
# $stderr.flush
|
309
|
+
# end
|
310
|
+
|
311
|
+
# schedule request forwarding to vm
|
312
|
+
new_event(Event::ET_REQUEST_FORWARDING, req, forwarding_time, new_vm)
|
313
|
+
|
154
314
|
else # workflow is finished
|
315
|
+
# calculate transmission time
|
316
|
+
transmission_time =
|
317
|
+
@latency_manager.sample_latency_between(
|
318
|
+
# data center location
|
319
|
+
data_center_repository[req.data_center_id].location_id,
|
320
|
+
# customer location
|
321
|
+
customer_repository.dig(req.customer_id, :location_id)
|
322
|
+
)
|
323
|
+
|
324
|
+
# if [5,6,9].include? req.workflow_type_id
|
325
|
+
# $stderr.puts "closing request for wf #{req.workflow_type_id}"
|
326
|
+
# $stderr.puts "e.time is #{e.time}"
|
327
|
+
# $stderr.puts "transmission_time is #{transmission_time}"
|
328
|
+
# $stderr.flush
|
329
|
+
# end
|
330
|
+
|
331
|
+
unless transmission_time >= 0.0
|
332
|
+
raise "Negative transmission time (#{transmission_time})!"
|
333
|
+
end
|
334
|
+
|
335
|
+
# keep track of transmission time
|
336
|
+
req.update_transfer_time(transmission_time)
|
337
|
+
|
155
338
|
# schedule request closure
|
156
|
-
new_event(Event::ET_REQUEST_CLOSURE, req, e.time +
|
339
|
+
new_event(Event::ET_REQUEST_CLOSURE, req, e.time + transmission_time, nil)
|
157
340
|
end
|
158
341
|
|
159
342
|
|
@@ -171,7 +354,9 @@ module SISFC
|
|
171
354
|
|
172
355
|
# collect request statistics
|
173
356
|
stats.record_request(req)
|
174
|
-
|
357
|
+
|
358
|
+
# collect request statistics in per_workflow_and_customer_stats
|
359
|
+
per_workflow_and_customer_stats[req.workflow_type_id][req.customer_id].record_request(req)
|
175
360
|
end
|
176
361
|
|
177
362
|
|
@@ -184,22 +369,24 @@ module SISFC
|
|
184
369
|
|
185
370
|
# puts "========== Simulation Finished =========="
|
186
371
|
|
187
|
-
|
188
|
-
|
189
|
-
:served_requests => stats.n,
|
190
|
-
:queued_requests => requests_being_worked_on }
|
191
|
-
dc_kpis = dc_stats.map do |s|
|
192
|
-
{ :mttr => s.mean,
|
193
|
-
:served_requests => s.n, }
|
194
|
-
end
|
195
|
-
fitness = @evaluator.evaluate_business_impact(kpis, dc_kpis, vm_allocation)
|
372
|
+
costs = @evaluator.evaluate_business_impact(stats, per_workflow_and_customer_stats,
|
373
|
+
vm_allocation)
|
196
374
|
puts "====== Evaluating new allocation ======\n" +
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
375
|
+
"costs: #{costs}\n" +
|
376
|
+
"vm_allocation: #{vm_allocation.inspect}\n" +
|
377
|
+
"stats: #{stats.to_s}\n" +
|
378
|
+
"per_workflow_and_customer_stats: #{per_workflow_and_customer_stats.to_s}\n" +
|
379
|
+
"=======================================\n"
|
380
|
+
|
381
|
+
# we want to minimize the cost, so we define fitness as the opposite of
|
382
|
+
# the sum of all costs incurred
|
383
|
+
fitness = - costs.values.inject(0.0){|s,x| s += x }
|
202
384
|
end
|
203
385
|
|
386
|
+
private
|
387
|
+
def communication_latency_between(loc1, loc2)
|
388
|
+
@latency_manager.sample_latency_between(loc1.to_i, loc2.to_i)
|
389
|
+
end
|
390
|
+
|
204
391
|
end
|
205
392
|
end
|
data/lib/sisfc/sorted_array.rb
CHANGED
data/lib/sisfc/statistics.rb
CHANGED
@@ -1,19 +1,35 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './request'
|
4
|
+
|
2
5
|
|
3
6
|
module SISFC
|
4
7
|
class Statistics
|
5
|
-
attr_reader :mean, :n
|
8
|
+
attr_reader :mean, :n, :received, :longer_than
|
9
|
+
alias_method :closed, :n
|
6
10
|
|
7
11
|
# see http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
|
8
|
-
|
12
|
+
# and https://www.johndcook.com/blog/standard_deviation/
|
13
|
+
def initialize(opts={})
|
9
14
|
@n = 0 # number of requests
|
10
15
|
@mean = 0.0
|
11
16
|
@m_2 = 0.0
|
17
|
+
@longer_than = init_counters_for_longer_than_stats(opts)
|
18
|
+
@received = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_received
|
22
|
+
@received += 1
|
12
23
|
end
|
13
24
|
|
14
25
|
def record_request(req)
|
15
26
|
# get new sample
|
16
27
|
x = req.ttr
|
28
|
+
raise "TTR #{x} for request #{req.rid} invalid!" unless x > 0.0
|
29
|
+
|
30
|
+
@longer_than.each_key do |k|
|
31
|
+
@longer_than[k] += 1 if x > k
|
32
|
+
end
|
17
33
|
|
18
34
|
# update counters
|
19
35
|
@n += 1
|
@@ -25,5 +41,23 @@ module SISFC
|
|
25
41
|
def variance
|
26
42
|
@m_2 / (@n - 1)
|
27
43
|
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
"received: #{@received}, closed: #{@n}, " +
|
47
|
+
"(mean: #{@mean}, variance: #{variance}, longer_than: #{@longer_than.to_s})"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def init_counters_for_longer_than_stats(custom_kpis_config)
|
52
|
+
# prepare an infinite length enumerator that always returns zero
|
53
|
+
zeros = Enumerator.new(){|x| loop do x << 0 end }
|
54
|
+
|
55
|
+
Hash[
|
56
|
+
# wrap the values in custom_kpis_config[:longer_than] in an array
|
57
|
+
Array(custom_kpis_config[:longer_than]).
|
58
|
+
# and interval the numbers contained in that array with zeroes
|
59
|
+
zip(zeros) ]
|
60
|
+
end
|
61
|
+
|
28
62
|
end
|
29
63
|
end
|