aws-reporting 0.9.1
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/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
|