sisfc 0.0.1

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