diff --git a/lib/AWS/Cloudwatch.rb b/lib/AWS/Cloudwatch.rb index 1db4266..dcd6418 100644 --- a/lib/AWS/Cloudwatch.rb +++ b/lib/AWS/Cloudwatch.rb @@ -14,7 +14,7 @@ module Cloudwatch DEFAULT_HOST = 'monitoring.amazonaws.com' end - API_VERSION = '2009-05-15' + API_VERSION = '2010-08-01' class Base < AWS::Base def api_version @@ -27,4 +27,4 @@ def default_host end end -end \ No newline at end of file +end diff --git a/lib/AWS/Cloudwatch/monitoring.rb b/lib/AWS/Cloudwatch/monitoring.rb index 538debe..4e54274 100644 --- a/lib/AWS/Cloudwatch/monitoring.rb +++ b/lib/AWS/Cloudwatch/monitoring.rb @@ -87,6 +87,87 @@ def get_metric_statistics ( options ={} ) end + # This method pushes custom metrics to AWS. See http://docs.amazonwebservices.com/AmazonCloudWatch/latest/APIReference/API_PutMetricData.html + # + # @option options [String] :namespace (nil) The namepsace of the metric you want to put data into. Cannot begin with 'AWS/'. + # @option options [String] :metric_name (nil) The name of the metric. No explicit creation is required, but it may take up to 15 minutes to show up. + # @option options [String] :unit ('None') One of the units that Amazon supports. See http://docs.amazonwebservices.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html + # @option options [Double] :value (nil) The value of the metric. Very large (10 ** 126) and very small (10 ** -130) will be truncated. + # @option options [optional, Hash] :dimensions ({}) Hash like {'Dimension1' => 'value1', 'Dimension2' => 'value2', ...} that describe the dimensions. + # @option options [Time] :timestamp (Time.now) The timestamp that the point(s) will be recorded fora. + # @option options [Array] :metric_data ([{}]) An array of hashes for the data points that will be sent to CloudWatch. All previous options except :namespace can be specified and will override options specified outside the element. Can only be 20 items long. + # @example + # # Put a single point measured now + # cw.put_metric_data({ + # :namespace => 'Foo', + # :metric_name => 'Clicks', + # :unit => 'Count', + # :value => 5 + # }) + # @example + # # Put one point for each of the last five minutes with one call + # points = (0..5).map do |min| + # {:timestamp => Time.now - min * 60, :value = rand()} + # end + # + # cw.put_metric_data({ + # :namespace => 'Foo' + # :metric_name => 'Clicks', + # :unit => 'Count', + # :metric_data => points, + # :dimensions => {'InstanceID' => 'i-af23c4b9', + # 'InstanceType' => 'm2.4xlarge'} + # }) + # + def put_metric_data( options={} ) + options = { + :timestamp => Time.now, + :metric_data => [{}], + :dimensions => {}, + :unit => 'None' + }.merge options + namespace = options.delete :namespace + metric_data = options.delete :metric_data + metric_data = [{}] unless metric_data.any? + merged_data = metric_data.map do |datum| + options.merge datum + end + + params = { + "Namespace" => namespace + } + raise ArgumentError, ":namespace cannot be blank" if namespace.nil? + + merged_data.each_with_index do |datum, idx| + prefix = "MetricData.member.#{idx+1}." + raise ArgumentError, ":metric_name cannot be blank" if datum[:metric_name].nil? + raise ArgumentError, ":unit cannot be blank" if datum[:unit].nil? + raise ArgumentError, ":value cannot be blank" if datum[:value].nil? + raise ArgumentError, ":timestamp cannot be blank" if datum[:timestamp].nil? + + datum_params = { + prefix + "MetricName" => datum[:metric_name], + prefix + "Unit" => datum[:unit], + prefix + "Value" => datum[:value].to_s, + prefix + "Timestamp" => datum[:timestamp].iso8601 + } + ii = 1 + datum[:dimensions].each_pair do |dimension, value| + dimension_prefix = prefix + "Dimensions.member.#{ii}." + raise ArgumentError, "value cannot be blank for a dimension" if value.nil? + datum_params.merge!({ + dimension_prefix + "Name" => dimension, + dimension_prefix + "Value" => value + }) + ii += 1 + end unless datum[:dimensions].nil? + + params.merge! datum_params + end + + return response_generator(:action => 'PutMetricData', :params => params) + end + end end diff --git a/test/test_Cloudwatch_monitoring.rb b/test/test_Cloudwatch_monitoring.rb new file mode 100644 index 0000000..7e42f9b --- /dev/null +++ b/test/test_Cloudwatch_monitoring.rb @@ -0,0 +1,138 @@ +#-- +# Amazon Web Services EC2 Query API Ruby library +# +# Ruby Gem Name:: amazon-ec2 +# Author:: Glenn Rempe (mailto:glenn@rempe.us) +# Copyright:: Copyright (c) 2007-2008 Glenn Rempe +# License:: Distributes under the same terms as Ruby +# Home:: http://github.com/grempe/amazon-ec2/tree/master +#++ + +require File.dirname(__FILE__) + '/test_helper.rb' + +context "CloudWatch monitoring" do + + before do + @cw = AWS::Cloudwatch::Base.new( :access_key_id => "not a key", :secret_access_key => "not a secret" ) + + @success_response = <<-XML + + + e16fc4d3-9a04-11e0-9362-093a1cae5385 + + + XML + + @now = Time.now + + @simple_request = { + 'Namespace' => 'App/Metrics', + 'MetricData.member.1.MetricName' => 'FooBar', + 'MetricData.member.1.Unit' => 'Count', + 'MetricData.member.1.Value' => '56265313', + 'MetricData.member.1.Timestamp' => Time.now.iso8601, + } + @more_complex_request = { + 'Namespace' => 'App/Metrics', + 'MetricData.member.1.MetricName' => 'FooBar', + 'MetricData.member.1.Unit' => 'Count', + 'MetricData.member.1.Value' => '56265313', + 'MetricData.member.1.Timestamp' => Time.now.iso8601, + 'Namespace' => 'App/Metrics', + 'MetricData.member.2.MetricName' => 'FooBar', + 'MetricData.member.2.Unit' => 'Count', + 'MetricData.member.2.Value' => '56265313', + 'MetricData.member.2.Timestamp' => Time.now.-(60).iso8601, + 'Namespace' => 'App/Metrics', + 'MetricData.member.3.MetricName' => 'FooBar', + 'MetricData.member.3.Unit' => 'Count', + 'MetricData.member.3.Value' => '56265313', + 'MetricData.member.3.Timestamp' => Time.now.-(120).iso8601, + 'Namespace' => 'App/Metrics', + 'MetricData.member.4.MetricName' => 'FooBar', + 'MetricData.member.4.Unit' => 'Count', + 'MetricData.member.4.Value' => '56265313', + 'MetricData.member.4.Timestamp' => Time.now.-(180).iso8601, + 'Namespace' => 'App/Metrics', + 'MetricData.member.5.MetricName' => 'FooBar', + 'MetricData.member.5.Unit' => 'Count', + 'MetricData.member.5.Value' => '56265313', + 'MetricData.member.5.Timestamp' => Time.now.-(240).iso8601, + } + @most_complex_request = { + 'Namespace' => 'App/Metrics', + 'MetricData.member.1.MetricName' => 'FooBar', + 'MetricData.member.1.Unit' => 'Count', + 'MetricData.member.1.Value' => '56265313', + 'MetricData.member.1.Timestamp' => Time.now.iso8601, + 'MetricData.member.1.Dimensions.member.1.Name' => 'InstanceID', + 'MetricData.member.1.Dimensions.member.1.Value' => 'i-12345678', + 'Namespace' => 'App/Metrics', + 'MetricData.member.2.MetricName' => 'FooBar', + 'MetricData.member.2.Unit' => 'Count', + 'MetricData.member.2.Value' => '56265313', + 'MetricData.member.2.Timestamp' => Time.now.-(60).iso8601, + 'MetricData.member.2.Dimensions.member.1.Name' => 'InstanceID', + 'MetricData.member.2.Dimensions.member.1.Value' => 'i-12345678', + 'Namespace' => 'App/Metrics', + 'MetricData.member.3.MetricName' => 'FooBar', + 'MetricData.member.3.Unit' => 'Count', + 'MetricData.member.3.Value' => '43', + 'MetricData.member.3.Timestamp' => Time.now.-(120).iso8601, + 'MetricData.member.3.Dimensions.member.1.Name' => 'InstanceID', + 'MetricData.member.3.Dimensions.member.1.Value' => 'i-12345678', + } + end + + specify "should put a single point without nested hashes" do + @cw.stubs(:make_request).with('PutMetricData', @simple_request). + returns(:body => @success_response, :is_a => true) + @cw.put_metric_data({ + :namespace => 'App/Metrics', + :metric_name => 'FooBar', + :unit => 'Count', + :value => 56265313 + }).should.be.an.instance_of Hash + end + + specify "should put several metric data points with in an array" do + @cw.stubs(:make_request).with('PutMetricData', @more_complex_request). + returns(:body => @success_response, :is_a => true) + @cw.put_metric_data({ + :namespace => 'App/Metrics', + :metric_data => [ + {:metric_name => 'FooBar', :unit => 'Count', :value => 56265313, :timestamp => @now}, + {:metric_name => 'FooBar', :unit => 'Count', :value => 56265313, :timestamp => @now - 60}, + {:metric_name => 'FooBar', :unit => 'Count', :value => 56265313, :timestamp => @now - 120}, + {:metric_name => 'FooBar', :unit => 'Count', :value => 56265313, :timestamp => @now - 180}, + {:metric_name => 'FooBar', :unit => 'Count', :value => 56265313, :timestamp => @now - 240} + ] + }).should.be.an.instance_of Hash + end + + specify "should allow the user to be DRY" do + @cw.stubs(:make_request).with('PutMetricData', @most_complex_request). + returns(:body => @success_response, :is_a => true) + @cw.put_metric_data({ + :namespace => 'App/Metrics', + :metric_name => 'FooBar', + :unit => 'Count', + :dimensions => {'InstanceID' => 'i-12345678'}, # It can accept more dimensions, + # but their order is unpredictable + :value => 56265313, + :metric_data => [ + {:timestamp => @now}, + {:timestamp => @now - 60}, + {:timestamp => @now - 120, :value => 43} # This was the odd one out + ] + }).should.be.an.instance_of Hash + end + + specify "should not allow bad arguments" do + lambda {@cw.put_metric_data()}.should.raise(AWS::ArgumentError) + lambda {@cw.put_metric_data({:namespace => 'TEST', :metric_name => 'FooBar', :unit => 'Click'})}.should.raise(AWS::ArgumentError) + lambda {@cw.put_metric_data({:metric_name => 'FooBar', :unit => 'Click', :value => 23})}.should.raise(AWS::ArgumentError) + lambda {@cw.put_metric_data({:namespace => 'TEST', :unit => 'Click', :value => 23})}.should.raise(AWS::ArgumentError) + end + +end