aa3d640122
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: I5613b2a2240f723295fbbd2783786922ef5d0f8b Story: 2002801 Task: 22687 Signed-off-by: Scott Little <scott.little@windriver.com>
1302 lines
57 KiB
Perl
Executable File
1302 lines
57 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
#Copyright (c) 2016 Wind River Systems, Inc.
|
|
#
|
|
#SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
# Usage: parse_schedtop OPTIONS file1 file2 file3.gz ...
|
|
# [--detail] [--threads] [--ignore=<fac>] [--help]
|
|
# [--field=<field,...>] [--family=<family,...>]
|
|
# [--sum=<tid,str,...>] [--sum-exact=<str,...>] [--tab=<str,...>] [--tab-exact=<str,...>]
|
|
# [--excl=<tid,str,...>] [--all]
|
|
#
|
|
# Purpose:
|
|
# parse_schedtop is an engineering tool used to summarize 'schedtop' data.
|
|
# - tool has 2 main modes of operation, 1) AVERAGE mode; 2) Time-series.
|
|
# - handles uncompressed, of gzip compressed files
|
|
#
|
|
# Average mode:
|
|
# - averages all data from multiple files and condenses on matching command name patterns
|
|
#
|
|
# Time-series mode:
|
|
# - specify --detail to select time-series.
|
|
# - specify --sum=<tid,str,...>, --sum-exact=<str,...> to match multiple strings or TIDs
|
|
# that will be aggregated together into a single column
|
|
# - specifiy --tab=<str,...>, --tab-exact=<str,...> to match multiple strings that will
|
|
# be displayed as separate table columns
|
|
# - specify --all to match all processes and threads and aggregate together in single sum
|
|
# - use --excl=<tid,str,...> to exclude specific strings or TIDs that would have
|
|
# matched with --sum=<str>, --tab=<str>, or --all
|
|
# - specify --field=<field,...> and/or --family=<family,...> options to refine
|
|
# which outputs are displayed.
|
|
# - see tool help for specification details.
|
|
#
|
|
# Modification History:
|
|
#
|
|
use 5.10.0;
|
|
use warnings;
|
|
use strict;
|
|
use File::Spec ();
|
|
use Data::Dumper;
|
|
use Time::Local 'timelocal_nocheck'; # inverse time functions
|
|
use Benchmark ':hireswallclock';
|
|
|
|
our $scriptName = "parse_schedtop";
|
|
our $scriptVersion = "v0.1";
|
|
|
|
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;
|
|
|
|
my ($bd, $b0, $b1);
|
|
my %SCALE_H = (none => 0, SI => 1, IEC => 2);
|
|
my %AGG_H = (none => 0, dtsum => 1, dtrate => 2, ctxt => 3, max => 4);
|
|
|
|
# Methodology for aggregation of different measurements
|
|
# - aggregate measurements are performed by summation of a given value multiplied by a weighting
|
|
# factor (such as time or context switches), and divided by a factor 'div' to convert output units
|
|
#
|
|
# Aggregation types:
|
|
# "none" - a value or string that we cannot sum
|
|
# "dtsum" - result = SUM [dt(i)*x(i)] / (SUM[dt(i)] * div), for each 'i'
|
|
# - if 'dt(i) = constant' then this is equivalent to simple average
|
|
# "dtrate" - result = SUM [x(i)] / (SUM[dt(i)] * div), for each 'i'
|
|
# - this is overall average rate "x/time" over a given time period
|
|
# "ctxt" - result = SUM [ctxt(i)*x(i)] / (SUM[ctxt(i)] * div), for each 'i'
|
|
# - if "x" is tlen, then (cputime = ctxt*tlen), result is overall average
|
|
# realtime divided by number of context switches which is average tlen
|
|
# "max" - a value that we cannot sum, rather would like to determine maximum value
|
|
#
|
|
# Families and Fields definition
|
|
our %families = (
|
|
ov => {
|
|
runq => {
|
|
ldavg => {scale => "none", agg => "dtsum", un => "(#)", fmtT => "%6s", fmt => "%6.2f", w => 1, div => 1, idx => 1},
|
|
runq => {scale => "none", agg => "dtsum", un => "(#)", fmtT => "%4s", fmt => "%4.0f", w => 1, div => 1, idx => 2},
|
|
blk => {scale => "none", agg => "dtsum", un => "(#)", fmtT => "%3s", fmt => "%3.0f", w => 1, div => 1, idx => 3},
|
|
},
|
|
MiB => {
|
|
Tot => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 1},
|
|
Ca => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 2},
|
|
Buf => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%6s", fmt => "%6.1f", w => 1, div => 1, idx => 3},
|
|
Free => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 4},
|
|
Slab => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%6s", fmt => "%6.1f", w => 1, div => 1, idx => 5},
|
|
tmpfs => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%6s", fmt => "%6.1f", w => 1, div => 1, idx => 6},
|
|
CAS => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 7},
|
|
Avail => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 8},
|
|
EAvail => {scale => "none", agg => "dtsum", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 9},
|
|
},
|
|
},
|
|
cmd => {
|
|
ps => {
|
|
TID => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 1},
|
|
PID => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 2},
|
|
PPID => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 3},
|
|
S => {scale => "none", agg => "none", un => "(#)", fmtT => "%3s", fmt => "%-3s", w => 1, div => 1, idx => 4},
|
|
P => {scale => "none", agg => "none", un => "(#)", fmtT => "%3s", fmt => "%3d", w => 1, div => 1, idx => 5},
|
|
AFF => {scale => "none", agg => "none", un => "(-)", fmtT => "%7s", fmt => "%-7s", w => 1, div => 1, idx => 6},
|
|
PO => {scale => "none", agg => "none", un => "(-)", fmtT => "%3s", fmt => "%-3s", w => 1, div => 1, idx => 7},
|
|
RTPRIO => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 8},
|
|
NI => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 9},
|
|
PR => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 10},
|
|
},
|
|
cpu => {
|
|
occ => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 1},
|
|
cpu => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 2},
|
|
user => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 3},
|
|
sys => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 4},
|
|
ctxt => {scale => "none", agg => "dtrate", un => "(#/s)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 5},
|
|
migr => {scale => "none", agg => "dtrate", un => "(#/s)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 6},
|
|
},
|
|
del => {
|
|
tlen => {scale => "none", agg => "ctxt", un => "(ms/S)", fmtT => "%7s", fmt => "%7.3f", w => 3, div => 1, idx => 1},
|
|
tmax => {scale => "none", agg => "max", un => "(ms/S)", fmtT => "%7s", fmt => "%7.3f", w => 3, div => 1, idx => 2},
|
|
delay => {scale => "none", agg => "ctxt", un => "(ms)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 3},
|
|
dmax => {scale => "none", agg => "max", un => "(ms)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 4},
|
|
},
|
|
io => {
|
|
iowait => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 0},
|
|
iowt => {scale => "none", agg => "dtsum", un => "(%)", fmtT => "%7s", fmt => "%7.2f", w => 2, div => 1, idx => 1},
|
|
iocnt => {scale => "SI", agg => "dtrate", un => "(#/s)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1, idx => 2},
|
|
read => {scale => "SI", agg => "dtrate", un => "(MB/s)", fmtT => "%10s", fmt => "%10.4f", w => 4, div => 1.0E6, idx => 3},
|
|
write => {scale => "SI", agg => "dtrate", un => "(MB/s)", fmtT => "%10s", fmt => "%10.4f", w => 4, div => 1.0E6, idx => 4},
|
|
wcncl => {scale => "SI", agg => "dtrate", un => "(MB/s)", fmtT => "%7s", fmt => "%7.4f", w => 4, div => 1.0E6, idx => 5},
|
|
rchar => {scale => "SI", agg => "dtrate", un => "(MB/s)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1.0E6, idx => 6},
|
|
wchar => {scale => "SI", agg => "dtrate", un => "(MB/s)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1.0E6, idx => 7},
|
|
rsysc => {scale => "SI", agg => "dtrate", un => "(r/s)", fmtT => "%8s", fmt => "%8.1f", w => 1, div => 1, idx => 8},
|
|
wsysc => {scale => "SI", agg => "dtrate", un => "(w/s)", fmtT => "%8s", fmt => "%8.1f", w => 1, div => 1, idx => 9},
|
|
},
|
|
mem => {
|
|
VSZ => {scale => "IEC", agg => "ctxt", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1024.0*1024.0, idx => 1},
|
|
RSS => {scale => "IEC", agg => "ctxt", un => "(MiB)", fmtT => "%7s", fmt => "%7.1f", w => 1, div => 1024.0*1024.0, idx => 2},
|
|
MajFlt => {scale => "SI", agg => "dtrate", un => "(F/s)", fmtT => "%8s", fmt => "%8.1f", w => 1, div => 1, idx => 3},
|
|
MinFlt => {scale => "SI", agg => "dtrate", un => "(F/s)", fmtT => "%8s", fmt => "%8.1f", w => 1, div => 1, idx => 4},
|
|
},
|
|
id => {
|
|
START_TIME => {scale => "none", agg => "none", un => "(time)", fmtT => "%16s", fmt => "%16s", w => 1, div => 1, idx => 1},
|
|
REALTIME => {scale => "none", agg => "none", un => "(d:hh:mm:ss)", fmtT => "%16s", fmt => "%16s", w => 1, div => 1, idx => 2},
|
|
TTY => {scale => "none", agg => "none", un => "(str)", fmtT => "%16s", fmt => "%16s", w => 1, div => 1, idx => 3},
|
|
WCHAN => {scale => "none", agg => "none", un => "(str)", fmtT => "%16s", fmt => "%16s", w => 1, div => 1, idx => 4},
|
|
},
|
|
cmd => {
|
|
wchan => {scale => "none", agg => "none", un => "(str)", fmtT => "%16s", fmt => "%16s", w => 1, div => 1, idx => 1},
|
|
comm => {scale => "none", agg => "none", un => "(name)", fmtT => "%16", fmt => "%16s", w => 1, div => 1, idx => 2},
|
|
cmdline => {scale => "none", agg => "none", un => "(name)", fmtT => "%s", fmt => "%s", w => 1, div => 1, idx => 3},
|
|
COMMAND => {scale => "none", agg => "none", un => "(name)", fmtT => "%s", fmt => "%s", w => 1, div => 1, idx => 4},
|
|
},
|
|
misc => {
|
|
cnt => {scale => "none", agg => "none", un => "(#)", fmtT => "%5s", fmt => "%5d", w => 1, div => 1, idx => 1},
|
|
},
|
|
},
|
|
);
|
|
my $MAX_CORES = 256;
|
|
for (my $core=0; $core <= $MAX_CORES; $core++) {
|
|
my $fld;
|
|
if ($core == $MAX_CORES) {
|
|
$fld = 'cpuT';
|
|
} else {
|
|
$fld = 'cpu'. $core;
|
|
}
|
|
$families{'ov'}{'percpu'}{$fld}{'scale'} = "none";
|
|
$families{'ov'}{'percpu'}{$fld}{'agg'} = "dtsum";
|
|
$families{'ov'}{'percpu'}{$fld}{'un'} = "(%)";
|
|
$families{'ov'}{'percpu'}{$fld}{'fmtT'} = "%6s";
|
|
$families{'ov'}{'percpu'}{$fld}{'fmt'} = "%6.2f";
|
|
$families{'ov'}{'percpu'}{$fld}{'w'} = 1;
|
|
$families{'ov'}{'percpu'}{$fld}{'div'} = 1;
|
|
$families{'ov'}{'percpu'}{$fld}{'idx'} = $core;
|
|
}
|
|
|
|
# Selected field parameters
|
|
our @select_families = ('cpu', 'del', 'io', 'mem'); # overall average mode
|
|
our @select_fields = ('cpu'); # time-series mode
|
|
our %select_H = ();
|
|
our %select_F = ();
|
|
our @select_list_cmds = ();
|
|
our @select_list_ov = ();
|
|
our %fields = ();
|
|
our %groups = ();
|
|
|
|
# Polulate formatting and cumulation methods for all fields
|
|
our %fmt = ();
|
|
our %fmtT = ();
|
|
our %units = ();
|
|
our %div = ();
|
|
our %dec = ();
|
|
our %valid_fields = ();
|
|
our %valid_families = ();
|
|
my ($vfam, $vcnt) = (0, 0);
|
|
my ($group, $family, $field, $scale, $agg);
|
|
foreach $group (sort keys %families) {
|
|
foreach $family (sort keys %{$families{$group}} ) {
|
|
foreach $field (sort {$families{$group}{$family}{$a}->{idx} <=> $families{$group}{$family}{$b}->{idx} }
|
|
keys %{$families{$group}{$family}}) {
|
|
$scale = $families{$group}{$family}{$field}->{scale};
|
|
$agg = $families{$group}{$family}{$field}->{agg};
|
|
$fmtT{$field} = $families{$group}{$family}{$field}->{fmtT};
|
|
$fmt{$field} = $families{$group}{$family}{$field}->{fmt};
|
|
$div{$field} = $families{$group}{$family}{$field}->{div};
|
|
$dec{$field} = $families{$group}{$family}{$field}->{w};
|
|
$units{$field} = $families{$group}{$family}{$field}->{un};
|
|
$fields{$field} = [($SCALE_H{$scale}, $AGG_H{$agg})];
|
|
$groups{$field} = $group;
|
|
|
|
# Create simple hashes containing a single key only so that
|
|
# any family or field can be used to determine validity for selection.
|
|
# Invalid entries arise because there are fields in schedtop that we must
|
|
# parse but cannot logically aggregated so we won't allow these to be specified
|
|
# for time-series. Using a single key prevents one from traversing entire %::families table.
|
|
# There is an assumption that there are no duplicate family or field names,
|
|
# although it is OK to have a family or field with the same name (eg. 'cpu').
|
|
if ($agg ne 'none') {
|
|
$valid_families{$family} = $vfam; $vfam++;
|
|
$valid_fields{$field} = $vcnt; $vcnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Argument list paramters
|
|
our ($arg_debug, @arg_field, @arg_family,
|
|
@arg_sum, @arg_sump, @arg_sume, @arg_tab, @arg_tabe,
|
|
@arg_excl, @arg_exclp, $arg_all, $arg_threads,
|
|
$arg_detail, $arg_ignore, $arg_from, $arg_to, @arg_files) = ();
|
|
|
|
# Determine location of gunzip binary
|
|
our $GUNZIP = which('gunzip');
|
|
if (!(defined $GUNZIP)) {
|
|
die "*error* cannot find 'gunzip' binary. Cannot continue.\n";
|
|
}
|
|
our $BUNZIP2 = which('bunzip2');
|
|
if (!(defined $BUNZIP2)) {
|
|
die "*error* cannot find 'bunzip2' binary. Cannot continue.\n";
|
|
}
|
|
|
|
# Parse input arguments and print tool usage if necessary
|
|
&get_parse_schedtop_args(
|
|
\$arg_debug, \@arg_field, \@arg_family,
|
|
\@arg_sum, \@arg_sump, \@arg_sume, \@arg_tab, \@arg_tabe,
|
|
\@arg_excl, \@arg_exclp, \$arg_all, \$arg_threads,
|
|
\$arg_detail, \$arg_ignore, \$arg_from, \$arg_to, \@arg_files);
|
|
|
|
# Print out some debugging information
|
|
if (defined $arg_debug) {
|
|
$Data::Dumper::Indent = 1;
|
|
if ($arg_debug == 2) {
|
|
print Data::Dumper->Dump([\%families], [qw(families)]);
|
|
print Data::Dumper->Dump([\%fields], [qw(fields)]);
|
|
} elsif ($arg_debug == 3) {
|
|
print Data::Dumper->Dump([\%select_H], [qw(select_H)]);
|
|
print Data::Dumper->Dump([\@arg_family], [qw(arg_family)]);
|
|
print Data::Dumper->Dump([\@arg_field], [qw(arg_field)]);
|
|
}
|
|
}
|
|
|
|
# Capture timestamp
|
|
$b0 = new Benchmark;
|
|
|
|
# Aggregate data by common command name patterns
|
|
&aggregate_time_series(
|
|
\$arg_debug, \@arg_field, \@arg_family,
|
|
\@arg_sum, \@arg_sump, \@arg_sume, \@arg_tab, \@arg_tabe,
|
|
\@arg_excl, \@arg_exclp, \$arg_all, \$arg_threads,
|
|
\$arg_detail, \@arg_files,
|
|
\@select_families, \@select_fields, \%select_H,
|
|
\@select_list_cmds, \@select_list_ov, \%fields,
|
|
\%fmt, \%fmtT, \%units, \%div, \%dec);
|
|
printf "done.\n";
|
|
|
|
# Capture timestamp and report delta
|
|
$b1 = new Benchmark; $bd = Benchmark::timediff($b1, $b0);
|
|
printf "processing time: %s\n", timestr($bd);
|
|
|
|
exit 0;
|
|
|
|
########################################################################################################
|
|
# Lightweight which(), derived from CPAN File::Which
|
|
sub which {
|
|
my ($exec) = @_;
|
|
return undef unless $exec;
|
|
my $all = wantarray;
|
|
my @results = ();
|
|
my @path = File::Spec->path;
|
|
foreach my $file ( map { File::Spec->catfile($_, $exec) } @path ) {
|
|
next if -d $file;
|
|
if (-x _) { return $file unless $all; push @results, $file; }
|
|
}
|
|
$all ? return @results : return undef;
|
|
}
|
|
|
|
# Aggregate time-series by common command name patterns
|
|
sub aggregate_time_series {
|
|
(local *::arg_debug, local *::arg_field, local *::arg_family,
|
|
local *::arg_sum, local *::arg_sump, local *::arg_sume, local *::arg_tab, local *::arg_tabe,
|
|
local *::arg_excl, local *::arg_exclp, local *::arg_all, local *::arg_threads, local *::arg_detail, local *::arg_files,
|
|
local *::select_families, local *::select_fields, local *::select_H,
|
|
local *::select_list_cmds, local *::select_list_ov, local *::fields,
|
|
local *::fmt, local *::fmtT, local *::units, local *::div, local *::dec) = @_;
|
|
|
|
my %data = (); my %sample = (); my %totals = (); my %cond = (); my %avg = (); my %matched = ();
|
|
my ($old_per_core_N, $old_per_task_N) = (0,0);
|
|
my @old_per_core_T = ();
|
|
my @old_per_task_T = ();
|
|
my %per_core_H = ();
|
|
my %per_task_H = ();
|
|
my ($COMMAND, $ctxt, $dt, $tid);
|
|
my ($idx, $field, $value, $unit, $family, $scale, $agg, $i, $lab, $cnt);
|
|
my (@MATCHES, $MATCH, $match) = ();
|
|
|
|
# Initialize totals
|
|
$totals{'dt'} = 0.0; $totals{'cnt'} = 0;
|
|
|
|
# Compile regular expressions command string patterns
|
|
# - list of expressions to include, and list to exclude
|
|
my (@re_sum, @re_sume, @re_tab, @re_tabe, @re_excl) = ();
|
|
foreach my $arg (@::arg_sum) { push @re_sum, qr/\Q$arg\E/; }
|
|
foreach my $arg (@::arg_sume) { push @re_sume, qr/^\Q$arg\E$/; }
|
|
foreach my $arg (@::arg_tab) { push @re_tab, qr/\Q$arg\E/; }
|
|
foreach my $arg (@::arg_tabe) { push @re_tabe, qr/^\Q$arg\E$/; }
|
|
foreach my $arg (@::arg_excl) { push @re_excl, qr/\Q$arg\E/; }
|
|
my ($n_sum, $n_sume) = (scalar(@::arg_sum), scalar(@::arg_sume));
|
|
my ($n_tab, $n_tabe) = (scalar(@::arg_tab), scalar(@::arg_tabe));
|
|
my $n_excl = scalar(@::arg_excl);
|
|
|
|
# Determine series commands and labels
|
|
my @series_cmds = ();
|
|
push @series_cmds, @::arg_tab if (@::arg_tab);
|
|
push @series_cmds, @::arg_tabe if (@::arg_tabe);
|
|
push @series_cmds, '_sum_' if ((@::arg_sum) || (@::arg_sume) || (@::arg_sump));
|
|
push @series_cmds, '_all_' if ($::arg_all);
|
|
|
|
my @series_ov = ();
|
|
push @series_ov, '_overall_' if (defined $::select_F{'ov'});
|
|
|
|
my @series_label =();
|
|
$cnt = 0;
|
|
my $n_label_ov = scalar(@series_ov);
|
|
for ($i=0; $i < $n_label_ov; $i++) { $series_label[$cnt] = sprintf("L%d", $cnt); $cnt++; }
|
|
my $n_label_cmds = scalar(@series_cmds);
|
|
for ($i=0; $i < $n_label_cmds; $i++) { $series_label[$cnt] = sprintf("L%d", $cnt); $cnt++; }
|
|
|
|
foreach my $file (@::arg_files) {
|
|
printf "processing file: %s\n", $file;
|
|
if ($file =~ /\.gz$/) {
|
|
open(FILE, "$::GUNZIP -c $file |") || die "Cannot open file: $file ($!)\n";
|
|
} elsif ($file =~ /\.bz2$/) {
|
|
open(FILE, "$::BUNZIP2 -c $file |") || die "Cannot open file: $file ($!)\n";
|
|
} else {
|
|
open(FILE, $file) || die "Cannot open file: $file ($!)\n";
|
|
}
|
|
|
|
# local scope
|
|
my ($len, $offset, $cmd_idx, $ctxt_idx, $tid_idx) = (0,0,0);
|
|
my $first = 1;
|
|
my @per_core_T = (); my $per_core_N = ();
|
|
my @per_core_x = (); my @per_core_tot = ();
|
|
my @per_task_T = (); my @per_task_x = (); my $per_task_N = ();
|
|
my ($tstamp, $ldavg, $runq, $blk, $del, $ignore) = ();
|
|
my ($yy, $month, $day, $hh, $mm, $ss, $frac);
|
|
my @overall = ();
|
|
|
|
# Parse file line at a time
|
|
READ_LOOP: while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
|
|
if (/^schedtop\s+\S+\s+\-\-\s+(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\.\d{3})\s+dt:\s*(\S+)\s+ms\s+ldavg:(\S+),\s+\S+,\s+\S+\s+runq:(\d+)\s+blk:(\d+)\s+/) { # header line
|
|
$tstamp = $1; $dt = $2/1000.0; $ldavg = $3; $runq = $4; $blk = $5;
|
|
$del = 0.0; $ignore = 0; @overall = ();
|
|
|
|
$_ = $tstamp;
|
|
($yy, $month, $day, $hh, $mm, $ss, $frac) = /(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\.(\d{3})/;
|
|
my $this_time = timelocal_nocheck($ss, $mm, $hh, $day, $month-1, $yy-1900);
|
|
|
|
# Skip samples that are outsize of $::arg_from and $::arg_to time range.
|
|
if (defined $arg_from && ($this_time lt $arg_from)) {
|
|
$ignore = 1;
|
|
printf "IGNORE: $tstamp < %s\n", scalar(localtime($arg_from));
|
|
}
|
|
if (defined $arg_to && ($this_time gt $arg_to)) {
|
|
$ignore = 1;
|
|
printf "IGNORE: $tstamp > %s\n", scalar(localtime($arg_to));
|
|
}
|
|
# Skip sample
|
|
if ($ignore > 0) {
|
|
# skip to per-task headings
|
|
while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
last if (/^\s+TID\s+PID\s+PPID\s+/);
|
|
}
|
|
# skip to end of sample
|
|
while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
last if (/^$/ || /^done/); # stop when we reach blank line or 'done'
|
|
}
|
|
|
|
# do not include this sample in the accounting
|
|
next READ_LOOP;
|
|
}
|
|
|
|
# Store this sample's overall fields
|
|
%sample = (); %cond = ();
|
|
$group = 'ov'; $MATCH = '_overall_';
|
|
$sample{'tstamp'} = $tstamp;
|
|
$sample{'dt'} = $dt;
|
|
$sample{'ldavg'} = $ldavg;
|
|
$sample{'runq'} = $runq;
|
|
$sample{'blk'} = $blk;
|
|
|
|
$group = 'ov'; $family = 'MiB';
|
|
foreach $field (keys %{ $::families{$group}{$family} }) { $sample{$field} = 0.0; }
|
|
|
|
# Cumulate totals
|
|
$totals{'dt'} += $dt;
|
|
$totals{'cnt'}++;
|
|
|
|
# skip over architecture header info
|
|
while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
if (/MiB:\s+Tot:(\S+)\s+Ca:(\S+)\s+Buf:(\S+)\s+Free:(\S+)\s+Slab:(\S+)\s+tmpfs:(\S+)\s+CAS:(\S+)\s+Avail:(\S+)\s+EAvail:(\S+)/) {
|
|
$sample{'Tot'} = $1;
|
|
$sample{'Ca'} = $2;
|
|
$sample{'Buf'} = $3;
|
|
$sample{'Free'} = $4;
|
|
$sample{'Slab'} = $5;
|
|
$sample{'tmpfs'} = $6;
|
|
$sample{'CAS'} = $7;
|
|
$sample{'Avail'} = $8;
|
|
$sample{'EAvail'} = $9;
|
|
}
|
|
last if /^$/; # stop when we reach blank line
|
|
#last if /^core:\s+/; # stop when we reach "core" titles line
|
|
}
|
|
if ( 0 ) {
|
|
# Parse per-core titles
|
|
if ($first) {
|
|
# split per-core titles
|
|
@per_core_T = split(/\s+/, $_);
|
|
$per_core_N = scalar(@per_core_T);
|
|
for ($idx=0; $idx < $per_core_N; $idx++) { $per_core_H{ $per_core_T[$idx] } = $idx; }
|
|
|
|
# Compare title headings with last file -- stop processing if they not identical
|
|
if (($old_per_core_N > 0) && ($old_per_core_N != $per_core_N)) {
|
|
die "Cannot proceed - per-core number of fields differ across multiple files.\n";
|
|
}
|
|
for ($idx=0; $idx < $old_per_core_N; $idx++) {
|
|
if ($per_core_T[$idx] ne $old_per_core_T[$idx]) {
|
|
die "Cannot proceed - per-core fields differ across multiple files.\n";
|
|
}
|
|
}
|
|
$old_per_core_N = $per_core_N; @old_per_core_T = @per_core_T;
|
|
|
|
}
|
|
# Cumulate per-core values
|
|
for ($idx=0; $idx < @per_core_T; $idx++) { $per_core_tot[$idx] = 0.0; }
|
|
while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
last if /^$/; # stop when we reach blank line
|
|
@per_core_x = split(/\s+/, $_); shift @per_core_x;
|
|
for ($idx=0; $idx < @per_core_T; $idx++) { $per_core_tot[$idx] += $per_core_x[$idx]; }
|
|
$idx = $per_core_H{'cpu'}; $field = 'cpu' . $per_core_x[ $per_core_H{'core'} ];
|
|
$sample{$field} = $per_core_x[$idx];
|
|
}
|
|
$field = 'cpuT';
|
|
$sample{$field} = $per_core_tot[$idx];
|
|
}
|
|
# Remove any field selections that don't exist
|
|
if ($first) {
|
|
foreach $field (keys %{ $::select_H{$group} }) {
|
|
delete $::select_H{$group}{$field} if ( !(defined $sample{$field}) );
|
|
}
|
|
# Generate sorted field list
|
|
@::select_list_ov = sort {$::select_H{$group}{$a} <=> $::select_H{$group}{$b}} keys %{ $::select_H{$group} };
|
|
}
|
|
|
|
# Cumulate overall fields using 'dtsum' aggregation method
|
|
foreach $field (@::select_list_ov) {
|
|
$value = $dt * $sample{$field};
|
|
$cond{$MATCH}{$field} += $value;
|
|
$avg{$MATCH}{$field} += $value;
|
|
}
|
|
} elsif (/^\s+TID\s+PID\s+PPID\s+/) {
|
|
$group = 'cmd';
|
|
# Parse per-task titles
|
|
if ($first) {
|
|
@per_task_T = split(/\s+/, $_); shift @per_task_T if (/^\s+/);
|
|
$per_task_N = scalar(@per_task_T);
|
|
|
|
# determine lower and upper indices for numerical fields
|
|
for ($idx=0; $idx < $per_task_N; $idx++) {
|
|
$field = $per_task_T[$idx];
|
|
$per_task_H{ $field } = $idx;
|
|
if (! (defined $::fields{$field}) ) {
|
|
die "Cannot proceed - per-task field = '$field' is not defined.\n";
|
|
}
|
|
}
|
|
##$cmd_idx = $per_task_H{'COMMAND'};
|
|
$cmd_idx = $per_task_H{'cmdline'};
|
|
$ctxt_idx = $per_task_H{'ctxt'};
|
|
$tid_idx = $per_task_H{'TID'};
|
|
|
|
# Remove any field selections that don't exist
|
|
foreach $field (keys %{ $::select_H{$group} }) {
|
|
delete $::select_H{$group}{$field} if ( !(defined $per_task_H{$field}) );
|
|
}
|
|
|
|
# Generate sorted field list
|
|
@::select_list_cmds = sort {$::select_H{$group}{$a} <=> $::select_H{$group}{$b}} keys %{ $::select_H{$group} };
|
|
|
|
# Compare title headings with last file -- stop processing if they not identical
|
|
if (($old_per_task_N > 0) && ($old_per_task_N != $per_task_N)) {
|
|
die "Cannot proceed - per-task number of fields differ across multiple files.\n";
|
|
}
|
|
for ($idx=0; $idx < $old_per_task_N; $idx++) {
|
|
if ($per_task_T[$idx] ne $old_per_task_T[$idx]) {
|
|
die "Cannot proceed - per-task fields differ across multiple files.\n";
|
|
}
|
|
}
|
|
$old_per_task_N = $per_task_N; @old_per_task_T = @per_task_T;
|
|
|
|
$first = 0;
|
|
}
|
|
|
|
# Parse each task one line at a time
|
|
TASK_LOOP: while ($_ = <FILE>) {
|
|
s/[\0\e\f\r\a]//g; chomp; # strip control characters if any
|
|
last if (/^$/ || /^done/); # stop when we reach blank line or 'done'
|
|
next if (/^- -/); # skip delimeter
|
|
@per_task_x = split(/\s+/, $_); shift @per_task_x if (/^\s+/);
|
|
$len = scalar(@per_task_x);
|
|
next if ($cmd_idx >= $len); # skip invalid lines
|
|
|
|
# join tail of list to form command - this also strips tailing whitespace
|
|
if ($::arg_threads) {
|
|
$offset = 0;
|
|
} else {
|
|
$offset = ($per_task_x[$cmd_idx] =~ /^\(/) ? 1 : 0; # offset if we need to drop threadname
|
|
}
|
|
$ctxt = $per_task_x[ $ctxt_idx ];
|
|
$tid = $per_task_x[ $tid_idx ];
|
|
$COMMAND = join(' ', splice(@per_task_x, -($len - $cmd_idx - $offset)));
|
|
|
|
# Match commands to aggregate data based on multiple regular expressions or pids
|
|
@MATCHES = ();
|
|
|
|
# Match exlusions commands for SUMMATION, TABLE, ALL
|
|
for ($i=0; $i < $n_excl; $i++) { # match command patterns
|
|
next TASK_LOOP if ($COMMAND =~ $re_excl[$i]);
|
|
}
|
|
foreach my $this_tid (@::arg_exclp) { # match tids
|
|
next TASK_LOOP if ($tid == $this_tid);
|
|
}
|
|
|
|
# Match commands for TABLE
|
|
for ($i=0; $i < $n_tab; $i++) { # match command patterns
|
|
if ($COMMAND =~ $re_tab[$i]) {
|
|
$match = $::arg_tab[$i]; push @MATCHES, $match;
|
|
$matched{$match}{$COMMAND}{$tid} = 1;
|
|
}
|
|
}
|
|
for ($i=0; $i < $n_tabe; $i++) { # match command patterns
|
|
if ($COMMAND =~ $re_tabe[$i]) {
|
|
$match = $::arg_tabe[$i]; push @MATCHES, $match;
|
|
$matched{$match}{$COMMAND}{$tid} = 1;
|
|
}
|
|
}
|
|
|
|
# Match commands for SUMMATION
|
|
if (defined $::arg_all) { # match all commands
|
|
$match = '_all_'; push @MATCHES, $match;
|
|
$matched{$match}{$COMMAND}{$tid} = 1;
|
|
goto FOUND_MATCH;
|
|
}
|
|
foreach my $this_tid (@::arg_sump) { # match tids
|
|
if ($tid == $this_tid ) {
|
|
$match = '_sum_'; push @MATCHES, $match;
|
|
$matched{$match.':'.$tid}{$COMMAND}{$tid} = 1;
|
|
goto FOUND_MATCH;
|
|
}
|
|
}
|
|
for ($i=0; $i < $n_sum; $i++) { # match command patterns
|
|
if ($COMMAND =~ $re_sum[$i]) {
|
|
$match = '_sum_'; push @MATCHES, $match;
|
|
$matched{$match.':'.$::arg_sum[$i]}{$COMMAND}{$tid} = 1;
|
|
goto FOUND_MATCH;
|
|
}
|
|
}
|
|
for ($i=0; $i < $n_sume; $i++) { # match command patterns
|
|
if ($COMMAND =~ $re_sume[$i]) {
|
|
$match = '_sum_'; push @MATCHES, $match;
|
|
$matched{$match.':'.$::arg_sume[$i]}{$COMMAND}{$tid} = 1;
|
|
goto FOUND_MATCH;
|
|
}
|
|
}
|
|
|
|
FOUND_MATCH: if (@MATCHES) {
|
|
##$matched{$COMMAND}{$tid} = 1;
|
|
} else {
|
|
next if ($::arg_detail == 1); ## do nothing for time-series, else aggregate
|
|
}
|
|
|
|
if ( !(defined $sample{$COMMAND}) ){
|
|
$sample{$COMMAND} = 1;
|
|
$data{$COMMAND}{'cnt'}++;
|
|
}
|
|
$data{$COMMAND}{'_ctxt_'} += $ctxt;
|
|
foreach $MATCH (@MATCHES) {
|
|
$cond{$MATCH}{'_ctxt_'} += $ctxt;
|
|
$avg{$MATCH}{'_ctxt_'} += $ctxt;
|
|
}
|
|
|
|
# Process only selected fields
|
|
foreach $field (@::select_list_cmds) {
|
|
$idx = $per_task_H{$field};
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if ($agg == 0);
|
|
$value = $per_task_x[$idx];
|
|
next if ($value eq '-');
|
|
|
|
# Convert units to nominal value
|
|
if ($scale > 0) {
|
|
$_ = $value;
|
|
if (($scale == 1) && /(\S+)(G|M|k)$/) { # SI suffix
|
|
$value = $1; $unit = $2;
|
|
if ($unit eq 'G') {
|
|
$value *= SI_G;
|
|
} elsif ($unit eq 'M') {
|
|
$value *= SI_M;
|
|
} elsif ($unit eq 'k') {
|
|
$value *= SI_k;
|
|
}
|
|
} elsif (($scale == 2) && /(\S+)(Gi|Mi|Ki)$/) { # IEC suffix
|
|
$value = $1; $unit = $2;
|
|
if ($unit eq 'Gi') {
|
|
$value *= Gi;
|
|
} elsif ($unit eq 'Mi') {
|
|
$value *= Mi;
|
|
} elsif ($unit eq 'Ki') {
|
|
$value *= Ki;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Aggregate stats based on field type
|
|
if ($agg < 4) {
|
|
# note, don't adjust $value for dtrate
|
|
if ($agg == 1) {
|
|
$value *= $dt; # dtsum
|
|
} elsif ($agg == 3) {
|
|
$value *= $ctxt; # ctxt
|
|
}
|
|
$data{$COMMAND}{$field} += $value;
|
|
foreach $MATCH (@MATCHES) {
|
|
$cond{$MATCH}{$field} += $value;
|
|
$avg{$MATCH}{$field} += $value;
|
|
}
|
|
} else { # agg = 4, max
|
|
if ( !(defined $data{$COMMAND}{$field}) || ($data{$COMMAND}{$field} < $value) ) {
|
|
$data{$COMMAND}{$field} = $value;
|
|
}
|
|
foreach $MATCH (@MATCHES) {
|
|
if ( !(defined $cond{$MATCH}{$field}) || ($cond{$MATCH}{$field} < $value) ) {
|
|
$cond{$MATCH}{$field} = $value;
|
|
}
|
|
if ( !(defined $avg{$MATCH}{$field}) || ($avg{$MATCH}{$field} < $value) ) {
|
|
$avg{$MATCH}{$field} = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Time series calculations
|
|
if ($::arg_detail == 1) {
|
|
$dt = $sample{'dt'}; if ($dt <= 0.0) { $dt = 1.0; }
|
|
foreach $MATCH (@series_ov) {
|
|
if ( !(defined $cond{$MATCH}) ) {
|
|
foreach $field (@::select_list_ov) { $cond{$MATCH}{$field} = 0.0; }
|
|
next;
|
|
}
|
|
foreach $field (@::select_list_ov) {
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if (($agg == 0) || ($agg == 4));
|
|
if ( !(defined $cond{$MATCH}{$field}) ) {
|
|
$cond{$MATCH}{$field} = 0.0;
|
|
next;
|
|
}
|
|
if (($agg <= 2)) {
|
|
$cond{$MATCH}{$field} /= ($dt*$::div{$field});
|
|
}
|
|
}
|
|
}
|
|
foreach $MATCH (@series_cmds) {
|
|
if ( !(defined $cond{$MATCH}) ) {
|
|
foreach $field (@::select_list_cmds) { $cond{$MATCH}{$field} = 0.0; }
|
|
next;
|
|
}
|
|
$ctxt = $cond{$MATCH}{'_ctxt_'};
|
|
$ctxt = 1.0 if ($ctxt <= 0.0);
|
|
foreach $field (@::select_list_cmds) {
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if (($agg == 0) || ($agg == 4));
|
|
if ( !(defined $cond{$MATCH}{$field}) ) {
|
|
$cond{$MATCH}{$field} = 0.0;
|
|
next;
|
|
}
|
|
if (($agg <= 2)) {
|
|
$cond{$MATCH}{$field} /= ($dt*$::div{$field});
|
|
} elsif ($agg == 3) {
|
|
$cond{$MATCH}{$field} /= ($ctxt*$::div{$field});
|
|
}
|
|
}
|
|
}
|
|
|
|
# Print summary line
|
|
if ($totals{'cnt'} == 1) {
|
|
# Print labels
|
|
printf "Labels:\n";
|
|
$cnt = 0;
|
|
for ($i=0; $i < $n_label_ov; $i++) {
|
|
printf " %3s: %s\n", $series_label[$cnt], $series_ov[$i];
|
|
$cnt++;
|
|
}
|
|
for ($i=0; $i < $n_label_cmds; $i++) {
|
|
if ($series_cmds[$i] eq '_all_') {
|
|
printf " %3s: %s", $series_label[$cnt], $series_cmds[$i];
|
|
printf " excl: (%s)", join(', ',@::arg_excl) if (@::arg_excl);
|
|
printf "\n";
|
|
} elsif ($series_cmds[$i] eq '_sum_') {
|
|
printf " %3s: %s (%s)", $series_label[$cnt], $series_cmds[$i], join(', ',@::arg_sum, @::arg_sume, @::arg_sump);
|
|
printf " excl: (%s)", join(', ',@::arg_excl,@::arg_exclp) if (@::arg_excl || @::arg_exclp);
|
|
printf "\n";
|
|
} else {
|
|
printf " %3s: %s\n", $series_label[$cnt], $series_cmds[$i];
|
|
}
|
|
$cnt++;
|
|
}
|
|
|
|
# Print headings
|
|
printf "%-10s %-12s %6s ", "-", "-", "-";
|
|
$cnt = 0;
|
|
for ($i=0; $i < $n_label_ov; $i++) {
|
|
foreach $field (@::select_list_ov) { printf $::fmtT{$field}. ' ', $series_label[$cnt]; }
|
|
$cnt++;
|
|
}
|
|
for ($i=0; $i < $n_label_cmds; $i++) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmtT{$field}. ' ', $series_label[$cnt]; }
|
|
$cnt++;
|
|
}
|
|
printf "\n";
|
|
|
|
printf "%-10s %-12s %6s ", "date", "time", "dt";
|
|
$cnt = 0;
|
|
for ($i=0; $i < $n_label_ov; $i++) {
|
|
foreach $field (@::select_list_ov) { printf $::fmtT{$field}. ' ', $field; }
|
|
$cnt++;
|
|
}
|
|
for ($i=0; $i < $n_label_cmds; $i++) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmtT{$field}. ' ', $field; }
|
|
$cnt++;
|
|
}
|
|
printf "\n";
|
|
|
|
# Print units
|
|
printf "%-10s %-12s %6s ", "yyyy/mm/dd", "hh:mm:ss.dec", "(s)";
|
|
foreach $COMMAND (@series_ov) {
|
|
foreach $field (@::select_list_ov) { printf $::fmtT{$field}. ' ', $::units{$field}; }
|
|
}
|
|
foreach $COMMAND (@series_cmds) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmtT{$field}. ' ', $::units{$field}; }
|
|
}
|
|
printf "\n";
|
|
}
|
|
|
|
# Print data
|
|
printf "%23s %6.3f ", $sample{'tstamp'}, $sample{'dt'};
|
|
foreach $COMMAND (@series_ov) {
|
|
foreach $field (@::select_list_ov) { printf $::fmt{$field}. ' ', $cond{$COMMAND}{$field}; }
|
|
}
|
|
foreach $COMMAND (@series_cmds) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmt{$field}. ' ', $cond{$COMMAND}{$field}; }
|
|
}
|
|
printf "\n";
|
|
}
|
|
}
|
|
}
|
|
close(FILE);
|
|
}
|
|
|
|
# Overall average calculations
|
|
$dt = $totals{'dt'}; if ($dt <= 0.0) { $dt = 1.0; }
|
|
if ($::arg_detail == 0) {
|
|
foreach $COMMAND (keys %data) {
|
|
$ctxt = $data{$COMMAND}{'_ctxt_'};
|
|
$ctxt = 1.0 if ($ctxt <= 0.0);
|
|
foreach $field (@::select_list_cmds) {
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if (($agg == 0) || ($agg == 4));
|
|
if ( !(defined $data{$COMMAND}{$field}) ) {
|
|
$data{$COMMAND}{$field} = 0.0;
|
|
next;
|
|
}
|
|
if ($agg <= 2) {
|
|
$data{$COMMAND}{$field} /= ($dt*$::div{$field});
|
|
} elsif ($agg == 3) {
|
|
$data{$COMMAND}{$field} /= ($ctxt*$::div{$field});
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach $MATCH (@series_ov) {
|
|
if ( !(defined $avg{$MATCH}) ) {
|
|
foreach $field (@::select_list_ov) { $avg{$MATCH}{$field} = 0.0; }
|
|
next;
|
|
}
|
|
foreach $field (@::select_list_ov) {
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if (($agg == 0) || ($agg == 4));
|
|
if ( !(defined $avg{$MATCH}{$field}) ) {
|
|
$avg{$MATCH}{$field} = 0.0;
|
|
next;
|
|
}
|
|
if ($agg <= 2) {
|
|
$avg{$MATCH}{$field} /= ($dt*$::div{$field});
|
|
}
|
|
}
|
|
}
|
|
foreach $MATCH (@series_cmds) {
|
|
if ( !(defined $avg{$MATCH}) ) {
|
|
$avg{$MATCH}{'_ctxt_'}= 0.0;
|
|
foreach $field (@::select_list_cmds) { $avg{$MATCH}{$field} = 0.0; }
|
|
next;
|
|
}
|
|
$ctxt = $avg{$MATCH}{'_ctxt_'};
|
|
$ctxt = 1.0 if ($ctxt <= 0.0);
|
|
foreach $field (@::select_list_cmds) {
|
|
($scale, $agg) = @{ $::fields{$field} };
|
|
next if (($agg == 0) || ($agg == 4));
|
|
if ( !(defined $avg{$MATCH}{$field}) ) {
|
|
$avg{$MATCH}{$field} = 0.0;
|
|
next;
|
|
}
|
|
if ($agg <= 2) {
|
|
$avg{$MATCH}{$field} /= ($dt*$::div{$field});
|
|
} elsif ($agg == 3) {
|
|
$avg{$MATCH}{$field} /= ($ctxt*$::div{$field});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Print overall averages
|
|
if ($::arg_detail == 1 && ($totals{'cnt'} > 0)) {
|
|
printf "%23s %6.3f ", 'Average:', $totals{'dt'}/$totals{'cnt'};
|
|
foreach $COMMAND (@series_ov) {
|
|
foreach $field (@::select_list_ov) { printf $::fmt{$field}. ' ', $avg{$COMMAND}{$field}; }
|
|
}
|
|
foreach $COMMAND (@series_cmds) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmt{$field}. ' ', $avg{$COMMAND}{$field}; }
|
|
}
|
|
printf "\n";
|
|
}
|
|
|
|
# Print overall summary
|
|
if ($totals{'cnt'} > 0) {
|
|
printf "Overall: Elapsed:%.1f s Samples:%d dt:%.2f s\n",
|
|
$totals{'dt'}, $totals{'cnt'}, $totals{'dt'}/$totals{'cnt'};
|
|
} else {
|
|
printf "Sample: no data available\n\n";
|
|
}
|
|
|
|
# Print matching list of tasks
|
|
if ($::arg_detail == 1) {
|
|
foreach my $match (sort keys %matched) {
|
|
printf "\nMatch = '%s'\n", $match;
|
|
foreach my $cmd (sort keys %{ $matched{$match} }) {
|
|
printf "%s: %s\n", $match, $cmd;
|
|
printf " (%s)\n", join(',', sort {$a <=> $b} keys %{ $matched{$match}{$cmd} });
|
|
}
|
|
}
|
|
}
|
|
|
|
# Print overall summary
|
|
if ($::arg_detail == 0) {
|
|
# Print headings
|
|
foreach $field (@::select_list_cmds) { printf $::fmtT{$field}. ' ', $field; }
|
|
printf $::fmtT{'cnt'}." ".$::fmtT{'COMMAND'}."\n", 'cnt', 'COMMAND';
|
|
|
|
# Print units
|
|
foreach $field (@::select_list_cmds) { printf $::fmtT{$field}. ' ', $::units{$field}; }
|
|
printf $::fmtT{'cnt'}." ".$::fmtT{'COMMAND'}."\n", $::units{'cnt'}, $::units{'COMMAND'};
|
|
|
|
# Print task data, sorting on numerically rounded keys
|
|
# (default keys to '_ctxt_' and 'cnt' since they always defined)
|
|
my ($s1, $s2, $s3) = ('_ctxt_', '_ctxt_', 'cnt'); my ($w1, $w2, $w3) = (100, 100, 100);
|
|
if (defined $::select_list_cmds[0]) { $s1 = $::select_list_cmds[0]; $w1 = 10**($::dec{$s1}+1); }
|
|
if (defined $::select_list_cmds[1]) { $s2 = $::select_list_cmds[1]; $w2 = 10**($::dec{$s2}+1); }
|
|
if (defined $::select_list_cmds[2]) { $s3 = $::select_list_cmds[2]; $w3 = 10**($::dec{$s3}+1); }
|
|
foreach $COMMAND (
|
|
sort { (int($w1*$data{$b}{$s1}+5) <=> int($w1*$data{$a}{$s1}+5)) ||
|
|
(int($w2*$data{$b}{$s2}+5) <=> int($w2*$data{$a}{$s2}+5)) ||
|
|
(int($w3*$data{$b}{$s3}+5) <=> int($w3*$data{$a}{$s3}+5)) ||
|
|
($a cmp $b)
|
|
} keys %data) {
|
|
foreach $field (@::select_list_cmds) { printf $::fmt{$field}. ' ', $data{$COMMAND}{$field}; }
|
|
printf $::fmt{'cnt'}." ".$::fmt{'COMMAND'}."\n", $data{$COMMAND}{'cnt'}, $COMMAND;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#############################################################################################################
|
|
|
|
# Parse input option arguments
|
|
sub get_parse_schedtop_args {
|
|
(local *::arg_debug, local *::arg_field, local *::arg_family,
|
|
local *::arg_sum, local *::arg_sump, local *::arg_sume, local *::arg_tab, local *::arg_tabe,
|
|
local *::arg_excl, local *::arg_exclp, local *::arg_all, local *::arg_threads,
|
|
local *::arg_detail, local *::arg_ignore,
|
|
local *::arg_from, local *::arg_to,
|
|
local *::arg_files) = @_;
|
|
|
|
# Local variables
|
|
my ($fail, $arg_help);
|
|
my ($fld, $group, $field, $family) = ();
|
|
my ($yy, $month, $day, $hh, $mm, $ss, $frac);
|
|
my @tmp = ();
|
|
|
|
# Use the Argument processing module
|
|
use Getopt::Long;
|
|
|
|
# Print usage if no arguments
|
|
if (!@::ARGV) {
|
|
&Usage();
|
|
exit 0;
|
|
}
|
|
|
|
# Process input arguments
|
|
$fail = 0;
|
|
GetOptions(
|
|
"debug:i", \$::arg_debug,
|
|
"field=s", \@::arg_field,
|
|
"family=s", \@::arg_family,
|
|
"sum=s", \@::arg_sum,
|
|
"sum-exact=s", \@::arg_sume,
|
|
"tab=s", \@::arg_tab,
|
|
"tab-exact=s", \@::arg_tabe,
|
|
"excl=s", \@::arg_excl,
|
|
"all", \$::arg_all,
|
|
"threads", \$::arg_threads,
|
|
"detail", \$::arg_detail,
|
|
"ignore=s", \$::arg_ignore,
|
|
"from=s", \$::arg_from,
|
|
"to=s", \$::arg_to,
|
|
"help|h", \$arg_help
|
|
) || GetOptionsMessage();
|
|
|
|
# Print help documentation if user has selected --help
|
|
&ListHelp() if (defined $arg_help);
|
|
|
|
# Listify @::arg_sum and split tids from commands
|
|
@tmp = ();
|
|
@::arg_sump = ();
|
|
if (@::arg_sum) {
|
|
@tmp = @::arg_sum; @::arg_sum = ();
|
|
foreach my $cmd (@tmp) { push @::arg_sum, (split /,/, $cmd); }
|
|
@tmp = @::arg_sum; @::arg_sum = ();
|
|
foreach my $cmd (@tmp) {
|
|
if ($cmd =~ /^\d+$/) {
|
|
push @::arg_sump, $cmd;
|
|
} else {
|
|
push @::arg_sum, $cmd;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Listify @::arg_sume
|
|
@tmp = ();
|
|
if (@::arg_sume) {
|
|
@tmp = @::arg_sume; @::arg_sume = ();
|
|
foreach my $cmd (@tmp) { push @::arg_sume, (split /,/, $cmd); }
|
|
@tmp = @::arg_sume; @::arg_sume = ();
|
|
foreach my $cmd (@tmp) {
|
|
if ($cmd =~ /^\d+$/) {
|
|
push @::arg_sump, $cmd;
|
|
} else {
|
|
push @::arg_sume, $cmd;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Listify @::arg_tab and split tids from commands
|
|
@tmp = ();
|
|
if (@::arg_tab) {
|
|
@tmp = @::arg_tab; @::arg_tab = ();
|
|
foreach my $cmd (@tmp) { push @::arg_tab, (split /,/, $cmd); }
|
|
@tmp = @::arg_tab; @::arg_tab = ();
|
|
foreach my $cmd (@tmp) {
|
|
if ($cmd =~ /^\d+$/) {
|
|
warn "$::scriptName: Input error: cannot specify --tab=$cmd, only strings supported.\n";
|
|
$fail = 1;
|
|
} else {
|
|
push @::arg_tab, $cmd;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Listify @::arg_tabe
|
|
@tmp = ();
|
|
if (@::arg_tabe) {
|
|
@tmp = @::arg_tabe; @::arg_tabe = ();
|
|
foreach my $cmd (@tmp) { push @::arg_tabe, (split /,/, $cmd); }
|
|
@tmp = @::arg_tabe; @::arg_tabe = ();
|
|
foreach my $cmd (@tmp) {
|
|
if ($cmd =~ /^\d+$/) {
|
|
warn "$::scriptName: Input error: cannot specify --tab-exact=$cmd, only strings supported.\n";
|
|
$fail = 1;
|
|
} else {
|
|
push @::arg_tabe, $cmd;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Listify @::arg_excl
|
|
@tmp = ();
|
|
if (@::arg_excl) {
|
|
@tmp = @::arg_excl; @::arg_excl = ();
|
|
foreach my $cmd (@tmp) { push @::arg_excl, (split /,/, $cmd); }
|
|
@tmp = @::arg_excl; @::arg_excl = (); @::arg_exclp = ();
|
|
foreach my $cmd (@tmp) {
|
|
if ($cmd =~ /^\d+$/) {
|
|
push @::arg_exclp, $cmd;
|
|
} else {
|
|
push @::arg_excl, $cmd;
|
|
}
|
|
}
|
|
}
|
|
# Listify @::arg_field
|
|
@tmp = ();
|
|
if (@::arg_field) {
|
|
@tmp = @::arg_field; @::arg_field = ();
|
|
foreach my $field (@tmp) { push @::arg_field, (split /,/, $field); }
|
|
}
|
|
# Listify @::arg_family
|
|
@tmp = ();
|
|
if (@::arg_family) {
|
|
@tmp = @::arg_family; @::arg_family = ();
|
|
foreach my $family (@tmp) { push @::arg_family, (split /,/, $family); }
|
|
}
|
|
|
|
# Give warning messages and usage when parameters are specified incorrectly.
|
|
my $count = 0;
|
|
$count++ if ((@::arg_sum) || (@::arg_sump) || (@::arg_sume) ||
|
|
(@::arg_tab) || (@::arg_tabe));
|
|
$count++ if (defined $::arg_all);
|
|
if ($count > 1) {
|
|
warn "$::scriptName: Input error: cannot specify --sum=<str> or --tab=<str> with --all.\n";
|
|
$fail = 1;
|
|
}
|
|
if (!@ARGV) {
|
|
warn "$::scriptName: Input error: must specify schedtop data files.\n";
|
|
$fail = 1;
|
|
}
|
|
|
|
# Determine whether time-series is selected
|
|
$::arg_detail = (defined $::arg_detail) ? 1 : 0;
|
|
|
|
# Check that we have selected time-series option if selecting commands
|
|
if (($::arg_detail == 0) && ($count > 0)) {
|
|
warn "$::scriptName: Input error: cannot specify --sum=<str>, --tab=<str>, or --all without also specifying --detail option.\n";
|
|
$fail = 1;
|
|
}
|
|
|
|
# Process selected families and fields
|
|
my $cnt = 0;
|
|
%::select_H = (); %::select_F = ();
|
|
foreach $field (@::arg_field) {
|
|
if ( !(defined $::valid_fields{$field}) ) {
|
|
my $list = join(',', sort {$::valid_fields{$a} <=> $::valid_fields{$b} } keys %::valid_fields);
|
|
warn "$::scriptName: Input error: cannot specify --field=$field, expect:<$list>\n";
|
|
$fail = 1;
|
|
next;
|
|
}
|
|
$group = $groups{$field};
|
|
if ( !(defined $::select_H{$group}{$field}) ) {
|
|
$::select_H{$group}{$field} = $cnt; $cnt++;
|
|
foreach $family (keys %::valid_families) {
|
|
foreach $fld (keys %{ $::families{$group}{$family} }) {
|
|
if ($fld eq $field) {
|
|
$::select_F{$group}{$family} = 1;
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
foreach $family (@::arg_family) {
|
|
if ( !(defined $::valid_families{$family}) ) {
|
|
my $list = join(',', sort {$::valid_families{$a} <=> $::valid_families{$b} } keys %::valid_families);
|
|
warn "$::scriptName: Input error: cannot specify --family=$family, expect:<$list>\n";
|
|
$fail = 1;
|
|
next;
|
|
}
|
|
foreach $group (keys %::families) {
|
|
if (defined $::families{$group}{$family}) {
|
|
foreach $field (sort {$::families{$group}{$family}{$a}->{idx} <=> $::families{$group}{$family}{$b}->{idx} }
|
|
keys %{ $::families{$group}{$family} }) {
|
|
if ( !(defined $::select_H{$group}{$field}) ) {
|
|
$::select_H{$group}{$field} = $cnt; $cnt++;
|
|
$::select_F{$group}{$family} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Determine default fields if none provided
|
|
if ( !(@::arg_field) && !(@::arg_family) ) {
|
|
if ($::arg_detail == 1) {
|
|
foreach $field (@::select_fields) {
|
|
$group = $groups{$field};
|
|
if ( !(defined $::select_H{$group}{$field}) ) {
|
|
$::select_H{$group}{$field} = $cnt; $cnt++;
|
|
foreach $family (keys %::valid_families) {
|
|
foreach $fld (keys %{ $::families{$group}{$family} }) {
|
|
if ($fld eq $field) {
|
|
$::select_F{$group}{$family} = 1;
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$group = 'cmd';
|
|
foreach $family (@::select_families) {
|
|
foreach $field (sort {$::families{$group}{$family}{$a}->{idx} <=> $::families{$group}{$family}{$b}->{idx} }
|
|
keys %{$::families{$group}{$family}}) {
|
|
if ( !(defined $::select_H{$group}{$field}) ) {
|
|
$::select_H{$group}{$field} = $cnt; $cnt++;
|
|
$::select_F{$group}{$family} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# if --tab or --sum or --all, then need at least one cmd group field.
|
|
if (($count > 0) && ( !(defined $::select_H{'cmd'}) || !(defined $::select_F{'cmd'}))) {
|
|
warn "$::scriptName: Input error: cannot specify --sum=<str>, --tab=<str>, or --all without specifying task fields/families.\n";
|
|
$fail = 1;
|
|
}
|
|
if ($::arg_detail == 0) { # average mode
|
|
# need at least one cmd group field
|
|
if ( !(defined $::select_H{'cmd'}) || !(defined $::select_F{'cmd'})) {
|
|
warn "$::scriptName: Input error: AVERAGE mode is missing specification of task fields/families.\n";
|
|
$fail = 1;
|
|
}
|
|
# need at least one overall group field
|
|
if ((defined $::select_H{'ov'}) || (defined $::select_F{'ov'})) {
|
|
warn "$::scriptName: Input error: AVERAGE mode cannot specify overall fields/families.\n";
|
|
$fail = 1;
|
|
}
|
|
}
|
|
if ($::arg_detail == 1) { # time-series
|
|
# no commands or overall group fields selected
|
|
if (($count == 0) && ( !(defined $::select_H{'ov'}) || !(defined $::select_F{'ov'}))) {
|
|
warn "$::scriptName: Input error: --detail requires overall fields/families and/or --sum=<str>, --tab=<str>, or --all options.\n";
|
|
$fail = 1;
|
|
}
|
|
}
|
|
|
|
# Validate arg_ignore
|
|
$::arg_ignore = 4.0 if (!defined $::arg_ignore);
|
|
if ($::arg_ignore < 2.0) {
|
|
warn "$::scriptName: Input error: cannot specify --ignore=$::arg_ignore, expect factor > 2.0\n";
|
|
$fail = 1;
|
|
}
|
|
|
|
# Convert date/time string into timelocal
|
|
if ($::arg_from) {
|
|
$_ = $::arg_from;
|
|
if (/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})/) {
|
|
$yy = $1; $month = $2; $day = $3; $hh = $4; $mm = $5; $ss = $6; $frac = $7;
|
|
$::arg_from = timelocal_nocheck($ss, $mm, $hh, $day, $month-1, $yy-1900);
|
|
} else {
|
|
warn "$::scriptName: Input error: invalid time format: '$::arg_from', expect: yyyy-mm-ddThh:mm:ss.fff\n";
|
|
$fail = 1;
|
|
}
|
|
}
|
|
if ($::arg_to) {
|
|
$_ = $::arg_to;
|
|
if (/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})/) {
|
|
$yy = $1; $month = $2; $day = $3; $hh = $4; $mm = $5; $ss = $6; $frac = $7;
|
|
$::arg_to = timelocal_nocheck($ss, $mm, $hh, $day, $month-1, $yy-1900);
|
|
} else {
|
|
warn "$::scriptName: Input error: invalid time format: '$::arg_to', expect: yyyy-mm-ddThh:mm:ss.fff\n";
|
|
$fail = 1;
|
|
}
|
|
}
|
|
|
|
# Upon missing or invalid options, print usage
|
|
if ($fail == 1) {
|
|
&Usage();
|
|
exit 1;
|
|
}
|
|
|
|
# Assume remaining options are filenames
|
|
@::arg_files = @ARGV;
|
|
}
|
|
|
|
# Print out a warning message and usage
|
|
sub GetOptionsMessage {
|
|
warn "$::scriptName: Error processing input arguments.\n";
|
|
&Usage();
|
|
exit 1;
|
|
}
|
|
|
|
# Print out program usage
|
|
sub Usage {
|
|
printf "Usage: $::scriptName OPTIONS file1 file2 file3.gz ...\n";
|
|
printf " [--detail] [--threads] [--ignore=<fac>] [--help]\n";
|
|
printf " [--field=<field,...>] [--family=<family,...>]\n";
|
|
printf " [--sum=<tid,str,...>] [--sum-exact=<str,...>] [--tab=<str,...>] [--tab-exact=<str,...>]\n";
|
|
printf " [--excl=<tid,str,...>] [--all]\n";
|
|
printf "\n";
|
|
}
|
|
|
|
# Print tool help
|
|
sub ListHelp {
|
|
my ($group, $family) = ();
|
|
my @list = (); my @tmp = ();
|
|
printf "$::scriptName $::scriptVersion -- parses schedtop output from multiple files\n";
|
|
&Usage();
|
|
printf "Options: miscellaneous\n";
|
|
printf " --detail : specify time-series mode (default mode is 'AVERAGE')\n";
|
|
printf " --threads : specify that unique thread names will match instead of the main 'pid'\n";
|
|
printf " --ignore=<fac> : ignore samples where 'sample_dt' > 'fac' x 'dt': default: 4.0\n";
|
|
printf " --help : this help\n";
|
|
printf "\n";
|
|
printf "Options: applicable to AVERAGE mode or time-series mode\n";
|
|
printf " --field=<field,...> : specify fields (default: 'cpu' in time-series mode)\n";
|
|
printf " --family=<family,...> : specify families (eg. %s) : default: none\n",
|
|
join(',', sort {$::valid_families{$a} <=> $::valid_families{$b}} keys %::valid_families);
|
|
printf "\n";
|
|
foreach $group (sort keys %::families) {
|
|
if ($group eq 'cmd') {
|
|
@list = (); @tmp = sort keys %{ $::families{$group} };
|
|
while ($family = shift @tmp) {
|
|
push @list, $family if ($::valid_families{$family});
|
|
}
|
|
printf "'task' familes (eg., %s) and their fields - applicable to AVERAGE and time-series\n", join(',', @list);
|
|
} elsif ($group eq 'ov') {
|
|
@list = (); @tmp = sort keys %{ $::families{$group} };
|
|
while ($family = shift @tmp) {
|
|
push @list, $family if ($::valid_families{$family});
|
|
}
|
|
printf "\n'overall' families (eg., %s) and their fields - applicable to time-series only\n", join(',', @list);
|
|
}
|
|
foreach $family (sort keys %{ $::families{$group} }) {
|
|
next if ( !defined $valid_families{$family} );
|
|
printf " %6s: %s\n", $family,
|
|
join(',', sort {$::families{$group}{$family}{$a}->{idx} <=> $::families{$group}{$family}{$b}->{idx} }
|
|
keys %{ $::families{$group}{$family} }
|
|
);
|
|
}
|
|
}
|
|
printf "\n";
|
|
printf "Options: applicable to time-series in conjunction with 'task' families\n";
|
|
printf " --sum=<tid,str,...> : specify TIDs or string patterns to include in aggregate sum\n";
|
|
printf " --sum-exact=<str,...> : specify exact string patterns to include in aggregate sum\n";
|
|
printf " --tab=<str,...> : specify string patterns to display as separate table columns\n";
|
|
printf " --tab-exact=<str,...> : specify exact string patterns to display as separate table columns\n";
|
|
printf " --excl=<tid,str,...> : specify TIDs or string patterns to be excluded from --sum,--tab,--all matches\n";
|
|
printf " --all : selects all command names to match in aggregate sum\n";
|
|
printf "\n";
|
|
printf "EXAMPLES: AVERAGE mode (condenses each matching command)\n";
|
|
printf "1) average mode, default displays all 'task' families\n";
|
|
printf " $::scriptName my_schedtop\n";
|
|
printf "\n";
|
|
printf "2) average mode, select specific 'task' fields\n";
|
|
printf " $::scriptName --field=occ,read,write my_schedtop\n";
|
|
printf "\n";
|
|
printf "EXAMPLES: time-series mode (shows each sample using selected criteria)\n";
|
|
printf "5) time-series, sum of 'irq/*' + 'postgres' string patterns\n";
|
|
printf " $::scriptName --detail --sum=irq/*,postgres my_schedtop\n";
|
|
printf "\n";
|
|
printf "6) time-series, sum of 'irq/*' + 'postgres', select specific fields\n";
|
|
printf " $::scriptName --detail --sum=irq/*,postgres --field=occ,ctxt --family=io my_schedtop\n";
|
|
printf "\n";
|
|
printf "7) time-series, sum of all processes excluding 'schedtop'\n";
|
|
printf " $::scriptName --detail --all --excl=schedtop my_schedtop\n";
|
|
printf "\n";
|
|
printf "8) time-series, select specific overall families 'runq', 'percpu', 'MiB'\n";
|
|
printf " $::scriptName --detail --family=runq,percpu,MiB my_schedtop\n";
|
|
printf "\n";
|
|
exit 0;
|
|
}
|
|
|
|
1;
|