arborist 0.0.1.pre20160106113421
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/.document +4 -0
- data/.simplecov +9 -0
- data/ChangeLog +417 -0
- data/Events.md +20 -0
- data/History.md +4 -0
- data/LICENSE +29 -0
- data/Manifest.txt +72 -0
- data/Monitors.md +141 -0
- data/Nodes.md +0 -0
- data/Observers.md +72 -0
- data/Protocol.md +214 -0
- data/README.md +75 -0
- data/Rakefile +81 -0
- data/TODO.md +24 -0
- data/bin/amanagerd +10 -0
- data/bin/amonitord +12 -0
- data/bin/aobserverd +12 -0
- data/lib/arborist.rb +182 -0
- data/lib/arborist/client.rb +191 -0
- data/lib/arborist/event.rb +61 -0
- data/lib/arborist/event/node_acked.rb +18 -0
- data/lib/arborist/event/node_delta.rb +20 -0
- data/lib/arborist/event/node_matching.rb +34 -0
- data/lib/arborist/event/node_update.rb +19 -0
- data/lib/arborist/event/sys_reloaded.rb +15 -0
- data/lib/arborist/exceptions.rb +21 -0
- data/lib/arborist/manager.rb +508 -0
- data/lib/arborist/manager/event_publisher.rb +97 -0
- data/lib/arborist/manager/tree_api.rb +207 -0
- data/lib/arborist/mixins.rb +363 -0
- data/lib/arborist/monitor.rb +377 -0
- data/lib/arborist/monitor/socket.rb +163 -0
- data/lib/arborist/monitor_runner.rb +217 -0
- data/lib/arborist/node.rb +700 -0
- data/lib/arborist/node/host.rb +87 -0
- data/lib/arborist/node/root.rb +60 -0
- data/lib/arborist/node/service.rb +112 -0
- data/lib/arborist/observer.rb +176 -0
- data/lib/arborist/observer/action.rb +125 -0
- data/lib/arborist/observer/summarize.rb +105 -0
- data/lib/arborist/observer_runner.rb +181 -0
- data/lib/arborist/subscription.rb +82 -0
- data/spec/arborist/client_spec.rb +282 -0
- data/spec/arborist/event/node_update_spec.rb +71 -0
- data/spec/arborist/event_spec.rb +64 -0
- data/spec/arborist/manager/event_publisher_spec.rb +66 -0
- data/spec/arborist/manager/tree_api_spec.rb +458 -0
- data/spec/arborist/manager_spec.rb +442 -0
- data/spec/arborist/mixins_spec.rb +195 -0
- data/spec/arborist/monitor/socket_spec.rb +195 -0
- data/spec/arborist/monitor_runner_spec.rb +152 -0
- data/spec/arborist/monitor_spec.rb +251 -0
- data/spec/arborist/node/host_spec.rb +104 -0
- data/spec/arborist/node/root_spec.rb +29 -0
- data/spec/arborist/node/service_spec.rb +98 -0
- data/spec/arborist/node_spec.rb +552 -0
- data/spec/arborist/observer/action_spec.rb +205 -0
- data/spec/arborist/observer/summarize_spec.rb +294 -0
- data/spec/arborist/observer_spec.rb +146 -0
- data/spec/arborist/subscription_spec.rb +71 -0
- data/spec/arborist_spec.rb +146 -0
- data/spec/data/monitors/pings.rb +80 -0
- data/spec/data/monitors/port_checks.rb +27 -0
- data/spec/data/monitors/system_resources.rb +30 -0
- data/spec/data/monitors/web_services.rb +17 -0
- data/spec/data/nodes/duir.rb +20 -0
- data/spec/data/nodes/localhost.rb +15 -0
- data/spec/data/nodes/sidonie.rb +29 -0
- data/spec/data/nodes/yevaud.rb +26 -0
- data/spec/data/observers/auditor.rb +23 -0
- data/spec/data/observers/webservices.rb +18 -0
- data/spec/spec_helper.rb +117 -0
- metadata +368 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'etc'
|
5
|
+
require 'ipaddr'
|
6
|
+
|
7
|
+
require 'arborist/node'
|
8
|
+
|
9
|
+
|
10
|
+
# A node type for Arborist trees that represent network-connected hosts.
|
11
|
+
class Arborist::Node::Host < Arborist::Node
|
12
|
+
|
13
|
+
# A union of IPv4 and IPv6 regular expressions.
|
14
|
+
IPADDR_RE = Regexp.union(
|
15
|
+
IPAddr::RE_IPV4ADDRLIKE,
|
16
|
+
IPAddr::RE_IPV6ADDRLIKE_COMPRESSED,
|
17
|
+
IPAddr::RE_IPV6ADDRLIKE_FULL
|
18
|
+
)
|
19
|
+
|
20
|
+
|
21
|
+
### Create a new Host node.
|
22
|
+
def initialize( identifier, &block )
|
23
|
+
@addresses = []
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
######
|
29
|
+
public
|
30
|
+
######
|
31
|
+
|
32
|
+
##
|
33
|
+
# The network address(es) of this Host as an Array of IPAddr objects
|
34
|
+
attr_reader :addresses
|
35
|
+
|
36
|
+
|
37
|
+
### Return the host's operational attributes.
|
38
|
+
def operational_values
|
39
|
+
properties = super
|
40
|
+
return properties.merge( addresses: self.addresses.map(&:to_s) )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
### Set an IP address of the host.
|
45
|
+
def address( new_address, options={} )
|
46
|
+
self.log.debug "Adding address %p to %p" % [ new_address, self ]
|
47
|
+
case new_address
|
48
|
+
when IPAddr
|
49
|
+
@addresses << new_address
|
50
|
+
when IPADDR_RE
|
51
|
+
@addresses << IPAddr.new( new_address )
|
52
|
+
when String
|
53
|
+
ip_addr = TCPSocket.gethostbyname( new_address )
|
54
|
+
@addresses << IPAddr.new( ip_addr[3] )
|
55
|
+
@addresses << IPAddr.new( ip_addr[4] ) if ip_addr[4]
|
56
|
+
else
|
57
|
+
raise "I don't know how to parse a %p host address (%p)" %
|
58
|
+
[ new_address.class, new_address ]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
|
64
|
+
def match_criteria?( key, val )
|
65
|
+
return case key
|
66
|
+
when 'address'
|
67
|
+
search_addr = IPAddr.new( val )
|
68
|
+
@addresses.any? {|a| search_addr.include?(a) }
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
### Add a service to the host
|
76
|
+
def service( name, options={}, &block )
|
77
|
+
return Arborist::Node.create( :service, name, self, options, &block )
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
### Return host-node-specific information for #inspect.
|
82
|
+
def node_description
|
83
|
+
return "{no addresses}" if self.addresses.empty?
|
84
|
+
return "{addresses: %s}" % [ self.addresses.map(&:to_s).join(', ') ]
|
85
|
+
end
|
86
|
+
|
87
|
+
end # class Arborist::Node::Host
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/node' unless defined?( Arborist::Node )
|
5
|
+
require 'arborist/mixins'
|
6
|
+
|
7
|
+
|
8
|
+
# The class of the root node of an Arborist tree. This class is a Singleton.
|
9
|
+
class Arborist::Node::Root < Arborist::Node
|
10
|
+
extend Arborist::MethodUtilities
|
11
|
+
|
12
|
+
|
13
|
+
# The instance of the Root node.
|
14
|
+
@instance = nil
|
15
|
+
|
16
|
+
### Create the instance of the Root node (if necessary) and return it.
|
17
|
+
def self::new( * )
|
18
|
+
@instance ||= super
|
19
|
+
return @instance
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
### Override the default constructor to use the singleton ::instance instead.
|
24
|
+
def self::instance( * )
|
25
|
+
@instance ||= new
|
26
|
+
return @instance
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
### Reset the singleton instance; mainly used for testing.
|
31
|
+
def self::reset
|
32
|
+
@instance = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
### Set up the root node.
|
37
|
+
def initialize( * )
|
38
|
+
super( '_' ) do
|
39
|
+
description "The root node."
|
40
|
+
source = URI( __FILE__ )
|
41
|
+
end
|
42
|
+
|
43
|
+
@status = 'up'
|
44
|
+
@status.freeze
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
### Ignore updates to the root node.
|
49
|
+
def update( properties )
|
50
|
+
self.log.warn "Update to the root node ignored."
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
### Override the reader mode of Node#parent for the root node, which never has
|
55
|
+
### a parent.
|
56
|
+
def parent( * )
|
57
|
+
return nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end # class Arborist::Node::Root
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'etc'
|
5
|
+
require 'ipaddr'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
require 'arborist/node'
|
9
|
+
|
10
|
+
|
11
|
+
# A node type for Arborist trees that represent services running on hosts.
|
12
|
+
class Arborist::Node::Service < Arborist::Node
|
13
|
+
|
14
|
+
# The default transport layer protocol to use for services that don't specify
|
15
|
+
# one
|
16
|
+
DEFAULT_PROTOCOL = 'tcp'
|
17
|
+
|
18
|
+
|
19
|
+
### Create a new Service node.
|
20
|
+
def initialize( identifier, host, options={}, &block )
|
21
|
+
my_identifier = "%s-%s" % [ host.identifier, identifier ]
|
22
|
+
super( my_identifier )
|
23
|
+
|
24
|
+
@host = host
|
25
|
+
@parent = host.identifier
|
26
|
+
@app_protocol = options[:app_protocol] || identifier
|
27
|
+
@protocol = options[:protocol] || DEFAULT_PROTOCOL
|
28
|
+
|
29
|
+
service_port = options[:port] || default_port_for( @app_protocol, @protocol ) or
|
30
|
+
raise ArgumentError, "can't determine the port for %s/%s" % [ @app_protocol, @protocol ]
|
31
|
+
@port = Integer( service_port )
|
32
|
+
|
33
|
+
self.instance_eval( &block ) if block
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
######
|
38
|
+
public
|
39
|
+
######
|
40
|
+
|
41
|
+
##
|
42
|
+
# The network port the service uses
|
43
|
+
attr_reader :port
|
44
|
+
|
45
|
+
##
|
46
|
+
# The transport layer protocol the service uses
|
47
|
+
attr_reader :protocol
|
48
|
+
|
49
|
+
##
|
50
|
+
# The (layer 7) protocol used by the service
|
51
|
+
attr_reader :app_protocol
|
52
|
+
|
53
|
+
|
54
|
+
### Delegate the service's address to its host.
|
55
|
+
def addresses
|
56
|
+
return @host.addresses
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
### Returns +true+ if the node matches the specified +key+ and +val+ criteria.
|
61
|
+
def match_criteria?( key, val )
|
62
|
+
self.log.debug "Matching %p: %p against %p" % [ key, val, self ]
|
63
|
+
return case key
|
64
|
+
when 'port'
|
65
|
+
val = default_port_for( val, @protocol ) unless val.is_a?( Fixnum )
|
66
|
+
self.port == val.to_i
|
67
|
+
when 'address'
|
68
|
+
search_addr = IPAddr.new( val )
|
69
|
+
self.addresses.any? {|a| search_addr.include?(a) }
|
70
|
+
when 'protocol' then self.protocol == val.downcase
|
71
|
+
when 'app', 'app_protocol' then self.app_protocol == val
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Return a Hash of the operational values that are included with the node's
|
79
|
+
### monitor state.
|
80
|
+
def operational_values
|
81
|
+
return super.merge(
|
82
|
+
addresses: self.addresses.map( &:to_s ),
|
83
|
+
port: self.port,
|
84
|
+
protocol: self.protocol,
|
85
|
+
app_protocol: self.app_protocol,
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
### Return service-node-specific information for #inspect.
|
91
|
+
def node_description
|
92
|
+
return "{listening on %s port %d}" % [
|
93
|
+
self.protocol,
|
94
|
+
self.port,
|
95
|
+
]
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
#######
|
100
|
+
private
|
101
|
+
#######
|
102
|
+
|
103
|
+
### Try to default the appropriate port based on the node's +identifier+
|
104
|
+
### and +protocol+. Raises a SocketError if the service port can't be
|
105
|
+
### looked up.
|
106
|
+
def default_port_for( identifier, protocol )
|
107
|
+
return Socket.getservbyname( identifier, protocol )
|
108
|
+
rescue SocketError
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
|
112
|
+
end # class Arborist::Node::Service
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist' unless defined?( Arborist )
|
5
|
+
|
6
|
+
|
7
|
+
# The Arborist entity responsible for observing changes to the tree and
|
8
|
+
# reporting on them.
|
9
|
+
class Arborist::Observer
|
10
|
+
extend Loggability,
|
11
|
+
Arborist::MethodUtilities
|
12
|
+
|
13
|
+
# Loggability API -- write logs to the Arborist log host
|
14
|
+
log_to :arborist
|
15
|
+
|
16
|
+
|
17
|
+
autoload :Action, 'arborist/observer/action'
|
18
|
+
autoload :Summarize, 'arborist/observer/summarize'
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
# The key for the thread local that is used to track instances as they're
|
23
|
+
# loaded.
|
24
|
+
LOADED_INSTANCE_KEY = :loaded_observer_instances
|
25
|
+
|
26
|
+
##
|
27
|
+
# The glob pattern to use for searching for observers
|
28
|
+
OBSERVER_FILE_PATTERN = '**/*.rb'
|
29
|
+
|
30
|
+
|
31
|
+
Arborist.add_dsl_constructor( :Observer ) do |description, &block|
|
32
|
+
Arborist::Observer.new( description, &block )
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
### Overridden to track instances of created nodes for the DSL.
|
38
|
+
def self::new( * )
|
39
|
+
new_instance = super
|
40
|
+
Arborist::Observer.add_loaded_instance( new_instance )
|
41
|
+
return new_instance
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
### Record a new loaded instance if the Thread-local variable is set up to track
|
46
|
+
### them.
|
47
|
+
def self::add_loaded_instance( new_instance )
|
48
|
+
instances = Thread.current[ LOADED_INSTANCE_KEY ] or return
|
49
|
+
instances << new_instance
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
### Load the specified +file+ and return any new Nodes created as a result.
|
54
|
+
def self::load( file )
|
55
|
+
self.log.info "Loading observer file %s..." % [ file ]
|
56
|
+
Thread.current[ LOADED_INSTANCE_KEY ] = []
|
57
|
+
Kernel.load( file )
|
58
|
+
return Thread.current[ LOADED_INSTANCE_KEY ]
|
59
|
+
ensure
|
60
|
+
Thread.current[ LOADED_INSTANCE_KEY ] = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
### Return an iterator for all the observer files in the specified +directory+.
|
65
|
+
def self::each_in( directory )
|
66
|
+
path = Pathname( directory )
|
67
|
+
paths = if path.directory?
|
68
|
+
Pathname.glob( directory + OBSERVER_FILE_PATTERN )
|
69
|
+
else
|
70
|
+
[ path ]
|
71
|
+
end
|
72
|
+
|
73
|
+
return paths.flat_map do |file|
|
74
|
+
file_url = "file://%s" % [ file.expand_path ]
|
75
|
+
observers = self.load( file )
|
76
|
+
self.log.debug "Loaded observers %p..." % [ observers ]
|
77
|
+
observers.each do |observer|
|
78
|
+
observer.source = file_url
|
79
|
+
end
|
80
|
+
observers
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
### Create a new Observer with the specified +description+.
|
86
|
+
def initialize( description, &block )
|
87
|
+
@description = description
|
88
|
+
@subscriptions = []
|
89
|
+
@actions = []
|
90
|
+
|
91
|
+
self.instance_exec( &block ) if block
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
######
|
96
|
+
public
|
97
|
+
######
|
98
|
+
|
99
|
+
##
|
100
|
+
# The observer's description
|
101
|
+
attr_reader :description
|
102
|
+
|
103
|
+
##
|
104
|
+
# The observer's actions
|
105
|
+
attr_reader :actions
|
106
|
+
|
107
|
+
##
|
108
|
+
# The source file the observer was loaded from
|
109
|
+
attr_accessor :source
|
110
|
+
|
111
|
+
|
112
|
+
#
|
113
|
+
# DSL Methods
|
114
|
+
#
|
115
|
+
|
116
|
+
### Specify a pattern for events the observer is interested in. Options:
|
117
|
+
### to::
|
118
|
+
### the name of the event; defaults to every event type
|
119
|
+
### where::
|
120
|
+
### a Hash of criteria to match against event data
|
121
|
+
### on::
|
122
|
+
### the identifier of the node to subscribe on, defaults to the root node
|
123
|
+
## which receives all node events.
|
124
|
+
def subscribe( to: nil, where: {}, on: nil )
|
125
|
+
@subscriptions << { criteria: where, identifier: on, event_type: to }
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
### Register an action that will be taken when a subscribed event is received.
|
130
|
+
def action( options={}, &block )
|
131
|
+
@actions << Arborist::Observer::Action.new( options, &block )
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
### Register a summary action.
|
136
|
+
def summarize( options={}, &block )
|
137
|
+
@actions << Arborist::Observer::Summarize.new( options, &block )
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
#
|
142
|
+
# Observe Methods
|
143
|
+
#
|
144
|
+
|
145
|
+
### Fetch the descriptions of which events this Observer would like to receive. If no
|
146
|
+
### subscriptions have been specified, a subscription that will match any event is returned.
|
147
|
+
def subscriptions
|
148
|
+
|
149
|
+
# Subscribe to all events if there are no subscription criteria.
|
150
|
+
self.subscribe if @subscriptions.empty?
|
151
|
+
|
152
|
+
return @subscriptions
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
### Handle a published event.
|
157
|
+
def handle_event( uuid, event )
|
158
|
+
self.actions.each do |action|
|
159
|
+
action.handle_event( event )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
### Return an Array of timer callbacks of the form:
|
165
|
+
###
|
166
|
+
### [ interval_seconds, callable ]
|
167
|
+
###
|
168
|
+
def timers
|
169
|
+
return self.actions.map do |action|
|
170
|
+
next nil unless action.respond_to?( :on_timer ) &&
|
171
|
+
action.time_threshold.nonzero?
|
172
|
+
[ action.time_threshold, action.method(:on_timer) ]
|
173
|
+
end.compact
|
174
|
+
end
|
175
|
+
|
176
|
+
end # class Arborist::Observer
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'schedulability'
|
5
|
+
require 'loggability'
|
6
|
+
|
7
|
+
require 'arborist/observer' unless defined?( Arborist::Observer )
|
8
|
+
|
9
|
+
|
10
|
+
# An action taken by an Observer.
|
11
|
+
class Arborist::Observer::Action
|
12
|
+
extend Loggability
|
13
|
+
|
14
|
+
|
15
|
+
# Loggability API -- log to the Arborist logger
|
16
|
+
log_to :arborist
|
17
|
+
|
18
|
+
|
19
|
+
### Create a new Action that will call the specified +block+ +during+ the given schedule,
|
20
|
+
### but only +after+ the specified number of events have arrived +within+ the given
|
21
|
+
### time threshold.
|
22
|
+
def initialize( within: 0, after: 1, during: nil, &block )
|
23
|
+
raise ArgumentError, "Action requires a block" unless block
|
24
|
+
|
25
|
+
@block = block
|
26
|
+
@time_threshold = within
|
27
|
+
@schedule = Schedulability::Schedule.parse( during ) if during
|
28
|
+
|
29
|
+
if within.zero?
|
30
|
+
@count_threshold = after
|
31
|
+
else
|
32
|
+
# It should always be 2 or more if there is a time threshold
|
33
|
+
@count_threshold = [ after, 2 ].max
|
34
|
+
end
|
35
|
+
|
36
|
+
@event_history = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
######
|
41
|
+
public
|
42
|
+
######
|
43
|
+
|
44
|
+
##
|
45
|
+
# The object to #call when the action is triggered.
|
46
|
+
attr_reader :block
|
47
|
+
|
48
|
+
##
|
49
|
+
# The maximum number of seconds between events that cause the action to be called
|
50
|
+
attr_reader :time_threshold
|
51
|
+
|
52
|
+
##
|
53
|
+
# The minimum number of events that cause the action to be called when the #time_threshold
|
54
|
+
# is met.
|
55
|
+
attr_reader :count_threshold
|
56
|
+
|
57
|
+
##
|
58
|
+
# The schedule that applies to this action.
|
59
|
+
attr_reader :schedule
|
60
|
+
|
61
|
+
##
|
62
|
+
# The Hash of recent events, keyed by their arrival time.
|
63
|
+
attr_reader :event_history
|
64
|
+
|
65
|
+
|
66
|
+
### Call the action for the specified +event+.
|
67
|
+
def handle_event( event )
|
68
|
+
self.record_event( event )
|
69
|
+
self.call_block( event ) if self.should_run?
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
### Execute the action block with the specified +event+.
|
74
|
+
###
|
75
|
+
def call_block( event )
|
76
|
+
if self.block.arity >= 2 || self.block.arity < 0
|
77
|
+
self.block.call( event.dup, self.event_history.dup )
|
78
|
+
else
|
79
|
+
self.block.call( event.dup )
|
80
|
+
end
|
81
|
+
ensure
|
82
|
+
self.event_history.clear
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
### Record the specified +event+ in the event history if within the scheduled period(s).
|
87
|
+
def record_event( event )
|
88
|
+
return if self.schedule && !self.schedule.now?
|
89
|
+
self.event_history[ Time.now ] = event
|
90
|
+
self.event_history.keys.sort.each do |event_time|
|
91
|
+
break if self.event_history.size <= self.count_threshold
|
92
|
+
self.event_history.delete( event_time )
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
### Returns +true+ if the threshold is exceeded and the current time is within the
|
98
|
+
### action's schedule.
|
99
|
+
def should_run?
|
100
|
+
return self.time_threshold_exceeded? && self.count_threshold_exceeded?
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
### Returns +true+ if the time between the first and last event in the #event_history is
|
105
|
+
### less than the #time_threshold.
|
106
|
+
def time_threshold_exceeded?
|
107
|
+
return true if self.time_threshold.zero?
|
108
|
+
return false unless self.count_threshold_exceeded?
|
109
|
+
|
110
|
+
first = self.event_history.keys.min
|
111
|
+
last = self.event_history.keys.max
|
112
|
+
|
113
|
+
self.log.debug "Time between the %d events in the record (%p): %0.5fs" %
|
114
|
+
[ self.event_history.size, self.event_history, last - first ]
|
115
|
+
return last - first <= self.time_threshold
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
### Returns +true+ if the number of events in the event history meet or exceed the
|
120
|
+
### #count_threshold.
|
121
|
+
def count_threshold_exceeded?
|
122
|
+
return self.event_history.size >= self.count_threshold
|
123
|
+
end
|
124
|
+
|
125
|
+
end # class Arborist::Observer::Action
|