reacter 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 78f3df07bc48af15f0de952a22a18e0ef37cec7d
4
+ data.tar.gz: e5269dca6726a6a13ff2caeb2ea637f32f35b4c2
5
+ SHA512:
6
+ metadata.gz: 28b7fdd002e758c901dcf64968633c16b473a9517da311ef423eefce35fa3fa7c8975514980a00a702e2439f59efc13e156a308e24400c63c38f4ed16e526d80
7
+ data.tar.gz: 0042e5ec4cba2ec33bad8d3562b33058c20c0847cb3ce07aafc8628d575c0ba4617447fae6e75618dd39dcc7439aa7e736b1f69ebc5052ecb2bb3cb229b25c7f
data/lib/reacter.rb ADDED
@@ -0,0 +1 @@
1
+ require 'reacter/core'
@@ -0,0 +1,61 @@
1
+ #------------------------------------------------------------------------------#
2
+ # Adapter
3
+ #
4
+ require 'reacter/message'
5
+ require 'reacter/config'
6
+ require 'reacter/util'
7
+
8
+ class AdapterConnectionFailed < Exception; end
9
+ class AdapterConnectionFaulted < Exception; end
10
+ class AdapterConnectionClosed < Exception; end
11
+
12
+ class Reacter
13
+ class Adapter
14
+ attr :config
15
+ attr :type
16
+
17
+ def initialize()
18
+ @config = Reacter.get('global.adapter', {})
19
+ @type = @config.get('type')
20
+ Util.info("Loading adapter #{@type}...")
21
+ end
22
+
23
+ # implement: connect to the data source
24
+ def connect(args={})
25
+ false
26
+ end
27
+
28
+ # implement: send a message suitable for consumption by an instance of reacter
29
+ # in listen mode
30
+ def send(message)
31
+ false
32
+ end
33
+
34
+ # implement: poll for a new message and return it
35
+ def poll()
36
+ false
37
+ end
38
+
39
+ # implement: manual disconnect / cleanup
40
+ def disconnect()
41
+ raise AdapterConnectionClosed
42
+ end
43
+
44
+ class<<self
45
+ def create(type)
46
+ if type
47
+ begin
48
+ require "reacter/adapters/#{type}"
49
+ rv = (Reacter.const_get("#{type.capitalize}Adapter").new())
50
+ return rv
51
+
52
+ rescue LoadError
53
+ nil
54
+ end
55
+ end
56
+
57
+ nil
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,45 @@
1
+ #------------------------------------------------------------------------------#
2
+ # AMQP Adapter
3
+ #
4
+ require 'reacter/adapter'
5
+ require 'reacter/config'
6
+ require 'reacter/util'
7
+
8
+ class Reacter
9
+ class AmqpAdapter < Adapter
10
+ require 'amqp'
11
+
12
+ DEFAULT_QUEUENAME='reacter'
13
+
14
+ def connect(args={})
15
+ @_connection = AMQP.connect({
16
+ :host => @config.get(:hostname, 'localhost'),
17
+ :port => @config.get(:port, 5672),
18
+ :username => @config.get(:username, 'guest'),
19
+ :password => @config.get(:password, 'guest'),
20
+ :vhost => @config.get(:vhost, '/')
21
+ })
22
+
23
+ @_channel = AMQP::Channel.new(@_connection)
24
+ @_queue = @_channel.queue(@config.get(:queue, DEFAULT_QUEUENAME), {
25
+ :auto_delete => @config.get(:autodelete, true)
26
+ })
27
+
28
+ @_exchange = @config.get(:exchange, '')
29
+ end
30
+
31
+ def send(message)
32
+ false
33
+ end
34
+
35
+ def poll(&block)
36
+ @_queue.bind(@_exchange).subscribe do |payload|
37
+ yield Message.parse(payload)
38
+ end
39
+ end
40
+
41
+ def disconnect()
42
+ raise AdapterConnectionClosed
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,39 @@
1
+ #------------------------------------------------------------------------------#
2
+ # Standard Input Adapter
3
+ #
4
+ require 'reacter/adapter'
5
+ require 'reacter/config'
6
+ require 'reacter/util'
7
+
8
+ class Reacter
9
+ class FileAdapter < Adapter
10
+ def connect(args={})
11
+ if @config.get(:filename) == 'stdin'
12
+ @_stdin = true
13
+ else
14
+ @_stdin = false
15
+ @_input = File.open(File.expand_path(@config.get(:filename)), 'r+')
16
+ end
17
+ end
18
+
19
+ def send(message)
20
+ false
21
+ end
22
+
23
+ def poll(&block)
24
+ loop do
25
+ line = @_input.gets
26
+ yield Message.parse(line)
27
+ end
28
+ end
29
+
30
+ def disconnect()
31
+ raise AdapterConnectionClosed
32
+ end
33
+
34
+ private
35
+ def stdin?()
36
+ @_stdin
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ #------------------------------------------------------------------------------#
2
+ # Agent
3
+ #
4
+ require 'reacter/message'
5
+ require 'reacter/config'
6
+ require 'reacter/util'
7
+
8
+ class Reacter
9
+ class Agent
10
+ attr :config
11
+ attr :type
12
+
13
+ def initialize()
14
+ @type = self.class.name.sub('Agent', '').split('::').last.downcase
15
+ @config = Reacter.get("agents.#{@type}", {})
16
+ Util.info("Loading agent #{@type}...")
17
+ end
18
+
19
+ def received(message)
20
+ false
21
+ end
22
+
23
+ class<<self
24
+ def create(type)
25
+ if type
26
+ begin
27
+ agentpath = Reacter['global.agents.path']
28
+ $: << File.expand_path(agentpath) if agentpath
29
+
30
+ require "reacter/agents/#{type}"
31
+ rv = (Reacter.const_get("#{type.capitalize}Agent").new())
32
+ return rv
33
+
34
+ rescue LoadError
35
+ nil
36
+ end
37
+ end
38
+
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,159 @@
1
+ #------------------------------------------------------------------------------#
2
+ # DeciderAgent
3
+ #
4
+ require 'reacter/agent'
5
+
6
+ class Reacter
7
+ class DeciderAgent < Agent
8
+ class EmitAlert < Exception; end
9
+
10
+ STATES=[:okay, :warning, :critical]
11
+ COMPARISONS=%w{not is above below}
12
+
13
+ def initialize()
14
+ super
15
+
16
+ @_state_names = {}
17
+ STATES.each_index do |i|
18
+ @_state_names[STATES[i]] = i
19
+ end
20
+
21
+ @_alerts = {}
22
+ end
23
+
24
+ def received(message)
25
+ # early exit conditions
26
+ return false unless @config
27
+ return false unless @config['sources']
28
+ return false unless message.metric
29
+ unless message.source
30
+ return false unless @config['sources']['any']
31
+ end
32
+
33
+ # 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 {})
36
+ rule = specific.deep_merge!(general)
37
+
38
+ # quit early for empty rules
39
+ return false if rule.empty?
40
+
41
+ # quit early for invalid rules
42
+ return false unless (rule['threshold'] and rule['actions'])
43
+
44
+ # default return value: false
45
+ rv = false
46
+
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
+ })
56
+
57
+ # get current state of this message according to the rule
58
+ state, comparison = state_of(message, rule)
59
+
60
+ begin
61
+ # a non-okay alert has occurred, this metric can now be said to have failed
62
+ unless state == :okay
63
+ @_alerts[message.source][message.metric][:has_ever_failed] = true
64
+ end
65
+
66
+ # 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
70
+ end
71
+
72
+ hits = ((rule['threshold'][state.to_s][hits] rescue rule['hits']) or rule['hits'] or 1).to_i
73
+ persist = ((rule['threshold'][state.to_s]['persist'] === true) rescue false)
74
+
75
+ # only raise every n alerts
76
+ if @_alerts[message.source][message.metric][:count] >= hits
77
+ # 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
79
+ # only emit if this metric has ever failed
80
+ if @_alerts[message.source][message.metric][:has_ever_failed]
81
+ raise EmitAlert
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ rescue EmitAlert
88
+ message[:state] = state
89
+ message[:comparison] = comparison
90
+ message[:check_value] = (rule['threshold'][state][comparison] rescue nil)
91
+ message[:alerted_at] = Time.now.to_i
92
+
93
+ rv = message
94
+
95
+ # alert is emitted, it's not new anymore
96
+ @_alerts[message.source][message.metric][:new_alert] = false
97
+
98
+ 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
102
+ end
103
+
104
+ return rv
105
+ end
106
+
107
+
108
+ private
109
+
110
+ def state_of(message, rule)
111
+ worst_state = 0
112
+ comparison = nil
113
+
114
+ # for each state, add all breached states
115
+ rule['threshold'].each do |state, check|
116
+ state = state.to_sym
117
+
118
+ check.each do |comp, test|
119
+ if compare(message.value, comp, test)
120
+ # this breached state is worse than what we've seen, make it the worst
121
+ if @_state_names[state] > worst_state
122
+ worst_state = @_state_names[state]
123
+ comparison = comp
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ return [STATES[worst_state], comparison]
130
+ end
131
+
132
+ def compare(value, comparison, test)
133
+ case comparison.to_sym
134
+ when :not
135
+ return (value != test)
136
+ when :is
137
+ return (value == test)
138
+ when :above
139
+ return (value > test)
140
+ when :below
141
+ return (value < test)
142
+ else
143
+ return false
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+
150
+ # {"complements"=>"df-var.df_complex-used",
151
+ # "attributes"=>{
152
+ # "owner"=>"This Guy <tguy@outbrain.com>",
153
+ # "description"=>"",
154
+ # "neon"=>{"url"=>"http://localhost:5000/alert/new"}},
155
+ # "actions"=>{
156
+ # "exec"=>["~/Dropbox/Outbrain/reacter-notify"]},
157
+ # "threshold"=>{
158
+ # "warning"=>{
159
+ # "below"=>"5%"}}}
@@ -0,0 +1,12 @@
1
+ #------------------------------------------------------------------------------#
2
+ # LoggerAgent
3
+ #
4
+ require 'reacter/agent'
5
+
6
+ class Reacter
7
+ class LoggerAgent < Agent
8
+ def received(message)
9
+ Util.info("LOGGER: [#{message.state or :unknown}] #{message.source}/#{message.metric}")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,81 @@
1
+ class Reacter
2
+ class<<self
3
+ require 'yaml'
4
+
5
+ DEFAULT_CONFIG={
6
+ 'global' => {
7
+ 'searchpath' => ['~/.reacter', '/etc/reacter'],
8
+ 'configs' => [
9
+ '~/.reacter/reacter.yaml',
10
+ '/etc/reacter/reacter.yaml'
11
+ ]
12
+ }
13
+ }
14
+
15
+ def load_config(config={})
16
+ @_config = DEFAULT_CONFIG
17
+ default_path = DEFAULT_CONFIG.get('global.configs')
18
+
19
+ # load from default paths
20
+ default_path.each do |path|
21
+ _load_file(path)
22
+ end
23
+
24
+ # load any supplemental configs
25
+ @_config.get('global.searchpath').each do |dir|
26
+ # adapters
27
+ adapter = @_config.get('global.adapter.type')
28
+ _load_file(File.join(dir, 'adapters', "#{adapter}.yaml")) if adapter
29
+
30
+ # agents
31
+ @_config.get('global.agents.enabled', []).each do |agent|
32
+ _load_file(File.join(dir, 'agents', "#{agent}.yaml"))
33
+ end
34
+ end
35
+
36
+ # load from any new paths that were loaded
37
+ (@_config.get('global.configs') - default_path).each do |path|
38
+ _load_file(path)
39
+ end
40
+
41
+ @_config = config.deep_merge!(@_config, {
42
+ :merge_hash_arrays => true
43
+ })
44
+ end
45
+
46
+ def [](key)
47
+ get(key)
48
+ end
49
+
50
+ def []=(key, value)
51
+ set(key, value)
52
+ end
53
+
54
+ def get(path, default=nil)
55
+ @_config.get(path, default)
56
+ end
57
+
58
+ def set(path, value)
59
+ @_config.set(path, value)
60
+ end
61
+
62
+ def dump_config()
63
+ @_config
64
+ end
65
+
66
+
67
+ private
68
+ def _load_file(path)
69
+ path = File.expand_path(path)
70
+
71
+ if File.exists?(path)
72
+ Util.info("Loading configuration #{path}...")
73
+ yaml = YAML.load(File.read(path))
74
+
75
+ @_config = yaml.deep_merge!(@_config, {
76
+ :merge_hash_arrays => true
77
+ })
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,102 @@
1
+ require 'eventmachine'
2
+ require 'deep_merge'
3
+ require 'hashlib'
4
+ require 'reacter/message'
5
+ require 'reacter/config'
6
+ require 'reacter/util'
7
+ require 'reacter/adapter'
8
+ require 'reacter/agent'
9
+
10
+ class Reacter
11
+ class Core < EM::Connection
12
+ def initialize(*args)
13
+ super
14
+
15
+ @_dispatch_queue = EM::Queue.new
16
+ @_adapter = nil
17
+ @_agents = []
18
+
19
+ Reacter.load_config()
20
+
21
+ load_adapters()
22
+ load_agents()
23
+ end
24
+
25
+ def load_adapters()
26
+ adapter_config = Reacter.get('global.adapter', nil)
27
+
28
+ if adapter_config
29
+ type = adapter_config.get('type')
30
+
31
+ @_adapter = Adapter.create(type)
32
+
33
+ if @_adapter
34
+ @_adapter.connect()
35
+ else
36
+ raise "Adapter '#{type}' not found, exiting"
37
+ exit 1
38
+ end
39
+ else
40
+ Util.fatal("No adapters specified, exiting")
41
+ exit 1
42
+ end
43
+ end
44
+
45
+ def load_agents()
46
+ Reacter.get('global.agents.enabled', []).each do |agent|
47
+ agent = Agent.create(agent)
48
+ @_agents << agent if agent
49
+ end
50
+
51
+ if @_agents.empty?
52
+ Util.fatal("No agents enabled, exiting")
53
+ exit 1
54
+ end
55
+ end
56
+
57
+ def run()
58
+ Util.info("Start listening using #{@_adapter.type} adapter...")
59
+
60
+
61
+ # agent message dispatch subprocess
62
+ dispatch = proc do |messages|
63
+ messages.each do |message|
64
+ rv = message
65
+
66
+ # send this message to all agents
67
+ @_agents.each do |agent|
68
+ next if rv === false
69
+ rv = agent.received(rv)
70
+ end
71
+ end
72
+ end
73
+
74
+ # enter polling loop
75
+ begin
76
+ @_adapter.poll do |messages|
77
+ @_dispatch_queue.push(messages)
78
+ @_dispatch_queue.pop(dispatch)
79
+ end
80
+
81
+ rescue AdapterConnectionFailed => e
82
+ Util.error("Adapter connection failed: #{e.message}")
83
+
84
+ rescue AdapterConnectionFaulted => e
85
+ Util.error("Adapter connection error: #{e.message}")
86
+
87
+ rescue AdapterConnectionClosed => e
88
+ Util.info("Adapter closed connection")
89
+
90
+ end
91
+ end
92
+ end
93
+
94
+ class<<self
95
+ def start()
96
+ EM.run do
97
+ reacter = EM.connect('127.0.0.1', 9000, Reacter::Core)
98
+ reacter.run()
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,90 @@
1
+ require 'reacter/util'
2
+ require 'reacter/config'
3
+
4
+ class Reacter
5
+ class Message
6
+ require 'hashlib'
7
+ DEFAULT_SOURCE_NAME='default'
8
+
9
+ def initialize(message={})
10
+ @_data = {
11
+ :raw => message,
12
+ :time => (Time.now.to_i * 1000),
13
+ :attributes => {},
14
+ :metric => nil,
15
+ :source => DEFAULT_SOURCE_NAME
16
+ }.merge(message)
17
+
18
+ if not @_data.has_key?(:event) and not @_data.has_key?(:value)
19
+ @_data[:event] = true
20
+ else
21
+ @_data[:event] = false
22
+ end
23
+ end
24
+
25
+ def to_h
26
+ @_data
27
+ end
28
+
29
+ def [](key)
30
+ @_data[key.to_sym]
31
+ end
32
+
33
+ def []=(key, value)
34
+ @_data[key.to_sym] = value
35
+ end
36
+
37
+ def method_missing(name, *args)
38
+ @_data[name.to_sym] or args.first
39
+ end
40
+
41
+ class<<self
42
+ def load_parsers()
43
+ unless defined?(@@_parsers)
44
+ @@_parsers = {}
45
+
46
+ # load and initialize parsers
47
+ Dir[File.join(File.dirname(__FILE__), 'parsers', '*.rb')].each do |i|
48
+ i = File.basename(i,'.rb')
49
+ require "reacter/parsers/#{i}"
50
+ @@_parsers[i.to_sym] = Message.const_get("#{i.capitalize}Parser")
51
+ end
52
+ end
53
+ end
54
+
55
+ def parse(body)
56
+ load_parsers()
57
+
58
+ # split strings into lines
59
+ body = body.lines if body.is_a?(String)
60
+ messages = []
61
+
62
+ if body.respond_to?(:each)
63
+ body.each do |message|
64
+ # strings need to be detected and parsed
65
+ if message.is_a?(String)
66
+ next if message.strip.chomp.empty?
67
+
68
+ # use first parser that claims it can handle this string
69
+ @@_parsers.each do |name, parser|
70
+ if parser.detected?(message)
71
+ #Util.debug("Using parser #{name}")
72
+ m = parser.parse(message)
73
+ (m.is_a?(Array) ? messages += m : messages << m)
74
+ next
75
+ end
76
+ end
77
+
78
+ # hashes go directly into the stack
79
+ elsif message.is_a?(Hash)
80
+ messages << message
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ messages.collect{|i| Message.new(i) }
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ require 'reacter/parsers/default'
2
+
3
+ class Reacter
4
+ class Message
5
+ class CollectdParser < DefaultParser
6
+ class<<self
7
+ def detected?(message)
8
+ (message[0..6] == 'PUTVAL ')
9
+ end
10
+
11
+ def parse(message)
12
+ message = message.split(' ')
13
+ source, plugin, type = message[1].split('/')
14
+ attributes = Hash[message[2].split(';').collect{|i| i.split('=',2) }]
15
+ time, value = message[3].split(':')
16
+
17
+ {
18
+ :source => source,
19
+ :metric => [plugin, type].join('.'),
20
+ :time => (time.to_i * 1000),
21
+ :value => value.to_f,
22
+ :attributes => attributes
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ class Reacter
2
+ class Message
3
+ class DefaultParser
4
+ class<<self
5
+ def detected?(message)
6
+ false
7
+ end
8
+
9
+ def parse(message)
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require 'reacter/parsers/default'
2
+
3
+ class Reacter
4
+ class Message
5
+ class GraphiteParser < DefaultParser
6
+ class<<self
7
+ # Graphite
8
+ # |metric-----|value|epoch----|
9
+ # => metric.name 12345 123456789
10
+ def detected?(message)
11
+ (message =~ /^[\w\.]+ [\d\.\-]+ \d+$/ ? true : false)
12
+ end
13
+
14
+ def parse(message)
15
+ message = message.split(' ')
16
+
17
+ {
18
+ :metric => message[0],
19
+ :value => message[1].to_f,
20
+ :time => (message[2].to_i * 1000)
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ require 'reacter/parsers/default'
2
+
3
+ class Reacter
4
+ class Message
5
+ class JsonParser < DefaultParser
6
+ class<<self
7
+ require 'json'
8
+
9
+ def detected?(message)
10
+ (message[0] == '{' or message[0] == '[')
11
+ end
12
+
13
+ def parse(message)
14
+ ([*JSON.load(message)] rescue [])
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ require 'reacter/parsers/default'
2
+
3
+ class Reacter
4
+ class Message
5
+ class TsdbParser < DefaultParser
6
+ class<<self
7
+ def detected?(message)
8
+ return (message.upcase[0..4] == 'PUT ')
9
+ end
10
+
11
+ def parse(message)
12
+ message = message.split(' ')
13
+
14
+ {
15
+ :metric => message[1],
16
+ :time => (message[2].to_i * 1000),
17
+ :value => message[3].to_f,
18
+ :attributes => Hash[message[4..-1].collect{|i| i.split('=',2) }]
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require 'reacter/config'
2
+
3
+ class Reacter
4
+ class Util
5
+ class<<self
6
+ require 'logger'
7
+
8
+ @@_logger = {
9
+ :default => Logger.new(STDOUT)
10
+ }
11
+
12
+ def log(message, severity=:info, log=:default)
13
+ @@_logger[log] = Logger.new(STDOUT) unless @@_logger[log]
14
+ @@_logger[log].send(severity, [*message].join(' '))
15
+ end
16
+
17
+ def info(message, log=:default)
18
+ log(message, :info, log)
19
+ end
20
+
21
+ def debug(message, log=:default)
22
+ log(message, :debug, log)
23
+ end
24
+
25
+ def warn(message, log=:default)
26
+ log(message, :warn, log)
27
+ end
28
+
29
+ def error(message, log=:default)
30
+ log(message, :error, log)
31
+ end
32
+
33
+ def fatal(message, log=:default)
34
+ log(message, :fatal, log)
35
+ end
36
+ end
37
+ end
38
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reacter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gary Hetzel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: eventmachine
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: deep_merge
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: hashlib
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A utility for consuming, transforming, and routing monitoring data from
56
+ various sources
57
+ email: ghetzel@outbrain.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - lib/reacter.rb
63
+ - lib/reacter/adapter.rb
64
+ - lib/reacter/adapters/amqp.rb
65
+ - lib/reacter/adapters/file.rb
66
+ - lib/reacter/agent.rb
67
+ - lib/reacter/agents/decider.rb
68
+ - lib/reacter/agents/logger.rb
69
+ - lib/reacter/config.rb
70
+ - lib/reacter/core.rb
71
+ - lib/reacter/message.rb
72
+ - lib/reacter/parsers/collectd.rb
73
+ - lib/reacter/parsers/default.rb
74
+ - lib/reacter/parsers/graphite.rb
75
+ - lib/reacter/parsers/json.rb
76
+ - lib/reacter/parsers/tsdb.rb
77
+ - lib/reacter/util.rb
78
+ homepage: http://outbrain.github.com/reacter/
79
+ licenses: []
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.0.0
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Reacter monitoring processor
101
+ test_files: []