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.
- data/.gitignore +1 -0
- data/CHANGELOG.md +44 -1
- data/README.md +11 -5
- data/lib/newrelic_platform_binding.rb +11 -0
- data/lib/newrelic_platform_binding/component.rb +27 -0
- data/lib/newrelic_platform_binding/config.rb +39 -0
- data/lib/newrelic_platform_binding/connection.rb +86 -0
- data/lib/newrelic_platform_binding/context.rb +49 -0
- data/lib/newrelic_platform_binding/logger.rb +40 -0
- data/lib/newrelic_platform_binding/metric.rb +49 -0
- data/lib/newrelic_platform_binding/request.rb +111 -0
- data/lib/newrelic_plugin.rb +1 -4
- data/lib/newrelic_plugin/agent.rb +14 -21
- data/lib/newrelic_plugin/config.rb +2 -1
- data/lib/newrelic_plugin/processors/rate_processor.rb +3 -3
- data/lib/newrelic_plugin/run.rb +97 -54
- data/lib/newrelic_plugin/setup.rb +10 -19
- data/lib/newrelic_plugin/version.rb +1 -1
- data/newrelic_plugin.gemspec +4 -4
- data/test/newrelic_platform_binding/component_test.rb +16 -0
- data/test/newrelic_platform_binding/config_test.rb +89 -0
- data/test/newrelic_platform_binding/connection_test.rb +67 -0
- data/test/newrelic_platform_binding/context_test.rb +35 -0
- data/test/newrelic_platform_binding/logger_test.rb +33 -0
- data/test/newrelic_platform_binding/metric_test.rb +97 -0
- data/test/newrelic_platform_binding/request_test.rb +127 -0
- data/test/newrelic_plugin/agent_test.rb +127 -0
- data/test/newrelic_plugin/run_test.rb +77 -0
- data/test/newrelic_plugin/setup_test.rb +17 -0
- data/test/test_helper.rb +11 -4
- metadata +59 -17
- data/lib/newrelic_plugin/data_collector.rb +0 -67
- data/lib/newrelic_plugin/logger.rb +0 -19
- data/lib/newrelic_plugin/new_relic_connection.rb +0 -67
- data/lib/newrelic_plugin/new_relic_message.rb +0 -173
- data/test/agent_test.rb +0 -153
- data/test/logger_test.rb +0 -21
- data/test/manual_test.rb +0 -20
- data/test/new_relic_message_test.rb +0 -76
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,50 @@
|
|
4
4
|
|
5
5
|
**Features**
|
6
6
|
|
7
|
-
|
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
|
6
|
-
|
7
|
-
|
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
|
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
|
+
|