reacter 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78f3df07bc48af15f0de952a22a18e0ef37cec7d
4
- data.tar.gz: e5269dca6726a6a13ff2caeb2ea637f32f35b4c2
3
+ metadata.gz: 85466ac92fce993fd08a3b321ac6080013cc0fba
4
+ data.tar.gz: 02b305a9c8195c60239e4801ab0507e7fd3748df
5
5
  SHA512:
6
- metadata.gz: 28b7fdd002e758c901dcf64968633c16b473a9517da311ef423eefce35fa3fa7c8975514980a00a702e2439f59efc13e156a308e24400c63c38f4ed16e526d80
7
- data.tar.gz: 0042e5ec4cba2ec33bad8d3562b33058c20c0847cb3ce07aafc8628d575c0ba4617447fae6e75618dd39dcc7439aa7e736b1f69ebc5052ecb2bb3cb229b25c7f
6
+ metadata.gz: 74fea13a574c9385fab7783b7d220c47acb692c4701123e093c518f0806e8e9c59e04c1502a84945359709f6d881f58cbd6bd193d0702fad12d4fffee1b302d4
7
+ data.tar.gz: 1eaeef3dc876f4b85f3e32dcb1700cfa91dffa76b35283c881c1e56e0f8407ec4c1caf73f4ebfcf9d84c61520ad15df4a2fb9dc9389e051320210373b4e14e46
data/bin/reacter ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # reacter - a utility for consuming, transforming, and routing monitoring data from various sources
4
+ #
5
+ # DESCRIPTION
6
+ # This script creates a mirror of a source directory, manipulating
7
+ # path components based on rules defined in dirmangle.yaml. The
8
+ # purpose of this is to provide links to (or copies of) data using
9
+ # an alternative directory tree.
10
+ #
11
+ # -C, --config FILE
12
+ # The location of the configuration YAML to load
13
+ #
14
+ # -o, --option NAME VALUE
15
+ # Set the option NAME to VALUE
16
+ #
17
+ # -v, --verbosity LEVEL
18
+ # Set the log verbosity level [debug, info (default), warn, error, fatal]
19
+ #
20
+ # AUTHOR
21
+ # Gary Hetzel <garyhetzel@gmail.com>
22
+ #
23
+ require 'reacter'
24
+ require 'optparse'
25
+
26
+ class Reacter
27
+ class CLI
28
+ class<<self
29
+ def run()
30
+ @options = {}
31
+
32
+ (OptionParser.new do |opts|
33
+ opts.banner = "Usage: reacter [options]"
34
+
35
+ # -----------------------------------------------------------------------------
36
+ opts.on('-C', '--config FILE', 'Configuration YAML location') do |file|
37
+ if File.exists?(file)
38
+ @options[:configfile] = file
39
+ else
40
+ raise "Configuration file #{file} does not exist"
41
+ end
42
+ end
43
+
44
+ # -----------------------------------------------------------------------------
45
+ @options[:options] = {}
46
+ opts.on('-o', '--option NAME=VALUE', 'Set the option NAME to VALUE') do |pair|
47
+ name, value = pair.split('=', 2)
48
+ @options[:options].set(name.strip.chomp, value) unless name.empty?
49
+ end
50
+
51
+ # -----------------------------------------------------------------------------
52
+ opts.on('-v', '--verbosity LEVEL', 'Logging verbosity') do |level|
53
+ level = level.to_s.downcase
54
+
55
+ if ['debug', 'info', 'warn', 'error', 'fatal'].include?(level)
56
+ @options[:loglevel] = level.to_sym
57
+ end
58
+ end
59
+ end).parse!
60
+
61
+ # options parsed, pass them to a new Reacter
62
+ Reacter.start(@options)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ Reacter::CLI.run()
@@ -14,8 +14,8 @@ class Reacter
14
14
  attr :config
15
15
  attr :type
16
16
 
17
- def initialize()
18
- @config = Reacter.get('global.adapter', {})
17
+ def initialize(config=nil)
18
+ @config = (config || Reacter.get('global.adapter', {}))
19
19
  @type = @config.get('type')
20
20
  Util.info("Loading adapter #{@type}...")
21
21
  end
@@ -27,12 +27,12 @@ class Reacter
27
27
 
28
28
  # implement: send a message suitable for consumption by an instance of reacter
29
29
  # in listen mode
30
- def send(message)
30
+ def send(message, format=nil)
31
31
  false
32
32
  end
33
33
 
34
34
  # implement: poll for a new message and return it
35
- def poll()
35
+ def poll(&block)
36
36
  false
37
37
  end
38
38
 
@@ -42,11 +42,11 @@ class Reacter
42
42
  end
43
43
 
44
44
  class<<self
45
- def create(type)
45
+ def create(type, config=nil)
46
46
  if type
47
47
  begin
48
48
  require "reacter/adapters/#{type}"
49
- rv = (Reacter.const_get("#{type.capitalize}Adapter").new())
49
+ rv = (Reacter.const_get("#{type.capitalize}Adapter").new(config))
50
50
  return rv
51
51
 
52
52
  rescue LoadError
@@ -28,7 +28,7 @@ class Reacter
28
28
  @_exchange = @config.get(:exchange, '')
29
29
  end
30
30
 
31
- def send(message)
31
+ def send(message, format=nil)
32
32
  false
33
33
  end
34
34
 
@@ -12,18 +12,33 @@ class Reacter
12
12
  @_stdin = true
13
13
  else
14
14
  @_stdin = false
15
- @_input = File.open(File.expand_path(@config.get(:filename)), 'r+')
15
+
16
+ readfile = (@config.get('filename') || @config.get('file.read'))
17
+ writefile = @config.get('file.write')
18
+
19
+ @_input = File.open(File.expand_path(readfile), 'r+') if readfile
20
+ @_output = File.open(File.expand_path(writefile), 'a') if writefile
16
21
  end
17
22
  end
18
23
 
19
- def send(message)
20
- false
24
+ def send(message, format=nil)
25
+ if defined?(@_output)
26
+ message = Message.dump(message, format)
27
+ @_output.puts(message) if message
28
+ @_output.flush()
29
+ else
30
+ Util.warn("file: Attempting to send without a valid output file handle")
31
+ end
21
32
  end
22
33
 
23
34
  def poll(&block)
24
- loop do
25
- line = @_input.gets
26
- yield Message.parse(line)
35
+ if @_input
36
+ loop do
37
+ line = @_input.gets
38
+ yield Message.parse(line)
39
+ end
40
+ else
41
+ Util.warn("file: Attempting to poll without a valid input file handle")
27
42
  end
28
43
  end
29
44
 
@@ -7,8 +7,56 @@ class Reacter
7
7
  class DeciderAgent < Agent
8
8
  class EmitAlert < Exception; end
9
9
 
10
+ # local memory persistence mechanism for state tracking
11
+ class MemoryPersist
12
+ class<<self
13
+ def setup()
14
+ @_alerts = {}
15
+ end
16
+
17
+ def init(source, metric)
18
+ # initialize in-memory alert tracking (per source, per metric)
19
+ @_alerts[source] ||= {}
20
+ @_alerts[source][metric] ||= ({
21
+ :count => 1,
22
+ :last_state => nil,
23
+ :last_seen => nil,
24
+ :new_alert => false,
25
+ :has_ever_failed => false
26
+ })
27
+ end
28
+
29
+ def get(source, metric, key)
30
+ @_alerts[source][metric][key] rescue nil
31
+ end
32
+
33
+ def set(source, metric, key, value)
34
+ (@_alerts[source][metric][key] = value) rescue nil
35
+ end
36
+ end
37
+ end
38
+
39
+ # redis persistence mechanism for shared state tracking
40
+ class RedisPersist
41
+ #require 'redis'
42
+ class<<self
43
+ def setup()
44
+ end
45
+
46
+ def init(source, metric)
47
+ end
48
+
49
+ def get(source, metric, key)
50
+ end
51
+
52
+ def set(source, metric, key, value)
53
+ end
54
+ end
55
+ end
56
+
10
57
  STATES=[:okay, :warning, :critical]
