symian 0.1.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.
@@ -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