recognizer 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.org CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  TCP plain text & AMQP support.
5
5
 
6
+ [[https://secure.travis-ci.org/portertech/recognizer.png]]
7
+
6
8
  [[https://github.com/portertech/recognizer/raw/master/recognizer.gif]]
7
9
  * Install
8
10
  Executable Java JAR (recommended)
@@ -1,20 +1,37 @@
1
1
  require "recognizer/cli"
2
2
  require "recognizer/config"
3
3
  require "recognizer/librato"
4
- require "recognizer/tcp"
5
- require "recognizer/amqp"
4
+ require "recognizer/inputs/tcp"
5
+ require "recognizer/inputs/amqp"
6
6
 
7
7
  module Recognizer
8
8
  def self.run
9
- cli = Recognizer::CLI.new
10
- cli_options = cli.read
11
- config = Recognizer::Config.new(cli_options)
12
- config_options = config.read
13
- carbon_queue = Queue.new
14
- logger = Logger.new(STDOUT)
15
- Recognizer::Librato.new(carbon_queue, logger, config_options)
16
- Recognizer::TCP.new(carbon_queue, logger, config_options)
17
- Recognizer::AMQP.new(carbon_queue, logger, config_options)
9
+ logger = Logger.new(STDOUT)
10
+ input_queue = Queue.new
11
+ cli = Recognizer::CLI.new
12
+ config = Recognizer::Config.new(cli.read)
13
+
14
+ librato = Recognizer::Librato.new(
15
+ :logger => logger,
16
+ :options => config.read,
17
+ :input_queue => input_queue
18
+ )
19
+ librato.run
20
+
21
+ tcp = Recognizer::Input::TCP.new(
22
+ :logger => logger,
23
+ :options => config.read,
24
+ :input_queue => input_queue
25
+ )
26
+ tcp.run
27
+
28
+ amqp = Recognizer::Input::AMQP.new(
29
+ :logger => logger,
30
+ :options => config.read,
31
+ :input_queue => input_queue
32
+ )
33
+ amqp.run
34
+
18
35
  loop do
19
36
  sleep 30
20
37
  end
@@ -1,4 +1,3 @@
1
- require "rubygems"
2
1
  require "mixlib/cli"
3
2
 
4
3
  module Recognizer
@@ -1,8 +1,5 @@
1
- require "rubygems"
2
1
  require "json"
3
2
 
4
- require File.join(File.dirname(__FILE__), "patches", "hash")
5
-
6
3
  module Recognizer
7
4
  class Config
8
5
  def initialize(options={})
@@ -12,17 +9,24 @@ module Recognizer
12
9
  if File.readable?(options[:config_file])
13
10
  config_file_contents = File.open(options[:config_file], "r").read
14
11
  begin
15
- @config = JSON.parse(config_file_contents)
12
+ @config = JSON.parse(config_file_contents, :symbolize_names => true)
16
13
  rescue JSON::ParserError => error
17
14
  raise "Config file must be valid JSON: #{error}"
18
15
  end
19
16
  else
20
17
  raise "Config file does not exist or is not readable: #{options[:config_file]}"
21
18
  end
19
+ validate
20
+ end
21
+
22
+ def validate
23
+ unless @config[:librato][:email] && @config[:librato][:api_key]
24
+ raise "You must provide a Librato Metrics account email and API key"
25
+ end
22
26
  end
23
27
 
24
28
  def read
25
- @config.symbolize_keys
29
+ @config
26
30
  end
27
31
  end
28
32
  end
@@ -0,0 +1,70 @@
1
+ require "recognizer/patches/openssl"
2
+ require "thread"
3
+ require "bunny"
4
+
5
+ module Recognizer
6
+ module Input
7
+ class AMQP
8
+ def initialize(options={})
9
+ @logger = options[:logger]
10
+ @options = options[:options]
11
+ @input_queue = options[:input_queue]
12
+
13
+ Thread.abort_on_exception = true
14
+ end
15
+
16
+ def run
17
+ if @options.has_key?(:amqp)
18
+ set_default_options
19
+ setup_consumer
20
+ else
21
+ @logger.warn("AMQP -- Not configured")
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def set_default_options
28
+ @options[:amqp][:exchange] ||= Hash.new
29
+ @options[:amqp][:exchange][:name] ||= "graphite"
30
+ @options[:amqp][:exchange][:durable] ||= false
31
+ @options[:amqp][:exchange][:routing_key] ||= "#"
32
+ @options[:amqp][:exchange][:type] ||= (@options[:amqp][:exchange][:type] || "topic").to_sym
33
+ end
34
+
35
+ def setup_consumer
36
+ amqp = Bunny.new(@options[:amqp].reject { |key, value| key == :exchange })
37
+ amqp.start
38
+
39
+ exchange = amqp.exchange(@options[:amqp][:exchange][:name], {
40
+ :type => @options[:amqp][:exchange][:type],
41
+ :durable => @options[:amqp][:exchange][:durable]
42
+ })
43
+
44
+ queue = amqp.queue("recognizer")
45
+ queue.bind(exchange, {
46
+ :key => @options[:amqp][:exchange][:routing_key]
47
+ })
48
+
49
+ Thread.new do
50
+ @logger.info("AMQP -- Awaiting metrics with impatience ...")
51
+ queue.subscribe do |message|
52
+ msg_routing_key = message[:routing_key] || message[:delivery_details][:routing_key]
53
+ lines = message[:payload].split("\n")
54
+ lines.each do |line|
55
+ line = line.strip
56
+ case line.split("\s").count
57
+ when 3
58
+ @input_queue.push(line)
59
+ when 2
60
+ @input_queue.push("#{msg_routing_key} #{line}")
61
+ else
62
+ @logger.warn("AMQP -- Received malformed metric :: #{msg_routing_key} :: #{line}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ require "thread"
2
+ require "socket"
3
+
4
+ module Recognizer
5
+ module Input
6
+ class TCP
7
+ def initialize(options={})
8
+ @logger = options[:logger]
9
+ @options = options[:options]
10
+ @input_queue = options[:input_queue]
11
+
12
+ @options[:tcp] ||= Hash.new
13
+ @tcp_connections = Queue.new
14
+
15
+ Thread.abort_on_exception = true
16
+ end
17
+
18
+ def run
19
+ setup_server
20
+ setup_thread_pool
21
+ end
22
+
23
+ private
24
+
25
+ def setup_server
26
+ port = @options[:tcp][:port] || 2003
27
+ tcp_server = TCPServer.new("0.0.0.0", port)
28
+ Thread.new do
29
+ @logger.info("TCP -- Awaiting metrics with impatience ...")
30
+ loop do
31
+ @tcp_connections.push(tcp_server.accept)
32
+ end
33
+ end
34
+ end
35
+
36
+ def create_server_thread
37
+ Thread.new do
38
+ loop do
39
+ if connection = @tcp_connections.shift
40
+ while line = connection.gets
41
+ line = line.strip
42
+ if line.split("\s").count == 3
43
+ @input_queue.push(line)
44
+ else
45
+ @logger.warn("TCP -- Received malformed metric :: #{line}")
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def setup_thread_pool
54
+ threads = @options[:tcp][:threads] || 20
55
+ threads.times do
56
+ create_server_thread
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -1,96 +1,124 @@
1
- require "rubygems"
2
1
  require "recognizer/version"
2
+ require "recognizer/patches/float"
3
+ require "recognizer/patches/openssl"
3
4
  require "thread"
4
5
  require "librato/metrics"
5
6
 
6
- require File.join(File.dirname(__FILE__), "patches", "float")
7
-
8
- if RUBY_PLATFORM == "java"
9
- require File.join(File.dirname(__FILE__), "patches", "openssl")
10
- end
11
-
12
7
  module Recognizer
13
8
  class Librato
14
- def initialize(carbon_queue, logger, options)
15
- unless carbon_queue && options.is_a?(Hash)
16
- raise "You must provide a thread queue and options"
17
- end
18
- unless options[:librato][:email] && options[:librato][:api_key]
19
- raise "You must provide a Librato Metrics account email and API key"
20
- end
9
+ def initialize(options={})
10
+ @logger = options[:logger]
11
+ @options = options[:options]
12
+ @input_queue = options[:input_queue]
21
13
 
22
- ::Librato::Metrics.authenticate(options[:librato][:email], options[:librato][:api_key])
14
+ ::Librato::Metrics.authenticate(@options[:librato][:email], @options[:librato][:api_key])
23
15
  ::Librato::Metrics.agent_identifier("recognizer", Recognizer::VERSION, "portertech")
24
- librato = ::Librato::Metrics::Queue.new
16
+ @librato_queue = ::Librato::Metrics::Queue.new
17
+ @librato_mutex = Mutex.new
25
18
 
26
- mutex = Mutex.new
27
19
  Thread.abort_on_exception = true
20
+ end
28
21
 
29
- Thread.new do
30
- loop do
31
- sleep(options[:librato][:flush_interval] || 10)
32
- unless librato.empty?
33
- logger.info("Attempting to flush metrics to Librato")
34
- mutex.synchronize do
35
- begin
36
- librato.submit
37
- logger.info("Successfully flushed metrics to Librato")
38
- rescue => error
39
- logger.error("Encountered an error when flushing metrics to Librato :: #{error}")
40
- end
41
- end
42
- end
43
- end
22
+ def run
23
+ setup_publisher
24
+ setup_consumer
25
+ end
26
+
27
+ def invalid_metric(metric, message)
28
+ @logger.warn("Invalid metric :: #{metric.inspect} :: #{message}")
29
+ false
30
+ end
31
+
32
+ def valid_carbon_metric?(carbon_formatted)
33
+ parts = carbon_formatted.split("\s")
34
+ if parts[0] !~ /^[A-Za-z0-9\._-]*$/
35
+ invalid_metric(carbon_formatted, "Metric name must only consist of alpha-numeric characters, periods, underscores, and dashes")
36
+ elsif parts[1] !~ /^[0-9]*\.?[0-9]*$/
37
+ invalid_metric(carbon_formatted, "Metric value must be an integer or float")
38
+ elsif parts[2] !~ /^[0-9]{10}$/
39
+ invalid_metric(carbon_formatted, "Metric timestamp must be epoch, 10 digits")
40
+ else
41
+ true
44
42
  end
43
+ end
45
44
 
46
- get_source = case options[:librato][:metric_source]
45
+ def metric_source(path)
46
+ @metric_source ||= case @options[:librato][:metric_source]
47
47
  when String
48
- if options[:librato][:metric_source].match("^/.*/$")
49
- @source_pattern = Regexp.new(options[:librato][:metric_source].delete("/"))
48
+ if @options[:librato][:metric_source].match("^/.*/$")
49
+ @source_pattern = Regexp.new(@options[:librato][:metric_source].delete("/"))
50
50
  Proc.new { |path| (matched = path.grep(@source_pattern).first) ? matched : "recognizer" }
51
51
  else
52
- Proc.new { options[:librato][:metric_source] }
52
+ Proc.new { @options[:librato][:metric_source] }
53
53
  end
54
54
  when Integer
55
- Proc.new { |path| path.slice(options[:librato][:metric_source]) }
55
+ Proc.new { |path| path.slice(@options[:librato][:metric_source]) || "recognizer" }
56
56
  else
57
57
  Proc.new { "recognizer" }
58
58
  end
59
+ @metric_source.call(path)
60
+ end
59
61
 
60
- Thread.new do
61
- loop do
62
- graphite_formated = carbon_queue.pop
63
- begin
64
- parts = graphite_formated.split("\s")
62
+ def create_metric(carbon_formatted)
63
+ if valid_carbon_metric?(carbon_formatted)
64
+ parts = carbon_formatted.split("\s")
65
65
 
66
- unless parts.first =~ /^[A-Za-z0-9\._-]*$/
67
- raise "metric name must only consist of alpha-numeric characters, periods, underscores, and dashes"
68
- end
69
- unless parts.last =~ /^[0-9]{10}$/
70
- raise "metric timestamp must be epoch, 10 digits"
71
- end
66
+ path = parts.shift.split(".")
67
+ value = Float(parts.shift).pretty
68
+ timestamp = Float(parts.shift).pretty
69
+ source = metric_source(path)
72
70
 
73
- path = parts.shift.split(".")
74
- value = Float(parts.shift).pretty
75
- timestamp = Float(parts.shift).pretty
76
- source = get_source.call(path)
71
+ path.delete(source)
77
72
 
78
- path.delete(source)
73
+ name = path.join(".")
79
74
 
80
- name = path.join(".")
75
+ metric = {
76
+ name => {
77
+ :value => value,
78
+ :measure_time => timestamp,
79
+ :source => source
80
+ }
81
+ }
81
82
 
82
- unless name.size <= 63
83
- raise "metric name must be 63 or fewer characters"
84
- end
83
+ unless name.size <= 63
84
+ invalid_metric(metric, "Metric name must be 63 or fewer characters")
85
+ else
86
+ metric
87
+ end
88
+ else
89
+ false
90
+ end
91
+ end
85
92
 
86
- metric = {name => {:value => value, :measure_time => timestamp, :source => source}}
93
+ private
94
+
95
+ def setup_publisher
96
+ Thread.new do
97
+ loop do
98
+ sleep(@options[:librato][:flush_interval] || 10)
99
+ unless @librato_queue.empty?
100
+ @logger.info("Attempting to flush metrics to Librato")
101
+ @librato_mutex.synchronize do
102
+ begin
103
+ @librato_queue.submit
104
+ @logger.info("Successfully flushed metrics to Librato")
105
+ rescue => error
106
+ @logger.error("Encountered an error when flushing metrics to Librato :: #{error}")
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
87
113
 
88
- mutex.synchronize do
89
- logger.info("Adding metric to queue :: #{metric.inspect}")
90
- librato.add(metric)
114
+ def setup_consumer
115
+ Thread.new do
116
+ loop do
117
+ if metric = create_metric(@input_queue.shift)
118
+ @logger.info("Adding metric to Librato queue :: #{metric.inspect}")
119
+ @librato_mutex.synchronize do
120
+ @librato_queue.add(metric)
91
121
  end
92
- rescue => error
93
- logger.info("Invalid metric :: #{graphite_formated} :: #{error}")
94
122
  end
95
123
  end
96
124
  end
@@ -1,8 +1,10 @@
1
- require "openssl"
1
+ if RUBY_PLATFORM == "java"
2
+ require "openssl"
2
3
 
3
- module OpenSSL
4
- module SSL
5
- remove_const(:VERIFY_PEER)
6
- const_set(:VERIFY_PEER, VERIFY_NONE)
4
+ module OpenSSL
5
+ module SSL
6
+ remove_const(:VERIFY_PEER)
7
+ const_set(:VERIFY_PEER, VERIFY_NONE)
8
+ end
7
9
  end
8
10
  end
@@ -1,3 +1,5 @@
1
1
  module Recognizer
2
- VERSION = "0.1.6"
2
+ unless defined?(Recognizer::VERSION)
3
+ VERSION = "0.2.0"
4
+ end
3
5
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.add_dependency("json")
21
- s.add_dependency("mixlib-cli", ">= 1.1.0")
22
- s.add_dependency("bunny", "0.7.9")
23
- s.add_dependency("librato-metrics", "0.6.0.pre3")
21
+ s.add_dependency("mixlib-cli", "1.2.2")
22
+ s.add_dependency("bunny", "0.8.0")
23
+ s.add_dependency("librato-metrics", "0.7.0")
24
24
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 6
10
- version: 0.1.6
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sean Porter
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-04-26 00:00:00 -07:00
18
+ date: 2012-08-03 00:00:00 -07:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -38,14 +38,14 @@ dependencies:
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
- - - ">="
41
+ - - "="
42
42
  - !ruby/object:Gem::Version
43
- hash: 19
43
+ hash: 27
44
44
  segments:
45
45
  - 1
46
- - 1
47
- - 0
48
- version: 1.1.0
46
+ - 2
47
+ - 2
48
+ version: 1.2.2
49
49
  type: :runtime
50
50
  version_requirements: *id002
51
51
  - !ruby/object:Gem::Dependency
@@ -56,12 +56,12 @@ dependencies:
56
56
  requirements:
57
57
  - - "="
58
58
  - !ruby/object:Gem::Version
59
- hash: 17
59
+ hash: 63
60
60
  segments:
61
61
  - 0
62
- - 7
63
- - 9
64
- version: 0.7.9
62
+ - 8
63
+ - 0
64
+ version: 0.8.0
65
65
  type: :runtime
66
66
  version_requirements: *id003
67
67
  - !ruby/object:Gem::Dependency
@@ -72,13 +72,12 @@ dependencies:
72
72
  requirements:
73
73
  - - "="
74
74
  - !ruby/object:Gem::Version
75
- hash: -1876988210
75
+ hash: 3
76
76
  segments:
77
77
  - 0
78
- - 6
78
+ - 7
79
79
  - 0
80
- - pre3
81
- version: 0.6.0.pre3
80
+ version: 0.7.0
82
81
  type: :runtime
83
82
  version_requirements: *id004
84
83
  description: A drop-in replacement for Graphite Carbon (TCP & AMQP), sending metrics to Librato Metrics.
@@ -92,14 +91,13 @@ extra_rdoc_files: []
92
91
 
93
92
  files:
94
93
  - bin/recognizer
95
- - lib/recognizer/amqp.rb
96
94
  - lib/recognizer/cli.rb
97
95
  - lib/recognizer/config.rb
96
+ - lib/recognizer/inputs/amqp.rb
97
+ - lib/recognizer/inputs/tcp.rb
98
98
  - lib/recognizer/librato.rb
99
99
  - lib/recognizer/patches/float.rb
100
- - lib/recognizer/patches/hash.rb
101
100
  - lib/recognizer/patches/openssl.rb
102
- - lib/recognizer/tcp.rb
103
101
  - lib/recognizer/version.rb
104
102
  - lib/recognizer.rb
105
103
  - recognizer.gemspec
@@ -1,56 +0,0 @@
1
- require "rubygems"
2
- require "thread"
3
- require "bunny"
4
-
5
- if RUBY_PLATFORM == "java"
6
- require File.join(File.dirname(__FILE__), "patches", "openssl")
7
- end
8
-
9
- module Recognizer
10
- class AMQP
11
- def initialize(carbon_queue, logger, options)
12
- unless carbon_queue && options.is_a?(Hash)
13
- raise "You must provide a thread queue and options"
14
- end
15
-
16
- if options.has_key?(:amqp)
17
- options[:amqp][:exchange] ||= Hash.new
18
-
19
- exchange_name = options[:amqp][:exchange][:name] || "graphite"
20
- durable = options[:amqp][:exchange][:durable] || false
21
- routing_key = options[:amqp][:exchange][:routing_key] || "#"
22
- exchange_type = options[:amqp][:exchange][:type] || :topic
23
-
24
- amqp = Bunny.new(options[:amqp].reject { |key, value| key == :exchange })
25
- amqp.start
26
-
27
- exchange = amqp.exchange(exchange_name, :type => exchange_type.to_sym, :durable => durable)
28
- queue = amqp.queue("recognizer")
29
- queue.bind(exchange, :key => routing_key)
30
-
31
- Thread.abort_on_exception = true
32
-
33
- Thread.new do
34
- logger.info("AMQP -- Awaiting metrics with impatience ...")
35
- queue.subscribe do |message|
36
- payload = message[:payload]
37
- msg_routing_key = message[:routing_key] || message[:delivery_details][:routing_key]
38
-
39
- lines = payload.split("\n")
40
- lines.each do |line|
41
- line = line.strip
42
- case line.split("\s").count
43
- when 3
44
- carbon_queue.push(line)
45
- when 2
46
- carbon_queue.push("#{msg_routing_key} #{line}")
47
- end
48
- end
49
- end
50
- end
51
- else
52
- logger.warn("AMQP -- Not configured")
53
- end
54
- end
55
- end
56
- end
@@ -1,10 +0,0 @@
1
- class Hash
2
- def symbolize_keys
3
- inject(Hash.new) do |result, (key, value)|
4
- new_key = key.is_a?(String) ? key.to_sym : key
5
- new_value = value.is_a?(Hash) ? value.symbolize_keys : value
6
- result[new_key] = new_value
7
- result
8
- end
9
- end
10
- end
@@ -1,46 +0,0 @@
1
- require "rubygems"
2
- require "timeout"
3
- require "thread"
4
- require "socket"
5
-
6
- module Recognizer
7
- class TCP
8
- def initialize(carbon_queue, logger, options)
9
- unless carbon_queue && options.is_a?(Hash)
10
- raise "You must provide a thread queue and options"
11
- end
12
-
13
- options[:tcp] ||= Hash.new
14
-
15
- threads = options[:tcp][:threads] || 20
16
- port = options[:tcp][:port] || 2003
17
-
18
- tcp_server = TCPServer.new("0.0.0.0", port)
19
- tcp_connections = Queue.new
20
-
21
- Thread.abort_on_exception = true
22
-
23
- threads.times do
24
- Thread.new do
25
- loop do
26
- if connection = tcp_connections.shift
27
- while line = connection.gets
28
- line = line.strip
29
- if line.split("\s").count == 3
30
- carbon_queue.push(line)
31
- end
32
- end
33
- end
34
- end
35
- end
36
- end
37
-
38
- Thread.new do
39
- logger.info("TCP -- Awaiting metrics with impatience ...")
40
- loop do
41
- tcp_connections.push(tcp_server.accept)
42
- end
43
- end
44
- end
45
- end
46
- end