11
58
  COMPARISONS=%w{not is above below}
59
+ DEFAULT_PERSISTENCE='memory'
12
60
 
13
61
  def initialize()
14
62
  super
@@ -18,7 +66,8 @@ class Reacter
18
66
  @_state_names[STATES[i]] = i
19
67
  end
20
68
 
21
- @_alerts = {}
69
+ @_persistence = DeciderAgent.const_get(@config.get('options.persistence.type', DEFAULT_PERSISTENCE).capitalize+'Persist')
70
+ @_persistence.setup()
22
71
  end
23
72
 
24
73
  def received(message)
@@ -30,9 +79,12 @@ class Reacter
30
79
  return false unless @config['sources']['any']
31
80
  end
32
81
 
82
+ s = message.source
83
+ m = message.metric
84
+
33
85
  # build effective configuration rule for this message
34
- general = (@config['sources']['any'][message.metric] or {} rescue {})
35
- specific = (@config['sources'][message.source][message.metric] or {} rescue {})
86
+ general = (@config['sources']['any'][m] or {} rescue {})
87
+ specific = (@config['sources'][s][m] or {} rescue {})
36
88
  rule = specific.deep_merge!(general)
37
89
 
38
90
  # quit early for empty rules
@@ -41,18 +93,17 @@ class Reacter
41
93
  # quit early for invalid rules
42
94
  return false unless (rule['threshold'] and rule['actions'])
43
95
 
96
+ # passthrough mode
97
+ # if in passthrough mode, all messages that are present in the configuration
98
+ # will be passed regardless of state. this is useful in conjunction with
99
+ # the relay agent to offload the decision making to another reacter instance
100
+ return message if (@config['options'] and @config['options']['passthrough'])
101
+
44
102
  # default return value: false
45
103
  rv = false
46
104
 
47
- # initialize in-memory alert tracking (per source, per metric)
48
- @_alerts[message.source] ||= {}
49
- @_alerts[message.source][message.metric] ||= ({
50
- :count => 1,
51
- :last_state => nil,
52
- :last_seen => nil,
53
- :new_alert => false,
54
- :has_ever_failed => false
55
- })
105
+ # initialize persistence for this source/metric
106
+ persist_init(s,m)
56
107
 
57
108
  # get current state of this message according to the rule
58
109
  state, comparison = state_of(message, rule)
@@ -60,24 +111,24 @@ class Reacter
60
111
  begin
61
112
  # a non-okay alert has occurred, this metric can now be said to have failed
62
113
  unless state == :okay
63
- @_alerts[message.source][message.metric][:has_ever_failed] = true
114
+ persist_set(s,m, :has_ever_failed, true)
64
115
  end
65
116
 
66
117
  # the state has changed, reset counter
67
- if @_alerts[message.source][message.metric][:last_state] != state
68
- @_alerts[message.source][message.metric][:new_alert] = true
69
- @_alerts[message.source][message.metric][:count] = 1
118
+ if persist_get(s,m, :last_state) != state
119
+ persist_set(s,m, :new_alert, true)
120
+ persist_set(s,m, :count, 1)
70
121
  end
71
122
 
72
123
  hits = ((rule['threshold'][state.to_s][hits] rescue rule['hits']) or rule['hits'] or 1).to_i
73
124
  persist = ((rule['threshold'][state.to_s]['persist'] === true) rescue false)
74
125
 
75
126
  # only raise every n alerts
76
- if @_alerts[message.source][message.metric][:count] >= hits
127
+ if persist_get(s,m, :count) >= hits
77
128
  # raise the alert if it's new or if we're persistently reminding of alerts
78
- if @_alerts[message.source][message.metric][:new_alert] or persist
129
+ if persist_get(s,m, :new_alert) or persist
79
130
  # only emit if this metric has ever failed
80
- if @_alerts[message.source][message.metric][:has_ever_failed]
131
+ if persist_get(s,m, :has_ever_failed)
81
132
  raise EmitAlert
