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