clear_skies 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b28817b1d9a2d115177165c9b1a2ab8d44aeb43
4
- data.tar.gz: 2033b9f62ed98513dc3eb01c3a3ab88d682d2860
3
+ metadata.gz: 82eb78bcb0b8d0e46d77a658b8a46390441f6a61
4
+ data.tar.gz: 96bd95ac7fe050aab5c42705751651d465cc00fc
5
5
  SHA512:
6
- metadata.gz: 86fbbf4d79173b447bd66d5539263ede10256bf23c129397b032ebed7e035560ee899f3b067165327ac149f44636f234cf0aa5519479bf7d6132c44774685fe4
7
- data.tar.gz: 4333fa6071ffeb618af49418fa01e644cf4729b4bf7ec996c45c5b1bdaf8cd99d81739ecc0f51ff7166b98b1792156b3ef7d6e257e3dac0a7214943ff21b143e
6
+ metadata.gz: ea88cec568d392ecdcaa6e9907c43d6d06558348234d83e5cfc4c6258377b986c0f4e0f38a778bbc9d9e3585f4543cff62c638207061debd3b887b741df4a1cd
7
+ data.tar.gz: adac7ec7203afb00fd939cabf39ea7b3e22d146946667372475506f3238727bb030e86345ae45e8ae7adaec1a9c8321c06b84c927016992183890ec9fd24b8e6
data/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
1
  /.bundle/
2
2
  /.yardoc
3
3
  .idea
4
- /Gemfile.lock
5
4
  /_yardoc/
6
5
  /coverage/
7
6
  /doc/
data/Gemfile.lock ADDED
@@ -0,0 +1,149 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ clear_skies (0.4.0)
5
+ awesome_print
6
+ aws-sdk
7
+ elasticsearch
8
+ greek_fire (>= 0.3.0)
9
+ puma
10
+ redis
11
+
12
+ GEM
13
+ remote: https://rubygems.org/
14
+ specs:
15
+ actioncable (5.1.2)
16
+ actionpack (= 5.1.2)
17
+ nio4r (~> 2.0)
18
+ websocket-driver (~> 0.6.1)
19
+ actionmailer (5.1.2)
20
+ actionpack (= 5.1.2)
21
+ actionview (= 5.1.2)
22
+ activejob (= 5.1.2)
23
+ mail (~> 2.5, >= 2.5.4)
24
+ rails-dom-testing (~> 2.0)
25
+ actionpack (5.1.2)
26
+ actionview (= 5.1.2)
27
+ activesupport (= 5.1.2)
28
+ rack (~> 2.0)
29
+ rack-test (~> 0.6.3)
30
+ rails-dom-testing (~> 2.0)
31
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
32
+ actionview (5.1.2)
33
+ activesupport (= 5.1.2)
34
+ builder (~> 3.1)
35
+ erubi (~> 1.4)
36
+ rails-dom-testing (~> 2.0)
37
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
38
+ activejob (5.1.2)
39
+ activesupport (= 5.1.2)
40
+ globalid (>= 0.3.6)
41
+ activemodel (5.1.2)
42
+ activesupport (= 5.1.2)
43
+ activerecord (5.1.2)
44
+ activemodel (= 5.1.2)
45
+ activesupport (= 5.1.2)
46
+ arel (~> 8.0)
47
+ activesupport (5.1.2)
48
+ concurrent-ruby (~> 1.0, >= 1.0.2)
49
+ i18n (~> 0.7)
50
+ minitest (~> 5.1)
51
+ tzinfo (~> 1.1)
52
+ arel (8.0.0)
53
+ awesome_print (1.8.0)
54
+ aws-sdk (2.10.10)
55
+ aws-sdk-resources (= 2.10.10)
56
+ aws-sdk-core (2.10.10)
57
+ aws-sigv4 (~> 1.0)
58
+ jmespath (~> 1.0)
59
+ aws-sdk-resources (2.10.10)
60
+ aws-sdk-core (= 2.10.10)
61
+ aws-sigv4 (1.0.0)
62
+ builder (3.2.3)
63
+ concurrent-ruby (1.0.5)
64
+ elasticsearch (5.0.4)
65
+ elasticsearch-api (= 5.0.4)
66
+ elasticsearch-transport (= 5.0.4)
67
+ elasticsearch-api (5.0.4)
68
+ multi_json
69
+ elasticsearch-transport (5.0.4)
70
+ faraday
71
+ multi_json
72
+ erubi (1.6.1)
73
+ faraday (0.12.1)
74
+ multipart-post (>= 1.2, < 3)
75
+ globalid (0.4.0)
76
+ activesupport (>= 4.2.0)
77
+ greek_fire (0.4.1)
78
+ rails (>= 4.2.0)
79
+ i18n (0.8.6)
80
+ jmespath (1.3.1)
81
+ loofah (2.0.3)
82
+ nokogiri (>= 1.5.9)
83
+ mail (2.6.6)
84
+ mime-types (>= 1.16, < 4)
85
+ method_source (0.8.2)
86
+ mime-types (3.1)
87
+ mime-types-data (~> 3.2015)
88
+ mime-types-data (3.2016.0521)
89
+ mini_portile2 (2.2.0)
90
+ minitest (5.10.2)
91
+ multi_json (1.12.1)
92
+ multipart-post (2.0.0)
93
+ nio4r (2.1.0)
94
+ nokogiri (1.8.0)
95
+ mini_portile2 (~> 2.2.0)
96
+ puma (3.9.1)
97
+ rack (2.0.3)
98
+ rack-test (0.6.3)
99
+ rack (>= 1.0)
100
+ rails (5.1.2)
101
+ actioncable (= 5.1.2)
102
+ actionmailer (= 5.1.2)
103
+ actionpack (= 5.1.2)
104
+ actionview (= 5.1.2)
105
+ activejob (= 5.1.2)
106
+ activemodel (= 5.1.2)
107
+ activerecord (= 5.1.2)
108
+ activesupport (= 5.1.2)
109
+ bundler (>= 1.3.0, < 2.0)
110
+ railties (= 5.1.2)
111
+ sprockets-rails (>= 2.0.0)
112
+ rails-dom-testing (2.0.3)
113
+ activesupport (>= 4.2.0)
114
+ nokogiri (>= 1.6)
115
+ rails-html-sanitizer (1.0.3)
116
+ loofah (~> 2.0)
117
+ railties (5.1.2)
118
+ actionpack (= 5.1.2)
119
+ activesupport (= 5.1.2)
120
+ method_source
121
+ rake (>= 0.8.7)
122
+ thor (>= 0.18.1, < 2.0)
123
+ rake (10.5.0)
124
+ redis (3.3.3)
125
+ sprockets (3.7.1)
126
+ concurrent-ruby (~> 1.0)
127
+ rack (> 1, < 3)
128
+ sprockets-rails (3.2.0)
129
+ actionpack (>= 4.0)
130
+ activesupport (>= 4.0)
131
+ sprockets (>= 3.0.0)
132
+ thor (0.19.4)
133
+ thread_safe (0.3.6)
134
+ tzinfo (1.2.3)
135
+ thread_safe (~> 0.1)
136
+ websocket-driver (0.6.5)
137
+ websocket-extensions (>= 0.1.0)
138
+ websocket-extensions (0.1.2)
139
+
140
+ PLATFORMS
141
+ ruby
142
+
143
+ DEPENDENCIES
144
+ bundler (~> 1.14)
145
+ clear_skies!
146
+ rake (~> 10.0)
147
+
148
+ BUNDLED WITH
149
+ 1.15.1
data/clear_skies.gemspec CHANGED
@@ -24,7 +24,9 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency "puma"
25
25
  spec.add_dependency "aws-sdk"
