scout_api 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGELOG +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +25 -0
- data/LICENSE +20 -0
- data/README.rdoc +81 -0
- data/Rakefile +43 -0
- data/lib/scout_api/account.rb +72 -0
- data/lib/scout_api/alert.rb +17 -0
- data/lib/scout_api/group.rb +73 -0
- data/lib/scout_api/metric.rb +243 -0
- data/lib/scout_api/metric_calculation.rb +61 -0
- data/lib/scout_api/metric_proxy.rb +112 -0
- data/lib/scout_api/person.rb +3 -0
- data/lib/scout_api/plugin.rb +61 -0
- data/lib/scout_api/server.rb +165 -0
- data/lib/scout_api/trigger.rb +11 -0
- data/lib/scout_api/version.rb +3 -0
- data/lib/scout_api.rb +22 -0
- data/scout_scout.gemspec +86 -0
- data/test/alert_test.rb +20 -0
- data/test/fixtures/activities.xml +33 -0
- data/test/fixtures/client.xml +22 -0
- data/test/fixtures/client_by_hostname.xml +23 -0
- data/test/fixtures/clients.xml +59 -0
- data/test/fixtures/data.xml +5 -0
- data/test/fixtures/descriptors.xml +93 -0
- data/test/fixtures/plugin_data.xml +62 -0
- data/test/fixtures/plugins.xml +127 -0
- data/test/fixtures/triggers.xml +40 -0
- data/test/global_test.rb +27 -0
- data/test/metric_proxy_test.rb +17 -0
- data/test/metric_test.rb +32 -0
- data/test/plugin_test.rb +12 -0
- data/test/server_test.rb +97 -0
- data/test/test_helper.rb +53 -0
- metadata +185 -0
data/.document
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
crack (0.1.6)
|
5
|
+
fakeweb (1.3.0)
|
6
|
+
git (1.2.5)
|
7
|
+
hashie (0.1.8)
|
8
|
+
httparty (0.5.2)
|
9
|
+
crack (= 0.1.6)
|
10
|
+
jeweler (1.5.2)
|
11
|
+
bundler (~> 1.0.0)
|
12
|
+
git (>= 1.2.5)
|
13
|
+
rake
|
14
|
+
nokogiri (1.4.4)
|
15
|
+
rake (0.8.7)
|
16
|
+
|
17
|
+
PLATFORMS
|
18
|
+
ruby
|
19
|
+
|
20
|
+
DEPENDENCIES
|
21
|
+
fakeweb
|
22
|
+
hashie (~> 0.1.8)
|
23
|
+
httparty (~> 0.5.0)
|
24
|
+
jeweler (~> 1.5.0)
|
25
|
+
nokogiri
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jesse Newland
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= scout_api
|
2
|
+
|
3
|
+
A library for interacting with Scout[http://scoutapp.com], a hosted server monitoring service.
|
4
|
+
Query for metric data, manage servers, and more.
|
5
|
+
|
6
|
+
== Getting Started
|
7
|
+
|
8
|
+
Install the gem if you haven't already:
|
9
|
+
|
10
|
+
gem install scout_api
|
11
|
+
|
12
|
+
Require the gem and authenticate to get your Scout on:
|
13
|
+
|
14
|
+
require 'scout_api'
|
15
|
+
scout = Scout::Account.new('youraccountname', 'your@awesome.com', 'sekret')
|
16
|
+
|
17
|
+
== Basics
|
18
|
+
|
19
|
+
You can query for the following high-level objects:
|
20
|
+
|
21
|
+
* Scout::Server
|
22
|
+
* Scout::Group
|
23
|
+
* Scout::Metric
|
24
|
+
|
25
|
+
Each of the classes have Rails-like finder methods. For example:
|
26
|
+
|
27
|
+
# all servers
|
28
|
+
Scout::Server.all
|
29
|
+
|
30
|
+
# first server
|
31
|
+
Scout::Server.first
|
32
|
+
|
33
|
+
# first metric with ID=1
|
34
|
+
Scout::Metric.first(1)
|
35
|
+
|
36
|
+
# all groups with name =~ 'Web'
|
37
|
+
Scout::Group.all(:name => 'Web')
|
38
|
+
|
39
|
+
== Metrics
|
40
|
+
|
41
|
+
Access your metric data stored in Scout. You can retrieve data for a specific metric,
|
42
|
+
a group of metrics, and aggregated metrics. For example:
|
43
|
+
|
44
|
+
# average value of the metric with ID=1 over the past hour
|
45
|
+
Scout::Metric.average(1)
|
46
|
+
|
47
|
+
# maximum rate of insert queries over the previous 24 hours
|
48
|
+
Scout::Server.first(:name => "DB Server").metrics.maximum('inserts',:start => Time.now.utc-(24*3600)*2,:end => Time.now.utc-(24*3600))
|
49
|
+
|
50
|
+
# average memory used across all servers in the 'web servers' group
|
51
|
+
Scout::Group.first(:name => 'web servers').metrics.average('mem_used')
|
52
|
+
Scout::Group.first(:name => 'web servers').metrics.average('mem_used', :format => 'sparkline').to_sparkline
|
53
|
+
Scout::Group.first(:name => 'web servers').metrics.average('mem_used').to_a
|
54
|
+
|
55
|
+
# total throughput across web servers
|
56
|
+
Scout::Group.first(:name => 'web servers').metrics.average('request_rate', :aggregate => true)
|
57
|
+
|
58
|
+
== Server Management
|
59
|
+
|
60
|
+
Create and delete servers:
|
61
|
+
|
62
|
+
# create a server using the server with id=6 as the template.
|
63
|
+
Scout::Server.create('heavy metal',:id => 6) => <#Scout::Server>
|
64
|
+
|
65
|
+
# delete the server with id=10
|
66
|
+
Scout::Server.delete(10)
|
67
|
+
|
68
|
+
== Note on Patches/Pull Requests
|
69
|
+
|
70
|
+
* Fork the project[https://github.com/highgroove/scout-api].
|
71
|
+
* Make your feature addition or bug fix.
|
72
|
+
* Add tests for it. This is important so I don't break it in a
|
73
|
+
future version unintentionally.
|
74
|
+
* Commit, do not mess with rakefile, version, or history.
|
75
|
+
(if you want to have your own version, that is fine but bump version in a
|
76
|
+
commit by itself I can ignore when I pull)
|
77
|
+
* Send me a pull request. Bonus points for topic branches.
|
78
|
+
|
79
|
+
== Copyright
|
80
|
+
|
81
|
+
Copyright (c) 2011 Jesse Newland, Derek Haynes. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup(:default, :development)
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift 'lib'
|
5
|
+
require 'scout_api/version'
|
6
|
+
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
gem.version = Scout::VERSION
|
10
|
+
gem.name = "scout_api"
|
11
|
+
gem.summary = %Q{API wrapper for scoutapp.com}
|
12
|
+
gem.description = %Q{A library for interacting with Scout (http://scoutapp.com), a hosted server monitoring service. Query for metric data, manage servers, and more.}
|
13
|
+
gem.email = "support@scoutapp.com"
|
14
|
+
gem.homepage = "https://scoutapp.com/info/api"
|
15
|
+
gem.authors = ["Jesse Newland", "Derek Haynes"]
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
# dependencies are handled in Gemfile
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
|
21
|
+
require 'rake/rdoctask'
|
22
|
+
Rake::RDocTask.new do |rdoc|
|
23
|
+
version = Scout::VERSION
|
24
|
+
|
25
|
+
rdoc.rdoc_dir = 'rdoc'
|
26
|
+
rdoc.title = "scout_api #{version}"
|
27
|
+
rdoc.rdoc_files.include('README*')
|
28
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Open an irb session preloaded with this library"
|
32
|
+
task :console do
|
33
|
+
sh "irb -rubygems -I lib -r scout.rb"
|
34
|
+
end
|
35
|
+
|
36
|
+
require "rake/testtask"
|
37
|
+
Rake::TestTask.new do |test|
|
38
|
+
test.libs << "test"
|
39
|
+
test.test_files = [ "test/*.rb" ]
|
40
|
+
test.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
task :default => :test
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class Scout::Account
|
2
|
+
include HTTParty
|
3
|
+
base_uri 'https://scoutapp.com'
|
4
|
+
#base_uri 'http://localhost:3000'
|
5
|
+
|
6
|
+
format :xml
|
7
|
+
mattr_inheritable :param
|
8
|
+
|
9
|
+
def initialize(account_param, username, password)
|
10
|
+
self.class.param = account_param
|
11
|
+
self.class.basic_auth username, password
|
12
|
+
end
|
13
|
+
|
14
|
+
# Recent alerts across all servers on this account
|
15
|
+
#
|
16
|
+
# @return [Array] An array of Scout::Alert objects
|
17
|
+
def alerts
|
18
|
+
response = self.class.get("/activities.xml")
|
19
|
+
response['alerts'] ? response['alerts'].map { |alert| Scout::Alert.new(alert) } : Array.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# All servers on this account
|
23
|
+
#
|
24
|
+
# @return [Array] An array of Scout::Server objects
|
25
|
+
def servers
|
26
|
+
warn "Scout#servers is deprecated. Use Scout::Server.all() instead."
|
27
|
+
response = self.class.get("/clients.xml")
|
28
|
+
response['clients'] ? response['clients'].map { |client| Scout::Server.new(client) } : Array.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def people
|
32
|
+
response = self.class.get("/account_memberships")
|
33
|
+
doc = Nokogiri::HTML(response.body)
|
34
|
+
|
35
|
+
tables = doc.css('table.list')
|
36
|
+
# first table is pending
|
37
|
+
# second is active
|
38
|
+
active_table = tables.last
|
39
|
+
user_rows = active_table.css('tr')[1..-1] # skip first row, which is headings
|
40
|
+
|
41
|
+
user_rows.map do |row|
|
42
|
+
name_td, email_td, admin_td, links_td = *row.css('td')
|
43
|
+
|
44
|
+
name = name_td.content.gsub(/[\t\n]/, '')
|
45
|
+
email = email_td.content.gsub(/[\t\n]/, '')
|
46
|
+
admin = admin_td.content.gsub(/[\t\n]/, '') == 'Yes'
|
47
|
+
id = if links_td.inner_html =~ %r{/#{self.class.param}/account_memberships/(\d+)}
|
48
|
+
$1.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
Scout::Person.new :id => id, :name => name, :email => email, :admin => admin
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
alias_method :http_get, :get
|
58
|
+
end
|
59
|
+
|
60
|
+
# Checks for errors via the HTTP status code. If an error is found, a
|
61
|
+
# Scout::Error is raised. Otherwise, the response.
|
62
|
+
#
|
63
|
+
# @return HTTParty::Response
|
64
|
+
def self.get(uri)
|
65
|
+
raise Scout::Error,
|
66
|
+
"Authentication is required (scout = Scout::Account.new('youraccountname', 'your@awesome.com', 'sekret'))" if param.nil?
|
67
|
+
uri = "/#{param}" + uri + (uri.include?('?') ? '&' : '?') + "api_version=#{Scout::VERSION}"
|
68
|
+
#puts "GET: #{uri}"
|
69
|
+
response = http_get(uri)
|
70
|
+
response.code.to_s =~ /^(4|5)/ ? raise( Scout::Error,response.message) : response
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Scout::Alert < Hashie::Mash
|
2
|
+
attr_writer :server
|
3
|
+
|
4
|
+
# The Scout server that generated this alert
|
5
|
+
#
|
6
|
+
# @return [Scout::Server]
|
7
|
+
def server
|
8
|
+
@server ||= Scout::Server.first(client_id)
|
9
|
+
end
|
10
|
+
|
11
|
+
# The Scout plugin that generated this alert
|
12
|
+
#
|
13
|
+
# @return [Scout::Plugin]
|
14
|
+
def plugin
|
15
|
+
server.plugin(plugin_id)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Groups represent a collection of servers.
|
2
|
+
# They can be created in the Scout UI to put similar servers together (ex: Web Servers, Database Servers).
|
3
|
+
class Scout::Group < Hashie::Mash
|
4
|
+
# Retrieve metric information. See Scout::Metric#average for a list of options for the calculation
|
5
|
+
# methods (average, minimum, maximum).
|
6
|
+
#
|
7
|
+
# Examples:
|
8
|
+
#
|
9
|
+
# * <tt>Scout::Group.metrics => All metrics associated with this group.</tt>
|
10
|
+
# * <tt>Scout::Group.metrics.all(:name => 'mem_used') => Metrics with name =~ 'mem_used' across all servers in this group.</tt>
|
11
|
+
# * <tt>Scout::Group.metrics.average(:name => 'mem_used') => Average value of metrics with name =~ 'mem_used' across all servers in the group.</tt>
|
12
|
+
# * <tt>Scout::Group.metrics.maximum(:name => 'mem_used')</tt>
|
13
|
+
# * <tt>Scout::Group.metrics.minimum(:name => 'mem_used')</tt>
|
14
|
+
# * <tt>Scout::Group.metrics.average(:name => 'request_rate', :aggregate => true) => Sum metrics, then take average</tt>
|
15
|
+
# * <tt>Scout::Group.metrics.average(:name => 'request_rate', :start => Time.now.utc-5*3600, :end => Time.now.utc-2*3600) => Retrieve data starting @ 5 hours ago ending at 2 hours ago</tt>
|
16
|
+
# * <tt>Scout::Group.metrics.average(:name => 'mem_used').to_array => An array of time series values over the past hour.</tt>
|
17
|
+
# * <tt>Scout::Group.metrics.average(:name => 'mem_used').to_sparkline => A Url to a Google Sparkline Chart.</tt>
|
18
|
+
attr_reader :metrics
|
19
|
+
|
20
|
+
def initialize(hash) #:nodoc:
|
21
|
+
@metrics = Scout::MetricProxy.new(self)
|
22
|
+
super(hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Finds the first group that meets the given conditions. Possible parameter formats:
|
26
|
+
#
|
27
|
+
# * <tt>Scout::Group.first</tt>
|
28
|
+
# * <tt>Scout::Group.first(1)</tt>
|
29
|
+
# * <tt>Scout::Group.first(:name => 'db slaves')</tt>
|
30
|
+
#
|
31
|
+
# For the <tt>:name</tt>, a {MySQL-formatted Regex}[http://dev.mysql.com/doc/refman/5.0/en/regexp.html] can be used.
|
32
|
+
#
|
33
|
+
# @return [Scout::Group]
|
34
|
+
def self.first(id_or_options = nil)
|
35
|
+
if id_or_options.nil?
|
36
|
+
response = Scout::Account.get("/groups.xml?limit=1")
|
37
|
+
Scout::Group.new(response['groups'].first)
|
38
|
+
elsif id_or_options.is_a?(Hash)
|
39
|
+
if name=id_or_options[:name]
|
40
|
+
response = Scout::Account.get("/groups.xml?name=#{CGI.escape(name)}")
|
41
|
+
raise Scout::Error, 'Not Found' if response['groups'].nil?
|
42
|
+
Scout::Group.new(response['groups'].first)
|
43
|
+
else
|
44
|
+
raise Scout::Error, "Invalid finder condition"
|
45
|
+
end
|
46
|
+
elsif id_or_options.is_a?(Fixnum)
|
47
|
+
response = Scout::Account.get("/groups/#{id_or_options}.xml")
|
48
|
+
Scout::Group.new(response['group'])
|
49
|
+
else
|
50
|
+
raise Scout::Error, "Invalid finder condition"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Finds all groups that meets the given conditions. Possible parameter formats:
|
55
|
+
#
|
56
|
+
# * <tt>Scout::Group.all</tt>
|
57
|
+
# * <tt>Scout::Group.all(:name => 'web')</tt>
|
58
|
+
#
|
59
|
+
# For the <tt>:name</tt>, a {MySQL-formatted Regex}[http://dev.mysql.com/doc/refman/5.0/en/regexp.html] can be used.
|
60
|
+
#
|
61
|
+
# @return [Array] An array of Scout::Group objects
|
62
|
+
def self.all(options = {})
|
63
|
+
if name=options[:name]
|
64
|
+
response = Scout::Account.get("/groups.xml?name=#{CGI.escape(name)}")
|
65
|
+
elsif options.empty?
|
66
|
+
response = Scout::Account.get("/groups.xml")
|
67
|
+
else
|
68
|
+
raise Scout::Error, "Invalid finder condition"
|
69
|
+
end
|
70
|
+
response['groups'] ? response['groups'].map { |g| Scout::Group.new(g) } : Array.new
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
class Scout::Metric < Hashie::Mash
|
2
|
+
attr_accessor :server, :plugin
|
3
|
+
|
4
|
+
def initialize(hash) #:nodoc:
|
5
|
+
super(hash)
|
6
|
+
@avg_calc = Scout::MetricCalculation.new(self,:AVG)
|
7
|
+
@avg_calc.metric_name = identifier
|
8
|
+
@min_calc = Scout::MetricCalculation.new(self,:MIN)
|
9
|
+
@min_calc.metric_name = identifier
|
10
|
+
@max_calc = Scout::MetricCalculation.new(self,:MAX)
|
11
|
+
@max_calc.metric_name = identifier
|
12
|
+
end
|
13
|
+
|
14
|
+
# Finds a single metric that meets the given conditions. Possible parameter formats:
|
15
|
+
#
|
16
|
+
# * <tt>Scout::Metric.first</tt> => Finds the metric
|
17
|
+
# * <tt>Scout::Metric.first(1)</tt> => Finds the metric with ID=1
|
18
|
+
# * <tt>Scout::Metric.first(:name => 'request_rate')</tt> => Finds the first metric where name=~'request_rate'
|
19
|
+
#
|
20
|
+
# For the <tt>:name</tt>, a {MySQL-formatted Regex}[http://dev.mysql.com/doc/refman/5.0/en/regexp.html] can be used.
|
21
|
+
#
|
22
|
+
# @return [Scout::Metric]
|
23
|
+
def self.first(id_or_options = nil)
|
24
|
+
if id_or_options.nil?
|
25
|
+
response = Scout::Account.get("/descriptors.xml?limit=1")
|
26
|
+
Scout::Metric.new(response['ar_descriptors'].first)
|
27
|
+
elsif id_or_options.is_a?(Hash)
|
28
|
+
if name=id_or_options[:name]
|
29
|
+
response = Scout::Account.get("/descriptors.xml?name=#{CGI.escape(name)}")
|
30
|
+
raise Scout::Error, 'Not Found' if response['ar_descriptors'].nil?
|
31
|
+
Scout::Metric.new(response['ar_descriptors'].first)
|
32
|
+
else
|
33
|
+
raise Scout::Error, "Invalid finder condition"
|
34
|
+
end
|
35
|
+
elsif id_or_options.is_a?(Fixnum)
|
36
|
+
response = Scout::Account.get("/descriptors/#{id_or_options}.xml")
|
37
|
+
Scout::Metric.new(response['ar_descriptor'])
|
38
|
+
else
|
39
|
+
raise Scout::Error, "Invalid finder condition"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Search for metrics. MetricProxy uses this method to search for metrics. Refer to MetricProxy#all.
|
44
|
+
#
|
45
|
+
# @return [Array] An array of Scout::Metric objects
|
46
|
+
def self.all(options = {})
|
47
|
+
raise Scout::Error, "A finder condition is required" if options.empty?
|
48
|
+
response = Scout::Account.get("/descriptors.xml?name=#{CGI.escape(options[:name].to_s)}&ids=&plugin_ids=#{options[:plugin_ids]}&server_ids=#{options[:server_ids]}&group_ids=#{options[:group_ids]}")
|
49
|
+
response['ar_descriptors'] ? response['ar_descriptors'].map { |descriptor| Scout::Metric.new(descriptor) } : Array.new
|
50
|
+
end
|
51
|
+
|
52
|
+
# Find the average value of a metric by ID or name (ex: <tt>'disk_used'</tt>). If the metric couldn't be found AND/OR
|
53
|
+
# hasn't reported since <tt>options[:start]</tt>, a [Scout::Error] is raised.
|
54
|
+
#
|
55
|
+
# A 3-element Hash is returned with the following keys:
|
56
|
+
# * <tt>:value</tt>
|
57
|
+
# * <tt>:units</tt>
|
58
|
+
# * <tt>:label</tt>
|
59
|
+
#
|
60
|
+
# <b>Options:</b>
|
61
|
+
#
|
62
|
+
# * <tt>:start</tt> - The start time for grabbing metrics. Default is 1 hour ago. Times will be converted to UTC.
|
63
|
+
# * <tt>:end</tt> - The end time for grabbing metrics. Default is <tt>Time.now.utc</tt>. Times will be converted to UTC.
|
64
|
+
# * <tt>:aggregate</tt> - Whether the metrics should be added together or an average across each metric should be returned.
|
65
|
+
# Default is false. Note that total is not necessary equal to the value on each server * num of servers.
|
66
|
+
#
|
67
|
+
# <b>When to use <tt>:aggregate</tt>?</b>
|
68
|
+
#
|
69
|
+
# If you have a number of web servers, you may be interested in the total throughput for your application, not just the average
|
70
|
+
# on each server. For example:
|
71
|
+
#
|
72
|
+
# * Web Server No. 1 Average Throughput => 100 req/sec
|
73
|
+
# * Web Server No. 2 Average Throughput => 150 req/sec
|
74
|
+
#
|
75
|
+
# <tt>:aggregate => true</tt> will return ~ 250 req/sec, giving the total throughput for your entire app.
|
76
|
+
# The default, <tt>:aggregate => false</tt>, will return ~ 125 req/sec, giving the average throughput across the web servers.
|
77
|
+
#
|
78
|
+
# <tt>:aggregate => true</tt> likely doesn't make sense for any metric that is on a 0-100 scale (like CPU Usage, Disk Capacity, etc.).
|
79
|
+
#
|
80
|
+
# <b>Examples:</b>
|
81
|
+
#
|
82
|
+
# # What is the average request time across my servers?
|
83
|
+
# Scout::Metric.average('request_time') => {:value => 0.20, :units => 'sec', :label => 'Request Time'}
|
84
|
+
#
|
85
|
+
# # What is the average TOTAL throughput across my servers?
|
86
|
+
# Scout::Metric.average('request_rate', :aggregate => true)
|
87
|
+
#
|
88
|
+
# # How much average memory did my servers use yesterday?
|
89
|
+
# # Scout::Metric.average('mem_used', :start => Time.now-(24*60*60)*2, :end => Time.now-(24*60*60))
|
90
|
+
#
|
91
|
+
# @return [Hash]
|
92
|
+
def self.average(id_or_name,options = {})
|
93
|
+
calculate('AVG',id_or_name,options)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Find the maximum value of a metric by ID or name (ex: 'last_minute').
|
97
|
+
#
|
98
|
+
# See +average+ for options and examples.
|
99
|
+
#
|
100
|
+
# @return [Hash]
|
101
|
+
def self.maximum(id_or_name,options = {})
|
102
|
+
calculate('MAX',id_or_name,options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Find the minimum value of a metric by ID or name (ex: 'last_minute').
|
106
|
+
#
|
107
|
+
# See +average+ for options and examples.
|
108
|
+
#
|
109
|
+
# @return [Hash]
|
110
|
+
def self.minimum(id_or_name,options = {})
|
111
|
+
calculate('MIN',id_or_name,options)
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns time series data.
|
115
|
+
#
|
116
|
+
# @return [Array]. This is a two-dimensional array, with the first element being the time in UTC and the second the value at that time.
|
117
|
+
def self.to_array(function,id_or_name,options = {})
|
118
|
+
start_time,end_time=format_times(options)
|
119
|
+
consolidate,name,ids=series_options(id_or_name,options)
|
120
|
+
|
121
|
+
response = Scout::Account.get("/descriptors/series.xml?name=#{CGI.escape(name.to_s)}&ids=#{ids}&function=#{function}&consolidate=#{consolidate}&plugin_ids=#{options[:plugin_ids]}&server_ids=#{options[:server_ids]}&group_ids=#{options[:group_ids]}&start=#{start_time}&end=#{end_time}")
|
122
|
+
|
123
|
+
if response['records']
|
124
|
+
response['records']
|
125
|
+
response['records'].values.flatten.map { |r| [Time.parse(r['time']),r['value'].to_f] }
|
126
|
+
else
|
127
|
+
raise Scout::Error, response['error']
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Generates a URL to a Google chart sparkline representation of the time series data.
|
132
|
+
#
|
133
|
+
# <b>Options:</b>
|
134
|
+
#
|
135
|
+
# * <tt>:size</tt> - The size of the image in pixels. Default is 200x30.
|
136
|
+
# * <tt>:line_color</tt> - The color of the line. Default is 0077cc (blue).
|
137
|
+
# * <tt>:line_width</tt> - The width of the line in pixels. Default is 2.
|
138
|
+
#
|
139
|
+
# @return [String]
|
140
|
+
def self.to_sparkline(function,id_or_name,options = {})
|
141
|
+
start_time,end_time=format_times(options)
|
142
|
+
consolidate,name,ids=series_options(id_or_name,options)
|
143
|
+
puts options.inspect
|
144
|
+
puts start_time.inspect
|
145
|
+
response = Scout::Account.get("/descriptors/sparkline?name=#{CGI.escape(name.to_s)}&ids=#{ids}&function=#{function}&consolidate=#{consolidate}&plugin_ids=#{options[:plugin_ids]}&server_ids=#{options[:server_ids]}&group_ids=#{options[:group_ids]}&start=#{start_time}&end=#{end_time}&size=#{options[:size]}&line_color=#{options[:line_color]}&line_width=#{options[:line_width]}")
|
146
|
+
|
147
|
+
if response['error']
|
148
|
+
raise Scout::Error, response['error']
|
149
|
+
else
|
150
|
+
response.body
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# See Scout::Metric#average for a list of options.
|
155
|
+
#
|
156
|
+
# @return [Hash]
|
157
|
+
def average(opts = {})
|
158
|
+
@avg_calc.options = opts
|
159
|
+
@avg_calc
|
160
|
+
end
|
161
|
+
alias avg average
|
162
|
+
|
163
|
+
# See Scout::Metric#average for a list of options.
|
164
|
+
#
|
165
|
+
# @return [Hash]
|
166
|
+
def maximum(opts = {})
|
167
|
+
@max_calc.options = opts
|
168
|
+
@max_calc
|
169
|
+
end
|
170
|
+
alias max maximum
|
171
|
+
|
172
|
+
# See Scout::Metric#average for a list of options.
|
173
|
+
#
|
174
|
+
# @return [Hash]
|
175
|
+
def minimum(opts = {})
|
176
|
+
@min_calc.options = opts
|
177
|
+
@min_calc
|
178
|
+
end
|
179
|
+
alias min minimum
|
180
|
+
|
181
|
+
# Metrics are identified by either their given ID or their name. If ID is present,
|
182
|
+
# use it.
|
183
|
+
def identifier #:nodoc:
|
184
|
+
id? ? id : name
|
185
|
+
end
|
186
|
+
|
187
|
+
# Used to apply finder conditions
|
188
|
+
def options_for_relationship(opts = {}) #:nodoc:
|
189
|
+
relationship_options = {}
|
190
|
+
if id?
|
191
|
+
relationship_options[:ids] = id
|
192
|
+
elsif plugin
|
193
|
+
relationship_options[:plugin_ids] = plugin.id
|
194
|
+
elsif server
|
195
|
+
relationship_options[:server_ids] = server.id
|
196
|
+
end
|
197
|
+
opts.merge(relationship_options)
|
198
|
+
end
|
199
|
+
|
200
|
+
protected
|
201
|
+
|
202
|
+
def self.series_options(id_or_name,options) #:nodoc:
|
203
|
+
consolidate = options[:aggregate] ? 'SUM' : 'AVG'
|
204
|
+
|
205
|
+
if id_or_name.is_a?(Fixnum)
|
206
|
+
name = nil
|
207
|
+
ids = id_or_name
|
208
|
+
else
|
209
|
+
name = id_or_name
|
210
|
+
ids = nil
|
211
|
+
end
|
212
|
+
|
213
|
+
return [consolidate,name,ids]
|
214
|
+
end
|
215
|
+
|
216
|
+
# The friendlier-named average, minimum, and maximum methods call this method.
|
217
|
+
def self.calculate(function,id_or_name,options = {}) #:nodoc:
|
218
|
+
start_time,end_time=format_times(options)
|
219
|
+
consolidate = options[:aggregate] ? 'SUM' : 'AVG'
|
220
|
+
|
221
|
+
if id_or_name.is_a?(Fixnum)
|
222
|
+
name = nil
|
223
|
+
ids = id_or_name
|
224
|
+
else
|
225
|
+
name = id_or_name
|
226
|
+
ids = nil
|
227
|
+
end
|
228
|
+
|
229
|
+
response = Scout::Account.get("/data/value?name=#{CGI.escape(name.to_s)}&ids=#{ids}&function=#{function}&consolidate=#{consolidate}&plugin_ids=#{options[:plugin_ids]}&server_ids=#{options[:server_ids]}&group_ids=#{options[:group_ids]}&start=#{start_time}&end=#{end_time}")
|
230
|
+
|
231
|
+
if response['data']
|
232
|
+
response['data']
|
233
|
+
else
|
234
|
+
raise Scout::Error, response['error']
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# API expects times in epoch.
|
239
|
+
def self.format_times(options)
|
240
|
+
options.values_at(:start,:end).map { |t| t ? t.to_i : nil }
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Scout::MetricCalculation
|
2
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
3
|
+
attr_accessor :metric_name, :options
|
4
|
+
|
5
|
+
def initialize(owner,function) #:nodoc:
|
6
|
+
@owner = owner
|
7
|
+
@function = function
|
8
|
+
@options = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_sparkline(opts = {})
|
12
|
+
options.merge!(opts).merge!(owner_key_value)
|
13
|
+
Scout::Metric.to_sparkline(
|
14
|
+
@function,metric_name,options
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_array
|
19
|
+
options.merge!(owner_key_value)
|
20
|
+
Scout::Metric.to_array(
|
21
|
+
@function,metric_name,options
|
22
|
+
)
|
23
|
+
end
|
24
|
+
alias to_a to_array
|
25
|
+
|
26
|
+
def load_target #:nodoc:
|
27
|
+
@target = find_target
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_target #:nodoc:
|
31
|
+
options.merge!(owner_key_value)
|
32
|
+
Scout::Metric.calculate(
|
33
|
+
@function,metric_name,options
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def owner_key_value
|
40
|
+
if @owner.is_a?(Scout::Metric)
|
41
|
+
@owner.options_for_relationship
|
42
|
+
else
|
43
|
+
{ (@owner.owner.class.to_s.sub('Scout::','').downcase+'_ids').to_sym => @owner.owner.id}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(method, *args)
|
48
|
+
if load_target
|
49
|
+
unless @target.respond_to?(method)
|
50
|
+
message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
|
51
|
+
raise NoMethodError, message
|
52
|
+
end
|
53
|
+
|
54
|
+
if block_given?
|
55
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
56
|
+
else
|
57
|
+
@target.send(method, *args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|