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,146 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
describe Arborist::Observer do
|
6
|
+
|
7
|
+
|
8
|
+
it "can be created with just a description" do
|
9
|
+
observer = described_class.new( "the description" )
|
10
|
+
expect( observer ).to be_a( described_class )
|
11
|
+
expect( observer.description ).to eq( "the description" )
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
it "yields itself to the provided block for the DSL" do
|
16
|
+
block_self = nil
|
17
|
+
observer = described_class.new( "testing observer" ) do
|
18
|
+
block_self = self
|
19
|
+
end
|
20
|
+
|
21
|
+
expect( block_self ).to be( observer )
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
it "can specify a subscription for an event it's interested in" do
|
26
|
+
observer = described_class.new( "testing observer" ) do
|
27
|
+
subscribe to: 'node.delta'
|
28
|
+
end
|
29
|
+
|
30
|
+
expect( observer.subscriptions ).to be_an( Array )
|
31
|
+
expect( observer.subscriptions.length ).to eq( 1 )
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
it "can specify a subscription for more than one event it's interested in" do
|
36
|
+
observer = described_class.new( "testing observer" ) do
|
37
|
+
subscribe to: 'node.delta'
|
38
|
+
subscribe to: 'sys.reload'
|
39
|
+
end
|
40
|
+
|
41
|
+
expect( observer.subscriptions ).to be_an( Array )
|
42
|
+
expect( observer.subscriptions.length ).to eq( 2 )
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it "can specify an action to run when a subscribed event is received" do
|
47
|
+
observer = described_class.new( "testing observer" ) do
|
48
|
+
action do |uuid, event|
|
49
|
+
puts( uuid )
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
expect( observer.actions ).to be_an( Array )
|
54
|
+
expect( observer.actions.length ).to eq( 1 )
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
it "can specify more than one action to run when a subscribed event is received" do
|
59
|
+
observer = described_class.new( "testing observer" ) do
|
60
|
+
action do |uuid, event|
|
61
|
+
puts( uuid )
|
62
|
+
end
|
63
|
+
action do |uuid, event|
|
64
|
+
$stderr.puts( uuid )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
expect( observer.actions ).to be_an( Array )
|
69
|
+
expect( observer.actions.length ).to eq( 2 )
|
70
|
+
expect( observer.actions ).to all( be_a(Arborist::Observer::Action) )
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
it "can specify a summary action" do
|
75
|
+
observer = described_class.new( "testing observer" ) do
|
76
|
+
summarize( every: 5 ) do |events|
|
77
|
+
puts( events.size )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
expect( observer.actions ).to be_an( Array )
|
82
|
+
expect( observer.actions.length ).to eq( 1 )
|
83
|
+
expect( observer.actions.first ).to be_a( Arborist::Observer::Summarize )
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
it "can specify a mix of regular and summary actions" do
|
88
|
+
observer = described_class.new( "testing observer" ) do
|
89
|
+
summarize( every: 5 ) do |events|
|
90
|
+
puts( events.size )
|
91
|
+
end
|
92
|
+
action do |uuid, event|
|
93
|
+
$stderr.puts( uuid )
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
expect( observer.actions ).to be_an( Array )
|
98
|
+
expect( observer.actions.length ).to eq( 2 )
|
99
|
+
expect( observer.actions.first ).to be_a( Arborist::Observer::Summarize )
|
100
|
+
expect( observer.actions.last ).to be_a( Arborist::Observer::Action )
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
it "passes events it is given to handle to its actions" do
|
105
|
+
observer = described_class.new( "testing observer" ) do
|
106
|
+
summarize( every: 5 ) do |events|
|
107
|
+
puts( events.size )
|
108
|
+
end
|
109
|
+
action do |uuid, event|
|
110
|
+
$stderr.puts( uuid )
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
event = {}
|
115
|
+
expect( observer.actions.first ).to receive( :handle_event ).with( event )
|
116
|
+
expect( observer.actions.last ).to receive( :handle_event ).with( event )
|
117
|
+
|
118
|
+
observer.handle_event( "eventid", event )
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
it "can build a list of timer callbacks for its actions" do
|
123
|
+
summarize_called = false
|
124
|
+
|
125
|
+
observer = described_class.new( "testing observer" ) do
|
126
|
+
summarize( every: 5 ) do |events|
|
127
|
+
summarize_called = true
|
128
|
+
end
|
129
|
+
action do |uuid, event|
|
130
|
+
$stderr.puts( uuid )
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
results = observer.timers
|
135
|
+
|
136
|
+
expect( results ).to be_an( Array )
|
137
|
+
expect( results.size ).to eq( 1 )
|
138
|
+
expect( results ).to all( be_an(Array) )
|
139
|
+
expect( results.last[0] ).to eq( 5 )
|
140
|
+
|
141
|
+
observer.handle_event( "eventid", {} )
|
142
|
+
expect { results.last[1].call }.to change { summarize_called }.to( true )
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'arborist/subscription'
|
6
|
+
|
7
|
+
|
8
|
+
describe Arborist::Subscription do
|
9
|
+
|
10
|
+
let( :host_node ) do
|
11
|
+
Arborist::Node.create( 'host', 'testnode' ) do
|
12
|
+
description "Test host"
|
13
|
+
address '192.168.1.1'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
let( :service_node ) do
|
17
|
+
host_node.service( 'ssh' )
|
18
|
+
end
|
19
|
+
let( :publisher ) do
|
20
|
+
instance_double( Arborist::Manager::EventPublisher )
|
21
|
+
end
|
22
|
+
let( :subscription ) do
|
23
|
+
described_class.new( publisher, 'node.delta', type: 'host' )
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
it "generates a unique ID when it's created" do
|
28
|
+
expect( subscription.id ).to match( /^\S{16,}$/ )
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "publishes events which are of the desired type and have matching criteria" do
|
33
|
+
event = Arborist::Event.create( 'node_delta', host_node, status: ['up', 'down'] )
|
34
|
+
|
35
|
+
expect( publisher ).to receive( :publish ).with( subscription.id, event )
|
36
|
+
|
37
|
+
subscription.on_events( event )
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "publishes events which are of any type if the specified type is `nil`" do
|
42
|
+
subscription = described_class.new( publisher )
|
43
|
+
event1 = Arborist::Event.create( 'node_delta', host_node, status: ['up', 'down'] )
|
44
|
+
event2 = Arborist::Event.create( 'sys_reloaded' )
|
45
|
+
|
46
|
+
expect( publisher ).to receive( :publish ).with( subscription.id, event1 )
|
47
|
+
expect( publisher ).to receive( :publish ).with( subscription.id, event2 )
|
48
|
+
|
49
|
+
subscription.on_events( event1, event2 )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "doesn't publish events which are of the desired type but don't have matching criteria" do
|
54
|
+
event = Arborist::Event.create( 'node_delta', service_node, status: ['up', 'down'] )
|
55
|
+
|
56
|
+
expect( publisher ).to_not receive( :publish )
|
57
|
+
|
58
|
+
subscription.on_events( event )
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "doesn't publish events which have matching criteria but aren't of the desired type" do
|
63
|
+
event = Arborist::Event.create( 'node_update', host_node )
|
64
|
+
|
65
|
+
expect( publisher ).to_not receive( :publish )
|
66
|
+
|
67
|
+
subscription.on_events( event )
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env rspec -cfd
|
2
|
+
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
|
5
|
+
require 'pathname'
|
6
|
+
require 'arborist'
|
7
|
+
|
8
|
+
|
9
|
+
describe Arborist do
|
10
|
+
|
11
|
+
before( :all ) do
|
12
|
+
@original_config_env = ENV[Arborist::CONFIG_ENV]
|
13
|
+
@data_dir = Pathname( __FILE__ ).dirname + 'data'
|
14
|
+
@nodes_dir = @data_dir + 'nodes'
|
15
|
+
end
|
16
|
+
|
17
|
+
before( :each ) do
|
18
|
+
ENV.delete(Arborist::CONFIG_ENV)
|
19
|
+
end
|
20
|
+
|
21
|
+
after( :all ) do
|
22
|
+
ENV[Arborist::CONFIG_ENV] = @original_config_env
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
it "has a semantic version" do
|
27
|
+
expect( described_class::VERSION ).to match( /^\d+\.\d+\.\d+/ )
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
describe "configurability" do
|
32
|
+
|
33
|
+
before( :each ) do
|
34
|
+
Configurability.configure_objects( Configurability.default_config )
|
35
|
+
end
|
36
|
+
|
37
|
+
after( :all ) do
|
38
|
+
Configurability.reset
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
it "can return the loaded configuration" do
|
43
|
+
expect( described_class.config ).to be( Configurability.loaded_config )
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
it "knows whether or not the config has been loaded" do
|
48
|
+
expect( described_class ).to be_config_loaded
|
49
|
+
Configurability.reset
|
50
|
+
expect( described_class ).to_not be_config_loaded
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "will load a local config file if it exists and none is specified" do
|
55
|
+
config_object = double( "Configurability::Config object" )
|
56
|
+
allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
|
57
|
+
|
58
|
+
expect( Configurability ).to receive( :gather_defaults ).
|
59
|
+
and_return( {} )
|
60
|
+
expect( Arborist::LOCAL_CONFIG_FILE ).to receive( :exist? ).
|
61
|
+
and_return( true )
|
62
|
+
expect( Configurability::Config ).to receive( :load ).
|
63
|
+
with( Arborist::LOCAL_CONFIG_FILE, {} ).
|
64
|
+
and_return( config_object )
|
65
|
+
expect( config_object ).to receive( :install )
|
66
|
+
|
67
|
+
Arborist.load_config
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
it "will load a default config file if none is specified and there's no local config" do
|
72
|
+
config_object = double( "Configurability::Config object" )
|
73
|
+
allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
|
74
|
+
|
75
|
+
expect( Configurability ).to receive( :gather_defaults ).
|
76
|
+
and_return( {} )
|
77
|
+
expect( Arborist::LOCAL_CONFIG_FILE ).to receive( :exist? ).
|
78
|
+
and_return( false )
|
79
|
+
expect( Configurability::Config ).to receive( :load ).
|
80
|
+
with( Arborist::DEFAULT_CONFIG_FILE, {} ).
|
81
|
+
and_return( config_object )
|
82
|
+
expect( config_object ).to receive( :install )
|
83
|
+
|
84
|
+
Arborist.load_config
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it "will load a config file given in an environment variable" do
|
89
|
+
ENV['ARBORIST_CONFIG'] = '/usr/local/etc/config.yml'
|
90
|
+
|
91
|
+
config_object = double( "Configurability::Config object" )
|
92
|
+
allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
|
93
|
+
|
94
|
+
expect( Configurability ).to receive( :gather_defaults ).
|
95
|
+
and_return( {} )
|
96
|
+
expect( Configurability::Config ).to receive( :load ).
|
97
|
+
with( '/usr/local/etc/config.yml', {} ).
|
98
|
+
and_return( config_object )
|
99
|
+
expect( config_object ).to receive( :install )
|
100
|
+
|
101
|
+
Arborist.load_config
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
it "will load a config file and install it if one is given" do
|
106
|
+
config_object = double( "Configurability::Config object" )
|
107
|
+
allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
|
108
|
+
|
109
|
+
expect( Configurability ).to receive( :gather_defaults ).
|
110
|
+
and_return( {} )
|
111
|
+
expect( Configurability::Config ).to receive( :load ).
|
112
|
+
with( 'a/configfile.yml', {} ).
|
113
|
+
and_return( config_object )
|
114
|
+
expect( config_object ).to receive( :install )
|
115
|
+
|
116
|
+
Arborist.load_config( 'a/configfile.yml' )
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
it "will override default values when loading the config if they're given" do
|
121
|
+
config_object = double( "Configurability::Config object" )
|
122
|
+
allow( config_object ).to receive( :[] ).with( :arborist ).and_return( {} )
|
123
|
+
|
124
|
+
expect( Configurability ).to_not receive( :gather_defaults )
|
125
|
+
expect( Configurability::Config ).to receive( :load ).
|
126
|
+
with( 'a/different/configfile.yml', {database: {dbname: 'test'}} ).
|
127
|
+
and_return( config_object )
|
128
|
+
expect( config_object ).to receive( :install )
|
129
|
+
|
130
|
+
Arborist.load_config( 'a/different/configfile.yml', database: {dbname: 'test'} )
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
it "can load all nodes in a directory and return a manager for them" do
|
137
|
+
expect( described_class.manager_for(@nodes_dir) ).to be_a( Arborist::Manager )
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
it "has a ZMQ context" do
|
142
|
+
expect( described_class.zmq_context ).to be_a( ZMQ::Context )
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'loggability'
|
5
|
+
require 'arborist/monitor'
|
6
|
+
require 'arborist/mixins'
|
7
|
+
|
8
|
+
using Arborist::TimeRefinements
|
9
|
+
|
10
|
+
module FPingWrapper
|
11
|
+
extend Loggability
|
12
|
+
log_to :arborist
|
13
|
+
|
14
|
+
attr_accessor :identifiers
|
15
|
+
|
16
|
+
def exec_arguments( nodes )
|
17
|
+
self.identifiers = nodes.each_with_object({}) do |(identifier, props), hash|
|
18
|
+
next unless props.key?( 'addresses' )
|
19
|
+
address = props[ 'addresses' ].first
|
20
|
+
hash[ address ] = identifier
|
21
|
+
end
|
22
|
+
|
23
|
+
return {} if self.identifiers.empty?
|
24
|
+
|
25
|
+
return self.identifiers.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def handle_results( pid, stdout, stderr )
|
29
|
+
# 8.8.8.8 is alive (32.1 ms)
|
30
|
+
# 8.8.4.4 is alive (14.9 ms)
|
31
|
+
# 8.8.0.1 is unreachable
|
32
|
+
|
33
|
+
return stdout.each_line.with_object({}) do |line, hash|
|
34
|
+
address, remainder = line.split( ' ', 2 )
|
35
|
+
identifier = self.identifiers[ address ] or next
|
36
|
+
|
37
|
+
self.log.debug " parsing result for %s(%s): %p" % [ identifier, address, remainder ]
|
38
|
+
|
39
|
+
if remainder =~ /is alive \((\d+\.\d+) ms\)/
|
40
|
+
hash[ identifier ] = { rtt: Float( $1 ) }
|
41
|
+
else
|
42
|
+
hash[ identifier ] = { error: remainder.chomp }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
Arborist::Monitor 'ping check' do
|
51
|
+
every 20.seconds
|
52
|
+
splay 5.seconds
|
53
|
+
match type: 'host'
|
54
|
+
exclude tag: :laptop
|
55
|
+
use :addresses
|
56
|
+
|
57
|
+
exec 'fping', '-e', '-t', '150'
|
58
|
+
exec_callbacks( FPingWrapper )
|
59
|
+
end
|
60
|
+
|
61
|
+
Arborist::Monitor 'ping check downed hosts' do
|
62
|
+
every 40.seconds
|
63
|
+
splay 15.seconds
|
64
|
+
match type: 'host', status: 'down'
|
65
|
+
include_down true
|
66
|
+
use :addresses
|
67
|
+
|
68
|
+
exec 'fping', '-e', '-t', '150'
|
69
|
+
exec_callbacks( FPingWrapper )
|
70
|
+
end
|
71
|
+
|
72
|
+
Arborist::Monitor 'transient host pings' do
|
73
|
+
every 5.minutes
|
74
|
+
match type: 'host', tag: 'laptop'
|
75
|
+
use :addresses
|
76
|
+
|
77
|
+
exec 'fping', '-e', '-t', '500'
|
78
|
+
exec_callbacks( FPingWrapper )
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require 'arborist/monitor'
|
5
|
+
require 'arborist/monitor/socket'
|
6
|
+
|
7
|
+
using Arborist::TimeRefinements
|
8
|
+
|
9
|
+
Arborist::Monitor 'port checks on all tcp services' do
|
10
|
+
every 5.seconds
|
11
|
+
match type: 'service', protocol: 'tcp'
|
12
|
+
use :addresses, :port
|
13
|
+
exec( Arborist::Monitor::Socket::TCP )
|
14
|
+
end
|
15
|
+
|
16
|
+
Arborist::Monitor 'port checks on downed tcp services' do
|
17
|
+
every 10.seconds
|
18
|
+
match type: 'service', protocol: 'tcp', status: 'down'
|
19
|
+
include_down true
|
20
|
+
use :addresses, :port
|
21
|
+
exec( Arborist::Monitor::Socket::TCP )
|
22
|
+
end
|
23
|
+
|
24
|
+
# :MAHLON: A possible equivalent way to provide the above as a default for some monitors:
|
25
|
+
# Arborist::Monitor::Socket::TCP.default_monitor
|
26
|
+
|
27
|
+
|