reacter 0.0.1

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