sisfc 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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/data_center.rb
CHANGED
@@ -1,57 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
1
5
|
module SISFC
|
2
6
|
|
3
7
|
class DataCenter
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegator :@vms, :has_key?, :has_vms_of_type?
|
11
|
+
|
12
|
+
attr_reader :dcid, :location_id
|
4
13
|
|
5
|
-
def initialize(id
|
6
|
-
@dcid
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
14
|
+
def initialize(id:, location_id:, name:, type:, **opts)
|
15
|
+
@dcid = id
|
16
|
+
@location_id = location_id
|
17
|
+
@vms = {}
|
18
|
+
@vm_type_count = {}
|
19
|
+
@name = name
|
20
|
+
@type = type
|
21
|
+
raise ArgumentError, "Unsupported type!" unless [ :private, :public ].include?(@type)
|
22
|
+
@availability_check_proc = opts[:maximum_vm_capacity]
|
10
23
|
end
|
11
24
|
|
25
|
+
# returns false in case no more VMs can be allocated
|
12
26
|
def add_vm(vm, component_name)
|
13
27
|
@vms[component_name] ||= []
|
28
|
+
@vm_type_count[vm.size] ||= 0
|
29
|
+
|
14
30
|
raise 'Error! VM is already present!' if @vms[component_name].include? vm
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
31
|
+
|
32
|
+
# defer availablility check to user specified procedure
|
33
|
+
if @availability_check_proc
|
34
|
+
return false unless @availability_check_proc.call(@vm_type_count)
|
19
35
|
end
|
36
|
+
|
37
|
+
# allocate VM
|
20
38
|
@vms[component_name] << vm
|
39
|
+
@vm_type_count[vm.size] += 1
|
21
40
|
end
|
22
41
|
|
23
42
|
def remove_vm(vm, component_name)
|
24
|
-
|
25
|
-
raise
|
26
|
-
|
27
|
-
|
28
|
-
raise 'Error! VM not present!'
|
43
|
+
if @vms.has_key? component_name and @vms[component_name].include? vm
|
44
|
+
raise 'Error! Inconsistent number of VMs!' unless @vm_type_count[vm.size] >= 1
|
45
|
+
@vm_type_count[vm.size] += 1
|
46
|
+
@vms.delete(vm)
|
29
47
|
end
|
30
|
-
@available[vm.size] += 1
|
31
|
-
@vms.delete(vm)
|
32
48
|
end
|
33
49
|
|
50
|
+
# returns nil in case no VM for component component_name is running
|
34
51
|
def get_random_vm(component_name)
|
35
|
-
|
36
|
-
|
52
|
+
if @vms.has_key? component_name
|
53
|
+
@vms[component_name].sample
|
37
54
|
end
|
38
|
-
@vms[component_name].sample
|
39
55
|
end
|
40
56
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
@vms[component_name].size
|
57
|
+
def to_s
|
58
|
+
"Data center #{@dcid}, with VMs:" +
|
59
|
+
@vms.inject("") {|s,(k,v)| s += " (#{k}: #{v.size})" }
|
46
60
|
end
|
47
61
|
|
48
|
-
def
|
49
|
-
@
|
62
|
+
def private?
|
63
|
+
@type == :private
|
50
64
|
end
|
51
65
|
|
52
|
-
def
|
53
|
-
|
54
|
-
@vms.inject("") {|s,(k,v)| s += " (#{k}: #{v.size})" }
|
66
|
+
def public?
|
67
|
+
@type == :public
|
55
68
|
end
|
56
69
|
|
57
70
|
end
|
data/lib/sisfc/evaluation.rb
CHANGED
@@ -1,35 +1,43 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './logger'
|
2
4
|
|
3
5
|
module SISFC
|
4
6
|
class Evaluator
|
7
|
+
|
8
|
+
include Logging
|
9
|
+
|
5
10
|
def initialize(conf)
|
6
11
|
@vm_hourly_cost = conf.evaluation[:vm_hourly_cost]
|
7
12
|
raise ArgumentError, 'No VM hourly costs provided!' unless @vm_hourly_cost
|
8
13
|
|
14
|
+
@fixed_hourly_cost = conf.evaluation[:fixed_hourly_cost] || {}
|
15
|
+
|
9
16
|
@penalties_func = conf.evaluation[:penalties]
|
10
17
|
end
|
11
18
|
|
12
|
-
def evaluate_business_impact(
|
13
|
-
|
19
|
+
def evaluate_business_impact(all_kpis, per_workflow_and_customer_kpis,
|
20
|
+
vm_allocation)
|
21
|
+
# evaluate variable hourly costs related to VM allocation
|
14
22
|
cost = vm_allocation.inject(0.0) do |s,x|
|
15
23
|
hc = @vm_hourly_cost.find{|i| i[:data_center] == x[:dc_id] and i[:vm_type] == x[:vm_size] }
|
16
|
-
unless hc
|
17
|
-
raise "Cannot find hourly cost for data center #{x[:dc_id]} and VM size #{x[:vm_size]}!"
|
18
|
-
end
|
24
|
+
raise "Cannot find hourly cost for data center #{x[:dc_id]} and VM size #{x[:vm_size]}!" unless hc
|
19
25
|
s += x[:vm_num] * hc[:cost]
|
20
26
|
end
|
27
|
+
|
28
|
+
# evaluate fixed hourly costs (for private Cloud data centers)
|
29
|
+
@fixed_hourly_cost.values.each do |fixed_cost|
|
30
|
+
cost += fixed_cost
|
31
|
+
end
|
32
|
+
|
33
|
+
# calculate daily cost
|
21
34
|
cost *= 24.0
|
22
|
-
kpis[:cost] = cost
|
23
|
-
# puts "vm allocation cost: #{cost}"
|
24
35
|
|
25
|
-
# consider SLO
|
26
|
-
penalties = (@penalties_func.nil? ? 0.0 : (@penalties_func.call(
|
27
|
-
kpis[:penalties] = penalties
|
28
|
-
# puts "slo penalties cost: #{penalties}"
|
29
|
-
cost += penalties
|
36
|
+
# consider SLO violation penalties
|
37
|
+
penalties = (@penalties_func.nil? ? 0.0 : (@penalties_func.call(all_kpis, per_workflow_and_customer_kpis) or 0.0))
|
30
38
|
|
31
|
-
|
32
|
-
|
39
|
+
{ it_cost: cost,
|
40
|
+
penalties: penalties }
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|
data/lib/sisfc/event.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SISFC
|
2
4
|
|
3
5
|
class Event
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
ET_REQUEST_GENERATION = 0
|
8
|
+
ET_REQUEST_ARRIVAL = 1
|
9
|
+
ET_REQUEST_FORWARDING = 2
|
10
|
+
ET_WORKFLOW_STEP_COMPLETED = 3
|
11
|
+
ET_REQUEST_CLOSURE = 4
|
12
|
+
# ET_VM_SUSPEND = 5
|
13
|
+
# ET_VM_RESUME = 6
|
11
14
|
ET_END_OF_SIMULATION = 100
|
12
15
|
|
13
16
|
# let the comparable mixin provide the < and > operators for us
|
data/lib/sisfc/generator.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
|
-
|
2
|
-
require 'sisfc/request'
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
3
|
module SISFC
|
6
4
|
|
7
5
|
class RequestGenerator
|
8
6
|
|
9
|
-
def initialize(opts)
|
7
|
+
def initialize(opts={})
|
10
8
|
if opts.has_key? :filename
|
11
9
|
filename = opts[:filename]
|
12
|
-
raise "File #{filename} does not exist!" unless File.exists?(filename)
|
10
|
+
raise ArgumentError, "File #{filename} does not exist!" unless File.exists?(filename)
|
13
11
|
@file = File.open(filename, mode='r')
|
14
12
|
elsif opts.has_key? :command
|
15
13
|
command = opts[:command]
|
16
14
|
@file = IO.popen(command)
|
17
15
|
else
|
18
|
-
raise ArgumentError,
|
16
|
+
raise ArgumentError, "Need to provide either a filename or a command!"
|
19
17
|
end
|
20
18
|
|
21
19
|
# throw away the first line (containing the CSV headers)
|
@@ -34,26 +32,21 @@ module SISFC
|
|
34
32
|
raise "End of input reached while reading request #{@next_rid}!" unless line
|
35
33
|
|
36
34
|
# parse data
|
37
|
-
tokens = line.
|
35
|
+
tokens = line.split(",") # should be faster than CSV parsing
|
38
36
|
generation_time = tokens[0].to_f
|
39
|
-
|
40
|
-
|
41
|
-
workflow_type_id = tokens[3].to_i
|
37
|
+
workflow_type_id = tokens[1].to_i
|
38
|
+
customer_id = tokens[2].to_i
|
42
39
|
|
43
40
|
# increase @next_rid
|
44
41
|
@next_rid += 1
|
45
42
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
generation_time,
|
54
|
-
data_center_id,
|
55
|
-
arrival_time,
|
56
|
-
workflow_type_id)
|
43
|
+
# return request
|
44
|
+
{
|
45
|
+
rid: @next_rid,
|
46
|
+
generation_time: generation_time,
|
47
|
+
workflow_type_id: workflow_type_id,
|
48
|
+
customer_id: customer_id,
|
49
|
+
}
|
57
50
|
end
|
58
51
|
|
59
52
|
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erv'
|
4
|
+
|
5
|
+
module SISFC
|
6
|
+
class LatencyManager
|
7
|
+
def initialize(latency_models)
|
8
|
+
# here we build a (strictly upper triangular) matrix of random variables
|
9
|
+
# that represent the communication latency models between the different
|
10
|
+
# locations
|
11
|
+
@latency_models_matrix = latency_models.map do |lms_conf|
|
12
|
+
lms_conf.map do |rv_conf|
|
13
|
+
rv_conf
|
14
|
+
ERV::RandomVariable.new(rv_conf)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# should we turn @latency_models_matrix from a (strictly upper)
|
19
|
+
# triangular to a full matrix, for convenience? probably not. it would
|
20
|
+
# require more memory and: 1) ruby is ***very*** memory hungry already,
|
21
|
+
# 2) ruby's performance is very sensitive to memory usage.
|
22
|
+
|
23
|
+
# precalculate average latencies
|
24
|
+
@average_latency_matrix = @latency_models_matrix.map do |lms|
|
25
|
+
lms.map{|x| x.mean }
|
26
|
+
end
|
27
|
+
|
28
|
+
# latency in the same location is implemented as a truncated gaussian
|
29
|
+
# with mean = 20ms, sd = 5ms, and a = 2ms
|
30
|
+
@same_location_latency = ERV::RandomVariable.new(distribution: :gaussian, args: { mean: 20E-3, sd: 5E-3 })
|
31
|
+
end
|
32
|
+
|
33
|
+
def sample_latency_between(loc1, loc2)
|
34
|
+
if loc1 == loc2
|
35
|
+
# rejection sampling to implement (crudely) PDF truncation
|
36
|
+
while (lat = @same_location_latency.next) < 2E-3; end
|
37
|
+
lat
|
38
|
+
else
|
39
|
+
l1, l2 = loc1 < loc2 ? [ loc1, loc2 ] : [ loc2, loc1 ]
|
40
|
+
|
41
|
+
# since we use a compact representation for @latency_models_matrix, the
|
42
|
+
# indexes become l1 and (l2-l1-1)
|
43
|
+
# rejection sampling to implement (crudely) PDF truncation to positive numbers
|
44
|
+
while (lat = @latency_models_matrix[l1][l2-l1-1].next) <= 0.0; end
|
45
|
+
lat
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def average_latency_between(loc1, loc2)
|
50
|
+
# the results returned by this method are not entirely accurate, because
|
51
|
+
# rejection sampling changes the shape of the PDF. see, e.g.,
|
52
|
+
# https://stackoverflow.com/questions/47933019/how-to-properly-sample-truncated-distributions
|
53
|
+
# still, it is an acceptable approximation
|
54
|
+
if loc1 == loc2
|
55
|
+
@same_location_latency.mean
|
56
|
+
else
|
57
|
+
l1, l2 = loc1 < loc2 ? [ loc1, loc2 ] : [ loc2, loc1 ]
|
58
|
+
|
59
|
+
# since we use a compact representation for @average_latency_between, the
|
60
|
+
# indexes become l1 and (l2-l1-1)
|
61
|
+
@average_latency_between[l1][l2-l1-1]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/sisfc/logger.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
|
6
|
+
module SISFC
|
7
|
+
module Logging
|
8
|
+
class << self
|
9
|
+
def logger
|
10
|
+
@logger ||= ::Logger.new(STDERR).tap{|l| l.level = ::Logger::INFO }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
class << base
|
16
|
+
# this version of the logger method will be called from class methods
|
17
|
+
def logger
|
18
|
+
Logging.logger
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# this version of the logger method will be called from instance methods
|
24
|
+
def logger
|
25
|
+
Logging.logger
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/sisfc/request.rb
CHANGED
@@ -1,101 +1,65 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module SISFC
|
3
4
|
class Request
|
4
5
|
|
5
|
-
# states
|
6
|
-
STATE_WORKING = 1
|
7
|
-
STATE_SUSPENDED = 2
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
# # states
|
7
|
+
# STATE_WORKING = 1
|
8
|
+
# STATE_SUSPENDED = 2
|
9
|
+
|
10
|
+
attr_reader :rid,
|
11
|
+
:arrival_time,
|
12
|
+
:closure_time,
|
13
|
+
# :communication_latency,
|
14
|
+
:customer_id,
|
15
|
+
:generation_time,
|
16
|
+
:next_step,
|
17
|
+
# :status,
|
18
|
+
:workflow_type_id
|
19
|
+
|
20
|
+
# the data_center_id attribute is updated as requests move from a Cloud
|
21
|
+
# data center to another
|
22
|
+
attr_accessor :data_center_id
|
23
|
+
|
24
|
+
def initialize(rid:,
|
25
|
+
generation_time:,
|
26
|
+
initial_data_center_id:,
|
27
|
+
arrival_time:,
|
28
|
+
workflow_type_id:,
|
29
|
+
customer_id:)
|
14
30
|
@rid = rid
|
15
31
|
@generation_time = generation_time
|
16
|
-
@data_center_id =
|
32
|
+
@data_center_id = initial_data_center_id
|
17
33
|
@arrival_time = arrival_time
|
18
34
|
@workflow_type_id = workflow_type_id
|
35
|
+
@customer_id = customer_id
|
19
36
|
|
20
|
-
# steps
|
21
|
-
@
|
37
|
+
# steps start counting from zero
|
38
|
+
@next_step = 0
|
22
39
|
|
23
40
|
# calculate communication latency
|
24
41
|
@communication_latency = @arrival_time - @generation_time
|
25
42
|
|
26
|
-
|
27
|
-
@
|
28
|
-
{
|
29
|
-
:type => :communication,
|
30
|
-
:at => @generation_time,
|
31
|
-
:duration => @communication_latency,
|
32
|
-
}
|
33
|
-
]
|
34
|
-
|
35
|
-
# NOTE: the format for each element of the @tracking_information array is:
|
36
|
-
# { :type => one of [ :queue, :work, :communication ]
|
37
|
-
# :at => begin time
|
38
|
-
# :duration => duration
|
39
|
-
# :vm => vm (optional)
|
40
|
-
# }
|
41
|
-
end
|
42
|
-
|
43
|
-
def queuing_completed(start, duration)
|
44
|
-
@tracking_information << {
|
45
|
-
:type => :queue,
|
46
|
-
:at => start,
|
47
|
-
:duration => duration,
|
48
|
-
# :vm =>
|
49
|
-
}
|
43
|
+
@queuing_time = 0.0
|
44
|
+
@working_time = 0.0
|
50
45
|
end
|
51
46
|
|
52
|
-
def
|
53
|
-
@
|
54
|
-
:type => :work,
|
55
|
-
:at => start,
|
56
|
-
:duration => duration,
|
57
|
-
# :vm =>
|
58
|
-
}
|
59
|
-
@step += 1
|
47
|
+
def update_queuing_time(duration)
|
48
|
+
@queuing_time += duration
|
60
49
|
end
|
61
50
|
|
62
|
-
def
|
63
|
-
|
64
|
-
@tracking_information << {
|
65
|
-
:type => :communication,
|
66
|
-
:at => time,
|
67
|
-
:duration => @communication_latency,
|
68
|
-
}
|
69
|
-
# save closure time
|
70
|
-
@closure_time = time + @communication_latency
|
51
|
+
def update_transfer_time(duration)
|
52
|
+
@communication_latency += duration
|
71
53
|
end
|
72
54
|
|
73
|
-
def
|
74
|
-
@
|
55
|
+
def step_completed(duration)
|
56
|
+
@working_time += duration
|
57
|
+
@next_step += 1
|
75
58
|
end
|
76
59
|
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
else
|
81
|
-
@tracking_information.select{|el| el.type == type }
|
82
|
-
end
|
83
|
-
|
84
|
-
selected_ti.each do |ti|
|
85
|
-
yield ti
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
def total_communication_time
|
90
|
-
calculate_time(:communication)
|
91
|
-
end
|
92
|
-
|
93
|
-
def total_work_time
|
94
|
-
calculate_time(:work)
|
95
|
-
end
|
96
|
-
|
97
|
-
def total_queue_time
|
98
|
-
calculate_time(:queue)
|
60
|
+
def finished_processing(time)
|
61
|
+
# save closure time
|
62
|
+
@closure_time = time
|
99
63
|
end
|
100
64
|
|
101
65
|
def closed?
|
@@ -110,13 +74,6 @@ module SISFC
|
|
110
74
|
def to_s
|
111
75
|
"rid: #{@rid}, generation_time: #{@generation_time}, data_center_id: #{@data_center_id}, arrival_time: #{@arrival_time}"
|
112
76
|
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def calculate_time(type)
|
117
|
-
return 0 unless @tracking_information
|
118
|
-
@tracking_information.inject(0) {|sum,x| sum += ((type == :all || type == x[:type]) ? x[:duration].to_i : 0) }
|
119
|
-
end
|
120
77
|
end
|
121
78
|
|
122
79
|
end
|