26
26
  spec.add_dependency "redis"
27
+ spec.add_dependency "elasticsearch"
27
28
  spec.add_dependency "greek_fire", ">= 0.3.0"
29
+
28
30
  spec.add_dependency "awesome_print"
29
31
 
30
32
  spec.add_development_dependency "bundler", "~> 1.14"
@@ -0,0 +1,16 @@
1
+ module ClearSkies
2
+ module AWS
3
+ module CloudWatch
4
+ class Billing < ClearSkies::AWS::CloudWatch::Gauge
5
+ def initialize(dimension, statistics, description: nil, &block)
6
+ super("AWS/Billing", "EstimatedCharges", dimension, statistics, description: description, aws_parameters: {start_time: {days: -1}, end_time: {minutes: -5}, period: 60*60*24}, &block)
7
+ end
8
+
9
+
10
+ def self.cloudwatch_client
11
+ @client ||= Aws::CloudWatch::Client.new(region: "us-east-1")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ module ClearSkies
2
+ module AWS
3
+ module CloudWatch
4
+ class ElasticBeanstalkGauge < ClearSkies::AWS::CloudWatch::Gauge
5
+ def self.beanstalk_client
6
+ @beanstalk_client ||= ::Aws::ElasticBeanstalk::Client.new
7
+ end
8
+ def initialize(metric_name, dimension, statistics, description: nil, &block)
9
+ super("AWS/ElasticBeanstalk", metric_name, dimension, statistics, description: description, &block)
10
+ end
11
+
12
+ def application_name(environment_name)
13
+ ElasticBeanstalkGauge.beanstalk_client.describe_environments({environment_names: [environment_name] }).environments.first&.application_name
14
+ end
15
+
16
+ def vpc_id(application_name, environment_name)
17
+ config = ElasticBeanstalkGauge.beanstalk_client.describe_configuration_settings({ application_name: application_name, environment_name: environment_name }).
18
+ configuration_settings.find { |config| config.application_name == application_name && config.environment_name == environment_name}
19
+ option = config.option_settings.find { |option| option.namespace == "aws:ec2:vpc" && option.option_name == "VPCId"}
20
+ option.value if option
21
+ end
22
+
23
+
24
+ def labels_from_metric(metric)
25
+ labels = super(metric)
26
+
27
+ if labels.has_key?( "environment_name") && !(Rails.cache.fetch("#{labels["environment_name"]}_skip"))
28
+ application_name = Rails.cache.fetch("#{labels["environment_name"]}_application_name", expires_in: 1.hour) do
29
+ application_name(labels["environment_name"])
30
+ end
31
+ if application_name
32
+ labels["application_name"] = application_name
33
+
34
+ labels["vpc_id"] = Rails.cache.fetch("#{labels["environment_name"]}_vpc_id_") do
35
+ vpc_id(labels["application_name"], labels["environment_name"])
36
+ end
37
+ end
38
+ else
39
+ Rails.cache.write("#{labels["environment_name"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
40
+ end
41
+
42
+ return labels
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,47 @@
1
+ module ClearSkies
2
+ module AWS
3
+
4
+ module CloudWatch
5
+ class ELBGauge < ClearSkies::AWS::CloudWatch::Gauge
6
+ def self.elb_client
7
+ @elb_client ||= Aws::ElasticLoadBalancing::Client.new
8
+ end
9
+ def initialize(metric_name, dimension, statistics, description: nil, &block)
10
+ super("AWS/ELB", metric_name, dimension, statistics, description: description, &block)
11
+ end
12
+
13
+ def tags(load_balancer_name)
14
+ labels = {}
15
+ ELBGauge.elb_client.
16
+ describe_tags({load_balancer_names: [load_balancer_name]}).
17
+ tag_descriptions.
18
+ select {|doc| doc.load_balancer_name == load_balancer_name}.each do |tag_description|
19
+ tag_description.tags.each do |tag|
20
+ labels[tag.key.downcase] = tag.value
21
+ end
22
+ end
23
+ labels
24
+ end
25
+
26
+ def labels_from_metric(metric)
27
+ labels = super(metric)
28
+
29
+ if labels.has_key?( "load_balancer_name") && !(Rails.cache.fetch("#{labels["load_balancer_name"]}_skip"))
30
+
31
+ labels["vpc_id"] = Rails.cache.fetch("#{labels["load_balancer_name"]}_vpc_id_") do
32
+ ELBGauge.elb_client.describe_load_balancers(load_balancer_names: [labels["load_balancer_name"]]).load_balancer_descriptions.first.vpc_id
33
+ end
34
+
35
+ labels.merge!(Rails.cache.fetch("#{labels["load_balancer_name"]}_tags_", expires_in: 1.hour, race_condition_ttl: 60.seconds) do
36
+ tags(labels["load_balancer_name"])
37
+ end)
38
+ end
39
+ labels
40
+ rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound
41
+ Rails.cache.write("#{labels["load_balancer_name"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
42
+ return labels
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,73 @@
1
+
2
+ module ClearSkies
3
+ module AWS
4
+ module CloudWatch
5
+ class Gauge < GreekFire::Gauge
6
+ include ActiveModel::Conversion
7
+
8
+ def self.register(*args, &block)
9
+ GreekFire::Metric.register(self.new(*args, &block))
10
+ end
11
+
12
+ def initialize(namespace, metric_name, dimensions, statistics, description:nil, aws_parameters:nil, &block)
13
+ super("#{namespace.underscore.gsub("/", "_")}_#{metric_name.underscore}", description: description)
14
+ @namespace = namespace
15
+ @metric_name = metric_name
16
+ @dimensions = dimensions
17
+ @statistics = statistics.select { |stat| ["SampleCount", "Average", "Sum", "Minimum", "Maximum"].include?(stat.to_s) }
18
+ @extended_statistics = statistics - @statistics
19
+ @aws_parameters = aws_parameters || { }
20
+
21
+ @block = block
22
+ end
23
+
24
+ def self.cloudwatch_client
25
+ @client ||= Aws::CloudWatch::Client.new
26
+ end
27
+
28
+ def aws_metrics
29
+ Aws::CloudWatch::Resource.new(client: self.class.cloudwatch_client).metrics(
30
+ namespace: @namespace,
31
+ metric_name: @metric_name,
32
+ dimensions: @dimensions.map {|dimension| {name: dimension} }
33
+ ).select { |metrics| metrics.dimensions.count == @dimensions.count }
34
+ end
35
+
36
+ def labels_from_metric(metric)
37
+ metric.dimensions.inject(ActiveSupport::HashWithIndifferentAccess.new) do |labels, dimension|
38
+ labels[dimension.name.underscore] = dimension.value
39
+ labels
40
+ end
41
+ end
42
+
43
+ def metrics
44
+ aws_metrics.map do |metric|
45
+ labels = labels_from_metric(metric)
46
+
47
+ next unless @block.call(labels) if @block
48
+
49
+ stats = metric.get_statistics(
50
+ start_time: Time.now.advance(@aws_parameters[:start_time] || {minutes: -6}),
51
+ end_time: Time.now.advance(@aws_parameters[:end_time] || {minutes: -5}),
52
+ period: @aws_parameters[:period] || 1,
53
+ statistics: @statistics,
54
+ extended_statistics: @extended_statistics,
55
+ dimensions: metric.dimensions
56
+ )
57
+
58
+ stats.datapoints.map do |datapoint|
59
+ datapoint.to_h.select {|k, v| ![:unit, :timestamp].include?(k) }.map do |key, value|
60
+ if (key == :extended_statistics)
61
+ value.map {|e_key, e_value| GreekFire::Metric.new(name, labels.merge({statistic: e_key}), e_value)}
62
+ else
63
+ GreekFire::Metric.new(name, labels.merge({statistic: key}), value)
64
+ end
65
+ end
66
+ end
67
+
68
+ end.flatten.compact
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,42 @@
1
+ module ClearSkies
2
+ module AWS
3
+
4
+ module CloudWatch
5
+ class RDSGauge < ClearSkies::AWS::CloudWatch::Gauge
6
+ def initialize(metric_name, dimension, statistics, description: nil, &block)
7
+ super("AWS/RDS", metric_name, dimension, statistics, description: description, &block)
8
+ end
9
+
10
+ def tags(db)
11
+ labels = {}
12
+ db.client.list_tags_for_resource(resource_name: db.db_instance_arn).tag_list.each do |tag|
13
+ labels[tag.key.downcase] = tag.value
14
+ end
15
+ labels
16
+ end
17
+
18
+ def labels_from_metric(metric)
19
+ labels = super(metric)
20
+
21
+ if labels.has_key?( "db_instance_identifier") && !(Rails.cache.fetch("#{labels["db_instance_identifier"]}_skip"))
22
+ db = Aws::RDS::DBInstance.new(labels["db_instance_identifier"])
23
+
24
+ vpc_id = Rails.cache.fetch("#{labels["db_instance_identifier"]}_vpc_id_") do
25
+ db.db_subnet_group&.vpc_id
26
+ end
27
+
28
+ labels["vpc_id"] = vpc_id if vpc_id.present?
29
+
30
+ labels.merge!(Rails.cache.fetch("#{labels["db_instance_identifier"]}_tags_", expires_in: 1.hour, race_condition_ttl: 60.seconds) do
31
+ tags(db)
32
+ end)
33
+ end
34
+ labels
35
+ rescue Aws::RDS::Errors::DBInstanceNotFound
36
+ Rails.cache.write("#{labels["db_instance_identifier"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
37
+ return labels
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ module ClearSkies
2
+ module AWS
3
+
4
+ module CloudWatch
5
+ class RequestCounter < Seahorse::Client::Handler
6
+ @mutex = Mutex.new
7
+ def self.increment
8
+ @mutex.synchronize do
9
+ @count ||= 0
10
+ @count += 1
11
+ end
12
+ end
13
+
14
+ def self.count
15
+ @count ||= 0
16
+ @count
17
+ end
18
+
19
+ def initialize(handler)
20
+ @handler = handler
21
+ end
22
+
23
+ def call(context)
24
+ RequestCounter.increment
25
+ @handler.call(context)
26
+ end
27
+ end
28
+
29
+ class RequestCounterPlugin < Seahorse::Client::Plugin
30
+
31
+ def add_handlers(handlers, config)
32
+ handlers.add(RequestCounter, step: :validate)
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ Aws::CloudWatch::Client.add_plugin(ClearSkies::AWS::CloudWatch::RequestCounterPlugin)
@@ -0,0 +1,129 @@
1
+ module ClearSkies
2
+ module AWS
3
+ class ReservationUtilization < GreekFire::MeasureSet
4
+ def items
5
+ client = Aws::EC2::Client.new()
6
+ reservations = client.describe_reserved_instances(filters: [{name: "state", values: ["active"]}]).reserved_instances.map { |x| ReservationMatcher.new(x) }
7
+
8
+ instance_counts = Hash.new { 0 }
9
+
10
+
11
+ instance_spawns = client.describe_instances(filters: [{name: "instance-state-name", values: ["running"]}])
12
+
13
+ instance_spawns.reservations.each do |spawn|
14
+ spawn.instances.each do |instance|
15
+ reservations.find { |reservation| reservation.match(instance) }
16
+
17
+ instance_counts[{instance_type: instance.instance_type, availability_zone: instance.placement.availability_zone, tenancy: instance.placement.tenancy}] += 1
18
+ end
19
+ end
20
+
21
+ [
22
+ ReservationExpirationGauge.new(reservations),
23
+ ReservationPurchasedGauge.new(reservations),
24
+ ReservationUsageGauge.new(reservations),
25
+ InstancesGauge.new(instance_counts)
26
+ ]
27
+ end
28
+ end
29
+
30
+ class ReservationMatcher
31
+ attr_reader :reservation, :match_count, :instance_count
32
+
33
+ def initialize(reservation)
34
+ @reservation = reservation
35
+ @instance_count = reservation.instance_count
36
+
37
+ @match_count = 0
38
+ end
39
+
40
+ def expires_in
41
+ @reservation.end - Time.now
42
+ end
43
+
44
+ def match(instance)
45
+ return false if match_count >= instance_count
46
+ return false unless @reservation.instance_type == instance.instance_type
47
+ return false unless @reservation.scope == "Region" || @reservation.availability_zone == instance.placement.availability_zone
48
+ return false unless @reservation.instance_tenancy == instance.placement.tenancy
49
+
50
+ @match_count += 1
51
+ return true
52
+ end
53
+
54
+ def labels
55
+ {
56
+ reserved_instances_id: @reservation.reserved_instances_id,
57
+ instance_type: @reservation.instance_type,
58
+ availability_zone: @reservation.availability_zone || "Region",
59
+ tenancy: @reservation.instance_tenancy,
60
+ }
61
+ end
62
+ end
63
+
64
+ class InstancesGauge < GreekFire::Gauge
65
+ def initialize(instance_counts)
66
+ super("aws_ec2_instances", description: "Number of instances running by type") do |labels|
67
+ labels.delete(:count)
68
+ end
69
+
70
+ @instance_counts = instance_counts
71
+ end
72
+
73
+ def labels
74
+ @instance_counts.map do |labels, count|
75
+ labels.merge(count: count)
76
+ end
77
+ end
78
+ end
79
+
80
+ class ReservationPurchasedGauge < GreekFire::Gauge
81
+ def initialize(reservations)
82
+ super("aws_ec2_reservation_purchases", description: "Number of instance reservations purchased") do |labels|
83
+ labels.delete(:reservation).instance_count
84
+ end
85
+
86
+ @reservations = reservations
87
+ end
88
+
89
+ def labels
90
+ @reservations.map do |reservation|
91
+ reservation.labels.merge(reservation: reservation)
92
+ end
93
+ end
94
+ end
95
+
96
+ class ReservationUsageGauge < GreekFire::Gauge
97
+ def initialize(reservations)
98
+ super("aws_ec2_reservation_usage", description: "Number of instance reservations in use") do |labels|
99
+ labels.delete(:reservation).match_count
100
+ end
101
+
102
+ @reservations = reservations
103
+ end
104
+
105
+ def labels
106
+ @reservations.map do |reservation|
107
+ reservation.labels.merge(reservation: reservation)
108
+ end
109
+ end
110
+ end
111
+
112
+ class ReservationExpirationGauge < GreekFire::Gauge
113
+ def initialize(reservations)
114
+ super("aws_ec2_reservation_expire_in", description: "seconds until instance reservation expires") do |labels|
115
+ labels.delete(:reservation).expires_in
116
+ end
117
+
118
+ @reservations = reservations
119
+ end
120
+
121
+ def labels
122
+ @reservations.map do |reservation|
123
+ reservation.labels.merge(reservation: reservation)
124
+ end
125
+ end
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1,76 @@
1
+ require 'elasticsearch'
2
+
3
+ module ClearSkies
4
+ module Elasticsearch
5
+ class Report
6
+
7
+ def self.register(url, extra_labels=nil)
8
+ reports << ClearSkies::Elasticsearch::Report.new(url, extra_labels || Hash.new)
9
+ end
10
+
11
+ def self.reports
12
+ @reports ||= []
13
+ end
14
+
15
+ attr_reader :url, :extra_labels, :metrics
16
+ def initialize(url, extra_labels)
17
+ @url = url
18
+ @extra_labels = extra_labels
19
+ GreekFire::Measure.before_metrics { refresh }
20
+ end
21
+
22
+ def elasticsearch_metrics(client, index)
23
+ metrics = OpenStruct.new
24
+ metrics.index = index
25
+ stats = client.indices.stats["indices"][index]["total"]
26
+ metrics.docs_count = stats["docs"]["count"]
27
+ metrics.docs_deleted = stats["docs"]["deleted"]
28
+ metrics
29
+ end
30
+
31
+ def refresh
32
+ client = ::Elasticsearch::Client.new(hosts: @url)
33
+ aliases = client.indices.get_aliases
34
+
35
+ @metrics = aliases.map do |index, _aliases|
36
+ elasticsearch_metrics(client, index)
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ class Measure < GreekFire::Measure
43
+ def initialize(name, &block)
44
+ super(name) do |label|
45
+ block.call(label.delete(:metric))
46
+ end
47
+ end
48
+
49
+
50
+ def labels
51
+ ClearSkies::Elasticsearch::Report.reports.map do |report|
52
+ report.metrics.map do |metric|
53
+ report.extra_labels.merge({index: metric.index, metric: metric })
54
+ end
55
+ end.flatten
56
+ end
57
+ end
58
+
59
+ class Gauge < ClearSkies::Elasticsearch::Measure
60
+ def to_partial_path
61
+ GreekFire::Gauge._to_partial_path
62
+ end
63
+ end
64
+ class Counter < ClearSkies::Elasticsearch::Measure
65
+ def to_partial_path
66
+ GreekFire::Counter._to_partial_path
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # GreekFire::Metric.register(ClearSkies::Elasticsearch::Gauge.new("elasticsearch_docs_count") { |metrics| metrics.docs_count })
73
+ # GreekFire::Metric.register(ClearSkies::Elasticsearch::Gauge.new("elasticsearch_docs_deleted") { |metrics| metrics.docs_deleted })
74
+
75
+
76
+ #For a <DIM>, grab N metrics.
@@ -1,52 +1,117 @@
1
1
  require 'redis'
2
+
2
3
  module ClearSkies
3
4
  module Redis
5
+
4
6
  class Report
7
+
5
8
  def self.register(host, port, extra_labels=nil)
6
- extra_labels = Hash.new unless extra_labels
7
-
8
- r = ClearSkies::Redis::Report.new(host, port)
9
- databases = Proc.new { ::Redis.new(:host => host, :port => port).info.keys.map {|k| k =~ /^db/ && k.sub("db", "")}.compact }
10
-
11
- GreekFire::Gauge.register("redis_keys", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).keys }
12
- GreekFire::Gauge.register("redis_last_save", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).last_save }
13
- GreekFire::Gauge.register("redis_uptime", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).uptime }
14
- GreekFire::Gauge.register("redis_connected_clients", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).connected_clients }
15
- GreekFire::Gauge.register("redis_blocked_clients", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).blocked_clients }
16
- GreekFire::Gauge.register("redis_used_memory", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).used_memory }
17
- GreekFire::Gauge.register("redis_mem_fragmentation_ratio", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).mem_fragmentation_ratio }
18
- GreekFire::Gauge.register("redis_rdb_changes_since_last_save", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).rdb_changes_since_last_save }
19
- GreekFire::Gauge.register("redis_rdb_last_bgsave_time_sec", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).rdb_last_bgsave_time_sec }
20
- GreekFire::Counter.register("redis_total_commands_processed", labels: {"database" => databases} ) { | labels| labels.merge!(extra_labels);r.redis_metrics(labels).total_commands_processed }
21
- end
22
-
23
-
24
- def redis_metrics(labels)
25
- cache_key = "redis_stats_#{@host}_#{@port}_#{labels["database"]}"
26
- Rails.cache.fetch(cache_key, expires_in: 1.second) do
27
- redis = ::Redis.new(host: @host, port: @port, db: labels["database"])
28
- redis_info = redis.info
29
- metrics = OpenStruct.new
30
-
31
- metrics.keys = redis.dbsize
32
- metrics.last_save = Time.now.to_i - redis.lastsave
33
- metrics.uptime = redis_info["uptime_in_seconds"].to_f
34
- metrics.connected_clients = redis_info["connected_clients"].to_i
35
- metrics.blocked_clients = redis_info["blocked_clients"].to_i
36
- metrics.used_memory = redis_info["used_memory"].to_f
37
- metrics.mem_fragmentation_ratio = redis_info["mem_fragmentation_ratio"].to_f
38
- metrics.rdb_changes_since_last_save = redis_info["rdb_changes_since_last_save"].to_f
39
- metrics.rdb_last_bgsave_time_sec = redis_info["rdb_last_bgsave_time_sec"].to_f
40
- metrics.total_commands_processed = redis_info["total_commands_processed"].to_f
41
- metrics
42
- end
9
+ reports << ClearSkies::Redis::Report.new(host, port, extra_labels || Hash.new)
10
+ end
11
+
12
+ def self.reports
13
+ @reports ||= []
43
14
  end
