Skip to content

Instantly share code, notes, and snippets.

@shokoe
Last active February 5, 2017 14:23
Show Gist options
  • Save shokoe/22ce47c4b8d23a24dea6ee4c0ad0bd17 to your computer and use it in GitHub Desktop.
Save shokoe/22ce47c4b8d23a24dea6ee4c0ad0bd17 to your computer and use it in GitHub Desktop.
Added load metrics (1,5, and 15 minuted) to 'Amazon CloudWatch Monitoring Scripts for Linux' from https://aws.amazon.com/code/8720044071969977. Adds hostname to all metrics. Requires datatime perl lib (apt-get install libdatetime-perl).
#!/usr/bin/perl -w
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not
# use this file except in compliance with the License. A copy of the License
# is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE" file accompanying this file. This file is distributed
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.
our $usage = <<USAGE;
Usage: mon-put-instance-data.pl [options]
Collects memory, swap, and disk space utilization on an Amazon EC2
instance and sends this data as custom metrics to Amazon CloudWatch.
Description of available options:
--mem-util Reports memory utilization in percentages.
--mem-used Reports memory used in megabytes.
--mem-avail Reports available memory in megabytes.
--load-1 Reports 1 minute load.
--load-5 Reports 5 minute load.
--load-15 Reports 15 minute load.
--swap-util Reports swap utilization in percentages.
--swap-used Reports allocated swap space in megabytes.
--disk-path=PATH Selects the disk by the path on which to report.
--disk-space-util Reports disk space utilization in percentages.
--disk-space-used Reports allocated disk space in gigabytes.
--disk-space-avail Reports available disk space in gigabytes.
--aggregated[=only] Adds aggregated metrics for instance type, AMI id, and region.
If =only is specified, does not report individual instance metrics
--auto-scaling[=only] Reports Auto Scaling metrics in addition to instance metrics.
If =only is specified, does not report individual instance metrics
--mem-used-incl-cache-buff Count memory that is cached and in buffers as used.
--memory-units=UNITS Specifies units for memory metrics.
--disk-space-units=UNITS Specifies units for disk space metrics.
Supported UNITS are bytes, kilobytes, megabytes, and gigabytes.
--aws-credential-file=PATH Specifies the location of the file with AWS credentials.
--aws-access-key-id=VALUE Specifies the AWS access key ID to use to identify the caller.
--aws-secret-key=VALUE Specifies the AWS secret key to use to sign the request.
--aws-iam-role=VALUE Specifies the IAM role used to provide AWS credentials.
--from-cron Specifies that this script is running from cron.
--verify Checks configuration and prepares a remote call.
--verbose Displays details of what the script is doing.
--version Displays the version number.
--help Displays detailed usage information.
Examples
To perform a simple test run without posting data to Amazon CloudWatch
./mon-put-instance-data.pl --mem-util --verify --verbose
To set a five-minute cron schedule to report memory and disk space utilization to CloudWatch
*/5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl --mem-util --disk-space-util --disk-path=/ --from-cron
For more information on how to use this utility, see Amazon CloudWatch Developer Guide at
http://docs.amazonwebservices.com/AmazonCloudWatch/latest/DeveloperGuide/mon-scripts-perl.html
USAGE
use strict;
use warnings;
use Getopt::Long;
use File::Basename;
use Sys::Hostname;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Sys::Syslog qw(:standard :macros);
BEGIN
{
my $script_dir = &File::Basename::dirname($0);
push @INC, $script_dir;
}
use CloudWatchClient;
use constant
{
KILO => 1024,
MEGA => 1048576,
GIGA => 1073741824,
};
use constant
{
INCL_AGGREGATED => 1,
AGGREGATED_ONLY => 2,
};
use constant
{
NOW => 0,
};
my $version = '1.2.1';
my $client_name = 'CloudWatch-PutInstanceData';
my $mcount = 0;
my $report_load_one;
my $report_load_five;
my $report_load_ft;
my $report_mem_util;
my $report_mem_used;
my $report_mem_avail;
my $report_swap_util;
my $report_swap_used;
my $report_disk_util;
my $report_disk_used;
my $report_disk_avail;
my $mem_used_incl_cache_buff;
my @mount_path;
my $mem_units;
my $disk_units;
my $mem_unit_div = 1;
my $disk_unit_div = 1;
my $aggregated;
my $auto_scaling;
my $from_cron;
my $verify;
my $verbose;
my $show_help;
my $show_version;
my $enable_compression;
my $aws_credential_file;
my $aws_access_key_id;
my $aws_secret_key;
my $aws_iam_role;
my $parse_result = 1;
my $parse_error = '';
my $argv_size = @ARGV;
{
# Capture warnings from GetOptions
local $SIG{__WARN__} = sub { $parse_error .= $_[0]; };
$parse_result = GetOptions(
'help|?' => \$show_help,
'version' => \$show_version,
'load-1' => \$report_load_one,
'load-5' => \$report_load_five,
'load-15' => \$report_load_ft,
'mem-util' => \$report_mem_util,
'mem-used' => \$report_mem_used,
'mem-avail' => \$report_mem_avail,
'swap-util' => \$report_swap_util,
'swap-used' => \$report_swap_used,
'disk-path:s' => \@mount_path,
'disk-space-util' => \$report_disk_util,
'disk-space-used' => \$report_disk_used,
'disk-space-avail' => \$report_disk_avail,
'auto-scaling:s' => \$auto_scaling,
'aggregated:s' => \$aggregated,
'memory-units:s' => \$mem_units,
'disk-space-units:s' => \$disk_units,
'mem-used-incl-cache-buff' => \$mem_used_incl_cache_buff,
'verify' => \$verify,
'from-cron' => \$from_cron,
'verbose' => \$verbose,
'aws-credential-file:s' => \$aws_credential_file,
'aws-access-key-id:s' => \$aws_access_key_id,
'aws-secret-key:s' => \$aws_secret_key,
'enable-compression' => \$enable_compression,
'aws-iam-role:s' => \$aws_iam_role,
);
}
# Prints out or logs an error and then exits.
sub exit_with_error
{
my $message = shift;
report_message(LOG_ERR, $message);
if (!$from_cron) {
print STDERR "\nFor more information, run 'mon-put-instance-data.pl --help'\n\n";
}
exit 1;
}
# Prints out or logs a message.
sub report_message
{
my $log_level = shift;
my $message = shift;
chomp $message;
if ($from_cron)
{
setlogsock('unix');
openlog($client_name, 'nofatal', LOG_USER);
syslog($log_level, $message);
closelog;
}
elsif ($log_level == LOG_ERR) {
print STDERR "\nERROR: $message\n";
}
elsif ($log_level == LOG_WARNING) {
print "\nWARNING: $message\n";
}
elsif ($log_level == LOG_INFO) {
print "\nINFO: $message\n";
}
}
if (!$parse_result) {
exit_with_error($parse_error);
}
if ($show_version) {
print "\n$client_name version $version\n\n";
exit 0;
}
if ($show_help || $argv_size < 1) {
print $usage;
exit 0;
}
if ($from_cron) {
$verbose = 0;
}
# check for empty values in provided arguments
if (defined($aws_credential_file) && length($aws_credential_file) == 0) {
exit_with_error("Path to AWS credential file is not provided.");
}
if (defined($aws_access_key_id) && length($aws_access_key_id) == 0) {
exit_with_error("Value of AWS access key id is not specified.");
}
if (defined($aws_secret_key) && length($aws_secret_key) == 0) {
exit_with_error("Value of AWS secret key is not specified.");
}
if (defined($mem_units) && length($mem_units) == 0) {
exit_with_error("Value of memory units is not specified.");
}
if (defined($disk_units) && length($disk_units) == 0) {
exit_with_error("Value of disk space units is not specified.");
}
if (defined($aws_iam_role) && length($aws_iam_role) == 0) {
exit_with_error("Value of AWS IAM role is not specified.");
}
# check for inconsistency of provided arguments
if (defined($aws_credential_file) && defined($aws_access_key_id)) {
exit_with_error("Do not provide AWS credential file and AWS access key id options together.");
}
elsif (defined($aws_credential_file) && defined($aws_secret_key)) {
exit_with_error("Do not provide AWS credential file and AWS secret key options together.");
}
elsif (defined($aws_access_key_id) && !defined($aws_secret_key)) {
exit_with_error("AWS secret key is not specified.");
}
elsif (!defined($aws_access_key_id) && defined($aws_secret_key)) {
exit_with_error("AWS access key id is not specified.");
}
elsif (defined($aws_iam_role) && defined($aws_credential_file)) {
exit_with_error("Do not provide AWS IAM role and AWS credential file options together.");
}
elsif (defined($aws_iam_role) && defined($aws_secret_key)) {
exit_with_error("Do not provide AWS IAM role and AWS access key id/secret key options together.");
}
# decide on the reporting units for memory and swap usage
if (!defined($mem_units) || lc($mem_units) eq 'megabytes') {
$mem_units = 'Megabytes';
$mem_unit_div = MEGA;
}
elsif (lc($mem_units) eq 'bytes') {
$mem_units = 'Bytes';
$mem_unit_div = 1;
}
elsif (lc($mem_units) eq 'kilobytes') {
$mem_units = 'Kilobytes';
$mem_unit_div = KILO;
}
elsif (lc($mem_units) eq 'gigabytes') {
$mem_units = 'Gigabytes';
$mem_unit_div = GIGA;
}
else {
exit_with_error("Unsupported memory units '$mem_units'. Use Bytes, Kilobytes, Megabytes, or Gigabytes.");
}
# decide on the reporting units for disk space usage
if (!defined($disk_units) || lc($disk_units) eq 'gigabytes') {
$disk_units = 'Gigabytes';
$disk_unit_div = GIGA;
}
elsif (lc($disk_units) eq 'bytes') {
$disk_units = 'Bytes';
$disk_unit_div = 1;
}
elsif (lc($disk_units) eq 'kilobytes') {
$disk_units = 'Kilobytes';
$disk_unit_div = KILO;
}
elsif (lc($disk_units) eq 'megabytes') {
$disk_units = 'Megabytes';
$disk_unit_div = MEGA;
}
else {
exit_with_error("Unsupported disk space units '$disk_units'. Use Bytes, Kilobytes, Megabytes, or Gigabytes.");
}
my $df_path = '';
my $report_disk_space;
foreach my $path (@mount_path) {
if (length($path) == 0) {
exit_with_error("Value of disk path is not specified.");
}
elsif (-e $path) {
$report_disk_space = 1;
$df_path .= ' '.$path;
}
else {
exit_with_error("Disk file path '$path' does not exist or cannot be accessed.");
}
}
if ($report_disk_space && !$report_disk_util && !$report_disk_used && !$report_disk_avail) {
exit_with_error("Disk path is provided but metrics to report disk space are not specified.");
}
if (!$report_disk_space && ($report_disk_util || $report_disk_used || $report_disk_avail)) {
exit_with_error("Metrics to report disk space are provided but disk path is not specified.");
}
# check that there is a need to monitor at least something
if (!$report_mem_util && !$report_mem_used && !$report_mem_avail
&& !$report_swap_util && !$report_swap_used && !$report_disk_space
&& !$report_load_one && !$report_load_five && !$report_load_ft)
{
exit_with_error("No metrics specified for collection and submission to CloudWatch.");
}
my $timestamp = CloudWatchClient::get_offset_time(NOW);
my $instance_id = CloudWatchClient::get_instance_id();
if (!defined($instance_id) || length($instance_id) == 0) {
exit_with_error("Cannot obtain instance id from EC2 meta-data.");
}
if ($aggregated && lc($aggregated) ne 'only') {
exit_with_error("Unrecognized value '$aggregated' for --aggregated option.");
}
if ($aggregated && lc($aggregated) eq 'only') {
$aggregated = AGGREGATED_ONLY;
}
elsif (defined($aggregated)) {
$aggregated = INCL_AGGREGATED;
}
my $image_id;
my $instance_type;
if ($aggregated) {
$image_id = CloudWatchClient::get_image_id();
$instance_type = CloudWatchClient::get_instance_type();
}
if ($auto_scaling && lc($auto_scaling) ne 'only') {
exit_with_error("Unrecognized value '$auto_scaling' for --auto-scaling option.");
}
if ($auto_scaling && lc($auto_scaling) eq 'only') {
$auto_scaling = AGGREGATED_ONLY;
}
elsif (defined($auto_scaling)) {
$auto_scaling = INCL_AGGREGATED;
}
my $as_group_name;
if ($auto_scaling)
{
my %opts = ();
$opts{'aws-credential-file'} = $aws_credential_file;
$opts{'aws-access-key-id'} = $aws_access_key_id;
$opts{'aws-secret-key'} = $aws_secret_key;
$opts{'verbose'} = $verbose;
$opts{'verify'} = $verify;
$opts{'user-agent'} = "$client_name/$version";
$opts{'aws-iam-role'} = $aws_iam_role;
my ($code, $reply) = CloudWatchClient::get_auto_scaling_group(\%opts);
if ($code == 200) {
$as_group_name = $reply;
}
else {
report_message(LOG_WARNING, "Failed to call EC2 to obtain Auto Scaling group name. ".
"HTTP Status Code: $code. Error Message: $reply");
}
if (!$as_group_name)
{
if (!$verify)
{
report_message(LOG_WARNING, "The Auto Scaling metrics will not be reported this time.");
if ($auto_scaling == AGGREGATED_ONLY) {
print("\n") if (!$from_cron);
exit 0;
}
}
else {
$as_group_name = 'VerificationOnly';
}
}
}
my %params = ();
$params{'Input'} = {};
my $input_ref = $params{'Input'};
$input_ref->{'Namespace'} = "System/Linux";
#
# Adds a new metric to the request
#
sub add_single_metric
{
my $name = shift;
my $unit = shift;
my $value = shift;
my $dims = shift;
my $metric = {};
$metric->{"MetricName"} = $name;
$metric->{"Timestamp"} = $timestamp;
$metric->{"RawValue"} = $value;
$metric->{"Unit"} = $unit;
my $dimensions = [];
foreach my $key (sort keys %$dims)
{
push(@$dimensions, {"Name" => $key, "Value" => $dims->{$key}});
}
# add hostname
my $hostname = `/bin/hostname`;
chomp($hostname);
push(@$dimensions, {"Name" => "HostName", "Value" => $hostname});
$metric->{"Dimensions"} = $dimensions;
push(@{$input_ref->{'MetricData'}}, $metric);
++$mcount;
}
#
# Adds all metric variations for the specified metric name
#
sub add_metric
{
my $name = shift;
my $unit = shift;
my $value = shift;
my $filesystem = shift;
my $mount = shift;
$input_ref->{'MetricData'} = [] if !(exists $input_ref->{'MetricData'});
my %dims = ();
my %xdims = ();
$xdims{'MountPath'} = $mount if $mount;
$xdims{'Filesystem'} = $filesystem if $filesystem;
my $auto_scaling_only = defined($auto_scaling) && $auto_scaling == AGGREGATED_ONLY;
my $aggregated_only = defined($aggregated) && $aggregated == AGGREGATED_ONLY;
if (!$auto_scaling_only && !$aggregated_only) {
%dims = (('InstanceId' => $instance_id), %xdims);
add_single_metric($name, $unit, $value, \%dims);
}
if ($as_group_name) {
%dims = (('AutoScalingGroupName' => $as_group_name), %xdims);
add_single_metric($name, $unit, $value, \%dims);
}
if ($instance_type) {
%dims = (('InstanceType' => $instance_type), %xdims);
add_single_metric($name, $unit, $value, \%dims);
}
if ($image_id) {
%dims = (('ImageId' => $image_id), %xdims);
add_single_metric($name, $unit, $value, \%dims);
}
if ($aggregated) {
%dims = %xdims;
add_single_metric($name, $unit, $value, \%dims);
}
print "$name [$mount]: $value ($unit)\n" if ($verbose && $mount);
print "$name: $value ($unit)\n" if ($verbose && !$mount);
}
# avoid a storm of calls at the beginning of a minute
if ($from_cron) {
sleep(rand(20));
}
# collect load metrics
if ($report_load_one || $report_load_five || $report_load_ft)
{
my @loadinfo;
@loadinfo = split(' ', `/usr/bin/uptime | /bin/sed 's#.*load average: ##; s#,##g;'`);
if ($report_load_one) {
add_metric('Load1', 'Count', $loadinfo[0]);
}
if ($report_load_five) {
add_metric('Load5', 'Count', $loadinfo[1]);
}
if ($report_load_ft) {
add_metric('Load15', 'Count', $loadinfo[2]);
}
}
# collect memory and swap metrics
if ($report_mem_util || $report_mem_used || $report_mem_avail || $report_swap_util || $report_swap_used)
{
my %meminfo;
foreach my $line (split('\n', `/bin/cat /proc/meminfo`)) {
if($line =~ /^(.*?):\s+(\d+)/) {
$meminfo{$1} = $2;
}
}
# meminfo values are in kilobytes
my $mem_total = $meminfo{'MemTotal'} * KILO;
my $mem_free = $meminfo{'MemFree'} * KILO;
my $mem_cached = $meminfo{'Cached'} * KILO;
my $mem_buffers = $meminfo{'Buffers'} * KILO;
my $mem_avail = $mem_free;
if (!defined($mem_used_incl_cache_buff)) {
$mem_avail += $mem_cached + $mem_buffers;
}
my $mem_used = $mem_total - $mem_avail;
my $swap_total = $meminfo{'SwapTotal'} * KILO;
my $swap_free = $meminfo{'SwapFree'} * KILO;
my $swap_used = $swap_total - $swap_free;
if ($report_mem_util) {
my $mem_util = 0;
$mem_util = 100 * $mem_used / $mem_total if ($mem_total > 0);
add_metric('MemoryUtilization', 'Percent', $mem_util);
}
if ($report_mem_used) {
add_metric('MemoryUsed', $mem_units, $mem_used / $mem_unit_div);
}
if ($report_mem_avail) {
add_metric('MemoryAvailable', $mem_units, $mem_avail / $mem_unit_div);
}
if ($report_swap_util) {
my $swap_util = 0;
$swap_util = 100 * $swap_used / $swap_total if ($swap_total > 0);
add_metric('SwapUtilization', 'Percent', $swap_util);
}
if ($report_swap_used) {
add_metric('SwapUsed', $mem_units, $swap_used / $mem_unit_div);
}
}
# collect disk space metrics
if ($report_disk_space)
{
my @df = `/bin/df -k -l -P $df_path`;
shift @df;
foreach my $line (@df)
{
my @fields = split('\s+', $line);
# Result of df is reported in 1k blocks
my $disk_total = $fields[1] * KILO;
my $disk_used = $fields[2] * KILO;
my $disk_avail = $fields[3] * KILO;
my $fsystem = $fields[0];
my $mount = $fields[5];
if ($report_disk_util) {
my $disk_util = 0;
$disk_util = 100 * $disk_used / $disk_total if ($disk_total > 0);
add_metric('DiskSpaceUtilization', 'Percent', $disk_util, $fsystem, $mount);
}
if ($report_disk_used) {
add_metric('DiskSpaceUsed', $disk_units, $disk_used / $disk_unit_div, $fsystem, $mount);
}
if ($report_disk_avail) {
add_metric('DiskSpaceAvailable', $disk_units, $disk_avail / $disk_unit_div, $fsystem, $mount);
}
}
}
# send metrics over to CloudWatch if any
if ($mcount > 0)
{
my %opts = ();
$opts{'aws-credential-file'} = $aws_credential_file;
$opts{'aws-access-key-id'} = $aws_access_key_id;
$opts{'aws-secret-key'} = $aws_secret_key;
$opts{'retries'} = 2;
$opts{'verbose'} = $verbose;
$opts{'verify'} = $verify;
$opts{'user-agent'} = "$client_name/$version";
$opts{'enable_compression'} = 1 if ($enable_compression);
$opts{'aws-iam-role'} = $aws_iam_role;
my $response = CloudWatchClient::call_json('PutMetricData', \%params, \%opts);
my $code = $response->code;
my $message = $response->message;
if ($code == 200 && !$from_cron) {
if ($verify) {
print "\nVerification completed successfully. No actual metrics sent to CloudWatch.\n\n";
} else {
my $request_id = $response->headers->{'x-amzn-requestid'};
print "\nSuccessfully reported metrics to CloudWatch. Reference Id: $request_id\n\n";
}
}
elsif ($code < 100) {
exit_with_error($message);
}
elsif ($code != 200) {
exit_with_error("Failed to call CloudWatch: HTTP $code. Message: $message");
}
}
else {
exit_with_error("No metrics prepared for submission to CloudWatch.");
}
exit 0;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment