#!/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=] [--help] # [--field=] [--family=] # [--sum=] [--sum-exact=] [--tab=] [--tab-exact=] # [--excl=] [--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=, --sum-exact= to match multiple strings or TIDs # that will be aggregated together into a single column # - specifiy --tab=, --tab-exact= 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= to exclude specific strings or TIDs that would have # matched with --sum=, --tab=, or --all # - specify --field= and/or --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 ($_ = ) { 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 ($_ = ) { 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 ($_ = ) { 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 ($_ = ) { 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 ($_ = ) { 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 ($_ = ) { 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= or --tab= 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=, --tab=, 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=, --tab=, 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=, --tab=, 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=] [--help]\n"; printf " [--field=] [--family=]\n"; printf " [--sum=] [--sum-exact=] [--tab=] [--tab-exact=]\n"; printf " [--excl=] [--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= : 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= : specify fields (default: 'cpu' in time-series mode)\n"; printf " --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= : specify TIDs or string patterns to include in aggregate sum\n"; printf " --sum-exact= : specify exact string patterns to include in aggregate sum\n"; printf " --tab= : specify string patterns to display as separate table columns\n"; printf " --tab-exact= : specify exact string patterns to display as separate table columns\n"; printf " --excl= : 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;