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