weatherman 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,55 @@
1
+ ## Weatherman
2
+
3
+ **Weatherman** is a utility to help report custom metrics from EC2 instances to CloudWatch.
4
+
5
+ ## Usage
6
+
7
+ *Simple example*
8
+
9
+ ```ruby
10
+ #Set credentials
11
+ Weatherman::AWS.aws_access_key_id = 'key_id'
12
+ Weatherman::AWS.aws_secret_access_key = 'key'
13
+
14
+ #Report a process count to CloudWatch
15
+ Weatherman::Report.run 'Process count' do
16
+ `ps -ef | wc -l`
17
+ end
18
+ ```
19
+
20
+ *Report options*
21
+
22
+ Report takes an optional hash as a second argument. Supported keys with the defaults, unless otherwise specified:
23
+
24
+ ```ruby
25
+ {
26
+ #Set the metric namespace
27
+ :namespace => 'Custom/Weatherman',
28
+
29
+ #Set the reporting period (in seconds)
30
+ :period => 12,
31
+
32
+ #Metadata for the metric, defaults to just InstanceId
33
+ #InstanceId is always included, even when this is set
34
+ :dimensions => {
35
+ :instance_name => 'super_cool_instance',
36
+ :deployment_id => 1
37
+ }
38
+ }
39
+ ```
40
+
41
+ *Daemonizing*
42
+
43
+ The gem installs a 'weatherman' executable to your path. Pass as many Weatherman scripts to it as you want, each of which will be loaded and run in the background.
44
+
45
+ ## License
46
+
47
+ (The MIT License)
48
+
49
+ Copyright (c) 2012 Mike Marion
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
52
+
53
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
54
+
55
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
data/bin/weatherman ADDED
@@ -0,0 +1,17 @@
1
+ require 'weatherman'
2
+
3
+ ARGV.each do |script|
4
+ if File.exists?(script)
5
+ begin
6
+ load script
7
+ rescue => e
8
+ puts "Error loading #{script}:"
9
+ puts e.message
10
+ puts e.backtrace
11
+ end
12
+ else
13
+ puts "Could not find #{script}"
14
+ end
15
+ end
16
+
17
+ Process.daemon
@@ -0,0 +1,57 @@
1
+ module Weatherman
2
+ module AWS
3
+
4
+ def self.aws_access_key_id
5
+ @aws_access_key_id || ENV['AWS_ACCESS_KEY_ID']
6
+ end
7
+
8
+ def self.aws_access_key_id=(id)
9
+ @aws_access_key_id = id
10
+ end
11
+
12
+ def self.aws_secret_access_key
13
+ @aws_secret_access_key || ENV['AWS_SECRET_ACCESS_KEY']
14
+ end
15
+
16
+ def self.aws_secret_access_key=(key)
17
+ @aws_secret_access_key = key
18
+ end
19
+
20
+ def self.region
21
+ @region || 'us-east-1'
22
+ end
23
+
24
+ def self.region=(region)
25
+ @region = region
26
+ end
27
+
28
+ def self.instance_id
29
+ ec2_attributes[:instance_id]
30
+ end
31
+
32
+ def self.ec2_attributes
33
+ unless @ohai
34
+ @ohai = Ohai::System.new
35
+ @ohai.all_plugins
36
+ @ohai.refresh_plugins
37
+ end
38
+ @ohai.ec2 || {}
39
+ end
40
+
41
+ class CloudWatchMock
42
+ def put_metric_data(*args); end
43
+ end
44
+
45
+ def cloud_watch
46
+ #TODO: Update Fog with mock support for CloudWatch
47
+ @cloud_watch ||= CloudWatchMock.new if Fog.mocking?
48
+
49
+ @cloud_watch ||= Fog::AWS::CloudWatch.new(
50
+ :region => AWS.region,
51
+ :aws_access_key_id => AWS.aws_access_key_id,
52
+ :aws_secret_access_key => AWS.aws_secret_access_key
53
+ )
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ module Weatherman
2
+ class Report
3
+
4
+ include AWS
5
+
6
+ attr_reader :name, :options
7
+
8
+ def self.run(name, options = {}, &collector)
9
+ report = new(name, options, &collector)
10
+ report.run
11
+ report
12
+ end
13
+
14
+ def initialize(name, options = {}, &collector)
15
+ @name = name
16
+ @collector = collector
17
+ @namespace = options[:namespace] || 'Custom/Weatherman'
18
+ @period = options[:period] || 12
19
+ @dimensions = options[:dimensions] || {}
20
+
21
+ @dimensions.merge! 'InstanceId' => AWS.instance_id
22
+ end
23
+
24
+ def run
25
+ @thread ||= Thread.new do
26
+ while true
27
+ report
28
+ sleep @period
29
+ end
30
+ end
31
+ end
32
+
33
+ def stop
34
+ @thread.kill if @thread
35
+ @thread = nil
36
+ end
37
+
38
+ def running?
39
+ !@thread.nil?
40
+ end
41
+
42
+ def report
43
+ value = @collector.call
44
+
45
+ cloud_watch.put_metric_data @namespace, [{
46
+ 'MetricName' => @name,
47
+ 'Value' => value,
48
+ 'Dimensions' => @dimensions.map { |k, v| { k.to_s => v } }
49
+ }]
50
+
51
+ value
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module Weatherman
2
+ VERSION = '0.0.1'
3
+ end
data/lib/weatherman.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'fog'
2
+ require 'ohai'
3
+
4
+ require 'weatherman/version'
5
+
6
+ require 'weatherman/aws'
7
+ require 'weatherman/report'
@@ -0,0 +1,158 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+
3
+ module Weatherman
4
+ describe Report do
5
+
6
+ before do
7
+ @initial_thread_count = Thread.list.length
8
+
9
+ @instance_id = 'i-test'
10
+ AWS.should_receive(:instance_id).at_least(:once).and_return(@instance_id)
11
+
12
+ @namespace = 'TestNamespace'
13
+ @metric_name = 'TestMetric'
14
+ @metric_value = 'some metric value'
15
+ @report = Report.new @metric_name, :namespace => @namespace do
16
+ @metric_value
17
+ end
18
+ end
19
+
20
+ describe 'run' do
21
+ it 'should create and run a report' do
22
+ report = Report.run 'test' do; end
23
+ report.should be_running
24
+ end
25
+ end
26
+
27
+ describe 'run' do
28
+ it 'should start a thread' do
29
+ @report.run
30
+
31
+ Thread.list.length.should == @initial_thread_count + 1
32
+ end
33
+
34
+ it 'should periodically call report' do
35
+ calls = []
36
+ report = Report.new 'test', :period => 1 do
37
+ calls << true
38
+ end
39
+
40
+ report.run
41
+
42
+ sleep 2
43
+ calls.length.should >= 2
44
+ end
45
+
46
+ it 'should do nothing if already running' do
47
+ @report.run
48
+
49
+ @report.run
50
+
51
+ Thread.list.length.should == @initial_thread_count + 1
52
+ end
53
+ end
54
+
55
+ describe 'stop' do
56
+ it 'should stop running report' do
57
+ @report.run
58
+
59
+ @report.stop
60
+
61
+ @report.should_not be_running
62
+ sleep 0.1
63
+ Thread.list.length.should <= @initial_thread_count
64
+ end
65
+
66
+ it 'should do nothing if not running' do
67
+ @report.stop
68
+
69
+ @report.should_not be_running
70
+ Thread.list.length.should <= @initial_thread_count
71
+ end
72
+ end
73
+
74
+ describe 'running?' do
75
+ it 'should be true if the report is running' do
76
+ @report.run
77
+
78
+ @report.should be_running
79
+ end
80
+
81
+ it 'should be false if the report is not running' do
82
+ @report.should_not be_running
83
+ end
84
+
85
+ it 'should be false if the report is stopped' do
86
+ @report.run
87
+
88
+ @report.stop
89
+
90
+ @report.should_not be_running
91
+ end
92
+ end
93
+
94
+ describe 'report' do
95
+ it 'should collect the metric' do
96
+ @report.report.should == @metric_value
97
+ end
98
+
99
+ it 'should report the metric' do
100
+ @report.cloud_watch.should_receive(:put_metric_data) do |namespace, metrics|
101
+ namespace.should == @namespace
102
+
103
+ metrics.length.should == 1
104
+ metric = metrics.first
105
+
106
+ metric['MetricName'].should == @metric_name
107
+ end
108
+
109
+ @report.report
110
+ end
111
+
112
+ it 'should include the instance ID as a default dimension' do
113
+ @report.cloud_watch.should_receive(:put_metric_data) do |namespace, metrics|
114
+ metrics.length.should == 1
115
+
116
+ metric = metrics.first
117
+ dimensions = metric['Dimensions']
118
+
119
+ dimensions.length.should == 1
120
+ puts dimensions.inspect
121
+ dimensions.first['InstanceId'].should == @instance_id
122
+ end
123
+
124
+ @report.report
125
+ end
126
+
127
+ it 'should include user-defined dimensions plus the instance ID' do
128
+ user_dimensions = {
129
+ :alpha => 'beta',
130
+ :gamma => 'delta'
131
+ }
132
+ report = Report.new @metric_name, :namespace => @namespace, :dimensions => user_dimensions do
133
+ @metric_value
134
+ end
135
+
136
+ report.cloud_watch.should_receive(:put_metric_data) do |namespace, metrics|
137
+ metrics.length.should == 1
138
+
139
+ metric = metrics.first
140
+ dimensions = metric['Dimensions']
141
+
142
+ dimensions.length.should == 3
143
+
144
+ user_dimensions['InstanceId'] = @instance_id
145
+ user_dimensions.each do |k, v|
146
+ dimension = dimensions.find { |d| d.keys.first == k.to_s}
147
+ dimension.should_not be_nil
148
+ dimension.length.should == 1
149
+ dimension.values.first.should == v
150
+ end
151
+ end
152
+
153
+ report.report
154
+ end
155
+ end
156
+
157
+ end
158
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'weatherman'
3
+
4
+ Fog.mock!
5
+ Weatherman::AWS.aws_access_key_id = 'test'
6
+ Weatherman::AWS.aws_secret_access_key = 'test'
7
+
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+ config.order = 'random'
13
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "weatherman/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "weatherman"
7
+ s.version = Weatherman::VERSION
8
+ s.authors = ["Mike Marion"]
9
+ s.email = ["mike.marion@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Helps collect custom CloudWatch metrics}
12
+ s.description = %q{Weatherman is for periodically collecting metrics and reporting them to CloudWatch}
13
+
14
+ s.rubyforge_project = "weatherman"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_runtime_dependency "fog", "~> 1.7.0"
23
+ s.add_runtime_dependency "ohai", "~> 6.14.0"
24
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weatherman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mike Marion
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-05 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &12940760 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *12940760
25
+ - !ruby/object:Gem::Dependency
26
+ name: fog
27
+ requirement: &12940260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 1.7.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *12940260
36
+ - !ruby/object:Gem::Dependency
37
+ name: ohai
38
+ requirement: &12939760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 6.14.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *12939760
47
+ description: Weatherman is for periodically collecting metrics and reporting them
48
+ to CloudWatch
49
+ email:
50
+ - mike.marion@gmail.com
51
+ executables:
52
+ - weatherman
53
+ extensions: []
54
+ extra_rdoc_files: []
55
+ files:
56
+ - .gitignore
57
+ - .rspec
58
+ - Gemfile
59
+ - README.md
60
+ - Rakefile
61
+ - bin/weatherman
62
+ - lib/weatherman.rb
63
+ - lib/weatherman/aws.rb
64
+ - lib/weatherman/report.rb
65
+ - lib/weatherman/version.rb
66
+ - spec/report_spec.rb
67
+ - spec/spec_helper.rb
68
+ - weatherman.gemspec
69
+ homepage: ''
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project: weatherman
89
+ rubygems_version: 1.8.10
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Helps collect custom CloudWatch metrics
93
+ test_files: []