scout_api 0.9.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/.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
|