44
15
 
45
- private
46
- def initialize(host, port)
16
+ attr_reader :host, :port, :extra_labels
17
+ def initialize(host, port, extra_labels)
47
18
  @host = host
48
19
  @port = port
20
+ @extra_labels = {host: host, port: port}.merge extra_labels
21
+ end
22
+
23
+ def dimensions
24
+ ::Redis.new(:host => @host, :port => @port).info.keys.map {|k| k =~ /^db/ && k.sub("db", "")}.compact
25
+ end
26
+
27
+ def report_dimensions
28
+ dimensions.map do |dimension|
29
+ ReportDimension.new(self, dimension)
30
+ end
31
+ end
32
+
33
+ def items
34
+ report_dimensions.map
35
+ end
36
+ end
37
+
38
+ class MeasureSet < GreekFire::MeasureSet
39
+ def items
40
+ report_dimensions = ClearSkies::Redis::Report.reports.map(&:report_dimensions).flatten
41
+ return [] unless report_dimensions.length > 0
42
+
43
+ [
44
+ "keys",
45
+ "last_save",
46
+ "uptime",
47
+ "connected_clients",
48
+ "blocked_clients",
49
+ "used_memory",
50
+ "mem_fragmentation_ratio",
51
+ "rdb_changes_since_last_save",
52
+ "rdb_last_bgsave_time_sec",
53
+ "total_commands_processed"
54
+ ].map do |metric_name|
55
+ ClearSkies::Redis::Gauge.new(report_dimensions, "redis", metric_name)
56
+ end
57
+ end
58
+ end
59
+
60
+ class ReportDimension
61
+ attr_reader :report, :dimension
62
+
63
+ def initialize(report, dimension)
64
+ @report = report
65
+ @dimension = dimension
66
+ end
67
+
68
+ def metrics
69
+ redis = ::Redis.new(host: report.host, port: report.port, db: @dimension)
70
+ redis_info = redis.info
71
+
72
+ metrics = OpenStruct.new
73
+ metrics.keys = redis.dbsize
74
+ metrics.last_save = Time.now.to_i - redis.lastsave
75
+ metrics.uptime = redis_info["uptime_in_seconds"].to_f
76
+ metrics.connected_clients = redis_info["connected_clients"].to_i
77
+ metrics.blocked_clients = redis_info["blocked_clients"].to_i
78
+ metrics.used_memory = redis_info["used_memory"].to_f
79
+ metrics.mem_fragmentation_ratio = redis_info["mem_fragmentation_ratio"].to_f
80
+ metrics.rdb_changes_since_last_save = redis_info["rdb_changes_since_last_save"].to_f
81
+ metrics.rdb_last_bgsave_time_szec = redis_info["rdb_last_bgsave_time_sec"].to_f
82
+ metrics.total_commands_processed = redis_info["total_commands_processed"].to_f
83
+ metrics
84
+ end
85
+ end
86
+
87
+ class Measure < GreekFire::Measure
88
+ def initialize(report_dimensions, prefix, name)
89
+ @report_dimensions = report_dimensions
90
+ super("#{prefix}_#{name}") do |label|
91
+ label.delete(:metric).metrics.to_h[name.to_sym]
92
+ end
93
+ end
94
+
95
+
96
+ def labels
97
+ @report_dimensions.map do |report_dimension|
98
+ report_dimension.report.extra_labels.merge({metric: report_dimension })
99
+ end
100
+ end
101
+ end
102
+
103
+ class Gauge < ClearSkies::Redis::Measure
104
+ def to_partial_path
105
+ GreekFire::Gauge._to_partial_path
106
+ end
107
+ end
108
+ class Counter < ClearSkies::Redis::Measure
109
+ def to_partial_path
110
+ GreekFire::Counter._to_partial_path
49
111
  end