82
133
  end
83
134
  end
@@ -93,12 +144,12 @@ class Reacter
93
144
  rv = message
94
145
 
95
146
  # alert is emitted, it's not new anymore
96
- @_alerts[message.source][message.metric][:new_alert] = false
147
+ persist_set(s,m, :new_alert, false)
97
148
 
98
149
  ensure
99
- @_alerts[message.source][message.metric][:count] += 1
100
- @_alerts[message.source][message.metric][:last_state] = state
101
- @_alerts[message.source][message.metric][:last_seen] = Time.now.to_i
150
+ persist_set(s,m, :count, persist_get(s,m, :count) + 1)
151
+ persist_set(s,m, :last_state, state)
152
+ persist_set(s,m, :last_seen, Time.now.to_i)
102
153
  end
103
154
 
104
155
  return rv
@@ -143,6 +194,18 @@ class Reacter
143
194
  return false
144
195
  end
145
196
  end
197
+
198
+ def persist_init(source, metric)
199
+ @_persistence.init(source, metric)
200
+ end
201
+
202
+ def persist_get(source, metric, key)
203
+ @_persistence.get(source, metric, key)
204
+ end
205
+
206
+ def persist_set(source, metric, key, value)
207
+ @_persistence.set(source, metric, key, value)
208
+ end
146
209
  end
147
210
  end
148
211
 
@@ -6,7 +6,8 @@ require 'reacter/agent'
6
6
  class Reacter
7
7
  class LoggerAgent < Agent
8
8
  def received(message)
9
- Util.info("LOGGER: [#{message.state or :unknown}] #{message.source}/#{message.metric}")
9
+ Util.info("logger: [#{message.state or :unknown}] #{message.source}/#{message.metric}")
10
+ message
10
11
  end
11
12
  end
12
13
  end
data/lib/reacter/core.rb CHANGED
@@ -16,7 +16,7 @@ class Reacter
16
16
  @_adapter = nil
17
17
  @_agents = []
18
18
 
19
- Reacter.load_config()
19
+ Reacter.load_config(args.first || {})
20
20
 
21
21
  load_adapters()
22
22
  load_agents()
@@ -92,9 +92,9 @@ class Reacter
92
92
  end
93
93
 
94
94
  class<<self
95
- def start()
95
+ def start(config={})
96
96
  EM.run do
97
- reacter = EM.connect('127.0.0.1', 9000, Reacter::Core)
97
+ reacter = EM.connect('127.0.0.1', 9000, Reacter::Core, config[:options])
98
98
  reacter.run()
99
99
  end
100
100
  end
@@ -5,6 +5,7 @@ class Reacter
5
5
  class Message
6
6
  require 'hashlib'
7
7
  DEFAULT_SOURCE_NAME='default'
8
+ DEFAULT_FORMAT=:json
8
9
 
9
10
  def initialize(message={})
10
11
  @_data = {
@@ -52,6 +53,20 @@ class Reacter
52
53
  end
53
54
  end
54
55
 
56
+ def dump(message, format=nil)
57
+ load_parsers() unless defined?(@@_parsers)
58
+
59
+ if @@_parsers
60
+ format = DEFAULT_FORMAT unless format
61
+
62
+ if @@_parsers.has_key?(format.to_sym)
63
+ return @@_parsers[format.to_sym].dump(message)
64
+ end
65
+ end
66
+
67
+ return nil
68
+ end
69
+
55
70
  def parse(body)
56
71
  load_parsers()
57
72
 
@@ -22,6 +22,22 @@ class Reacter
22
22
  :attributes => attributes
23
23
  }
24
24
  end
