symian 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a2b94c03a7439d507a21cbd1f3b5591796993816
4
+ data.tar.gz: 8964d9303d4ff851ff3c969b1077150516a29b25
5
+ SHA512:
6
+ metadata.gz: ebe39ffb3dbd463beaacf6e309161ee2e05cf42c2d70ba47ac29b48c937776b01208426dd6962f399f708471c2257b5dc2fde85003e080337369c7e9271f77a3
7
+ data.tar.gz: 90fc7bb7efad95ac893e545834a87ddbcee467c0564d63fca4510fa108caedbed6d07eb8e4a482332b37a73f18bb21da2b295a4def88166f022dd3e6c5a17cf8
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,21 @@
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
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,63 @@
1
+ # SYMIAN
2
+
3
+ Symian is a tool, based on what-if scenario analysis and discrete-event
4
+ simulation, for the modeling of a hierarchical and multi-level incident
5
+ management structures. The simulative approach allows to extract important
6
+ metrics and statistics in complex and dynamic systems, which would otherwise be
7
+ impossible to obtain by means of the analytical approach. Symian allows to test
8
+ corrective actions on the architecture itself of support groups before
9
+ executing them in practice.
10
+
11
+
12
+ ## References
13
+
14
+ Symian (more precisely an earlier version of it) was used in the following
15
+ research papers:
16
+
17
+ 1. C. Bartolini, C.Stefanelli, M. Tortonesi, "SYMIAN: Analysis and Performance
18
+ Improvement of the IT Incident Management Process", IEEE Transactions on
19
+ Network and Service Management, IEEE Communications Society Press, ISSN
20
+ 1932-4537, Vol. 7, No. 3, pp. 132-144, September 2010.
21
+ 2. C. Bartolini, C. Stefanelli, M. Tortonesi, "SYMIAN: a Simulation Tool for
22
+ the Optimization of the IT Incident Management Process", in Proceedings of
23
+ 19th IFIP/IEEE International Workshop on Distributed Systems: Operations and
24
+ Management (DSOM 2008), pp. 83-94, ISBN: 978-3-540-85999-4, 25-26 September
25
+ 2008, Pythagorion, Samos Island, Greece.
26
+ 3. C. Bartolini, C. Stefanelli, M. Tortonesi, "Business-impact analysis and
27
+ simulation of critical incidents in IT service management", in Proceedings
28
+ of the 11th IFIP/IEEE International Symposium on Integrated Network Management
29
+ (IM 2009) - Main track, pp. 9-16, ISBN: 978-1-4244-3486-2, 1-5 June 2009, New
30
+ York, NY, USA.
31
+ 4. C. Bartolini, C. Stefanelli, M. Tortonesi, "Modeling IT Support
32
+ Organizations from Transactional Logs", in Proceedings of the 12th IEEE/IFIP
33
+ Network Operations and Management Symposium (NOMS 2010) - Main track, pp.
34
+ 256-263, ISBN: 978-1-4244-5367-2, 19-23 April 2010, Osaka, Japan.
35
+ 5. C. Bartolini, C. Stefanelli, M. Tortonesi, D. Targa, "A Web-based What-If
36
+ Scenario Analysis Tool for Performance Improvement of IT Support
37
+ Organizations" (short paper), in Proceedings of 7th International Conference on
38
+ Network and Service Management (CNSM 2011), 24-28 October 2011, Paris, France.
39
+ 6. C. Bartolini, C. Stefanelli, M. Tortonesi, "Modeling IT Support
40
+ Organizations Using Multiple-Priority Queues", in Proceedings of the 13th
41
+ IEEE/IFIP Network Operations and Management Symposium (NOMS 2012) - Main track,
42
+ 16-20 April 2012, Maui, Hawaii, USA.
43
+ 7. C. Bartolini, C. Stefanelli, M. Tortonesi, D. Targa, "A Cloud-based Solution
44
+ for the Performance Improvement of IT Support Organizations", in Proceedings
45
+ of the 13th IEEE/IFIP Network Operations and Management Symposium (NOMS 2012) -
46
+ Mini-conference track, 16-20 April 2012, Maui, Hawaii, USA.
47
+ 8. C. Bartolini, C. Stefanelli, M. Tortonesi, "Potential Benefits and
48
+ Challenges of Closed-Loop Optimization Processes for IT Support
49
+ Organizations", in Proceedings of the 7th IFIP/IEEE International Workshop on
50
+ Business-driven IT Management (BDIM 2012), 20 April 2012, Maui, Hawaii, USA.
51
+ 9. C. Bartolini, C. Stefanelli, M. Tortonesi, "Synthetic Incident Generation in
52
+ the Reenactment of IT Support Organization Behavior", in Proceedings of the
53
+ 13th IFIP/IEEE Integrated Network Management Symposium (IM 2013) - Technical
54
+ (main) track, 27-31 May 2013, Ghent, Belgium.
55
+
56
+ Please, consider citing some of these papers if you find Symian useful for your
57
+ research.
58
+
59
+
60
+ ## NOTE
61
+
62
+ I am still in the process of cleaning up the Symian code. As soon as I am done,
63
+ I will commit it here and upload updated installation and usage information.
@@ -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
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'symian'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'symian'
8
+ end
9
+
10
+ if File.exists?(ARGV[0])
11
+ begin
12
+ configuration = Symian::Configuration.load_from_file(ARGV[0])
13
+ kpis = Symian::Simulation.new(configuration,
14
+ Symian::PerformanceAnalyzer.new(configuration)).run
15
+ puts kpis.inspect
16
+ costs = Symian::CostAnalyzer.new(configuration).evaluate(kpis)
17
+ puts costs
18
+ rescue Exception => e
19
+ puts e.inspect
20
+ puts e.backtrace
21
+ end
22
+ else
23
+ puts 'Usage: symian configuration_file'
24
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_support/time'
2
+
3
+ require 'symian/configuration'
4
+ require 'symian/simulation'
5
+ require 'symian/cost_analyzer'
@@ -0,0 +1,97 @@
1
+ require 'symian/support/dsl_helper'
2
+
3
+ require 'ice_nine'
4
+ require 'ice_nine/core_ext/object'
5
+
6
+
7
+ module Symian
8
+
9
+ module Configurable
10
+ dsl_accessor :start_time,
11
+ :duration,
12
+ :warmup_duration,
13
+ :incident_generation,
14
+ :transition_matrix,
15
+ :cost_analysis,
16
+ :support_groups
17
+ end
18
+
19
+ class Configuration
20
+ include Configurable
21
+
22
+ attr_accessor :filename
23
+
24
+ def initialize(filename)
25
+ @filename = filename
26
+ end
27
+
28
+ def end_time
29
+ @start_time + @duration
30
+ end
31
+
32
+ def validate
33
+ # @start_time = @start_time.to_i
34
+ # @duration = @duration.to_i
35
+ # @warmup_duration = @warmup_duration.to_i
36
+
37
+ if @incident_generation[:type] == :file
38
+ @incident_generation[:source][:path].gsub!('<pwd>', File.expand_path(File.dirname(@filename)))
39
+ end
40
+
41
+ # freeze everything!
42
+ @start_time.deep_freeze
43
+ @duration.deep_freeze
44
+ @warmup_duration.deep_freeze
45
+ @incident_generation.deep_freeze
46
+ @transition_matrix.deep_freeze
47
+ @cost_analysis.deep_freeze
48
+ @support_groups.deep_freeze
49
+ end
50
+
51
+ def reallocate_ops_and_clone(operators)
52
+ raise 'Wrong allocation' unless operators.size == @support_groups.size
53
+
54
+ new_conf = Configuration.new(@filename)
55
+ new_conf.start_time @start_time
56
+ new_conf.duration @duration
57
+ new_conf.warmup_duration @warmup_duration
58
+ new_conf.incident_generation @incident_generation
59
+ new_conf.transition_matrix @transition_matrix
60
+ new_conf.cost_analysis @cost_analysis
61
+
62
+ new_sgs = {}
63
+ @support_groups.zip(operators) do |(sg_name,sg_conf),num_ops|
64
+ new_sgs[sg_name] = {
65
+ work_time: sg_conf[:work_time], # this is already frozen
66
+ operators: {
67
+ number: num_ops,
68
+ workshift: sg_conf[:operators][:workshift], # this is already frozen
69
+ },
70
+ }
71
+ end
72
+ new_sgs.deep_freeze
73
+
74
+ new_conf.support_groups(new_sgs)
75
+
76
+ new_conf
77
+ end
78
+
79
+ def self.load_from_file(filename)
80
+ # allow filename, string, and IO objects as input
81
+ raise ArgumentError, "File #{filename} does not exist!" unless File.exists?(filename)
82
+
83
+ # create configuration object
84
+ conf = Configuration.new(filename)
85
+
86
+ # take the file content and pass it to instance_eval
87
+ conf.instance_eval(File.new(filename, 'r').read)
88
+
89
+ # validate and finalize configuration
90
+ conf.validate
91
+
92
+ # return new object
93
+ conf
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,37 @@
1
+ require 'symian/configuration'
2
+
3
+ module Symian
4
+ class CostAnalyzer
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ unless @configuration.cost_analysis[:operations]
8
+ raise ArgumentError, 'No operations cost configuration provided!'
9
+ end
10
+ end
11
+
12
+ def evaluate(kpis)
13
+ # evaluate operation costs
14
+ operations_cost = @configuration.support_groups.inject(0.0) do |sum,(sg_name,sg_conf)|
15
+ sg_costs = @configuration.cost_analysis[:operations].find{|sg| sg[:sg_name] == sg_name }
16
+ unless sg_costs
17
+ raise "Cannot find salaries for support group #{sg_name}!"
18
+ end
19
+ # ugly hack
20
+ sum += sg_conf[:operators][:number] * sg_costs[:operator_salary]
21
+ end
22
+ # need to consider daily costs (salaries are annual)
23
+ operations_cost /= 365.0
24
+
25
+ # evaluate contracting costs (SLO violations)
26
+ contracting_func = @configuration.cost_analysis[:contracting]
27
+ contracting_cost = (contracting_func.nil? ? 0.0 : (contracting_func.call(kpis) or 0.0))
28
+
29
+ # evaluate drift costs
30
+ drift_func = @configuration.cost_analysis[:drift]
31
+ drift_cost = (drift_func.nil? ? 0.0 : (drift_func.call(kpis) or 0.0))
32
+
33
+ # return result
34
+ { :operations => operations_cost, :contracting => contracting_cost, :drift => drift_cost }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,53 @@
1
+ require 'symian/support/yaml_io'
2
+
3
+ module Symian
4
+
5
+ class Event
6
+
7
+ ET_INCIDENT_ARRIVAL = 0
8
+ ET_INCIDENT_ASSIGNMENT = 1
9
+ ET_INCIDENT_RESCHEDULING = 2
10
+ ET_INCIDENT_ESCALATION = 3
11
+ ET_OPERATOR_RETURNING = 10
12
+ ET_OPERATOR_LEAVING = 11
13
+ ET_OPERATOR_ACTIVITY_STARTS = 12
14
+ ET_OPERATOR_ACTIVITY_FINISHES = 13
15
+ ET_SUPPORT_GROUP_QUEUE_SIZE_CHANGE = 20
16
+ ET_END_OF_SIMULATION = 90
17
+
18
+
19
+ # let the comparable mixin provide the < and > operators for us
20
+ include Comparable
21
+
22
+
23
+ # setup readable/accessible attributes
24
+ ATTRIBUTES = [ :type, :data, :time, :destination ]
25
+
26
+ attr_reader *ATTRIBUTES # should this be attr_accessor instead?
27
+
28
+
29
+ # setup attributes saved in traces
30
+ include YAMLSerializable
31
+
32
+ TRACED_ATTRIBUTES = ATTRIBUTES
33
+
34
+
35
+ def initialize(type, data, time, destination)
36
+ @type = type
37
+ @data = data
38
+ @time = time
39
+ @destination = destination
40
+ end
41
+
42
+ def <=> (event)
43
+ @time <=> event.time
44
+ end
45
+
46
+ def to_s
47
+ "Event type: #{@type}, data: #{@data}, time: #{@time}, #{@destination}"
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
@@ -0,0 +1,89 @@
1
+ require 'erv'
2
+
3
+ require 'symian/configuration'
4
+ require 'symian/incident'
5
+
6
+
7
+ module Symian
8
+ class IncidentGenerator
9
+
10
+ def initialize(simulation, options={})
11
+ @simulation = simulation
12
+
13
+ @arrival_times = Sequence.create(options)
14
+ raise ArgumentError unless @arrival_times
15
+
16
+ # NOTE: so far we support only sequential integer iids
17
+ @next_iid = 0
18
+ end
19
+
20
+
21
+ def generate
22
+ # get next incident arrival time
23
+ next_arrival = @arrival_times.next
24
+
25
+ # handle case where arrival times is limited source
26
+ return nil unless next_arrival
27
+
28
+ # increase @next_iid
29
+ @next_iid += 1
30
+
31
+ # generate and return incident
32
+ i = Incident.new(@next_iid, next_arrival,
33
+ :category => 'normal', # not supported at the moment
34
+ :priority => 0) # not supported at the moment
35
+
36
+ @simulation.new_event(Event::ET_INCIDENT_ARRIVAL, i, next_arrival, nil)
37
+ end
38
+ end
39
+
40
+
41
+ class Sequence
42
+ def self.create(args)
43
+ case args[:type]
44
+ when :file
45
+ FileInputSequence.new(args[:source])
46
+ when :sequential_random_variable
47
+ ERV::SequentialRandomVariable.new(args[:source])
48
+ # TODO: also support CSV files and arrays as input source
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ class FileInputSequence
55
+ def initialize(args)
56
+ @file = File.open(args[:path], 'r')
57
+
58
+ # throw away the first line (containing the CSV headers)
59
+ @file.gets
60
+
61
+ @curr_val = args[:first_value]
62
+ end
63
+
64
+ # returns nil when EOF occurs
65
+ def next
66
+ displacement = @file.gets.try(:chomp).try(:to_f)
67
+ return nil unless displacement
68
+
69
+ ret = @curr_val
70
+ @curr_val += displacement
71
+ ret
72
+ end
73
+
74
+ private
75
+ # After object destruction, make sure that the input file is closed or
76
+ # the input command process is killed.
77
+ def setup_finalizer
78
+ ObjectSpace.define_finalizer(self, self.class.close_io(@file))
79
+ end
80
+
81
+ # Need to make this a class method, or the deallocation won't take place. See:
82
+ # http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
83
+ def self.close_io(file)
84
+ Proc.new do
85
+ file.close
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,135 @@
1
+ require 'symian/support/yaml_io'
2
+
3
+
4
+ module Symian
5
+ class Incident
6
+ include YAMLSerializable
7
+
8
+ # priorities are not currently used
9
+ MIN_PRIORITY = 0
10
+ MAX_PRIORITY = 9
11
+
12
+ REQUIRED_ATTRIBUTES = [
13
+ :iid, # incident ID
14
+ :arrival_time, # time of incident arrival at the IT support organization
15
+ ]
16
+
17
+ OTHER_ATTRIBUTES = [
18
+ :category, # incident category
19
+ # :severity, # incident severity - not implemented yet
20
+ # :state, # incident state - not implemented yet
21
+ :priority, # incident priority - not currently used
22
+ :visited_support_groups, # number of visited SGs
23
+ # :needed_work_time, # needed work time before incident escalation
24
+ # # - initialized at the arrival in a support group
25
+ # # - decreased as operator works on the incident
26
+ # # - when it reaches 0, the incident is escalated
27
+ :start_work_time, # time when the first operator starts working on the incident
28
+ # - set in Operator#assign
29
+ # :total_work_time, # total work time on the incident
30
+ # # - initialized to 0 in the constructor
31
+ # :total_queue_time, # total time spent waiting for an operator
32
+ # # - initialized to 0 in the constructor
33
+ # :total_suspension_time, # total suspension time
34
+ # # - initialized to 0 in the constructor
35
+ :closure_time, # time at which the incident was closed
36
+ ]
37
+
38
+ attr_accessor *(REQUIRED_ATTRIBUTES + OTHER_ATTRIBUTES)
39
+
40
+
41
+ # setup attributes saved in traces
42
+ include YAMLSerializable
43
+
44
+ TRACED_ATTRIBUTES = REQUIRED_ATTRIBUTES + OTHER_ATTRIBUTES + [ :tracking_information ]
45
+
46
+
47
+ def initialize(iid, arrival_time, opts={})
48
+ @iid = iid
49
+ @arrival_time = arrival_time
50
+
51
+ # set correspondent instance variables for optional arguments
52
+ opts.each do |k, v|
53
+ # ignore invalid attributes
54
+ instance_variable_set("@#{k}", v) if OTHER_ATTRIBUTES.include?(k)
55
+ end
56
+
57
+ @tracking_information ||= []
58
+ @visited_support_groups ||= 0
59
+ end
60
+
61
+
62
+ # the format of track_info is:
63
+ # { :type => one of [ :queue, :work, :suspend ]
64
+ # :at => begin time
65
+ # :duration => duration
66
+ # :sg => support_group_name
67
+ # :operator => operator_id (if applicable)
68
+ # }
69
+ def add_tracking_information(track_info)
70
+ @tracking_information << track_info
71
+ end
72
+
73
+ def with_tracking_information(type=:all)
74
+ selected_ti = if type == :all
75
+ @tracking_information
76
+ else
77
+ @tracking_information.select{|el| el.type == type }
78
+ end
79
+
80
+ selected_ti.each do |ti|
81
+ yield ti
82
+ end
83
+ end
84
+
85
+ def total_work_time
86
+ calculate_time(:work)
87
+ end
88
+
89
+ def total_queue_time
90
+ calculate_time(:queue)
91
+ end
92
+
93
+ def total_suspend_time
94
+ calculate_time(:suspend)
95
+ end
96
+
97
+ def total_time_at_last_sg
98
+ calculate_time_at_last_support_group(:all)
99
+ end
100
+
101
+ def queue_time_at_last_sg
102
+ calculate_time_at_last_support_group(:queue)
103
+ end
104
+
105
+ def closed?
106
+ !@closure_time.nil?
107
+ end
108
+
109
+ def ttr
110
+ # if incident isn't closed yet, just return nil without raising an exception.
111
+ @closure_time.nil? ? nil : (@closure_time - @arrival_time).to_int
112
+ end
113
+
114
+
115
+ private
116
+
117
+ def calculate_time(type)
118
+ return 0 unless @tracking_information
119
+ @tracking_information.inject(0){|sum,x| sum += ((type == :all || type == x[:type]) ? x[:duration].to_i : 0) }
120
+ end
121
+
122
+ def calculate_time_at_last_support_group(*types)
123
+ time = 0
124
+ last_sg = nil
125
+ @tracking_information.reverse_each do |ti|
126
+ last_sg ||= ti[:sg]
127
+ break unless ti[:sg] == last_sg
128
+ time += ti[:duration].to_i if (types.include?(:all) || types.include?(ti[:type]))
129
+ end
130
+ time
131
+ end
132
+
133
+ end
134
+
135
+ end