50
112
  end
51
113
  end
52
114
  end
115
+
116
+ GreekFire::Metric.register ClearSkies::Redis::MeasureSet.new
117
+
@@ -1,3 +1,3 @@
1
1
  module ClearSkies
2
- VERSION = "0.2.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/clear_skies.rb CHANGED
@@ -7,9 +7,12 @@ require "greek_fire"
7
7
  require "aws-sdk"
8
8
 
9
9
  require "clear_skies/app"
10
- require "clear_skies/cloud_watch/gauge"
11
- require "clear_skies/cloud_watch/rds_gauge"
12
- require "clear_skies/cloud_watch/elb_gauge"
13
- require "clear_skies/cloud_watch/elastic_beanstalk_gauge"
14
- require "clear_skies/cloud_watch/request_counter"
15
- require "clear_skies/redis/report"
10
+ require "clear_skies/aws/reservation_utilization"
11
+ require "clear_skies/aws/cloud_watch/gauge"
12
+ require "clear_skies/aws/cloud_watch/billing"
13
+ require "clear_skies/aws/cloud_watch/rds_gauge"
14
+ require "clear_skies/aws/cloud_watch/elb_gauge"
15
+ require "clear_skies/aws/cloud_watch/elastic_beanstalk_gauge"
16
+ require "clear_skies/aws/cloud_watch/request_counter"
17
+ require "clear_skies/redis/report"
18
+ require "clear_skies/elasticsearch/report"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: clear_skies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Constantine
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-23 00:00:00.000000000 Z
11
+ date: 2017-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: puma
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: elasticsearch
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: greek_fire
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +135,7 @@ files:
121
135
  - ".ruby-version"
