9e02357329
Move content from stx-utils into stx-integ or stx-update Packages will be relocated to stx-update: enable-dev-patch extras stx-integ: config-files/ io-scheduler filesystem/ filesystem-scripts grub/ grubby logging/ logmgmt tools/ collector monitor-tools tools/engtools/ hostdata-collectors parsers utilities/ build-info branding (formerly wrs-branding) platform-util Change-Id: Id181ee00015b41c50c75250384e7fbf36a300c1c Story: 2002801 Task: 22687 Signed-off-by: Scott Little <scott.little@windriver.com>
345 lines
8.8 KiB
Perl
Executable File
345 lines
8.8 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
########################################################################
|
|
#
|
|
# Copyright (c) 2015 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
#
|
|
########################################################################
|
|
#
|
|
# Description:
|
|
# This displays overall memory information per sample period.
|
|
# Output includes total, used, avail, per-numa node breakdown of avail
|
|
# and free hugepages memory.
|
|
#
|
|
# Usage: memtop OPTIONS
|
|
# memtop [--delay=<seconds>] [--repeat=<num>] [--period=<seconds>] [--help]
|
|
#
|
|
|
|
# Summarize high-level memory usage.
|
|
use 5.10.0;
|
|
use warnings;
|
|
use strict;
|
|
use Benchmark ':hireswallclock';
|
|
use POSIX qw(strftime);
|
|
use Data::Dumper;
|
|
use File::Basename;
|
|
use File::Spec ();
|
|
use Time::HiRes qw(time usleep);
|
|
use Carp qw(croak carp);
|
|
|
|
# IEC and SI constants
|
|
use constant SI_k => 1.0E3;
|
|
use constant SI_M => 1.0E6;
|
|
use constant SI_G => 1.0E9;
|
|
use constant Ki => 1024.0;
|
|
use constant Mi => 1024.0*1024.0;
|
|
use constant Gi => 1024.0*1024.0*1024.0;
|
|
|
|
# Name of this program
|
|
our $TOOLNAME = basename($0);
|
|
our $VERSION = "0.1";
|
|
|
|
# Argument list parameters
|
|
our ($arg_debug,
|
|
$arg_delay,
|
|
$arg_repeat,
|
|
$arg_period) = ();
|
|
|
|
# Globals
|
|
our $t_0 = ();
|
|
our $t_1 = ();
|
|
our $t_elapsed = ();
|
|
our $t_final = ();
|
|
our $is_strict = ();
|
|
our $num_nodes = ();
|
|
|
|
#-------------------------------------------------------------------------------
|
|
# MAIN Program
|
|
#-------------------------------------------------------------------------------
|
|
# benchmark variables
|
|
my ($bd, $b0, $b1);
|
|
|
|
# Autoflush output
|
|
select(STDERR);
|
|
$| = 1;
|
|
select(STDOUT); # default
|
|
$| = 1;
|
|
|
|
# Parse input arguments and print tool usage if necessary
|
|
&parse_memtop_args(
|
|
\$::arg_debug,
|
|
\$::arg_delay,
|
|
\$::arg_repeat,
|
|
\$::arg_period,
|
|
);
|
|
|
|
# Print out some debugging information
|
|
if (defined $::arg_debug) {
|
|
$Data::Dumper::Indent = 1;
|
|
}
|
|
|
|
# Strict vs non-strict memory accounting
|
|
$::is_strict = &is_strict();
|
|
|
|
# Number of numa nodes
|
|
$::num_nodes = &num_numa_nodes();
|
|
|
|
# Print tool header and selected options
|
|
printf "%s %s -- ".
|
|
"selected options: delay = %.3fs, repeat = %d, period = %.3fs, %s, unit = %s\n",
|
|
$::TOOLNAME, $::VERSION,
|
|
$::arg_delay, $::arg_repeat, $::arg_period,
|
|
$::is_strict ? 'strict' : 'non-strict',
|
|
'MiB';
|
|
|
|
# Capture timestamp
|
|
$b0 = new Benchmark;
|
|
|
|
# Get current hires epoc timestamp
|
|
$::t_1 = time();
|
|
$::t_final = $::t_1 + $::arg_period;
|
|
|
|
# Set initial delay
|
|
$::t_elapsed = $::arg_delay;
|
|
|
|
# Main loop
|
|
my $delay = SI_M*$::arg_delay - 600.0;
|
|
REPEAT_LOOP: for (my $rep=1; $rep <= $::arg_repeat; $rep++) {
|
|
# Copy all state variables
|
|
$::t_0 = $::t_1;
|
|
|
|
# Sleep for desired interarrival time
|
|
usleep( $delay );
|
|
|
|
# Current hires epoc timestamp
|
|
$::t_1 = time();
|
|
|
|
# Delta calculation
|
|
$::t_elapsed = $::t_1 - $::t_0;
|
|
|
|
# Print summary
|
|
&print_memory(\$::t_1);
|
|
|
|
# Exit if we have reached period
|
|
last if ((defined $::t_final) && ($::t_1 > $::t_final));
|
|
}
|
|
|
|
# Print that tool has finished
|
|
print "done\n";
|
|
|
|
# Capture timestamp and report delta
|
|
if (defined $::arg_debug) {
|
|
$b1 = new Benchmark; $bd = Benchmark::timediff($b1, $b0);
|
|
printf "processing time: %s\n", timestr($bd);
|
|
}
|
|
exit 0;
|
|
|
|
|
|
################################################################################
|
|
|
|
# Parse input option arguments
|
|
sub parse_memtop_args {
|
|
(local *::arg_debug,
|
|
local *::arg_delay,
|
|
local *::arg_repeat,
|
|
local *::arg_period,
|
|
) = @_;
|
|
|
|
# Local variables
|
|
my ($fail, $arg_help);
|
|
|
|
# Use the Argument processing module
|
|
use Getopt::Long;
|
|
|
|
# Process input arguments
|
|
$fail = 0;
|
|
GetOptions(
|
|
"debug:i", \$::arg_debug,
|
|
"delay=f", \$::arg_delay,
|
|
"repeat=i", \$::arg_repeat,
|
|
"period=i", \$::arg_period,
|
|
"help|h", \$arg_help
|
|
) || GetOptionsMessage();
|
|
|
|
# Print help documentation if user has selected --help
|
|
&ListHelp() if (defined $arg_help);
|
|
|
|
# Validate options
|
|
if ((defined $::arg_repeat) && (defined $::arg_period)) {
|
|
$fail = 1;
|
|
warn "$::TOOLNAME: Input error: cannot specify both --repeat and --period options.\n";
|
|
}
|
|
if ((defined $::arg_delay) && ($::arg_delay < 0.01)) {
|
|
$fail = 1;
|
|
warn "$::TOOLNAME: Input error: --delay %f is less than 0.01.\n",
|
|
$::arg_delay;
|
|
}
|
|
if (@::ARGV) {
|
|
$fail = 1;
|
|
warn "$::TOOLNAME: Input error: not expecting these options: '@::ARGV'.\n";
|
|
}
|
|
|
|
# Set reasonable defaults
|
|
$::arg_delay ||= 1.0;
|
|
$::arg_repeat ||= 1;
|
|
if ($::arg_period) {
|
|
$::arg_repeat = $::arg_period / $::arg_delay;
|
|
} else {
|
|
$::arg_period = $::arg_delay * $::arg_repeat;
|
|
}
|
|
|
|
# Upon missing or invalid options, print usage
|
|
if ($fail == 1) {
|
|
&Usage();
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
# Print out a warning message and usage
|
|
sub GetOptionsMessage {
|
|
warn "$::TOOLNAME: Error processing input arguments.\n";
|
|
&Usage();
|
|
exit 1;
|
|
}
|
|
|
|
# Print out program usage
|
|
sub Usage {
|
|
printf "Usage: $::TOOLNAME OPTIONS\n";
|
|
printf " [--delay=<seconds>] [--repeat=<num>] [--period=<seconds>]\n";
|
|
printf " [--help]\n";
|
|
printf "\n";
|
|
}
|
|
|
|
# Print tool help
|
|
sub ListHelp {
|
|
printf "$::TOOLNAME -- displays high memory usage at high level\n";
|
|
&Usage();
|
|
printf " --delay=<seconds> : output interval (seconds): default: 1.0\n";
|
|
printf " --repeat=<num> : number of repeat samples: default: 1\n";
|
|
printf " --period=<seconds> : overall tool duration (seconds): default: --\n";
|
|
printf " --help : this help\n";
|
|
printf "\n";
|
|
exit 0;
|
|
}
|
|
|
|
# Print memory summary
|
|
sub print_memory {
|
|
(local *::t_1) = @_;
|
|
|
|
# counter
|
|
our $count;
|
|
$::count++; $::count %= 15;
|
|
|
|
my ($file, $n);
|
|
my %mem = ();
|
|
my %node = ();
|
|
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst);
|
|
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($::t_1);
|
|
my $msec = 1000.0*($::t_1 - int($::t_1));
|
|
|
|
# Process all entries of MEMINFO
|
|
$file = '/proc/meminfo';
|
|
open(FILE, $file) || die "Cannot open file: $file ($!)";
|
|
while($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
if (/^(\S+):\s+(\d+)\b/) {
|
|
$mem{$1} = $2;
|
|
}
|
|
}
|
|
close(FILE);
|
|
|
|
# Process all entries of per-Node MEMINFO
|
|
for ($n=0; $n < $::num_nodes; $n++) {
|
|
$file = sprintf('/sys/devices/system/node/node%d/meminfo', $n);
|
|
open(FILE, $file) || die "Cannot open file: $file ($!)";
|
|
while($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
if (/^Node\s+(\d+)\s+(\S+):\s+(\d+)\b/) {
|
|
$node{$1}{$2} = $3;
|
|
}
|
|
}
|
|
close(FILE);
|
|
}
|
|
|
|
# Calculate available memory
|
|
if ($::is_strict) {
|
|
$mem{'Avail'} = $mem{'CommitLimit'} - $mem{'Committed_AS'};
|
|
} else {
|
|
$mem{'Avail'} = $mem{'MemFree'} +
|
|
$mem{'Cached'} +
|
|
$mem{'Buffers'} +
|
|
$mem{'SReclaimable'};
|
|
}
|
|
$mem{'Used'} = $mem{'MemTotal'} - $mem{'Avail'};
|
|
$mem{'Anon'} = $mem{'AnonPages'};
|
|
for ($n=0; $n < $::num_nodes; $n++) {
|
|
$node{$n}{'Avail'} = $node{$n}{'MemFree'} +
|
|
$node{$n}{'FilePages'} +
|
|
$node{$n}{'SReclaimable'};
|
|
$node{$n}{'HFree'} = $node{$n}{'HugePages_Free'} * $mem{'Hugepagesize'};
|
|
}
|
|
|
|
# Print heading every so often
|
|
if ($::count == 1) {
|
|
printf "%s ".
|
|
"%8s %8s %8s %7s %6s %6s %8s %8s %7s %7s %8s %8s",
|
|
'yyyy-mm-dd hh:mm:ss.fff',
|
|
'Tot', 'Used', 'Free', 'Ca', 'Buf', 'Slab', 'CAS', 'CLim', 'Dirty', 'WBack', 'Anon', 'Avail';
|
|
for ($n=0; $n < $::num_nodes; $n++) {
|
|
printf " %8s %8s", sprintf('%d:Avail', $n), sprintf('%d:HFree', $n);
|
|
}
|
|
printf "\n";
|
|
}
|
|
|
|
# Print one line memory summary
|
|
printf "%4d-%02d-%02d %02d:%02d:%02d.%03d ".
|
|
"%8.1f %8.1f %8.1f %7.1f %6.1f %6.1f %8.1f %8.1f %7.1f %7.1f %8.1f %8.1f",
|
|
1900+$year, 1+$mon, $mday, $hour, $min, $sec, $msec,
|
|
$mem{'MemTotal'}/Ki,
|
|
$mem{'Used'}/Ki,
|
|
$mem{'MemFree'}/Ki,
|
|
$mem{'Cached'}/Ki,
|
|
$mem{'Buffers'}/Ki,
|
|
$mem{'Slab'}/Ki,
|
|
$mem{'Committed_AS'}/Ki,
|
|
$mem{'CommitLimit'}/Ki,
|
|
$mem{'Dirty'}/Ki,
|
|
$mem{'Writeback'}/Ki,
|
|
$mem{'Anon'}/Ki,
|
|
$mem{'Avail'}/Ki;
|
|
for ($n=0; $n < $::num_nodes; $n++) {
|
|
printf " %8.1f %8.1f", $node{$n}{'Avail'}/Ki, $node{$n}{'HFree'}/Ki;
|
|
}
|
|
printf "\n";
|
|
|
|
}
|
|
|
|
sub num_numa_nodes {
|
|
my $file = '/proc/cpuinfo';
|
|
my %nodes = ();
|
|
open(FILE, $file) || die "Cannot open file: $file ($!)";
|
|
while($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
if (/^physical\s+id\s+:\s+(\d+)\b/) {
|
|
$nodes{$1} = 1;
|
|
}
|
|
}
|
|
close(FILE);
|
|
return scalar keys %nodes;
|
|
}
|
|
|
|
sub is_strict {
|
|
my $value = 0;
|
|
my $file = '/proc/sys/vm/overcommit_memory';
|
|
open(FILE, $file) || die "Cannot open file: $file ($!)";
|
|
$_ = <FILE>;
|
|
$value = /(\d+)/;
|
|
close(FILE);
|
|
return ($value == 2) ? 1 : 0;
|
|
}
|
|
|
|
1;
|