25
+
26
+ def dump(message)
27
+ message = message.to_h if message.is_a?(Message)
28
+ return nil unless message.is_a?(Hash)
29
+ return nil unless message[:source]
30
+ return nil unless message[:metric]
31
+ return nil unless message[:value]
32
+ return nil unless message[:time]
33
+
34
+ ([
35
+ 'PUTVAL',
36
+ "#{message[:source]}/#{message[:metric]}",
37
+ (message[:attributes] || {}).collect{|k,v| "#{k}=#{v}"}.join(';'),
38
+ "#{(message[:time] / 1000).to_i}:#{message[:value]}"
39
+ ].join(' ')).strip
40
+ end
25
41
  end
26
42
  end
27
43
  end
@@ -9,6 +9,10 @@ class Reacter
9
9
  def parse(message)
10
10
  nil
11
11
  end
12
+
13
+ def dump(message)
14
+ nil
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -20,6 +20,17 @@ class Reacter
20
20
  :time => (message[2].to_i * 1000)
21
21
  }
22
22
  end
23
+
24
+ def dump(message)
25
+ message = message.to_h if message.is_a?(Message)
26
+ return nil unless message.is_a?(Hash)
27
+ #return nil unless message[:source]
28
+ return nil unless message[:metric]
29
+ return nil unless message[:value]
30
+ return nil unless message[:time]
31
+
32
+ "#{message[:metric]} #{message[:value]} #{(message[:time] / 1000).to_i}".strip
33
+ end
23
34
  end
24
35
  end
25
36
  end
@@ -13,6 +13,12 @@ class Reacter
13
13
  def parse(message)
14
14
  ([*JSON.load(message)] rescue [])
15
15
  end
16
+
17
+ def dump(message)
18
+ message = message.to_h if message.is_a?(Message)
19
+ return nil unless message.is_a?(Hash)
20
+ JSON.dump(message)
21
+ end
16
22
  end
17
23
  end
18
24
  end
@@ -18,6 +18,27 @@ class Reacter
18
18
  :attributes => Hash[message[4..-1].collect{|i| i.split('=',2) }]
19
19
  }
20
20
  end
21
+
22
+ def dump(message)
23
+ message = message.to_h if message.is_a?(Message)
24
+ return nil unless message.is_a?(Hash)
25
+ return nil unless message[:source]
26
+ return nil unless message[:metric]
27
+ return nil unless message[:value]
28
+ return nil unless message[:time]
29
+
30
+ attributes = {
31
+ :host => message[:source]
32
+ }.merge(message[:attributes] || {})
33
+
34
+ ([
35
+ 'PUT',
36
+ message[:metric],
37
+ (message[:time] / 1000).to_i,
38
+ message[:value],
39
+ attributes.collect{|k,v| "#{k}=#{v}" }.join(' ')
40
+ ].join(' ')).strip
41
+ end
21
42
  end
22
43
  end
23
44
  end
data/lib/reacter/util.rb CHANGED
@@ -4,11 +4,16 @@ class Reacter
4
4
  class Util
5
5
  class<<self
6
6
  require 'logger'
7
+ require 'socket'
7
8
 
8
9
  @@_logger = {
9
10
  :default => Logger.new(STDOUT)
10
11
  }
11
12
 
13
+ def signature(custom=nil)
14
+ [%x{hostname -f}, Process.pid, custom].compact.join(':')
15
+ end
16
+
12
17
  def log(message, severity=:info, log=:default)
13
18
  @@_logger[log] = Logger.new(STDOUT) unless @@_logger[log]
14
19
  @@_logger[log].send(severity, [*message].join(' '))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reacter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gary Hetzel
@@ -55,7 +55,8 @@ dependencies:
55
55
  description: A utility for consuming, transforming, and routing monitoring data from
56
56
  various sources
57
57
  email: ghetzel@outbrain.com
58
- executables: []
58
+ executables:
59
+ - reacter
59
60
  extensions: []
60
61
  extra_rdoc_files: []
61
62
  files:
@@ -75,6 +76,7 @@ files:
75
76
  - lib/reacter/parsers/json.rb
76
77
  - lib/reacter/parsers/tsdb.rb
77
78
  - lib/reacter/util.rb
79
+ - bin/reacter
78
80
  homepage: http://outbrain.github.com/reacter/
79
81
  licenses: []
80
82
  metadata: {}