122
136
  - Dockerfile
123
137
  - Gemfile
138
+ - Gemfile.lock
124
139
  - LICENSE.txt
125
140
  - README.md
126
141
  - Rakefile
@@ -130,11 +145,14 @@ files:
130
145
  - exe/clear_skies
131
146
  - lib/clear_skies.rb
132
147
  - lib/clear_skies/app.rb
133
- - lib/clear_skies/cloud_watch/elastic_beanstalk_gauge.rb
134
- - lib/clear_skies/cloud_watch/elb_gauge.rb
135
- - lib/clear_skies/cloud_watch/gauge.rb
136
- - lib/clear_skies/cloud_watch/rds_gauge.rb
137
- - lib/clear_skies/cloud_watch/request_counter.rb
148
+ - lib/clear_skies/aws/cloud_watch/billing.rb
149
+ - lib/clear_skies/aws/cloud_watch/elastic_beanstalk_gauge.rb
150
+ - lib/clear_skies/aws/cloud_watch/elb_gauge.rb
151
+ - lib/clear_skies/aws/cloud_watch/gauge.rb
152
+ - lib/clear_skies/aws/cloud_watch/rds_gauge.rb
153
+ - lib/clear_skies/aws/cloud_watch/request_counter.rb
154
+ - lib/clear_skies/aws/reservation_utilization.rb
155
+ - lib/clear_skies/elasticsearch/report.rb
138
156
  - lib/clear_skies/redis/report.rb
