newrelic_plugin 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +46 -0
- data/Rakefile +19 -0
- data/lib/newrelic_plugin.rb +12 -0
- data/lib/newrelic_plugin/agent.rb +155 -0
- data/lib/newrelic_plugin/config.rb +84 -0
- data/lib/newrelic_plugin/data_collector.rb +67 -0
- data/lib/newrelic_plugin/error.rb +22 -0
- data/lib/newrelic_plugin/new_relic_connection.rb +67 -0
- data/lib/newrelic_plugin/new_relic_message.rb +173 -0
- data/lib/newrelic_plugin/processor.rb +18 -0
- data/lib/newrelic_plugin/processors/epoch_counter_processor.rb +21 -0
- data/lib/newrelic_plugin/processors/rate_processor.rb +35 -0
- data/lib/newrelic_plugin/run.rb +120 -0
- data/lib/newrelic_plugin/setup.rb +54 -0
- data/lib/newrelic_plugin/simple_syntax.rb +54 -0
- data/lib/newrelic_plugin/version.rb +5 -0
- data/newrelic_plugin.gemspec +47 -0
- data/test/agent_test.rb +153 -0
- data/test/fixtures/valid_payload.json +17 -0
- data/test/manual_test.rb +20 -0
- data/test/new_relic_message_test.rb +76 -0
- data/test/test_helper.rb +15 -0
- metadata +141 -0
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module NewRelic
|
4
|
+
module Plugin
|
5
|
+
#
|
6
|
+
# NewRelic driver. Provides all methods necessary for accessing the NewRelic service.
|
7
|
+
# Used to store data into NewRelic service.
|
8
|
+
#
|
9
|
+
# Usage:
|
10
|
+
#
|
11
|
+
# nrobj=NewRelic::Plugin::NewRelicConnection.new license_key:"xxx",host:"xxx",port:80,log_http:true,log_metrics:true # log_http,host and port are optional; license key is not.
|
12
|
+
# msg=nrobj.start_message <component_name>,<component_guid>,<component_version>,<duration_in_seconds>
|
13
|
+
# msg.add_stat basename,count_unit,value_unit,count,value,opts={} # opts can include :min, :max, and :sum_of_squares
|
14
|
+
# msg.send_message # Send the list of stats to New Relic
|
15
|
+
#
|
16
|
+
# Example:
|
17
|
+
# nrobj=NewRelic::Plugin::NewRelicConnection.new license_key:"bootstrap_newrelic_admin_license_key_000",host:"localhost",port:8081,log_http:true,log_metrics:true
|
18
|
+
# msg=nrobj.start_message "A Kewl Component","12345678","0.0.1",60
|
19
|
+
# msg.add_stat_fullname "Component/TestMetric1[Bytes/Seconds]",2,34,min:31,max:54,sum_of_squares:1234
|
20
|
+
# msg.add_stat_fullname "Component/AnotherTest[Bytes/Seconds]",2,34,min:31,max:54,sum_of_squares:1234
|
21
|
+
# msg.add_stat_fullname "Component/TestMetric2[Bytes/Seconds]",2,34,min:31,max:54,sum_of_squares:1234
|
22
|
+
# msg.add_stat_fullname "Component/AnotherMetric[Bytes/Seconds]",2,34,min:31,max:54,sum_of_squares:1234
|
23
|
+
# msg.send_metrics # Send the list of stats to New Relic
|
24
|
+
#
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
class NewRelicMessage
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# Create an object to send metrics
|
32
|
+
#
|
33
|
+
#
|
34
|
+
def initialize connection,component_name,component_guid,component_version,duration_in_seconds
|
35
|
+
@connection = connection
|
36
|
+
@component_name = component_name
|
37
|
+
@component_guid = component_guid
|
38
|
+
@component_version = component_version
|
39
|
+
@duration_in_seconds = duration_in_seconds
|
40
|
+
@metrics = [] # Metrics being saved
|
41
|
+
end
|
42
|
+
def add_stat_fullname metric_name,count,value,opts={}
|
43
|
+
entry = {}
|
44
|
+
entry[:metric_name] = metric_name
|
45
|
+
entry[:count] = count
|
46
|
+
entry[:total] = value
|
47
|
+
[:min,:max,:sum_of_squares].each do |key|
|
48
|
+
entry[key] = opts[key]
|
49
|
+
end
|
50
|
+
@metrics << entry
|
51
|
+
end
|
52
|
+
|
53
|
+
def metrics
|
54
|
+
@metrics
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_metrics
|
58
|
+
return_errors = []
|
59
|
+
puts "Metrics for #{@component_name}[#{@component_guid}] for last #{@duration_in_seconds} seconds:" if new_relic_connection.log_metrics?
|
60
|
+
#
|
61
|
+
# Send all metrics in a single transaction
|
62
|
+
#
|
63
|
+
response = deliver_metrics
|
64
|
+
evaluate_response(response)
|
65
|
+
log_send_metrics
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def build_metrics_hash
|
71
|
+
metrics_hash = {}
|
72
|
+
metrics.each do |metric|
|
73
|
+
metric_value = []
|
74
|
+
[:total,:count,:max,:min,:sum_of_squares].each do |key|
|
75
|
+
metric_value.push(metric[key]) if metric[key]
|
76
|
+
end
|
77
|
+
metrics_hash[metric[:metric_name]] = metric_value
|
78
|
+
end
|
79
|
+
return metrics_hash
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_request_payload
|
83
|
+
data = {
|
84
|
+
"agent" => {
|
85
|
+
"name" => @component_name,
|
86
|
+
"version" => @component_version,
|
87
|
+
"host" => ""
|
88
|
+
},
|
89
|
+
"components" => [
|
90
|
+
{
|
91
|
+
"name" => @component_name,
|
92
|
+
"guid" => @component_guid,
|
93
|
+
"duration" => @duration_in_seconds,
|
94
|
+
"metrics" => build_metrics_hash
|
95
|
+
}
|
96
|
+
]
|
97
|
+
}
|
98
|
+
return data.to_json
|
99
|
+
end
|
100
|
+
|
101
|
+
def deliver_metrics
|
102
|
+
begin
|
103
|
+
response = new_relic_connection.connect.post do |req|
|
104
|
+
req.url new_relic_connection.uri
|
105
|
+
req.headers['Content-Type'] = 'application/json'
|
106
|
+
req.body = build_request_payload
|
107
|
+
end
|
108
|
+
rescue => err
|
109
|
+
puts "HTTP Connection Error: #{err.inspect} #{err.message}"
|
110
|
+
end
|
111
|
+
|
112
|
+
return response
|
113
|
+
end
|
114
|
+
|
115
|
+
def evaluate_response(response)
|
116
|
+
return_status = nil
|
117
|
+
if response.nil?
|
118
|
+
last_result={"error" => "no response"}
|
119
|
+
return_status = "FAILED: No response"
|
120
|
+
elsif response && response.status == 200
|
121
|
+
last_result = JSON.parse(response.body)
|
122
|
+
if last_result["status"] != "ok"
|
123
|
+
return_status = "FAILED[#{response.status}] <#{new_relic_connection.url}>: #{last_result["error"]}"
|
124
|
+
end
|
125
|
+
elsif response && response.status == 403 && response.body == "DISABLE_NEW_RELIC"
|
126
|
+
puts "Agent has been disabled remotely by New Relic"
|
127
|
+
abort "Agent has been disabled remotely by New Relic"
|
128
|
+
else
|
129
|
+
begin
|
130
|
+
if response.body.size>0
|
131
|
+
last_result = JSON.parse(response.body)
|
132
|
+
else
|
133
|
+
last_result = {"error" => "no data returned"}
|
134
|
+
end
|
135
|
+
return_status = "FAILED[#{response.status}] <#{new_relic_connection.url}>: #{last_result["error"]}"
|
136
|
+
rescue => err
|
137
|
+
if response
|
138
|
+
return_status = "FAILED[#{response.status}] <#{new_relic_connection.url}>: Could not parse response: #{err}"
|
139
|
+
else
|
140
|
+
return_status = "FAILED: #{err}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
if return_status
|
145
|
+
if response and response.status == 503 and !new_relic_connection.log_metrics?
|
146
|
+
# If logging not enabled, and it's a 503...be less error-ish...
|
147
|
+
puts " Collector temporarily unavailable...continuing"
|
148
|
+
else
|
149
|
+
# Otherwise, in all cases (logging enabled or not) print an error message
|
150
|
+
puts " ****ERROR: #{return_status}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def log_send_metrics
|
156
|
+
if new_relic_connection.log_metrics?
|
157
|
+
puts " Sent #{metrics.size} metrics to New Relic [#{new_relic_connection.url}]:"
|
158
|
+
metrics.each do |metric|
|
159
|
+
val_strs = []
|
160
|
+
[:count,:total,:min,:max,:sum_of_squares].each do |key|
|
161
|
+
val_strs << "#{key}: #{metric[key]}" if metric[key]
|
162
|
+
end
|
163
|
+
puts " #{metric[:metric_name]}: #{val_strs.join(', ')}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def new_relic_connection
|
169
|
+
@connection
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module NewRelic::Processor
|
2
|
+
class EpochCounter<NewRelic::Plugin::Processor::Base
|
3
|
+
def initialize
|
4
|
+
super :epoch_counter,"Epoch Counter"
|
5
|
+
end
|
6
|
+
def process val
|
7
|
+
val=val.to_f
|
8
|
+
ret=nil
|
9
|
+
curr_time=Time.now
|
10
|
+
if @last_value and @last_time and curr_time>@last_time
|
11
|
+
ret=(val-@last_value)/(curr_time-@last_time).to_f
|
12
|
+
end
|
13
|
+
@last_value=val
|
14
|
+
@last_time=curr_time
|
15
|
+
# This next line is to avoid large negative spikes during epoch reset events...
|
16
|
+
return nil if ret.nil? or ret<0
|
17
|
+
ret
|
18
|
+
end
|
19
|
+
#Component::Setup.install_processor EpochCounter
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module NewRelic::Processor
|
2
|
+
#
|
3
|
+
#
|
4
|
+
# OBSOLESCENCE WARNING!!!
|
5
|
+
#
|
6
|
+
# The "Rate" processor is being obsoleted. Please do not use in any new agent
|
7
|
+
# development.
|
8
|
+
#
|
9
|
+
# If you feel you need this processor, please check out the "Epoch Counter" processor
|
10
|
+
# and see if that will meet your needs. If not, then you can always do this calculation
|
11
|
+
# yourself within your agent.
|
12
|
+
#
|
13
|
+
# This processor will be removed from the code base shortly...
|
14
|
+
#
|
15
|
+
#
|
16
|
+
class Rate<NewRelic::Plugin::Processor::Base
|
17
|
+
def initialize
|
18
|
+
puts "OBSOLESCENCE WARNING: The 'Rate' processor is obsolete and should not be used."
|
19
|
+
puts "OBSOLESCENCE WARNING: It will be completely removed in the near future."
|
20
|
+
super :rate,"Rate"
|
21
|
+
end
|
22
|
+
def process val
|
23
|
+
val=val.to_f
|
24
|
+
ret=nil
|
25
|
+
curr_time=Time.now
|
26
|
+
if @last_value and @last_time and curr_time>@last_time
|
27
|
+
ret=(val-@last_value)/(curr_time-@last_time).to_f
|
28
|
+
end
|
29
|
+
@last_value=val
|
30
|
+
@last_time=curr_time
|
31
|
+
ret
|
32
|
+
end
|
33
|
+
#Component::Setup.install_processor Rate
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module NewRelic
|
2
|
+
module Plugin
|
3
|
+
#
|
4
|
+
# Run class. Provides entry points and polling initiation support.
|
5
|
+
#
|
6
|
+
# Author:: Lee Atchison <lee@newrelic.com>
|
7
|
+
# Copyright:: Copyright (c) 2012 New Relic, Inc.
|
8
|
+
#
|
9
|
+
class Run
|
10
|
+
#
|
11
|
+
# Primary Driver entry point
|
12
|
+
#
|
13
|
+
def self.setup_and_run component_type_filter=nil
|
14
|
+
run=new
|
15
|
+
run.setup_from_config component_type_filter
|
16
|
+
run.setup_no_config_agents
|
17
|
+
run.loop_forever
|
18
|
+
end
|
19
|
+
def initialize
|
20
|
+
@poll_cycle = (NewRelic::Plugin::Config.config.newrelic["poll"] || 60).to_i
|
21
|
+
@poll_cycle = 60 if (@poll_cycle <= 0) or (@poll_cycle >= 600)
|
22
|
+
puts "WARNING: Poll cycle differs from 60 seconds (current is #{@poll_cycle})" if @poll_cycle!=60
|
23
|
+
end
|
24
|
+
def installed_agents
|
25
|
+
if Setup.installed_agents.size==0
|
26
|
+
puts "No agents installed!"
|
27
|
+
raise NoAgents, "No agents installed"
|
28
|
+
end
|
29
|
+
Setup.installed_agents
|
30
|
+
end
|
31
|
+
#def installed_processors
|
32
|
+
# Setup.installed_processors
|
33
|
+
#end
|
34
|
+
def configured_agents
|
35
|
+
agent_setup.agents
|
36
|
+
end
|
37
|
+
def setup_from_config component_type_filter=nil
|
38
|
+
return unless NewRelic::Plugin::Config.config.agents
|
39
|
+
installed_agents.each do |agent_id,installed_agent|
|
40
|
+
next if component_type_filter and agent_id!=component_type_filter
|
41
|
+
config_list=NewRelic::Plugin::Config.config.agents[agent_id.to_s]
|
42
|
+
next unless config_list
|
43
|
+
[config_list].flatten.each do |config|
|
44
|
+
next unless config
|
45
|
+
# Convert keys to symbols...
|
46
|
+
config.keys.each {|key|config[(key.to_sym rescue key) || key] = config.delete(key)}
|
47
|
+
name=config.delete(:name) # Pull out name and remove from hash
|
48
|
+
agent_setup.create_agent agent_id,name,config
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
#
|
53
|
+
# Add an entry for agents that require no configuration (and hence no instances)
|
54
|
+
#
|
55
|
+
def setup_no_config_agents
|
56
|
+
installed_agents.each do |agent_id,installed_agent|
|
57
|
+
unless installed_agent[:agent_class].config_required?
|
58
|
+
agent_setup.create_agent agent_id,installed_agent[:agent_class].label,{}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
def setup &block
|
63
|
+
block.call(agent_setup)
|
64
|
+
end
|
65
|
+
#
|
66
|
+
# Call this method to loop forever. This will delay an appropriate amount until
|
67
|
+
# the next metric pull is needed, then it will loop thru all configured agents
|
68
|
+
# and call each one in turn so it can perform it's appropriate metric pull.
|
69
|
+
#
|
70
|
+
def loop_forever
|
71
|
+
if configured_agents.size==0
|
72
|
+
err_msg = "No agents configured!"
|
73
|
+
err_msg+= " Check the agents portion of your yml file." unless NewRelic::Plugin::Config.config.options.empty?
|
74
|
+
puts err_msg
|
75
|
+
raise NoAgents, err_msg
|
76
|
+
end
|
77
|
+
installed_agents.each do |agent_id,installed_agent|
|
78
|
+
version = installed_agent[:agent_class].version
|
79
|
+
puts "Agent #{installed_agent[:label]} is at version #{version}" if version
|
80
|
+
end
|
81
|
+
configured_agents.each do |agent|
|
82
|
+
agent.startup if agent.respond_to? :startup
|
83
|
+
end
|
84
|
+
@done=false
|
85
|
+
begin
|
86
|
+
while !@done
|
87
|
+
#
|
88
|
+
# Set last run time
|
89
|
+
@last_run_time=Time.now
|
90
|
+
#
|
91
|
+
# Call each agent
|
92
|
+
cnt=0
|
93
|
+
configured_agents.each do |agent|
|
94
|
+
begin
|
95
|
+
cnt+=agent.run @poll_cycle
|
96
|
+
rescue => err
|
97
|
+
puts "Error occurred in poll cycle: #{err}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
puts "Gathered #{cnt} statistics"
|
101
|
+
#
|
102
|
+
# Delay until next run
|
103
|
+
secs_to_delay=@poll_cycle-(Time.now-@last_run_time)
|
104
|
+
sleep secs_to_delay if secs_to_delay>0
|
105
|
+
end
|
106
|
+
rescue Interrupt =>err
|
107
|
+
puts "Shutting down..."
|
108
|
+
end
|
109
|
+
configured_agents.each do |agent|
|
110
|
+
agent.shutdown if agent.respond_to? :shutdown
|
111
|
+
end
|
112
|
+
puts "Shutdown complete"
|
113
|
+
end
|
114
|
+
#private
|
115
|
+
def agent_setup
|
116
|
+
@agent_setup||=AgentSetup.new
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module NewRelic
|
2
|
+
module Plugin
|
3
|
+
#
|
4
|
+
# Setup support methods.
|
5
|
+
#
|
6
|
+
# Author:: Lee Atchison <lee@newrelic.com>
|
7
|
+
# Copyright:: Copyright (c) 2012 New Relic, Inc.
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# Setup and Register new agent types and new processors
|
11
|
+
#
|
12
|
+
class Setup
|
13
|
+
class << self
|
14
|
+
def install_agent ident,klass
|
15
|
+
@installed_agents||={}
|
16
|
+
@installed_agents[ident] = {
|
17
|
+
:agent_class => klass::Agent,
|
18
|
+
:label => klass::Agent.label,
|
19
|
+
:ident => ident
|
20
|
+
}
|
21
|
+
end
|
22
|
+
#def install_processor klass
|
23
|
+
# @installed_processors||={}
|
24
|
+
# tmp_instance=klass.new
|
25
|
+
# @installed_processors[tmp_instance.ident]={ident: tmp_instance.ident,processor_class: klass,label: tmp_instance.label}
|
26
|
+
#end
|
27
|
+
def installed_agents
|
28
|
+
@installed_agents||{}
|
29
|
+
end
|
30
|
+
#def installed_processors
|
31
|
+
# @installed_processors||{}
|
32
|
+
#end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Setup and instantiate new agent instances (of agent type previously setup in NewRelic::Plugin::Setup)
|
38
|
+
#
|
39
|
+
class AgentSetup
|
40
|
+
attr_reader :agents
|
41
|
+
def initialize
|
42
|
+
@agents=[]
|
43
|
+
end
|
44
|
+
def create_agent ident,name,options=nil,&block
|
45
|
+
agent_info=Setup.installed_agents[ident]
|
46
|
+
raise UnknownInstalledAgent,"Unrecognized agent '#{ident}'" unless agent_info
|
47
|
+
agent=agent_info[:agent_class].new name,agent_info,options
|
48
|
+
raise CouldNotInitializeAgent unless agent
|
49
|
+
block.call(agent) if block_given?
|
50
|
+
@agents<<agent
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module NewRelic
|
2
|
+
module Plugin
|
3
|
+
#
|
4
|
+
#
|
5
|
+
# SimpleSyntax:
|
6
|
+
#
|
7
|
+
# This is prototype code...the interface is not finalized nor is the implementation
|
8
|
+
# complete. It's proof-of-concept only and subject to change without notice.
|
9
|
+
#
|
10
|
+
#
|
11
|
+
module SimpleSyntax
|
12
|
+
class Agent < NewRelic::Plugin::Agent::Base
|
13
|
+
no_config_required
|
14
|
+
class << self
|
15
|
+
def guid= guid
|
16
|
+
agent_guid guid
|
17
|
+
end
|
18
|
+
def version= version
|
19
|
+
agent_version version
|
20
|
+
end
|
21
|
+
def human_labels label,&block
|
22
|
+
agent_human_labels label,&block
|
23
|
+
end
|
24
|
+
def poll_cycle_proc= block
|
25
|
+
@@poll_cycle_proc = block
|
26
|
+
end
|
27
|
+
end
|
28
|
+
def poll_cycle
|
29
|
+
mod=Module.new
|
30
|
+
mod.send :define_method,:call_poll_cycle,@@poll_cycle_proc
|
31
|
+
self.extend mod
|
32
|
+
self.call_poll_cycle self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.setup
|
38
|
+
yield SimpleSyntax::Agent
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.agent_config
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.poll_cycle &block
|
45
|
+
SimpleSyntax::Agent.poll_cycle_proc = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.run
|
49
|
+
NewRelic::Plugin::Setup.install_agent :simple_syntax,NewRelic::Plugin::SimpleSyntax
|
50
|
+
NewRelic::Plugin::Run.setup_and_run
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|