newrelic_plugin 1.0.3 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG.md +44 -1
  3. data/README.md +11 -5
  4. data/lib/newrelic_platform_binding.rb +11 -0
  5. data/lib/newrelic_platform_binding/component.rb +27 -0
  6. data/lib/newrelic_platform_binding/config.rb +39 -0
  7. data/lib/newrelic_platform_binding/connection.rb +86 -0
  8. data/lib/newrelic_platform_binding/context.rb +49 -0
  9. data/lib/newrelic_platform_binding/logger.rb +40 -0
  10. data/lib/newrelic_platform_binding/metric.rb +49 -0
  11. data/lib/newrelic_platform_binding/request.rb +111 -0
  12. data/lib/newrelic_plugin.rb +1 -4
  13. data/lib/newrelic_plugin/agent.rb +14 -21
  14. data/lib/newrelic_plugin/config.rb +2 -1
  15. data/lib/newrelic_plugin/processors/rate_processor.rb +3 -3
  16. data/lib/newrelic_plugin/run.rb +97 -54
  17. data/lib/newrelic_plugin/setup.rb +10 -19
  18. data/lib/newrelic_plugin/version.rb +1 -1
  19. data/newrelic_plugin.gemspec +4 -4
  20. data/test/newrelic_platform_binding/component_test.rb +16 -0
  21. data/test/newrelic_platform_binding/config_test.rb +89 -0
  22. data/test/newrelic_platform_binding/connection_test.rb +67 -0
  23. data/test/newrelic_platform_binding/context_test.rb +35 -0
  24. data/test/newrelic_platform_binding/logger_test.rb +33 -0
  25. data/test/newrelic_platform_binding/metric_test.rb +97 -0
  26. data/test/newrelic_platform_binding/request_test.rb +127 -0
  27. data/test/newrelic_plugin/agent_test.rb +127 -0
  28. data/test/newrelic_plugin/run_test.rb +77 -0
  29. data/test/newrelic_plugin/setup_test.rb +17 -0
  30. data/test/test_helper.rb +11 -4
  31. metadata +59 -17
  32. data/lib/newrelic_plugin/data_collector.rb +0 -67
  33. data/lib/newrelic_plugin/logger.rb +0 -19
  34. data/lib/newrelic_plugin/new_relic_connection.rb +0 -67
  35. data/lib/newrelic_plugin/new_relic_message.rb +0 -173
  36. data/test/agent_test.rb +0 -153
  37. data/test/logger_test.rb +0 -21
  38. data/test/manual_test.rb +0 -20
  39. data/test/new_relic_message_test.rb +0 -76
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  Gemfile.lock
2
+ *.swp
@@ -4,7 +4,50 @@
4
4
 
5
5
  **Features**
6
6
 
7
- * To fill in
7
+
8
+ ### v1.2.1 - September 10, 2013 ###
9
+
10
+ **Bug Fixes**
11
+
12
+ * Send agent version to the HTTP API, not the SDK version.
13
+ * Stop using SSL by default in Ruby versions below 1.9 (this fixes an issue where the agent stops reporting)
14
+ * Set timeouts on HTTP API connection (fixes an issue where the agent stops reporting in Ruby 1.9 and higher)
15
+
16
+
17
+ ### v1.2.0 - August 19, 2013 ###
18
+
19
+ **Features**
20
+
21
+ * Aggregate data when the collector is unreachable.
22
+
23
+ **Bug Fixes**
24
+
25
+ * Fixed issue where the ssl_host_verification flag was not working.
26
+ * Fixed ordering of min and max in metric array that is sent to the HTTP API.
27
+
28
+
29
+ ### v1.1.1 - August 13, 2013 ###
30
+
31
+ **Bug Fixes**
32
+
33
+ * Fixed issue where to_set method was not found when requiring this gem without using bundler.
34
+ * Added JSON as a dependency to provide Ruby 1.8.7 support.
35
+
36
+ ### v1.1.0 - August 5, 2013 ###
37
+
38
+ **Features**
39
+
40
+ * Improved logging
41
+ * Support for proxies
42
+
43
+ **Bug Fixes**
44
+
45
+ * Duration of data collection time is now calculated to match actual duration
46
+
47
+ **Changes**
48
+
49
+ * Dropped dependency on Faraday
50
+
8
51
  ### v1.0.3 - June 25, 2013 ###
9
52
 
10
53
  **Features**
data/README.md CHANGED
@@ -2,9 +2,16 @@
2
2
 
3
3
  ## Requirements
4
4
 
5
- * Tested with Ruby 1.8.7 and 1.9.3
6
- * New Relic account on rpm.newrelic.com, with early access enabled
7
- (contact cooper@newrelic.com to set up early access)
5
+ * Tested with Ruby 1.8.7, 1.9.3, 2.0.0
6
+
7
+ Note: In Ruby 1.8.7 SSL is disabled by default due to issues with how Net::HTTP handles connection timeouts.
8
+ If you override this the plugin may occasionally stop reporting data and require a restart.
9
+ To override you can add the following to `newrelic:` section of the newrelic_config.yml.
10
+
11
+ ```
12
+ endpoint: 'https://platform-api.newrelic.com'
13
+ ```
14
+
8
15
 
9
16
  ## Get Started
10
17
 
@@ -36,8 +43,7 @@ Reach out to us at
36
43
  There you'll find documentation, FAQs, and forums where you can submit
37
44
  suggestions and discuss with staff and other users.
38
45
 
39
- Also available is community support on IRC: we generally use #newrelic
40
- on irc.freenode.net
46
+ Also available is [community support on Stack Overflow](http://stackoverflow.com/questions/tagged/newrelic-platform).
41
47
 
42
48
  Find a bug? E-mail <support@newrelic.com>, or submit a ticket to
43
49
 
@@ -0,0 +1,11 @@
1
+ require 'newrelic_platform_binding/logger'
2
+ require 'newrelic_platform_binding/config'
3
+ require 'newrelic_platform_binding/request'
4
+ require 'newrelic_platform_binding/context'
5
+ require 'newrelic_platform_binding/component'
6
+ require 'newrelic_platform_binding/metric'
7
+ require 'newrelic_platform_binding/connection'
8
+
9
+ ################################################################################
10
+ # This is a Provisional API which is subject to change.
11
+ ################################################################################
@@ -0,0 +1,27 @@
1
+ module NewRelic
2
+ module Binding
3
+ class Component
4
+ attr_reader :name, :guid
5
+ attr_accessor :last_delivered_at
6
+
7
+ def initialize(name, guid)
8
+ @name = name
9
+ @guid = guid
10
+ @last_delivered_at = nil
11
+ end
12
+
13
+ def key
14
+ return (name + guid)
15
+ end
16
+
17
+ def duration
18
+ if last_delivered_at.nil?
19
+ return NewRelic::Binding::Config.poll_cycle_period
20
+ else
21
+ return (Time.now - last_delivered_at).ceil
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module NewRelic
2
+ module Binding
3
+ class Config
4
+ def self.endpoint=(url)
5
+ @endpoint = url
6
+ if self.use_ssl? and !self.ssl_supported?
7
+ Logger.warn('Using SSL is not recommended when using Ruby versions below 1.9')
8
+ end
9
+ end
10
+
11
+ def self.use_ssl?
12
+ @endpoint.start_with?('https')
13
+ end
14
+
15
+ def self.ssl_supported?
16
+ !(!defined?(RUBY_ENGINE) || (RUBY_ENGINE == 'ruby' && RUBY_VERSION < '1.9.0'))
17
+ end
18
+
19
+ def self.skip_ssl_host_verification?
20
+ !@ssl_host_verification
21
+ end
22
+
23
+ if self.ssl_supported?
24
+ @endpoint = 'https://platform-api.newrelic.com'
25
+ else
26
+ @endpoint = 'http://platform-api.newrelic.com'
27
+ Logger.warn('SSL is disabled by default when using Ruby 1.8.x')
28
+ end
29
+ @uri = '/platform/v1/metrics'
30
+ @ssl_host_verification = true
31
+ @poll_cycle_period = 60
32
+ @proxy = nil
33
+ class << self
34
+ attr_accessor :ssl_host_verification, :uri, :poll_cycle_period, :proxy
35
+ attr_reader :endpoint
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,86 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'net/https'
5
+
6
+ module NewRelic
7
+ module Binding
8
+ class Connection
9
+ attr_reader :license_key, :url
10
+
11
+ def initialize(context)
12
+ @url = Config.endpoint + Config.uri
13
+ @license_key = context.license_key
14
+ end
15
+
16
+ def send_request(data)
17
+ begin
18
+ Logger.debug("JSON payload: #{data}")
19
+ uri = URI.parse(url)
20
+ if Config.proxy.nil?
21
+ http = Net::HTTP.new(uri.host, uri.port)
22
+ else
23
+ proxy = Config.proxy
24
+ http = Net::HTTP.new(uri.host, uri.port, proxy['address'], proxy['port'], proxy['user'], proxy['password'])
25
+ end
26
+ if Config.use_ssl?
27
+ http.use_ssl = true
28
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Config.skip_ssl_host_verification?
29
+ end
30
+ http.open_timeout = 20
31
+ http.read_timeout = 20
32
+ request = Net::HTTP::Post.new(uri.path)
33
+ request['content-type'] = 'application/json'
34
+ request['X-License-Key'] = @license_key
35
+ request.body = data
36
+ response = http.request(request)
37
+ return evaluate_response(response)
38
+ rescue Timeout::Error => err
39
+ Logger.warn "Connection Timeout Error: #{err.inspect} #{err.message}"
40
+ return false
41
+ rescue => err
42
+ Logger.warn "HTTP Connection Error: #{err.inspect} #{err.message}"
43
+ return false
44
+ end
45
+ end
46
+
47
+ private
48
+ def evaluate_response(response)
49
+ return_status = nil
50
+ begin
51
+ if response.nil?
52
+ last_result = { "error" => "no response" }
53
+ return_status = "FAILED: No response"
54
+ elsif response && response.code == '200'
55
+ last_result = JSON.parse(response.body)
56
+ if last_result["status"] != "ok"
57
+ return_status = "FAILED[#{response.code}] <#{url}>: #{last_result["error"]}"
58
+ end
59
+ elsif response && response.code == '403' && response.body == "DISABLE_NEW_RELIC"
60
+ Logger.fatal "Agent has been disabled remotely by New Relic"
61
+ abort "Agent has been disabled remotely by New Relic"
62
+ else
63
+ if response.body.size > 0
64
+ last_result = JSON.parse(response.body)
65
+ else
66
+ last_result = {"error" => "no data returned"}
67
+ end
68
+ return_status = "FAILED[#{response.code}] <#{url}>: #{last_result["error"]}"
69
+ end
70
+ rescue => err
71
+ return_status = "FAILED[#{response.code}] <#{url}>: Could not parse response: #{err.message}"
72
+ end
73
+ if return_status
74
+ if response and response.code == '503'
75
+ Logger.warn("Collector temporarily unavailable. Continuing.")
76
+ else
77
+ Logger.error("#{return_status}")
78
+ end
79
+ end
80
+
81
+ return_status.nil?
82
+ end
83
+
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,49 @@
1
+ module NewRelic
2
+ module Binding
3
+ class Context
4
+ AGGREGATION_LIMIT = 20
5
+ attr_reader :components, :license_key
6
+ attr_accessor :version, :host, :pid
7
+
8
+ def initialize(license_key)
9
+ @version = nil
10
+ @host = nil
11
+ @request = Request.new(self)
12
+ @pid = nil
13
+ @license_key = license_key
14
+ @components = []
15
+ @aggregation_start = Time.now
16
+ end
17
+
18
+ def create_component(name, guid)
19
+ component = Component.new(name, guid)
20
+ @components.push(component)
21
+ return component
22
+ end
23
+
24
+ def get_request()
25
+ if past_aggregation_limit?
26
+ @components.each do |component|
27
+ component.last_delivered_at = nil
28
+ end
29
+ @request = Request.new(self)
30
+ elsif @request.delivered?
31
+ @request = Request.new(self)
32
+ else
33
+ @request
34
+ end
35
+ end
36
+
37
+ def last_request_delivered_at=(delivered_at)
38
+ @aggregation_start = delivered_at
39
+ end
40
+
41
+ private
42
+ def past_aggregation_limit?
43
+ @aggregation_start < Time.now - AGGREGATION_LIMIT * 60
44
+ end
45
+
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,40 @@
1
+ module NewRelic
2
+ require 'time'
3
+ require 'logger'
4
+
5
+ class Logger
6
+ @log = ::Logger.new(STDOUT)
7
+ @log.level = ::Logger::WARN
8
+ @log.formatter = proc { |severity, datetime, progname, msg| "[#{Time.iso8601(Time.now.utc.iso8601).to_s}] #{severity}: #{msg}\n" }
9
+ class << self
10
+ def log_level=(level)
11
+ @log.level = level
12
+ end
13
+
14
+ def fatal(message)
15
+ @log.fatal(message)
16
+ end
17
+
18
+ def error(message)
19
+ @log.error(message)
20
+ end
21
+
22
+ def warn(message)
23
+ @log.warn(message)
24
+ end
25
+
26
+ def info(message)
27
+ @log.info(message)
28
+ end
29
+
30
+ def debug(message)
31
+ @log.debug(message)
32
+ end
33
+
34
+ def log_metrics=(value)
35
+ @log_metrics = value
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,49 @@
1
+ require 'set'
2
+ module NewRelic
3
+ module Binding
4
+ class Metric
5
+ attr_reader :component, :name, :value, :count, :min, :max, :sum_of_squares
6
+
7
+ def initialize(component, name, input_value, options = {} )
8
+ value = input_value.to_f
9
+ @component = component
10
+ @name = name
11
+ @value = value
12
+ if options_has_required_keys(options)
13
+ @count = options[:count].to_i
14
+ @min = options[:min].to_f
15
+ @max = options[:max].to_f
16
+ @sum_of_squares = options[:sum_of_squares].to_f
17
+ else
18
+ Logger.warn("Metric #{@name} count, min, max, and sum_of_squares are all required if one is set, falling back to value only") unless options.size == 0
19
+ @count = 1
20
+ @min = value
21
+ @max = value
22
+ @sum_of_squares = (value * value)
23
+ end
24
+ end
25
+
26
+ def aggregate(metric)
27
+ @value += metric.value
28
+ @count += metric.count
29
+ @min = [@min, metric.min].min
30
+ @max = [@max, metric.max].max
31
+ @sum_of_squares += metric.sum_of_squares
32
+ end
33
+
34
+ def to_hash
35
+ {
36
+ name => [
37
+ @value, @count, @min, @max, @sum_of_squares
38
+ ]
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def options_has_required_keys(options)
45
+ options.keys.to_set.superset?(Set.new([:count, :min, :max, :sum_of_squares]))
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,111 @@
1
+ require 'json'
2
+ module NewRelic
3
+ module Binding
4
+ class Request
5
+ attr_reader :context
6
+
7
+ def initialize(context)
8
+ @context = context
9
+ @duration = nil
10
+ @metrics = {}
11
+ @delivered = false
12
+ return self
13
+ end
14
+
15
+ def deliver
16
+ metrics_hash = build_request_data_structure
17
+ connection = Connection.new(@context)
18
+ if connection.send_request(metrics_hash.to_json)
19
+ @delivered = true
20
+ delivered_at = Time.now
21
+ context.last_request_delivered_at = delivered_at
22
+ duration_warning = false
23
+ @context.components.each do |component|
24
+ if @metrics.has_key?(component.key)
25
+ duration_warning = true if component.duration > 600
26
+ component.last_delivered_at = delivered_at
27
+ end
28
+ end
29
+ Logger.warn("Duration of more than 10 minutes between sending data to New Relic, this will cause plugins to show up as not reporting") if duration_warning
30
+ end
31
+ end
32
+
33
+ def add_metric(component, name, value, options = {})
34
+ metric = find_metric(component, name)
35
+ new_metric = Metric.new(self, name, value, options)
36
+ if metric.nil?
37
+ metric = new_metric
38
+ @metrics[component.key] ||= []
39
+ @metrics[component.key].push(metric)
40
+ else
41
+ metric.aggregate(new_metric)
42
+ end
43
+ return metric
44
+ end
45
+
46
+ def metric_count
47
+ count = 0
48
+ @metrics.each do |m|
49
+ count = count + m.size
50
+ end
51
+ count
52
+ end
53
+
54
+ def component_count
55
+ @metrics.size
56
+ end
57
+
58
+ def delivered?
59
+ @delivered
60
+ end
61
+ private
62
+ def find_metric(component, name)
63
+ @metrics[component.key].find { |m| m.name == name } unless @metrics[component.key].nil?
64
+ end
65
+
66
+ def build_request_data_structure
67
+ {
68
+ 'agent' => build_agent_hash(),
69
+ 'components' => build_components_array()
70
+ }
71
+ end
72
+
73
+ def build_agent_hash
74
+ agent_hash = {
75
+ 'version' => @context.version,
76
+ }
77
+ agent_hash['host'] = @context.host unless @context.host.nil?
78
+ agent_hash['pid'] = @context.pid unless @context.pid.nil?
79
+ agent_hash
80
+ end
81
+
82
+ def build_components_array
83
+ components_array = []
84
+ @context.components.each do |component|
85
+ component_hash = {
86
+ 'name' => component.name,
87
+ 'guid' => component.guid,
88
+ 'duration' => component.duration,
89
+ 'metrics' => build_metrics_hash(component)
90
+ }
91
+ components_array.push(component_hash)
92
+ end
93
+ return components_array
94
+ end
95
+
96
+ def build_metrics_hash(component)
97
+ metrics_hash = {}
98
+ if @metrics.has_key?(component.key)
99
+ @metrics[component.key].each do |metric|
100
+ metrics_hash.merge!(metric.to_hash)
101
+ end
102
+ else
103
+ Logger.warn("Component with name \"#{component.name}\" and guid \"#{component.guid}\" had no metrics")
104
+ end
105
+ metrics_hash
106
+ end
107
+
108
+ end
109
+ end
110
+ end
111
+