clear_skies 0.2.0 → 0.4.0

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.
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)