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 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