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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +63 -0
- data/Rakefile +9 -0
- data/bin/symian +24 -0
- data/lib/symian.rb +5 -0
- data/lib/symian/configuration.rb +97 -0
- data/lib/symian/cost_analyzer.rb +37 -0
- data/lib/symian/event.rb +53 -0
- data/lib/symian/generator.rb +89 -0
- data/lib/symian/incident.rb +135 -0
- data/lib/symian/operator.rb +97 -0
- data/lib/symian/performance_analyzer.rb +60 -0
- data/lib/symian/simulation.rb +171 -0
- data/lib/symian/sorted_array.rb +57 -0
- data/lib/symian/support/dsl_helper.rb +18 -0
- data/lib/symian/support/yaml_io.rb +9 -0
- data/lib/symian/support_group.rb +182 -0
- data/lib/symian/trace_collector.rb +107 -0
- data/lib/symian/transition_matrix.rb +168 -0
- data/lib/symian/version.rb +3 -0
- data/lib/symian/work_shift.rb +158 -0
- data/symian.gemspec +29 -0
- data/test/symian/configuration_test.rb +66 -0
- data/test/symian/cost_analyzer_test.rb +52 -0
- data/test/symian/generator_test.rb +27 -0
- data/test/symian/incident_test.rb +104 -0
- data/test/symian/operator_test.rb +60 -0
- data/test/symian/reference_configuration.rb +107 -0
- data/test/symian/support_group_test.rb +111 -0
- data/test/symian/trace_collector_test.rb +203 -0
- data/test/symian/transition_matrix_test.rb +88 -0
- data/test/symian/work_shift_test.rb +68 -0
- data/test/test_helper.rb +5 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -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
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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
data/bin/symian
ADDED
@@ -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
|
data/lib/symian.rb
ADDED
@@ -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
|
data/lib/symian/event.rb
ADDED
@@ -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
|