scout_api 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/CHANGELOG ADDED
@@ -0,0 +1,3 @@
1
+ == 0.9.0
2
+
3
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem "hashie", "~> 0.1.8"
4
+ gem "httparty", "~> 0.5.0"
5
+ gem "nokogiri"
6
+
7
+ group :development do
8
+ gem "fakeweb"
9
+ gem "jeweler", "~> 1.5.0"
10
+ end
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