aws-reporting 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/aws-reporting +54 -0
- data/lib/aws-reporting.rb +30 -0
- data/lib/aws-reporting/alarm.rb +36 -0
- data/lib/aws-reporting/command/config.rb +75 -0
- data/lib/aws-reporting/command/run.rb +35 -0
- data/lib/aws-reporting/command/serve.rb +31 -0
- data/lib/aws-reporting/command/version.rb +11 -0
- data/lib/aws-reporting/config.rb +24 -0
- data/lib/aws-reporting/error.rb +9 -0
- data/lib/aws-reporting/generator.rb +186 -0
- data/lib/aws-reporting/helper.rb +3 -0
- data/lib/aws-reporting/plan.rb +57 -0
- data/lib/aws-reporting/resolver/ebs.rb +32 -0
- data/lib/aws-reporting/resolver/ec2.rb +32 -0
- data/lib/aws-reporting/resolvers.rb +29 -0
- data/lib/aws-reporting/server.rb +18 -0
- data/lib/aws-reporting/statistics.rb +43 -0
- data/lib/aws-reporting/store.rb +35 -0
- data/lib/aws-reporting/version.rb +9 -0
- data/metrics/metrics.csv +220 -0
- data/metrics/metrics.yaml +1127 -0
- data/template/css/nightmare.css +209 -0
- data/template/img/logo.png +0 -0
- data/template/index.html +41 -0
- data/template/js/graph.js +159 -0
- data/template/js/gui.js +294 -0
- data/template/js/legend.js +51 -0
- data/template/js/lib/d3.min.js +5 -0
- data/template/js/lib/jquery-1.11.1.min.js +4 -0
- data/template/js/lib/jquery.hashchange.min.js +5 -0
- metadata +140 -0
data/bin/aws-reporting
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.expand_path('../../lib/aws-reporting', __FILE__)
|
4
|
+
|
5
|
+
slop = Slop.new(:strict => true) do
|
6
|
+
command 'config' do
|
7
|
+
banner "Usage: aws-reporting config [-h] [-f] [--access-key-id ACCESS_KEY_ID --secret-access-key SECRET_ACCESS_KEY]"
|
8
|
+
on 'h', 'help', "Show this messages"
|
9
|
+
on 'f', 'force', "Force output config file"
|
10
|
+
on 'access-key-id=', "AWS Access Key ID"
|
11
|
+
on 'secret-access-key=', "AWS Secret Access Key"
|
12
|
+
|
13
|
+
run do |opts, args|
|
14
|
+
AwsReporting::Command::Config.run(opts, args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
command 'run' do
|
19
|
+
banner "Usage: aws-reporting run [-f] REPORT_PATH"
|
20
|
+
on 'h', 'help', "Show this messages"
|
21
|
+
on 'f', 'force', "Force output report"
|
22
|
+
|
23
|
+
run do |opts, args|
|
24
|
+
AwsReporting::Command::Run.run(opts, args)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
command 'serve' do
|
29
|
+
banner "Usage: aws-reporting serve [-p PORT] REPORT_PATH"
|
30
|
+
on 'h', 'help', "Show this messages"
|
31
|
+
on 'p=', 'port=', 'Port to listen on'
|
32
|
+
|
33
|
+
run do |opts, args|
|
34
|
+
AwsReporting::Command::Serve.run(opts, args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
command 'version' do
|
39
|
+
run do |opts, args|
|
40
|
+
AwsReporting::Command::Version.run(opts, args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
run do |opts, args|
|
45
|
+
puts opts.help
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
begin
|
50
|
+
slop.parse
|
51
|
+
rescue Slop::Error => e
|
52
|
+
puts e.message
|
53
|
+
puts slop
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../aws-reporting', __FILE__)
|
2
|
+
|
3
|
+
# standard libraries
|
4
|
+
require 'yaml'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
# 3rd party libraries
|
8
|
+
require 'slop'
|
9
|
+
require 'aws-sdk'
|
10
|
+
require 'formatador'
|
11
|
+
require 'parallel'
|
12
|
+
|
13
|
+
# aws-reporting files
|
14
|
+
require 'error'
|
15
|
+
require 'version'
|
16
|
+
require 'server'
|
17
|
+
require 'generator'
|
18
|
+
require 'plan'
|
19
|
+
require 'config'
|
20
|
+
require 'command/config'
|
21
|
+
require 'command/run'
|
22
|
+
require 'command/serve'
|
23
|
+
require 'command/version'
|
24
|
+
require 'resolvers'
|
25
|
+
require 'resolver/ec2'
|
26
|
+
require 'resolver/ebs'
|
27
|
+
require 'helper'
|
28
|
+
require 'alarm'
|
29
|
+
require 'statistics'
|
30
|
+
require 'store'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
module Alarm
|
3
|
+
def serialize(dimensions)
|
4
|
+
dimensions.sort_by{|dimension| dimension[:name]}.map{|dimension| dimension[:name] + "=>" + dimension[:value]}.join(',')
|
5
|
+
end
|
6
|
+
|
7
|
+
def get_alarm_info(region, alarm)
|
8
|
+
info = {:name => alarm.name,
|
9
|
+
:region => region,
|
10
|
+
:namespace => alarm.namespace,
|
11
|
+
:dimensions => alarm.dimensions,
|
12
|
+
:metric_name => alarm.metric_name,
|
13
|
+
:status => get_status(alarm)}
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_status(alarm)
|
17
|
+
return :ALARM if alarm.state_value == 'ALARM'
|
18
|
+
return :ALARM if alarm.history_items.to_a.select{|history| history.history_item_type == 'StateUpdate'}.length > 0
|
19
|
+
return :OK
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_alarms()
|
23
|
+
alarms = []
|
24
|
+
AWS.regions.each{|r|
|
25
|
+
Config.update_region(r.name)
|
26
|
+
cw = AWS::CloudWatch.new
|
27
|
+
cw.alarms.each do |alarm|
|
28
|
+
alarms << get_alarm_info(r.name, alarm)
|
29
|
+
end
|
30
|
+
}
|
31
|
+
alarms.sort_by{|alarm| [alarm[:namespace], serialize(alarm[:dimensions]), alarm[:metric_name], alarm[:name]].join(' ')}
|
32
|
+
end
|
33
|
+
|
34
|
+
module_function :get_alarms
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
module Command
|
3
|
+
module Config
|
4
|
+
DEFAULT_PATH = '~/.aws-reporting/config.yaml'
|
5
|
+
MESSAGE_ALREADY_EXIST = " already exists. If you want to overwrite this, use '-f' option."
|
6
|
+
|
7
|
+
def run(opts, args)
|
8
|
+
Signal.trap(:INT){
|
9
|
+
puts
|
10
|
+
puts "interrupted."
|
11
|
+
exit
|
12
|
+
}
|
13
|
+
|
14
|
+
help = opts['h']
|
15
|
+
if help
|
16
|
+
puts opts.help
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
access = opts['access-key-id']
|
22
|
+
secret = opts['secret-access-key']
|
23
|
+
force = opts['f']
|
24
|
+
|
25
|
+
if access and secret
|
26
|
+
run_batch(access, secret, force)
|
27
|
+
elsif !!!access and !!!secret
|
28
|
+
run_interactive(force)
|
29
|
+
else
|
30
|
+
raise CommandArgumentError.new
|
31
|
+
end
|
32
|
+
rescue AwsReporting::Error::CommandArgumentError
|
33
|
+
puts opts.help
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def run_batch(access, secret, force)
|
38
|
+
path = File.expand_path(DEFAULT_PATH)
|
39
|
+
if force or !File.exist?(path)
|
40
|
+
update_config(access, secret, path)
|
41
|
+
puts 'done.'
|
42
|
+
else
|
43
|
+
puts path + MESSAGE_ALREADY_EXIST
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_interactive(force)
|
48
|
+
path = File.expand_path(DEFAULT_PATH)
|
49
|
+
if force or !File.exist?(path)
|
50
|
+
print 'Access Key ID :'
|
51
|
+
access = $stdin.gets.chomp
|
52
|
+
print 'Secret Access Key:'
|
53
|
+
secret = $stdin.gets.chomp
|
54
|
+
|
55
|
+
update_config(access, secret, path)
|
56
|
+
puts 'done.'
|
57
|
+
else
|
58
|
+
puts path + MESSAGE_ALREADY_EXIST
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def update_config(access, secret, path)
|
63
|
+
yaml = YAML.dump(:access_key_id => access, :secret_access_key => secret)
|
64
|
+
|
65
|
+
FileUtils.mkdir_p(File.dirname(path))
|
66
|
+
|
67
|
+
open(path, 'w'){|f|
|
68
|
+
f.print yaml
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
module_function :run, :run_batch, :run_interactive, :update_config
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
module Command
|
3
|
+
module Run
|
4
|
+
MESSAGE_ALREADY_EXIST = " already exists. If you want to overwrite this, use '-f' option."
|
5
|
+
MESSAGE_NOT_CONFIGURED = "Can not access config file. Run `aws-reporting config` command first."
|
6
|
+
|
7
|
+
def run(opts, args)
|
8
|
+
begin
|
9
|
+
help = opts['h']
|
10
|
+
if help
|
11
|
+
puts opts.help
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
force = opts['f']
|
16
|
+
raise AwsReporting::Error::CommandArgumentError.new unless args.length == 1
|
17
|
+
path = args[0]
|
18
|
+
|
19
|
+
generator = AwsReporting::Generator.new
|
20
|
+
generator.path = path
|
21
|
+
generator.force = force
|
22
|
+
generator.generate
|
23
|
+
rescue AwsReporting::Error::CommandArgumentError
|
24
|
+
puts opts.help
|
25
|
+
rescue AwsReporting::Error::OverwriteError => e
|
26
|
+
puts e.path + MESSAGE_ALREADY_EXIST
|
27
|
+
rescue AwsReporting::Error::ConfigFileLoadError
|
28
|
+
puts MESSAGE_NOT_CONFIGURED
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module_function :run
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
module Command
|
3
|
+
module Serve
|
4
|
+
def run(opts, args)
|
5
|
+
begin
|
6
|
+
help = opts['h']
|
7
|
+
if help
|
8
|
+
puts opts.help
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
port = opts['port'] || 23456
|
13
|
+
raise AwsReporting::Error::CommandArgumentError.new unless args.length == 1
|
14
|
+
path = args[0]
|
15
|
+
|
16
|
+
server = AwsReporting::Server.new(path, port)
|
17
|
+
|
18
|
+
Signal.trap(:INT){
|
19
|
+
server.stop
|
20
|
+
}
|
21
|
+
|
22
|
+
server.start
|
23
|
+
rescue AwsReporting::Error::CommandArgumentError
|
24
|
+
puts opts.help
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module_function :run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
class Config
|
3
|
+
def self.config_file_path()
|
4
|
+
File.expand_path('~/.aws-reporting/config.yaml')
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.load()
|
8
|
+
@@config = YAML.load(open(config_file_path()){|f| f.read})
|
9
|
+
rescue
|
10
|
+
raise AwsReporting::Error::ConfigFileLoadError.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.update_region(region)
|
14
|
+
AWS.config(:access_key_id => @@config[:access_key_id],
|
15
|
+
:secret_access_key => @@config[:secret_access_key],
|
16
|
+
:region => region)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.get()
|
20
|
+
AWS.config(:access_key_id => @@config[:access_key_id],
|
21
|
+
:secret_access_key => @@config[:secret_access_key])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module AwsReporting
|
2
|
+
class Generator
|
3
|
+
attr_accessor :path, :force
|
4
|
+
|
5
|
+
def format_path(path)
|
6
|
+
path.split('/')[-2..-1]
|
7
|
+
.join('/')
|
8
|
+
end
|
9
|
+
|
10
|
+
def transform(metrics, name_resolvers)
|
11
|
+
temp = Hash.new{|h, k| h[k] = Hash.new{|h, k| h[k] = [] }}
|
12
|
+
metrics.each{|namespace, value|
|
13
|
+
value.each{|identification, files|
|
14
|
+
element = {:region => identification[:region],
|
15
|
+
:namespace => namespace,
|
16
|
+
:dimensions => identification[:dimensions],
|
17
|
+
:files => files.sort_by{|entry| entry[:info][:metric_name]}.map{|entry| format_path(entry[:path])}}
|
18
|
+
|
19
|
+
element[:name] = name_resolvers.get_name(element)
|
20
|
+
|
21
|
+
dimension_type = identification[:dimensions].map{|item| item[:name]}.sort
|
22
|
+
|
23
|
+
temp[namespace][dimension_type] << element
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
temp2 = Hash.new{|h, k| h[k] = [] }
|
28
|
+
temp.each{|namespace, dimension_table|
|
29
|
+
dimension_table.each{|dimension_type, elements|
|
30
|
+
temp2[namespace] << {:dimension_type => dimension_type,
|
31
|
+
:elements => elements}
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
result = []
|
36
|
+
temp2.each{|namespace, dimension_table|
|
37
|
+
result << {:namespace => namespace, :dimension_table => dimension_table}
|
38
|
+
}
|
39
|
+
|
40
|
+
sort_result!(result)
|
41
|
+
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
def sort_result!(result)
|
46
|
+
result.sort_by!{|namespace_table| namespace_table[:namespace]}
|
47
|
+
result.each{|namespace_table|
|
48
|
+
namespace_table[:dimension_table].sort_by!{|dimension_table| dimension_table[:dimension_type].join(' ')}
|
49
|
+
}
|
50
|
+
|
51
|
+
result.each{|namespace_table|
|
52
|
+
namespace_table[:dimension_table].each{|dimension_table|
|
53
|
+
dimension_type = dimension_table[:dimension_type]
|
54
|
+
dimension_table[:elements].sort_by!{|element| expand(dimension_type, element[:dimensions]) }
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def expand(dimension_type, dimensions)
|
60
|
+
dimension_hash = {}
|
61
|
+
|
62
|
+
dimensions.each{|dimension|
|
63
|
+
dimension_hash[dimension[:name]] = dimension[:value]
|
64
|
+
}
|
65
|
+
|
66
|
+
values = []
|
67
|
+
dimension_type.each{|dimension_name|
|
68
|
+
values << dimension_hash[dimension_name]
|
69
|
+
}
|
70
|
+
values.join(' ')
|
71
|
+
end
|
72
|
+
|
73
|
+
def merge_status(s0, s1)
|
74
|
+
if s0 == nil or s1 == nil
|
75
|
+
s0 || s1
|
76
|
+
else
|
77
|
+
if s0 == :ALARM or s1 == :ALARM
|
78
|
+
'ALARM'
|
79
|
+
else
|
80
|
+
'OK'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_alarm_tree(alarms)
|
86
|
+
alarm_tree = {}
|
87
|
+
alarms.each{|alarm|
|
88
|
+
key = {:region => alarm[:region],
|
89
|
+
:namespace => alarm[:namespace],
|
90
|
+
:dimensions => alarm[:dimensions],
|
91
|
+
:metric_name => alarm[:metric_name]}
|
92
|
+
alarm_tree[key] = merge_status(alarm_tree[key], alarm[:status])
|
93
|
+
}
|
94
|
+
alarm_tree
|
95
|
+
end
|
96
|
+
|
97
|
+
def set_status(data, alarm_tree)
|
98
|
+
key = {:region => data[:info][:region],
|
99
|
+
:namespace => data[:info][:namespace],
|
100
|
+
:dimensions => data[:info][:dimensions],
|
101
|
+
:metric_name => data[:info][:metric_name]}
|
102
|
+
data[:info][:status] = alarm_tree[key] if alarm_tree[key]
|
103
|
+
end
|
104
|
+
|
105
|
+
def download(base_path, plan, start_time, end_time, period, name_resolvers, timestamp)
|
106
|
+
metrics = Hash.new{|h, k| h[k] = Hash.new{|h, k| h[k] = []}}
|
107
|
+
|
108
|
+
alarms = AwsReporting::Alarm.get_alarms()
|
109
|
+
alarm_tree = build_alarm_tree(alarms)
|
110
|
+
|
111
|
+
mutex = Mutex.new
|
112
|
+
started_at = Time.now
|
113
|
+
num_of_metrics = plan.length
|
114
|
+
num_of_downloaded = 0
|
115
|
+
Parallel.each_with_index(plan, :in_threads => 8){|entry, i|
|
116
|
+
namespace = entry[:namespace]
|
117
|
+
metric_name = entry[:metric_name]
|
118
|
+
dimensions = entry[:dimensions]
|
119
|
+
statistics = entry[:statistics]
|
120
|
+
region = entry[:region]
|
121
|
+
|
122
|
+
mutex.synchronize do
|
123
|
+
num_of_downloaded += 1
|
124
|
+
Formatador.redisplay_progressbar(num_of_downloaded, num_of_metrics, {:started_at => started_at})
|
125
|
+
end
|
126
|
+
|
127
|
+
data = AwsReporting::Statistics.get(region, namespace, metric_name, start_time, end_time, period, dimensions, statistics)
|
128
|
+
set_status(data, alarm_tree)
|
129
|
+
file = AwsReporting::Store.save(base_path, data)
|
130
|
+
|
131
|
+
identification = {:dimensions => dimensions, :region => region}
|
132
|
+
metrics[namespace][identification] << file
|
133
|
+
}
|
134
|
+
|
135
|
+
report_info = {:start_time => start_time.to_s,
|
136
|
+
:end_time => end_time.to_s,
|
137
|
+
:period => period.to_s,
|
138
|
+
:timestamp => timestamp.to_s,
|
139
|
+
:num_of_metrics => plan.length.to_s,
|
140
|
+
:version => Version.get}
|
141
|
+
|
142
|
+
open(base_path + '/metrics.json', 'w'){|f|
|
143
|
+
f.print JSON.dump({:report_info => report_info, :metrics => transform(metrics, name_resolvers), :alarms => alarms})
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
def copy_template(path, force)
|
148
|
+
report_path = File.expand_path(path)
|
149
|
+
if force != true and File.exist?(path)
|
150
|
+
error = AwsReporting::Error::OverwriteError.new
|
151
|
+
error.path = report_path
|
152
|
+
raise error
|
153
|
+
end
|
154
|
+
|
155
|
+
template_path = File.expand_path('../../../template', __FILE__)
|
156
|
+
template_files = Dir.glob(template_path + '/*')
|
157
|
+
FileUtils.mkdir_p(report_path)
|
158
|
+
FileUtils.cp_r(template_files, report_path)
|
159
|
+
end
|
160
|
+
|
161
|
+
def generate
|
162
|
+
timestamp = Time.now
|
163
|
+
|
164
|
+
AwsReporting::Config.load()
|
165
|
+
|
166
|
+
puts 'Report generating started.'
|
167
|
+
copy_template(@path, @force)
|
168
|
+
data_dir = @path + '/data'
|
169
|
+
|
170
|
+
puts '(1/3) Planning...'
|
171
|
+
plan = Plan.generate
|
172
|
+
puts "Planning complete."
|
173
|
+
start_time = Time.now - 24 * 60 * 60 * 14
|
174
|
+
end_time = Time.now
|
175
|
+
period = 60 * 60
|
176
|
+
puts '(2/3) Building name tables...'
|
177
|
+
name_resolvers = AwsReporting::Resolvers.new
|
178
|
+
name_resolvers.init
|
179
|
+
puts 'Name tables were builded.'
|
180
|
+
puts '(3/3) Downloading metrics...'
|
181
|
+
download(data_dir, plan, start_time, end_time, period, name_resolvers, timestamp)
|
182
|
+
puts 'Downloading metrics done.'
|
183
|
+
puts 'Report generating complete!'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|