sisfc 0.0.1

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.
@@ -0,0 +1,205 @@
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'
7
+
8
+
9
+ module SISFC
10
+ class Simulation
11
+
12
+ attr_reader :start_time
13
+
14
+
15
+ def initialize(opts = {})
16
+ @configuration = opts[:configuration]
17
+ @evaluator = opts[:evaluator]
18
+ end
19
+
20
+
21
+ def new_event(type, data, time, destination)
22
+ e = Event.new(type, data, time, destination)
23
+ @event_queue << e
24
+ end
25
+
26
+
27
+ def now
28
+ @current_time
29
+ end
30
+
31
+
32
+ def evaluate_allocation(vm_allocation)
33
+ # setup simulation start and current time
34
+ @current_time = @start_time = @configuration.start_time
35
+
36
+ # create data centers
37
+ data_centers = @configuration.data_centers.map {|k,v| DataCenter.new(k,v) }
38
+
39
+ # initialize statistics
40
+ stats = Statistics.new
41
+ dc_stats = data_centers.map {|k,v| Statistics.new }
42
+
43
+ # create VMs
44
+ @vms = []
45
+ vmid = 0
46
+ vm_allocation.each do |opts|
47
+ # setup service_time_distribution
48
+ opts[:service_time_distribution] = @configuration.service_component_types[opts[:component_type]][:service_time_distribution]
49
+
50
+ # allocate the VMs
51
+ opts[:vm_num].times do
52
+ # create VM ...
53
+ vm = VM.new(vmid, opts) # vm = VM.new(vmid, opts.except(:vm_num))
54
+ # ... add it to the vm list ...
55
+ @vms << vm
56
+ # ... and register it in the corresponding data center
57
+ data_centers[opts[:dc_id]-1].add_vm(vm, opts[:component_type])
58
+ # update vm id
59
+ vmid += 1
60
+ end
61
+ end
62
+
63
+ # create event queue
64
+ @event_queue = SortedArray.new
65
+
66
+ # puts "========== Simulation Start =========="
67
+
68
+ # generate first request
69
+ 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)
72
+
73
+ # schedule end of simulation
74
+ unless @configuration.end_time.nil?
75
+ # puts "Simulation ends at: #{@configuration.end_time}"
76
+ new_event(Event::ET_END_OF_SIMULATION, nil, @configuration.end_time, nil)
77
+ end
78
+
79
+ # calculate warmup threshold
80
+ warmup_threshold = @configuration.start_time + @configuration.warmup_duration.to_i
81
+
82
+ requests_being_worked_on = 0
83
+ events = 0
84
+
85
+ # launch simulation
86
+ until @event_queue.empty?
87
+ e = @event_queue.shift
88
+
89
+ events += 1
90
+ # sanity check on simulation time flow
91
+ if @current_time > e.time
92
+ raise "Error: simulation time inconsistency for event #{events} " +
93
+ "e.type=#{e.type} @current_time=#{@current_time}, e.time=#{e.time}"
94
+ end
95
+
96
+ @current_time = e.time
97
+
98
+ case e.type
99
+ when Event::ET_REQUEST_ARRIVAL
100
+ # get request
101
+ req = e.data
102
+
103
+ # find data center
104
+ data_center = data_centers[req.data_center_id-1]
105
+
106
+ # find next component name
107
+ workflow = @configuration.workflow_types[req.workflow_type_id]
108
+ next_component_name = workflow[:component_sequence][req.next_step][:name]
109
+
110
+ # get random vm providing next service component type
111
+ vm = data_center.get_random_vm(next_component_name)
112
+
113
+ # forward request to the vm
114
+ vm.new_request(self, req, e.time)
115
+
116
+ # update stats
117
+ if req.arrival_time > warmup_threshold
118
+ # increase the number of requests being worked on
119
+ requests_being_worked_on += 1
120
+ end
121
+
122
+ # generate next request
123
+ new_req = rg.generate
124
+ new_event(Event::ET_REQUEST_ARRIVAL, new_req, new_req.arrival_time, nil)
125
+
126
+
127
+ # Leave these events for when we add VM migration support
128
+ # when Event::ET_VM_SUSPEND
129
+ # when Event::ET_VM_RESUME
130
+
131
+
132
+ when Event::ET_WORKFLOW_STEP_COMPLETED
133
+ # retrieve request and vm
134
+ req = e.data
135
+ vm = e.destination
136
+
137
+ # tell the old vm that it can start processing another request
138
+ vm.request_finished(self, e.time)
139
+
140
+ # find data center and workflow
141
+ data_center = data_centers[req.data_center_id-1]
142
+ workflow = @configuration.workflow_types[req.workflow_type_id]
143
+
144
+ # check if there are other steps left to complete the workflow
145
+ if req.next_step < workflow[:component_sequence].size
146
+ # find next component name
147
+ next_component_name = workflow[:component_sequence][req.next_step][:name]
148
+
149
+ # get random vm providing next service component type
150
+ new_vm = data_center.get_random_vm(next_component_name)
151
+
152
+ # forward request to the new vm
153
+ new_vm.new_request(self, req, e.time)
154
+ else # workflow is finished
155
+ # schedule request closure
156
+ new_event(Event::ET_REQUEST_CLOSURE, req, e.time + req.communication_latency, nil)
157
+ end
158
+
159
+
160
+ when Event::ET_REQUEST_CLOSURE
161
+ # retrieve request and vm
162
+ req = e.data
163
+
164
+ # request is closed
165
+ req.finished_processing(e.time)
166
+
167
+ # update stats
168
+ if req.arrival_time > warmup_threshold
169
+ # decrease the number of requests being worked on
170
+ requests_being_worked_on -= 1
171
+
172
+ # collect request statistics
173
+ stats.record_request(req)
174
+ dc_stats[req.data_center_id - 1].record_request(req)
175
+ end
176
+
177
+
178
+ when Event::ET_END_OF_SIMULATION
179
+ # puts "#{e.time}: end simulation"
180
+ break
181
+
182
+ end
183
+ end
184
+
185
+ # puts "========== Simulation Finished =========="
186
+
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)
196
+ 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
202
+ end
203
+
204
+ end
205
+ end
@@ -0,0 +1,57 @@
1
+ module SISFC
2
+ # the SortedArray class was taken from the ruby cookbook
3
+ class SortedArray < Array
4
+ def initialize(*args, &sort_by)
5
+ @sort_by = sort_by || Proc.new { |x,y| x <=> y }
6
+ super(*args)
7
+ sort! &sort_by
8
+ end
9
+
10
+ def insert(i, v)
11
+ if size == 0 or v < self[0]
12
+ super(0, v)
13
+ elsif v > self[-1]
14
+ super(-1, v)
15
+ else
16
+ left = 0
17
+ right = size - 1
18
+ middle = (left + right)/2
19
+ while left < right
20
+ if v >= self[middle]
21
+ left = middle + 1
22
+ else
23
+ right = middle
24
+ end
25
+ middle = (left + right)/2
26
+ end
27
+ super(middle, v)
28
+ end
29
+ end
30
+
31
+ def <<(el)
32
+ insert(0, el)
33
+ end
34
+
35
+ alias push <<
36
+ alias unshift <<
37
+
38
+ # some methods, like collect!, can modify the items in an array,
39
+ # taking them out of sort order. we need to redefine those
40
+ # methods.
41
+ # we can't use define_method to define these methods because in
42
+ # Ruby 1.8 you can't use define_method to create a method that
43
+ # takes a block argument.
44
+ [ "collect!", "flatten!", "[]=" ].each do |method_name|
45
+ class_eval %{
46
+ def #{method_name}(*args)
47
+ super
48
+ sort! &@sort_by
49
+ end
50
+ }
51
+ end
52
+
53
+ def reverse!
54
+ # do nothing: reversing the array would disorder it.
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ require 'sisfc/request'
2
+
3
+ module SISFC
4
+ class Statistics
5
+ attr_reader :mean, :n
6
+
7
+ # see http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
8
+ def initialize
9
+ @n = 0 # number of requests
10
+ @mean = 0.0
11
+ @m_2 = 0.0
12
+ end
13
+
14
+ def record_request(req)
15
+ # get new sample
16
+ x = req.ttr
17
+
18
+ # update counters
19
+ @n += 1
20
+ delta = x - @mean
21
+ @mean += delta / @n
22
+ @m_2 += delta * (x - @mean)
23
+ end
24
+
25
+ def variance
26
+ @m_2 / (@n - 1)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # taken from Jim Freeze's excellent article "Creating DSLs with Ruby"
2
+ # http://www.artima.com/rubycs/articles/ruby_as_dsl.html
3
+
4
+ class Module
5
+ def dsl_accessor(*symbols)
6
+ symbols.each { |sym|
7
+ class_eval %{
8
+ def #{sym}(*val)
9
+ if val.empty?
10
+ @#{sym}
11
+ else
12
+ @#{sym} = val.size == 1 ? val[0] : val
13
+ end
14
+ end
15
+ }
16
+ }
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module SISFC
2
+ VERSION = '0.0.1'
3
+ end
data/lib/sisfc/vm.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'sisfc/event'
2
+ require 'erv'
3
+
4
+
5
+ module SISFC
6
+
7
+ class VM
8
+
9
+ # setup readable/accessible attributes
10
+ ATTRIBUTES = [ :vmid, :dc_id, :size ]
11
+
12
+ attr_reader *ATTRIBUTES
13
+
14
+ def initialize(vmid, opts)
15
+ # make sure all the required opts were provided
16
+ [ :dc_id, :vm_size, :service_time_distribution ].each do |k|
17
+ raise ArgumentError, "opts[:#{k}] missing" unless opts.has_key? k
18
+ end
19
+
20
+ @vmid = vmid
21
+ @dcid = opts[:dc_id]
22
+ @size = opts[:vm_size]
23
+ @service_times_rv = ERV::RandomVariable.new(opts[:service_time_distribution][@size])
24
+
25
+ # initialize request queue and related tracking information
26
+ @busy = false
27
+ @request_queue = []
28
+
29
+ # @request_queue_info = []
30
+ # @request_currently_servicing = nil
31
+ end
32
+
33
+ def new_request(sim, r, time)
34
+ # put the (request, service time, arrival time) tuple at the end of the queue
35
+ @request_queue << [ r, @service_times_rv.next, time ]
36
+
37
+ # update queue size tracking information
38
+ # @request_queue_info << { :size => @request_queue.size, :time => time }
39
+
40
+ unless @busy
41
+ # try to allocate operator
42
+ try_servicing_new_request(sim, time)
43
+ end
44
+ end
45
+
46
+ def request_finished(sim, time)
47
+ @busy = false
48
+ try_servicing_new_request(sim, time)
49
+ end
50
+
51
+ # def suspend(time)
52
+ # raise "VM is already suspended!" if @suspended
53
+ # @request_currently_servicing.finishing_time =
54
+ # @request_currently_servicing.status = Request::STATE_WORKING
55
+ # end
56
+
57
+ # def resume(time)
58
+ # raise "VM is already working!" unless @suspended
59
+ # end
60
+
61
+ private
62
+
63
+ def try_servicing_new_request(sim, time)
64
+ raise "Busy VM (vmid: #{@vmid})!" if @busy
65
+
66
+ unless @request_queue.empty?
67
+ # pick request and metadata from the incoming request queue
68
+ req, service_time, arrival_time = @request_queue.shift
69
+
70
+ # update the request's queuing information
71
+ req.queuing_completed(arrival_time, time - arrival_time)
72
+
73
+ # the VM is busy now
74
+ @busy = true
75
+
76
+ # update the request's working information
77
+ req.step_completed(time, time + service_time)
78
+
79
+ # schedule completion of workflow step
80
+ sim.new_event(Event::ET_WORKFLOW_STEP_COMPLETED, req, time + service_time, self)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ end
data/lib/sisfc.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'active_support/time'
2
+
3
+ require 'sisfc/configuration'
4
+ require 'sisfc/simulation'
data/sisfc.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sisfc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sisfc'
8
+ spec.version = SISFC::VERSION
9
+ spec.authors = ['Mauro Tortonesi']
10
+ spec.email = ['mauro.tortonesi@unife.it']
11
+ spec.description = %q{Simulator for IT Services in Federated Clouds}
12
+ spec.summary = %q{A simulator for business-driven IT management research capable of evaluating IT service component placement in federated Cloud environments}
13
+ spec.homepage = 'https://github.com/mtortonesi/sisfc'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/).reject{|x| x == '.gitignore' }
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activesupport', '~> 4.0.0'
22
+ spec.add_dependency 'awesome_print', '~> 1.2.0'
23
+ spec.add_dependency 'erv', '~> 0.0.2'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.5.3'
26
+ spec.add_development_dependency 'rake', '~> 10.1.1'
27
+ end
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ require 'sisfc/reference_configuration'
4
+
5
+
6
+ describe SISFC::Configuration do
7
+
8
+ describe 'simulation-related parameters' do
9
+
10
+ it 'should correctly load simulation start' do
11
+ with_reference_config do |conf|
12
+ conf.start_time.must_equal START_TIME
13
+ end
14
+ end
15
+
16
+ it 'should correctly load simulation duration' do
17
+ with_reference_config do |conf|
18
+ conf.duration.must_equal DURATION
19
+ end
20
+ end
21
+
22
+ it 'should correctly load simulation end time' do
23
+ with_reference_config do |conf|
24
+ conf.end_time.must_equal START_TIME + DURATION
25
+ end
26
+ end
27
+
28
+ it 'should correctly load warmup phase duration' do
29
+ with_reference_config do |conf|
30
+ conf.warmup_duration.must_equal WARMUP_DURATION
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+
37
+ describe 'service_component_types' do
38
+
39
+ it 'should have 3 items' do
40
+ with_reference_config do |conf|
41
+ conf.service_component_types.size.must_equal 3
42
+ end
43
+ end
44
+
45
+ it 'should define a Web Server' do
46
+ with_reference_config do |conf|
47
+ conf.service_component_types.keys.must_include('Web Server')
48
+ end
49
+ end
50
+
51
+ it 'should define an App Server' do
52
+ with_reference_config do |conf|
53
+ conf.service_component_types.keys.must_include('App Server')
54
+ end
55
+ end
56
+
57
+ it 'should define a Financial Transaction Server' do
58
+ with_reference_config do |conf|
59
+ conf.service_component_types.keys.must_include('Financial Transaction Server')
60
+ end
61
+ end
62
+
63
+ describe 'the Web Server' do
64
+ it 'should work on medium or large VMs' do
65
+ with_reference_config do |conf|
66
+ item_conf = conf.service_component_types['Web Server']
67
+ item_conf[:allowed_vm_types].must_include(:medium)
68
+ item_conf[:allowed_vm_types].must_include(:large)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'the App Server' do
74
+ it 'should work on large and huge VMs' do
75
+ with_reference_config do |conf|
76
+ item_conf = conf.service_component_types['App Server']
77
+ item_conf[:allowed_vm_types].must_include(:large)
78
+ item_conf[:allowed_vm_types].must_include(:huge)
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+
86
+ describe 'data_centers' do
87
+ it 'should have 5 items' do
88
+ with_reference_config do |conf|
89
+ conf.data_centers.size.must_equal 5
90
+ end
91
+ end
92
+ end
93
+
94
+ end
@@ -0,0 +1,63 @@
1
+ require 'tempfile'
2
+
3
+ require 'test_helper'
4
+ require 'sisfc/generator'
5
+
6
+
7
+ describe SISFC::RequestGenerator do
8
+
9
+ GENERATION_TIMES = [ Time.now, Time.now + 1.second, Time.now + 2.seconds ].map(&:to_f)
10
+ DATA_CENTER_IDS = (1..GENERATION_TIMES.size).to_a
11
+ ARRIVAL_TIMES = GENERATION_TIMES.map {|x| x + 1.0 }
12
+ WORKFLOW_IDS = GENERATION_TIMES.map { rand(10) }
13
+ REQUEST_GENERATION_DATA=<<-END
14
+ Generation Time,Data Center ID,Arrival Time,Workflow ID
15
+ #{GENERATION_TIMES[0]},#{DATA_CENTER_IDS[0]},#{ARRIVAL_TIMES[0]},#{WORKFLOW_IDS[0]}
16
+ #{GENERATION_TIMES[1]},#{DATA_CENTER_IDS[1]},#{ARRIVAL_TIMES[1]},#{WORKFLOW_IDS[1]}
17
+ #{GENERATION_TIMES[2]},#{DATA_CENTER_IDS[2]},#{ARRIVAL_TIMES[2]},#{WORKFLOW_IDS[2]}
18
+ END
19
+
20
+ it 'should read from CSV file' do
21
+ begin
22
+ # create temporary file with request generation information
23
+ tf = Tempfile.new('generator_test')
24
+ tf.write(REQUEST_GENERATION_DATA)
25
+ tf.close
26
+
27
+ with_reference_config(request_generation: { filename: tf.path }) do |conf|
28
+ rg = SISFC::RequestGenerator.new(conf.request_generation)
29
+ r = rg.generate
30
+ r.rid.must_equal 1
31
+ r.generation_time.must_equal GENERATION_TIMES[0]
32
+ r.data_center_id.must_equal DATA_CENTER_IDS[0]
33
+ r.arrival_time.must_equal ARRIVAL_TIMES[0]
34
+ end
35
+
36
+ ensure
37
+ # delete temporary file
38
+ tf.delete
39
+ end
40
+ end
41
+
42
+ it 'should read from command' do
43
+ begin
44
+ # create temporary file with request generation information
45
+ tf = Tempfile.new('generator_test')
46
+ tf.write(REQUEST_GENERATION_DATA)
47
+ tf.close
48
+
49
+ with_reference_config(request_generation: { command: "cat #{tf.path}" }) do |conf|
50
+ rg = SISFC::RequestGenerator.new(conf.request_generation)
51
+ r = rg.generate
52
+ r.rid.must_equal 1
53
+ r.generation_time.must_equal GENERATION_TIMES[0]
54
+ r.data_center_id.must_equal DATA_CENTER_IDS[0]
55
+ r.arrival_time.must_equal ARRIVAL_TIMES[0]
56
+ end
57
+ ensure
58
+ # delete temporary file
59
+ tf.delete
60
+ end
61
+ end
62
+
63
+ end