From 31b5295335cff67c95a83b0c25b4d1ce12b22ada Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Fri, 31 Oct 2014 16:49:04 +0000 Subject: [PATCH 1/8] WIP Use cloudwatch to draw an ASCII CPU graph --- lib/capify-ec2.rb | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index 7376d9e..738f291 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -1,3 +1,6 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- + require 'rubygems' require 'fog' require 'colored' @@ -5,6 +8,18 @@ require 'net/https' require File.expand_path(File.dirname(__FILE__) + '/capify-ec2/server') + +def get_spark values + ticks = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇] + min, range, scale = values.min, 100 - values.min, ticks.length - 1 + if !(range == 0) + bar = values.map { |x| ticks[(x / 100.0 * scale).floor] }.join + return bar + " #{values.last.floor}%" + else + return values.map { |x| ticks[1] }.join + end +end + class CapifyEc2 attr_accessor :load_balancer, :instances, :ec2_config @@ -77,6 +92,37 @@ def aws_secret_access_key @ec2_config[:aws_secret_access_key] || Fog.credentials[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY'] || @ec2_config[:use_iam_profile] || raise("Missing AWS Secret Access Key") end + def fetch_cpu instance_id + @cw = Fog::AWS::CloudWatch.new(:aws_access_key_id => aws_access_key_id, + :aws_secret_access_key => aws_secret_access_key) + + time = Time.new + time.gmtime + + dimensions = [{ + "Name" => "InstanceId", + "Value" => instance_id + }] + + result = @cw.get_metric_statistics({'Namespace' => 'AWS/EC2', + 'MetricName' => 'CPUUtilization', + 'Period' => 120, + 'Statistics' => ['Average'], + 'StartTime' => DateTime.parse((time - 60*60).to_s), + 'EndTime' => DateTime.parse(time.to_s), + 'Dimensions' => dimensions}) + + datapoints = result.body.fetch("GetMetricStatisticsResult", {})["Datapoints"] + + require "pry" + binding.pry + + if datapoints + return get_spark datapoints.map {|x| x["Average"]} + end + return "" + end + def display_instances unless desired_instances and desired_instances.any? puts "[Capify-EC2] No instances were found using your 'ec2.yml' configuration.".red.bold @@ -111,6 +157,7 @@ def display_instances status_output << 'Type' .ljust( column_widths[:type] ).bold status_output << 'DNS' .ljust( column_widths[:dns] ).bold status_output << 'Zone' .ljust( 10 ).bold + status_output << 'CPU' .ljust( 16 ).bold status_output << @ec2_config[:aws_stages_tag] .ljust( column_widths[:stages] ).bold if stages_present status_output << @ec2_config[:aws_roles_tag] .ljust( column_widths[:roles] ).bold if roles_present status_output << @ec2_config[:aws_options_tag].ljust( column_widths[:options] ).bold if options_present @@ -124,6 +171,7 @@ def display_instances status_output << instance.flavor_id .ljust( column_widths[:type] ).cyan status_output << instance.contact_point .ljust( column_widths[:dns] ).blue.bold status_output << instance.availability_zone .ljust( 10 ).magenta + status_output << fetch_cpu(instance.id).ljust( 16 ).green status_output << (instance.tags[@ec2_config[:aws_stages_tag]] || '').ljust( column_widths[:stages] ).yellow if stages_present status_output << (instance.tags[@ec2_config[:aws_roles_tag]] || '').ljust( column_widths[:roles] ).yellow if roles_present status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present From a8b263266a640c2d0a85011eec399c009895527f Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 17:57:32 +0000 Subject: [PATCH 2/8] Remove debug --- lib/capify-ec2.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index 738f291..a32e4c7 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -114,9 +114,6 @@ def fetch_cpu instance_id datapoints = result.body.fetch("GetMetricStatisticsResult", {})["Datapoints"] - require "pry" - binding.pry - if datapoints return get_spark datapoints.map {|x| x["Average"]} end From 0aa1f0b546fedc4b9ede8023ac47e0978dab7033 Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 20:20:09 +0000 Subject: [PATCH 3/8] Move cloudwatch stuff out --- lib/capify-ec2.rb | 44 +++------------------------------ lib/capify-ec2/cloudwatch.rb | 48 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 40 deletions(-) create mode 100644 lib/capify-ec2/cloudwatch.rb diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index a32e4c7..55764fa 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -7,19 +7,9 @@ require 'net/http' require 'net/https' require File.expand_path(File.dirname(__FILE__) + '/capify-ec2/server') +require File.expand_path(File.dirname(__FILE__) + '/capify-ec2/cloudwatch') -def get_spark values - ticks = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇] - min, range, scale = values.min, 100 - values.min, ticks.length - 1 - if !(range == 0) - bar = values.map { |x| ticks[(x / 100.0 * scale).floor] }.join - return bar + " #{values.last.floor}%" - else - return values.map { |x| ticks[1] }.join - end -end - class CapifyEc2 attr_accessor :load_balancer, :instances, :ec2_config @@ -92,40 +82,14 @@ def aws_secret_access_key @ec2_config[:aws_secret_access_key] || Fog.credentials[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY'] || @ec2_config[:use_iam_profile] || raise("Missing AWS Secret Access Key") end - def fetch_cpu instance_id - @cw = Fog::AWS::CloudWatch.new(:aws_access_key_id => aws_access_key_id, - :aws_secret_access_key => aws_secret_access_key) - - time = Time.new - time.gmtime - - dimensions = [{ - "Name" => "InstanceId", - "Value" => instance_id - }] - - result = @cw.get_metric_statistics({'Namespace' => 'AWS/EC2', - 'MetricName' => 'CPUUtilization', - 'Period' => 120, - 'Statistics' => ['Average'], - 'StartTime' => DateTime.parse((time - 60*60).to_s), - 'EndTime' => DateTime.parse(time.to_s), - 'Dimensions' => dimensions}) - - datapoints = result.body.fetch("GetMetricStatisticsResult", {})["Datapoints"] - - if datapoints - return get_spark datapoints.map {|x| x["Average"]} - end - return "" - end - def display_instances unless desired_instances and desired_instances.any? puts "[Capify-EC2] No instances were found using your 'ec2.yml' configuration.".red.bold return end + cw = CapifyCloudwatch.new(aws_access_key_id, aws_secret_access_key) + # Set minimum widths for the variable length instance attributes. column_widths = { :name_min => 4, :type_min => 4, :dns_min => 5, :roles_min => @ec2_config[:aws_roles_tag].length, :stages_min => @ec2_config[:aws_stages_tag].length, :options_min => @ec2_config[:aws_options_tag].length } @@ -168,7 +132,7 @@ def display_instances status_output << instance.flavor_id .ljust( column_widths[:type] ).cyan status_output << instance.contact_point .ljust( column_widths[:dns] ).blue.bold status_output << instance.availability_zone .ljust( 10 ).magenta - status_output << fetch_cpu(instance.id).ljust( 16 ).green + status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green status_output << (instance.tags[@ec2_config[:aws_stages_tag]] || '').ljust( column_widths[:stages] ).yellow if stages_present status_output << (instance.tags[@ec2_config[:aws_roles_tag]] || '').ljust( column_widths[:roles] ).yellow if roles_present status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present diff --git a/lib/capify-ec2/cloudwatch.rb b/lib/capify-ec2/cloudwatch.rb new file mode 100644 index 0000000..0d88f13 --- /dev/null +++ b/lib/capify-ec2/cloudwatch.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# -*- coding: utf-8 -*- + +require 'fog' + +class CapifyCloudwatch + def initialize(key_id, secret) + @ticks = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇] + @cw = Fog::AWS::CloudWatch.new(:aws_access_key_id => key_id, + :aws_secret_access_key => secret) + end + + def get_metric(instance_id, metric, hours = 1) + range = hours * (60 * 60) + time = Time.new + start = DateTime.parse((time - range).to_s) + finish = DateTime.parse(time.to_s) + + dimensions = [{ + "Name" => "InstanceId", + "Value" => instance_id + }] + + result = @cw.get_metric_statistics({'Namespace' => 'AWS/EC2', + 'MetricName' => metric, + 'Period' => 120, + 'Statistics' => ['Average'], + 'StartTime' => start, + 'EndTime' => finish, + 'Dimensions' => dimensions}) + + dp = result.body.fetch("GetMetricStatisticsResult", {})["Datapoints"] + + require "pry" + binding.pry + + if dp + return get_spark_line(dp.map {|x| x["Average"]}) + end + return "" + end + + def get_spark_line values + scale = @ticks.length - 1 + bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join + return bar + " #{values.last.floor}%" + end +end From 24d86937a09056a7c7325cd960202a1f5a665132 Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 20:40:38 +0000 Subject: [PATCH 4/8] Move to a new flag `ec2:graph` --- lib/capify-ec2.rb | 8 ++++---- lib/capify-ec2/capistrano.rb | 7 ++++++- lib/capify-ec2/cloudwatch.rb | 11 ++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index 55764fa..8ef2290 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -82,13 +82,13 @@ def aws_secret_access_key @ec2_config[:aws_secret_access_key] || Fog.credentials[:aws_secret_access_key] || ENV['AWS_SECRET_ACCESS_KEY'] || @ec2_config[:use_iam_profile] || raise("Missing AWS Secret Access Key") end - def display_instances + def display_instances(graph: false) unless desired_instances and desired_instances.any? puts "[Capify-EC2] No instances were found using your 'ec2.yml' configuration.".red.bold return end - cw = CapifyCloudwatch.new(aws_access_key_id, aws_secret_access_key) + cw = CapifyCloudwatch.new(aws_access_key_id, aws_secret_access_key) if graph # Set minimum widths for the variable length instance attributes. column_widths = { :name_min => 4, :type_min => 4, :dns_min => 5, :roles_min => @ec2_config[:aws_roles_tag].length, :stages_min => @ec2_config[:aws_stages_tag].length, :options_min => @ec2_config[:aws_options_tag].length } @@ -118,7 +118,7 @@ def display_instances status_output << 'Type' .ljust( column_widths[:type] ).bold status_output << 'DNS' .ljust( column_widths[:dns] ).bold status_output << 'Zone' .ljust( 10 ).bold - status_output << 'CPU' .ljust( 16 ).bold + status_output << 'CPU' .ljust( 16 ).bold if graph status_output << @ec2_config[:aws_stages_tag] .ljust( column_widths[:stages] ).bold if stages_present status_output << @ec2_config[:aws_roles_tag] .ljust( column_widths[:roles] ).bold if roles_present status_output << @ec2_config[:aws_options_tag].ljust( column_widths[:options] ).bold if options_present @@ -132,7 +132,7 @@ def display_instances status_output << instance.flavor_id .ljust( column_widths[:type] ).cyan status_output << instance.contact_point .ljust( column_widths[:dns] ).blue.bold status_output << instance.availability_zone .ljust( 10 ).magenta - status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green + status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green if graph status_output << (instance.tags[@ec2_config[:aws_stages_tag]] || '').ljust( column_widths[:stages] ).yellow if stages_present status_output << (instance.tags[@ec2_config[:aws_roles_tag]] || '').ljust( column_widths[:roles] ).yellow if roles_present status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present diff --git a/lib/capify-ec2/capistrano.rb b/lib/capify-ec2/capistrano.rb index e624b8c..3faf0c2 100644 --- a/lib/capify-ec2/capistrano.rb +++ b/lib/capify-ec2/capistrano.rb @@ -14,6 +14,11 @@ def capify_ec2 capify_ec2.display_instances end + desc "As status but with CPU usage graphs (slower due to Cloudwatch requests)" + task :graph do + capify_ec2.display_instances(graph: true) + end + desc "Prints out all ec2 load balancers" task :elbs do capify_ec2.display_elbs @@ -331,4 +336,4 @@ def remove_default_roles roles.reject! { true } end -end \ No newline at end of file +end diff --git a/lib/capify-ec2/cloudwatch.rb b/lib/capify-ec2/cloudwatch.rb index 0d88f13..a249282 100644 --- a/lib/capify-ec2/cloudwatch.rb +++ b/lib/capify-ec2/cloudwatch.rb @@ -31,9 +31,6 @@ def get_metric(instance_id, metric, hours = 1) dp = result.body.fetch("GetMetricStatisticsResult", {})["Datapoints"] - require "pry" - binding.pry - if dp return get_spark_line(dp.map {|x| x["Average"]}) end @@ -42,7 +39,11 @@ def get_metric(instance_id, metric, hours = 1) def get_spark_line values scale = @ticks.length - 1 - bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join - return bar + " #{values.last.floor}%" + if values + bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join + return bar + " #{values.last.round}%" + else + "" + end end end From afa728fa508ddbb092f4e6cbd5e24a6f998e2ad2 Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 20:41:50 +0000 Subject: [PATCH 5/8] Move to the end --- lib/capify-ec2.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index 8ef2290..be87d6d 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -118,10 +118,10 @@ def display_instances(graph: false) status_output << 'Type' .ljust( column_widths[:type] ).bold status_output << 'DNS' .ljust( column_widths[:dns] ).bold status_output << 'Zone' .ljust( 10 ).bold - status_output << 'CPU' .ljust( 16 ).bold if graph status_output << @ec2_config[:aws_stages_tag] .ljust( column_widths[:stages] ).bold if stages_present status_output << @ec2_config[:aws_roles_tag] .ljust( column_widths[:roles] ).bold if roles_present status_output << @ec2_config[:aws_options_tag].ljust( column_widths[:options] ).bold if options_present + status_output << 'CPU' .ljust( 16 ).bold if graph puts status_output.join(" ") desired_instances.each_with_index do |instance, i| @@ -132,10 +132,10 @@ def display_instances(graph: false) status_output << instance.flavor_id .ljust( column_widths[:type] ).cyan status_output << instance.contact_point .ljust( column_widths[:dns] ).blue.bold status_output << instance.availability_zone .ljust( 10 ).magenta - status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green if graph status_output << (instance.tags[@ec2_config[:aws_stages_tag]] || '').ljust( column_widths[:stages] ).yellow if stages_present status_output << (instance.tags[@ec2_config[:aws_roles_tag]] || '').ljust( column_widths[:roles] ).yellow if roles_present status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present + status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green if graph puts status_output.join(" ") end end From 5a64a6bd2f7e06c43655cc99e128fa15e3e1cda2 Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 20:56:41 +0000 Subject: [PATCH 6/8] Thresholds with different colors --- lib/capify-ec2.rb | 2 +- lib/capify-ec2/cloudwatch.rb | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/capify-ec2.rb b/lib/capify-ec2.rb index be87d6d..776d31f 100644 --- a/lib/capify-ec2.rb +++ b/lib/capify-ec2.rb @@ -135,7 +135,7 @@ def display_instances(graph: false) status_output << (instance.tags[@ec2_config[:aws_stages_tag]] || '').ljust( column_widths[:stages] ).yellow if stages_present status_output << (instance.tags[@ec2_config[:aws_roles_tag]] || '').ljust( column_widths[:roles] ).yellow if roles_present status_output << (instance.tags[@ec2_config[:aws_options_tag]] || '').ljust( column_widths[:options] ).yellow if options_present - status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16).green if graph + status_output << cw.get_metric(instance.id, "CPUUtilization").ljust(16) if graph puts status_output.join(" ") end end diff --git a/lib/capify-ec2/cloudwatch.rb b/lib/capify-ec2/cloudwatch.rb index a249282..7bb439b 100644 --- a/lib/capify-ec2/cloudwatch.rb +++ b/lib/capify-ec2/cloudwatch.rb @@ -4,6 +4,14 @@ require 'fog' class CapifyCloudwatch + # Threshold => color + # Color is applied if metric exceeds the threshold, KEEP IN ORDER + Colors = { + 0 => :green, + 50 => :yellow, + 90 => :red + } + def initialize(key_id, secret) @ticks = %w[▁ ▂ ▃ ▄ ▅ ▆ ▇] @cw = Fog::AWS::CloudWatch.new(:aws_access_key_id => key_id, @@ -37,11 +45,23 @@ def get_metric(instance_id, metric, hours = 1) return "" end - def get_spark_line values + def colorize_output(output, value) + colored = output + Colors.each do |threshold, color| + if value >= threshold + colored = output.send(color) + end + end + colored + end + + def get_spark_line(values) scale = @ticks.length - 1 + if values + final = values.last.round bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join - return bar + " #{values.last.round}%" + return colorize_output(bar.ljust(10) + " #{final}%", final) else "" end From 5747c84bdd252f40a157665246d8a61395dd72a7 Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Sun, 2 Nov 2014 21:04:59 +0000 Subject: [PATCH 7/8] Check for results from cloudwatch --- lib/capify-ec2/cloudwatch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/capify-ec2/cloudwatch.rb b/lib/capify-ec2/cloudwatch.rb index 7bb439b..4cbb1a3 100644 --- a/lib/capify-ec2/cloudwatch.rb +++ b/lib/capify-ec2/cloudwatch.rb @@ -58,7 +58,7 @@ def colorize_output(output, value) def get_spark_line(values) scale = @ticks.length - 1 - if values + if values and values.count > 0 final = values.last.round bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join return colorize_output(bar.ljust(10) + " #{final}%", final) From 70c4b8226e2f3eb2a231a6e4d1fd5a5842aa4cbb Mon Sep 17 00:00:00 2001 From: Stuart Quin Date: Mon, 3 Nov 2014 10:41:36 +0000 Subject: [PATCH 8/8] Fix alignment --- lib/capify-ec2/cloudwatch.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/capify-ec2/cloudwatch.rb b/lib/capify-ec2/cloudwatch.rb index 4cbb1a3..fd4c0dc 100644 --- a/lib/capify-ec2/cloudwatch.rb +++ b/lib/capify-ec2/cloudwatch.rb @@ -8,7 +8,7 @@ class CapifyCloudwatch # Color is applied if metric exceeds the threshold, KEEP IN ORDER Colors = { 0 => :green, - 50 => :yellow, + 70 => :yellow, 90 => :red } @@ -61,7 +61,7 @@ def get_spark_line(values) if values and values.count > 0 final = values.last.round bar = values.map { |x| @ticks[(x / 100.0 * scale).floor] }.join - return colorize_output(bar.ljust(10) + " #{final}%", final) + return colorize_output(bar.rjust(13, @ticks.first) + " #{final}%", final) else "" end