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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4db7cb93e8cfd8e52ef91b888359f61d513761a0
4
+ data.tar.gz: 3370237ff41037080a11ce54c4efb04510d24cab
5
+ SHA512:
6
+ metadata.gz: 153d84d057e7011351998a401f44139827b72edb6051d274b8ab09bd9aaefc2a61083e26807073bd0e0a9ccfa5b53f5f4f66d654d0415cfa4743ebb2a84d0e90
7
+ data.tar.gz: f59a60e8c490cca293ef9836fbc990ce56663d55c681f9ecc32017ace690947a6d9ecc06728e5f1fd004762f9c87afeb050a02b790cec82079cc1ae1af244160
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sisfc.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Mauro Tortonesi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # A Simulator for IT Service in Federated Clouds (SISFC)
2
+
3
+ SISFC is a simulator designed to reenact the behaviour of IT services in
4
+ federated Cloud environments.
5
+
6
+
7
+ ## References
8
+
9
+ This simulator (more precisely an earlier version of it) was used in the
10
+ following research papers:
11
+
12
+ 1. L. Foschini, M. Tortonesi, "Adaptive and Business-driven Service Placement
13
+ in Federated Cloud Computing Environments", in Proceedings of the 8th
14
+ IFIP/IEEE International Workshop on Business-driven IT Management (BDIM 2013),
15
+ 27 May 2013, Ghent, Belgium.
16
+
17
+ 2. G. Grabarnik, L. Shwartz, M. Tortonesi, "Business-Driven Optimization of
18
+ Component Placement for Complex Services in Federated Clouds", to appear in
19
+ Proceedings of the 14th IEEE/IFIP Network Operations and Management Symposium
20
+ (NOMS 2014) - Mini-conference track, 5-9 May 2014, Krakow, Poland.
21
+
22
+ Please, consider citing some of these papers if you find this simulator useful
23
+ for your research.
24
+
25
+
26
+ ## Installation
27
+
28
+ You can install the SISFC simulator through RubyGems:
29
+
30
+ gem install sisfc
31
+
32
+ While SISFC should work on MRI and Rubinius without problems, we highly
33
+ recommend you to run it on top of JRuby. Since JRuby is our reference
34
+ development platform, you will be very likely to have a smoother installation
35
+ and usage experience when deploying SISFC on top of JRuby.
36
+
37
+
38
+ ## Usage
39
+
40
+ To run the simulator, simply digit:
41
+
42
+ sisfc simulator.conf vm_allocation.conf
43
+
44
+ where simulator.conf and vm\_allocation.conf are your simulation environment
45
+ and vm allocation configuration files respectively.
46
+
47
+ The examples directory contains a set of example configuration files, including
48
+ an [R](http://www.r-project.org) script that models a stochastic request
49
+ generation process. To use that script, you will need R with the VGAM and
50
+ truncnorm packages installed.
51
+
52
+ Note that the SISFC was not designed to be run directly by users, but instead
53
+ to be integrated within automated tools that implement a continuous
54
+ optimization framework (for instance, built on top of our [mhl metaheuristics
55
+ library](https://github.com/mtortonesi/ruby-mhl)).
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'test'
7
+ t.test_files = Dir.glob('test/**/*_test.rb').sort
8
+ t.verbose = true
9
+ end
data/bin/sisfc ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # should we actually decomment these lines?
4
+ # require 'rubygems'
5
+ # require 'bundler/setup'
6
+
7
+ require 'awesome_print'
8
+
9
+ require 'sisfc'
10
+ require 'sisfc/evaluation'
11
+
12
+
13
+ if __FILE__ == $0
14
+ unless File.exists? ARGV[0] and File.exists? ARGV[1]
15
+ abort("Usage: #{File.basename(__FILE__)} simulator_config_file vm_allocation_config_file")
16
+ end
17
+
18
+ # load simulation configuration
19
+ conf = SISFC::Configuration.load_from_file(ARGV[0])
20
+
21
+ # load vm allocation
22
+ vm_allocation = eval(File.read(ARGV[1]))
23
+
24
+ # create a simulator and launch it
25
+ sim = SISFC::Simulation.new(configuration: conf,
26
+ evaluator: SISFC::Evaluator.new(conf))
27
+ res = sim.evaluate_allocation(vm_allocation)
28
+
29
+ # Print results
30
+ puts 'Result:'
31
+ ap(res, indent: 2)
32
+ end
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/Rscript
2
+
3
+ suppressMessages(library(VGAM))
4
+ library(truncnorm) # for truncated normal distribution
5
+
6
+ # request generation is modeled with a Pareto distribution
7
+ # these values were chosen to generate roughly 6666.667 requests per second
8
+ location <- 1.2E-4
9
+ shape <- 5
10
+ requests.per.second <- (shape - 1) / (shape * location)
11
+
12
+ # generate 10 minutes of requests
13
+ simulation.time <- 10 * 60
14
+
15
+ # number of requests
16
+ num.requests <- 1.2 * simulation.time * requests.per.second
17
+
18
+ # latency is modeled as a gaussian distribution
19
+ # these values were chosen to model roughly 100ms of communication latency
20
+ mu <- 1E-1 # mean latency is 100ms
21
+ sigma <- 2.5E-2 # standard deviation is 25ms
22
+ min.latency <- 2E-2 # minimum latency is 20ms
23
+
24
+ # number of data centers
25
+ num.data.centers <- 2
26
+
27
+ # request generation times
28
+ first.request.time <- as.POSIXct(as.Date("18/1/2013", "%d/%m/%Y"))
29
+ request.interarrival.times <- rpareto(num.requests, location, shape)
30
+ generation.times <- diffinv(request.interarrival.times, xi=first.request.time)
31
+
32
+ # data center ids
33
+ data.center.ids <- sample.int(num.data.centers, length(generation.times), replace=T)
34
+
35
+ # request arrival time
36
+ latencies <- rtruncnorm(length(generation.times), a=min.latency, b=Inf, mean=mu, sd=sigma)
37
+ arrival.times <- generation.times + latencies
38
+ workflow.type.ids <- rep(1, length(generation.times))
39
+
40
+ # prepare data frame and output it on the console
41
+ df <- data.frame(Generation.Time = generation.times,
42
+ Data.Center.ID = data.center.ids,
43
+ Arrival.Time = arrival.times,
44
+ Workflow.Type.ID = workflow.type.ids)
45
+ write.csv(df[order(df$Arrival.Time),], row.names=F)
@@ -0,0 +1,70 @@
1
+ start_time DateTime.civil(2013,1,18,0,0,0)
2
+ warmup_duration 10.seconds
3
+ duration 1.minute
4
+
5
+
6
+ data_centers \
7
+ 1 => {
8
+ :maximum_vm_capacity => {
9
+ :medium => 50_000,
10
+ :large => 50_000,
11
+ },
12
+ },
13
+ 2 => {
14
+ :maximum_vm_capacity => {
15
+ :medium => 50_000,
16
+ :large => 50_000,
17
+ },
18
+ }
19
+
20
+
21
+ service_component_types \
22
+ 'Web Server' => {
23
+ :allowed_vm_types => [ :medium ],
24
+ :service_time_distribution => {
25
+ :medium => { :distribution => :gaussian,
26
+ :mean => 0.009, # 1 request processed every 9ms
27
+ :sd => 0.001 },
28
+ },
29
+ :estimated_workload => 50,
30
+ },
31
+ 'App Server' => {
32
+ :allowed_vm_types => [ :large ],
33
+ :service_time_distribution => {
34
+ :large => { :distribution => :gaussian,
35
+ :mean => 0.012, # 1 request processed every 12ms
36
+ :sd => 0.002 },
37
+ },
38
+ :estimated_workload => 70,
39
+ }
40
+
41
+
42
+ # workflow (or job) types descriptions
43
+ workflow_types \
44
+ 1 => {
45
+ :component_sequence => [
46
+ { :name => 'Web Server' }, # no need for :type => dedicated / shared
47
+ { :name => 'App Server' },
48
+ ],
49
+ :next_component_selection => :random,
50
+ }
51
+
52
+
53
+ # input request source (the generator.R script)
54
+ request_generation \
55
+ :command => '<pwd>/generator.R'
56
+
57
+
58
+ # evaluation model
59
+ evaluation \
60
+ :vm_hourly_cost => [
61
+ { :data_center => 1, :vm_type => :medium, :cost => 0.160 },
62
+ { :data_center => 1, :vm_type => :large, :cost => 0.320 },
63
+ { :data_center => 2, :vm_type => :medium, :cost => 0.184 },
64
+ { :data_center => 2, :vm_type => :large, :cost => 0.368 }
65
+ ],
66
+ # 500$ penalties if MTTR takes more than 50 msecs
67
+ :penalties => lambda {|kpis,dc_kpis| 500.0 if kpis[:mttr] > 0.050 }
68
+
69
+
70
+ # vim: filetype=ruby
@@ -0,0 +1,8 @@
1
+ [
2
+ { dc_id: 1, vm_num: 7, vm_size: :medium, component_type: 'Web Server' },
3
+ { dc_id: 1, vm_num: 10, vm_size: :large, component_type: 'App Server' },
4
+ { dc_id: 2, vm_num: 15, vm_size: :medium, component_type: 'Web Server' },
5
+ { dc_id: 2, vm_num: 20, vm_size: :large, component_type: 'App Server' }
6
+ ]
7
+
8
+ # vim: filetype=ruby
@@ -0,0 +1,60 @@
1
+ require 'sisfc/support/dsl_helper'
2
+
3
+ module SISFC
4
+
5
+ module Configurable
6
+ dsl_accessor :start_time,
7
+ :duration,
8
+ :warmup_duration,
9
+ :request_generation,
10
+ :data_centers,
11
+ :service_component_types,
12
+ :evaluation,
13
+ :workflow_types
14
+ end
15
+
16
+ class Configuration
17
+ include Configurable
18
+
19
+ attr_accessor :filename
20
+
21
+ def initialize(filename)
22
+ @filename = filename
23
+ end
24
+
25
+ def end_time
26
+ @start_time + @duration
27
+ end
28
+
29
+ def validate
30
+ # convert datetimes and integers into floats
31
+ @start_time = @start_time.to_f
32
+ @duration = @duration.to_f
33
+ @warmup_duration = @warmup_duration.to_f
34
+
35
+ # TODO: might want to restrict this substitution only to the :filename
36
+ # and :command keys
37
+ @request_generation.each do |k,v|
38
+ v.gsub!('<pwd>', File.expand_path(File.dirname(@filename)))
39
+ end
40
+ end
41
+
42
+ def self.load_from_file(filename)
43
+ # allow filename, string, and IO objects as input
44
+ raise ArgumentError, "File #{filename} does not exist!" unless File.exists?(filename)
45
+
46
+ # create configuration object
47
+ conf = Configuration.new(filename)
48
+
49
+ # take the file content and pass it to instance_eval
50
+ conf.instance_eval(File.new(filename, 'r').read)
51
+
52
+ # validate and finalize configuration
53
+ conf.validate
54
+
55
+ # return new object
56
+ conf
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,59 @@
1
+ module SISFC
2
+
3
+ class DataCenter
4
+
5
+ def initialize(id, opts)
6
+ @dcid = id
7
+ @capacity = opts[:maximum_vm_capacity]
8
+ @available = @capacity.dup
9
+ @vms = {}
10
+ end
11
+
12
+ def add_vm(vm, component_name)
13
+ @vms[component_name] ||= []
14
+ raise 'Error! VM is already present!' if @vms[component_name].include? vm
15
+ if @available[vm.size] > 0
16
+ @available[vm.size] -= 1
17
+ else
18
+ raise 'Error! VM capacity exceeded!'
19
+ end
20
+ @vms[component_name] << vm
21
+ end
22
+
23
+ def remove_vm(vm, component_name)
24
+ unless @vms.has_key? component_name
25
+ raise "Error! Service component type #{component_name} not present in data center #{@dcid}!"
26
+ end
27
+ unless @vms[component_name].include? vm
28
+ raise 'Error! VM not present!'
29
+ end
30
+ @available[vm.size] += 1
31
+ @vms.delete(vm)
32
+ end
33
+
34
+ def get_random_vm(component_name)
35
+ unless @vms.has_key? component_name
36
+ raise "Error! Service component type #{component_name} not present in data center #{@dcid}!"
37
+ end
38
+ @vms[component_name].sample
39
+ end
40
+
41
+ def get_number_of_vms(component_name)
42
+ unless @vms.has_key? component_name
43
+ raise "Error! Service component type #{component_name} not present in data center #{@dcid}!"
44
+ end
45
+ @vms[component_name].size
46
+ end
47
+
48
+ def available(size)
49
+ @available[size]
50
+ end
51
+
52
+ def to_s
53
+ "Data center #{@dcid}, with VMs:" +
54
+ @vms.inject("") {|s,(k,v)| s += " (#{k}: #{v.size})" }
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,36 @@
1
+ require 'sisfc/configuration'
2
+
3
+ module SISFC
4
+ class Evaluator
5
+ def initialize(conf)
6
+ @vm_hourly_cost = conf.evaluation[:vm_hourly_cost]
7
+ raise ArgumentError, 'No VM hourly costs provided!' unless @vm_hourly_cost
8
+
9
+ @penalties_func = conf.evaluation[:penalties]
10
+ end
11
+
12
+ def evaluate_business_impact(kpis, dc_kpis, vm_allocation)
13
+ # evaluate VM daily costs
14
+ cost = vm_allocation.inject(0.0) do |s,x|
15
+ hc = @vm_hourly_cost.find{|i| i[:data_center] == x[:dc_id] and i[:vm_type] == x[:vm_size] }
16
+ unless hc
17
+ raise Error, "Cannot find hourly cost for data center #{x[:dc_id]} and VM size #{x[:vm_size]}!"
18
+ end
19
+ s += x[:vm_num] * hc[:cost]
20
+ end
21
+ cost *= 24.0
22
+ kpis[:cost] = cost
23
+ # puts "vm allocation cost: #{cost}"
24
+
25
+ # consider SLO violations
26
+ penalties = (@penalties_func.nil? ? 0.0 : @penalties_func.call(kpis, dc_kpis))
27
+ kpis[:penalties] = penalties
28
+ # puts "slo penalties cost: #{penalties}"
29
+ cost += penalties
30
+
31
+ # we want to minimize the cost, so we define fitness as -cost
32
+ fitness = -cost
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,36 @@
1
+ module SISFC
2
+
3
+ class Event
4
+
5
+ ET_REQUEST_ARRIVAL = 0
6
+ ET_WORKFLOW_STEP_COMPLETED = 1
7
+ ET_VM_FREE = 2
8
+ ET_VM_SUSPEND = 3
9
+ ET_VM_RESUME = 4
10
+ ET_REQUEST_CLOSURE = 5
11
+ ET_END_OF_SIMULATION = 100
12
+
13
+ # let the comparable mixin provide the < and > operators for us
14
+ include Comparable
15
+
16
+ # should this be attr_accessor instead?
17
+ attr_reader :type, :data, :time, :destination
18
+
19
+ def initialize(type, data, time, destination)
20
+ @type = type
21
+ @data = data
22
+ @time = time
23
+ @destination = destination
24
+ end
25
+
26
+ def <=> (event)
27
+ @time <=> event.time
28
+ end
29
+
30
+ def to_s
31
+ "Event type: #{@type}, data: #{@data}, time: #{@time}, #{@destination}"
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,80 @@
1
+ require 'csv'
2
+ require 'sisfc/request'
3
+
4
+
5
+ module SISFC
6
+
7
+ class RequestGenerator
8
+
9
+ def initialize(opts)
10
+ if opts.has_key? :filename
11
+ filename = opts[:filename]
12
+ raise "File #{filename} does not exist!" unless File.exists?(filename)
13
+ @file = File.open(filename, mode='r')
14
+ elsif opts.has_key? :command
15
+ command = opts[:command]
16
+ @file = IO.popen(command)
17
+ else
18
+ raise ArgumentError, 'Need to provide either a filename or a command!'
19
+ end
20
+
21
+ # throw away the first line (containing the CSV headers)
22
+ @file.gets
23
+
24
+ # NOTE: so far we support only sequential integer rids
25
+ @next_rid = 0
26
+
27
+ setup_finalizer
28
+ end
29
+
30
+
31
+ def generate
32
+ # read next line from file
33
+ line = @file.gets
34
+ raise "End of input reached while reading request #{@next_rid}!" unless line
35
+
36
+ # parse data
37
+ tokens = line.parse_csv
38
+ generation_time = tokens[0].to_f
39
+ data_center_id = tokens[1].to_i
40
+ arrival_time = tokens[2].to_f
41
+ workflow_type_id = tokens[3].to_i
42
+
43
+ # increase @next_rid
44
+ @next_rid += 1
45
+
46
+ # sanity check
47
+ if generation_time > arrival_time
48
+ raise "Generation time (#{generation_time}) is larger than arrival time (#{arrival_time})!"
49
+ end
50
+
51
+ # generate and return request
52
+ Request.new(@next_rid,
53
+ generation_time,
54
+ data_center_id,
55
+ arrival_time,
56
+ workflow_type_id)
57
+ end
58
+
59
+
60
+ private
61
+ # After object destruction, make sure that the input file is closed or
62
+ # the input command process is killed.
63
+ def setup_finalizer
64
+ ObjectSpace.define_finalizer(self, self.class.close_io(@file))
65
+ end
66
+
67
+ # Need to make this a class method, or the deallocation won't take place. See:
68
+ # http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
69
+ def self.close_io(file)
70
+ Proc.new do
71
+ if file.respond_to? :pid
72
+ Process.kill('INT', file.pid)
73
+ elsif
74
+ file.close
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,122 @@
1
+ module SISFC
2
+
3
+ class Request
4
+
5
+ # states
6
+ STATE_WORKING = 1
7
+ STATE_SUSPENDED = 2
8
+
9
+ attr_accessor :rid, :generation_time, :data_center_id, :arrival_time,
10
+ :workflow_type_id, :closure_time #, :status
11
+ attr_reader :communication_latency
12
+
13
+ def initialize(rid, generation_time, data_center_id, arrival_time, workflow_type_id)
14
+ @rid = rid
15
+ @generation_time = generation_time
16
+ @data_center_id = data_center_id
17
+ @arrival_time = arrival_time
18
+ @workflow_type_id = workflow_type_id
19
+
20
+ # steps count from zero
21
+ @step = 0
22
+
23
+ # calculate communication latency
24
+ @communication_latency = @arrival_time - @generation_time
25
+
26
+ # consider initial communication latency
27
+ @tracking_information = [
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
+ }
50
+ end
51
+
52
+ def step_completed(start, duration)
53
+ @tracking_information << {
54
+ :type => :work,
55
+ :at => start,
56
+ :duration => duration,
57
+ # :vm =>
58
+ }
59
+ @step += 1
60
+ end
61
+
62
+ def finished_processing(time)
63
+ # consider final communication latency
64
+ @tracking_information << {
65
+ :type => :communication,
66
+ :at => time,
67
+ :duration => @communication_latency,
68
+ }
69
+ # save closure time
70
+ @closure_time = time + @communication_latency
71
+ end
72
+
73
+ def next_step
74
+ @step
75
+ end
76
+
77
+ def with_tracking_information(type=:all)
78
+ selected_ti = if type == :all
79
+ @tracking_information
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)
99
+ end
100
+
101
+ def closed?
102
+ !@closure_time.nil?
103
+ end
104
+
105
+ def ttr
106
+ # if incident isn't closed yet, just return nil without raising an exception.
107
+ @closure_time.nil? ? nil : (@closure_time - @arrival_time)
108
+ end
109
+
110
+ def to_s
111
+ "rid: #{@rid}, generation_time: #{@generation_time}, data_center_id: #{@data_center_id}, arrival_time: #{@arrival_time}"
112
+ 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
+ end
121
+
122
+ end
@@ -0,0 +1,16 @@
1
+ require 'erv'
2
+
3
+ module SISFC
4
+ class ServiceType
5
+ attr_reader :level
6
+
7
+ def initialize(opts)
8
+ @level = opts[:level]
9
+ @rv = ERV::RandomVariable.new(opts[:service_time_distribution])
10
+ end
11
+
12
+ def get_random_service_time
13
+ @rv.next
14
+ end
15
+ end
16
+ end