139
157
  - lib/clear_skies/version.rb
140
158
  homepage: https://github.com/omadahealth/clear_skies
@@ -1,45 +0,0 @@
1
- module ClearSkies
2
- module CloudWatch
3
- class ElasticBeanstalkGauge < ClearSkies::CloudWatch::Gauge
4
- def self.client
5
- @client ||= Aws::ElasticBeanstalk::Client.new
6
- end
7
- def initialize(metric_name, dimension, statistics, description: nil, &block)
8
- super("AWS/ElasticBeanstalk", metric_name, dimension, statistics, description: description, &block)
9
- end
10
-
11
- def application_name(environment_name)
12
- ElasticBeanstalkGauge.client.describe_environments({environment_names: [environment_name] }).environments.first&.application_name
13
- end
14
-
15
- def vpc_id(application_name, environment_name)
16
- config = ElasticBeanstalkGauge.client.describe_configuration_settings({ application_name: application_name, environment_name: environment_name }).
17
- configuration_settings.find { |config| config.application_name == application_name && config.environment_name == environment_name}
18
- option = config.option_settings.find { |option| option.namespace == "aws:ec2:vpc" && option.option_name == "VPCId"}
19
- option.value if option
20
- end
21
-
22
-
23
- def labels_from_metric(metric)
24
- labels = super(metric)
25
-
26
- if labels.has_key?( "environment_name") && !(Rails.cache.fetch("#{labels["environment_name"]}_skip"))
27
- application_name = Rails.cache.fetch("#{labels["environment_name"]}_application_name", expires_in: 1.hour) do
28
- application_name(labels["environment_name"])
29
- end
30
- if application_name
31
- labels["application_name"] = application_name
32
-
33
- labels["vpc_id"] = Rails.cache.fetch("#{labels["environment_name"]}_vpc_id_") do
34
- vpc_id(labels["application_name"], labels["environment_name"])
35
- end
36
- end
37
- else
38
- Rails.cache.write("#{labels["environment_name"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
39
- end
40
-
41
- return labels
42
- end
43
- end
44
- end
45
- end
@@ -1,44 +0,0 @@
1
- module ClearSkies
2
- module CloudWatch
3
- class ELBGauge < ClearSkies::CloudWatch::Gauge
4
- def self.client
5
- @client ||= Aws::ElasticLoadBalancing::Client.new
6
- end
7
- def initialize(metric_name, dimension, statistics, description: nil, &block)
8
- super("AWS/ELB", metric_name, dimension, statistics, description: description, &block)
9
- end
10
-
11
- def tags(load_balancer_name)
12
- labels = {}
13
- ELBGauge.client.
14
- describe_tags({load_balancer_names: [load_balancer_name]}).
15
- tag_descriptions.
16
- select {|doc| doc.load_balancer_name == load_balancer_name}.each do |tag_description|
17
- tag_description.tags.each do |tag|
18
- labels[tag.key.downcase] = tag.value
19
- end
20
- end
21
- labels
22
- end
23
-
24
- def labels_from_metric(metric)
25
- labels = super(metric)
26
-
27
- if labels.has_key?( "load_balancer_name") && !(Rails.cache.fetch("#{labels["load_balancer_name"]}_skip"))
28
-
29
- labels["vpc_id"] = Rails.cache.fetch("#{labels["load_balancer_name"]}_vpc_id_") do
30
- ELBGauge.client.describe_load_balancers(load_balancer_names: [labels["load_balancer_name"]]).load_balancer_descriptions.first.vpc_id
31
- end
32
-
33
- labels.merge!(Rails.cache.fetch("#{labels["load_balancer_name"]}_tags_", expires_in: 1.hour, race_condition_ttl: 60.seconds) do
34
- tags(labels["load_balancer_name"])
35
- end)
36
- end
37
- labels
38
- rescue Aws::ElasticLoadBalancing::Errors::LoadBalancerNotFound
39
- Rails.cache.write("#{labels["load_balancer_name"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
40
- return labels
41
- end
42
- end
43
- end
44
- end
@@ -1,70 +0,0 @@
1
-
2
- module ClearSkies
3
- module CloudWatch
4
- class Gauge < GreekFire::Gauge
5
- include ActiveModel::Conversion
6
-
7
- def self.register(*args, &block)
8
- GreekFire::Metric.register(self.new(*args, &block))
9
- end
10
-
11
- def initialize(namespace, metric_name, dimensions, statistics, description:nil, &block)
12
- super("#{namespace.underscore.gsub("/", "_")}_#{metric_name.underscore}", description: description)
13
- @namespace = namespace
14
- @metric_name = metric_name
15
- @dimensions = dimensions
16
- @statistics = statistics.select { |stat| ["SampleCount", "Average", "Sum", "Minimum", "Maximum"].include?(stat.to_s) }
17
- @extended_statistics = statistics - @statistics
18
-
19
- @block = block
20
- end
21
-
22
- def self.cloudwatch_client
23
- @client ||= Aws::CloudWatch::Client.new
24
- end
25
-
26
- def aws_metrics
27
- Aws::CloudWatch::Resource.new(client: Gauge.cloudwatch_client).metrics(
28
- namespace: @namespace,
29
- metric_name: @metric_name,
30
- dimensions: @dimensions.map {|dimension| {name: dimension} }
31
- ).select { |metrics| metrics.dimensions.count == @dimensions.count }
32
- end
33
-
34
- def labels_from_metric(metric)
35
- metric.dimensions.inject(ActiveSupport::HashWithIndifferentAccess.new) do |labels, dimension|
36
- labels[dimension.name.underscore] = dimension.value
37
- labels
38
- end
39
- end
40
-
41
- def metrics
42
- aws_metrics.map do |metric|
43
- labels = labels_from_metric(metric)
44
-
45
- next unless @block.call(labels) if @block
46
-
47
- stats = metric.get_statistics(
48
- start_time: Time.now.advance(minutes: -6),
49
- end_time: Time.now.advance(minutes: -5),
50
- period: 1,
51
- statistics: @statistics,
52
- extended_statistics: @extended_statistics,
53
- dimensions: metric.dimensions
54
- )
55
-
56
- stats.datapoints.map do |datapoint|
57
- datapoint.to_h.select {|k, v| ![:unit, :timestamp].include?(k) }.map do |key, value|
58
- if (key == :extended_statistics)
59
- value.map {|e_key, e_value| GreekFire::Metric.new(name, labels.merge({statistic: e_key}), e_value)}
60
- else
61
- GreekFire::Metric.new(name, labels.merge({statistic: key}), value)
62
- end
63
- end
64
- end
65
-
66
- end.flatten.compact
67
- end
68
- end
69
- end
70
- end
@@ -1,37 +0,0 @@
1
- module ClearSkies
2
- module CloudWatch
3
- class RDSGauge < ClearSkies::CloudWatch::Gauge
4
- def initialize(metric_name, dimension, statistics, description: nil, &block)
5
- super("AWS/RDS", metric_name, dimension, statistics, description: description, &block)
6
- end
7
-
8
- def tags(db)
9
- labels = {}
10
- db.client.list_tags_for_resource(resource_name: db.db_instance_arn).tag_list.each do |tag|
11
- labels[tag.key.downcase] = tag.value
12
- end
13
- labels
14
- end
15
-
16
- def labels_from_metric(metric)
17
- labels = super(metric)
18
-
19
- if labels.has_key?( "db_instance_identifier") && !(Rails.cache.fetch("#{labels["db_instance_identifier"]}_skip"))
20
- db = Aws::RDS::DBInstance.new(labels["db_instance_identifier"])
21
-
22
- labels["vpc_id"] = Rails.cache.fetch("#{labels["db_instance_identifier"]}_vpc_id_") do
23
- db.db_subnet_group.vpc_id
24
- end
25
-
26
- labels.merge!(Rails.cache.fetch("#{labels["db_instance_identifier"]}_tags_", expires_in: 1.hour, race_condition_ttl: 60.seconds) do
27
- tags(db)
28
- end)
29
- end
30
- labels
31
- rescue Aws::RDS::Errors::DBInstanceNotFound
32
- Rails.cache.write("#{labels["db_instance_identifier"]}_skip", true, expires_in: 1.hour, race_condition_ttl: 60.seconds)
33
- return labels
34
- end
35
- end
36
- end
37
- end
@@ -1,37 +0,0 @@
1
- module ClearSkies
2
- module CloudWatch
3
- class RequestCounter < Seahorse::Client::Handler
4
- @mutex = Mutex.new
5
- def self.increment
6
- @mutex.synchronize do
7
- @count ||= 0
8
- @count += 1
9
- end
10
- end
11
-
12
- def self.count
13
- @count ||= 0
14
- @count
15
- end
16
-
17
- def initialize(handler)
18
- @handler = handler
19
- end
20
-
21
- def call(context)
22
- RequestCounter.increment
23
- @handler.call(context)
24
- end
25
- end
26
-
27
- class RequestCounterPlugin < Seahorse::Client::Plugin
28
-
29
- def add_handlers(handlers, config)
30
- handlers.add(RequestCounter, step: :validate)
31
- end
32
-
33
- end
34
- end
35
- end
36
-
37
- Aws::CloudWatch::Client.add_plugin(ClearSkies::CloudWatch::RequestCounterPlugin)