sisfc 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erv'
2
4
 
3
5
  module SISFC
@@ -1,9 +1,12 @@
1
- require 'sisfc/data_center'
2
- require 'sisfc/event'
3
- require 'sisfc/generator'
4
- require 'sisfc/sorted_array'
5
- require 'sisfc/statistics'
6
- require 'sisfc/vm'
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
- data_centers = @configuration.data_centers.map {|k,v| DataCenter.new(k,v) }
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 = Statistics.new
41
- dc_stats = data_centers.map {|k,v| Statistics.new }
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
- opts[:service_time_distribution] = @configuration.service_component_types[opts[:component_type]][:service_time_distribution]
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
- vm = VM.new(vmid, opts) # vm = VM.new(vmid, opts.except(:vm_num))
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
- data_centers[opts[:dc_id]-1].add_vm(vm, opts[:component_type])
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
- new_req = rg.generate
71
- new_event(Event::ET_REQUEST_ARRIVAL, new_req, new_req.arrival_time, nil)
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
- events = 0
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
- events += 1
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 #{events} " +
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 = data_centers[req.data_center_id-1]
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 = @configuration.workflow_types[req.workflow_type_id]
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
- # forward request to the vm
114
- vm.new_request(self, req, e.time)
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
- # generate next request
123
- new_req = rg.generate
124
- new_event(Event::ET_REQUEST_ARRIVAL, new_req, new_req.arrival_time, nil)
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 = data_centers[req.data_center_id-1]
142
- workflow = @configuration.workflow_types[req.workflow_type_id]
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 vm providing next service component type
251
+ # get random VM providing next service component type
150
252
  new_vm = data_center.get_random_vm(next_component_name)
151
253
 
152
- # forward request to the new vm
153
- new_vm.new_request(self, req, e.time)
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 + req.communication_latency, nil)
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
- dc_stats[req.data_center_id - 1].record_request(req)
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
- # calculate kpis (for the moment, we only have mttr)
188
- kpis = { :mttr => stats.mean,
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
- vm_allocation.map{|x| x.except(:service_time_distribution) }.inspect + "\n" +
198
- "kpis: #{kpis.to_s}\n" +
199
- "dc_kpis: #{dc_kpis.to_s}\n" +
200
- "=======================================\n"
201
- fitness
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SISFC
2
4
  # the SortedArray class was taken from the ruby cookbook
3
5
  class SortedArray < Array
@@ -1,19 +1,35 @@
1
- require 'sisfc/request'
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
- def initialize
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