recognizer 0.1.6 → 0.2.0

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.
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