reacter 0.0.1 → 0.0.2

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