#!/usr/bin/perl



###########################################################################
# THIS PROGRAM IS DISTRIBUTED WITH THE BOOK:
#   How to Build Your Desktop Supercomputer ISBN 9798161121221 Oct 2026 
#
# Copyright © 2015-2026 Bassem W. Jamaleddine
# Copyright © 2026 Total Computing & Network Design, Inc.
# All rights reserved.
#
# READ THE LICENSE AGREEMENT BEFORE USING THIS SOFTWARE
#
# The Licensed Software is copyrighted by TOTAL COMPUTING & NETWORK DESIGN, INC.
# The source code, which is this program and all substantial portion of the
# Software on which the above copyright appears, cannot me modified or
# altered from the original without prior written authorization from
# TOTAL COMPUTING & NETWORK DESIGN, INC.
#
# The above copyright notice shall be included in
# all copies or substantial portions of the Software.
#
# TOTAL COMPUTING & NETWORK DESIGN INC. DISCLAIMS ALL WARRANTIES
# RELATING TO THIS SOFTWARE, WHETHER EXPRESS OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES OF 
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND ALL
# SUCH WARRANTIES ARE EXPRESSLY AND SPECIFICALLY DISCLAIMED.
# NEITHER TOTAL COMPUTING & NETWORK DESIGN, INC. NOR
# ANYONE ELSE WHO HAS BEEN INVOLVED IN THE CREATION,
# PRODUCTION, OR DELIVERY OF THIS SOFTWARE SHALL BE LIABLE
# FOR ANY INDIRECT, CONSEQUENTIAL, OR INCIDENTAL DAMAGES
# ARISING OUT OF THE USE OR INABILITY TO USE SUCH SOFTWARE
# EVEN IF TOTAL COMPUTING & NETWORK DESIGN, INC. HAS BEEN
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR CLAIMS. IN NO
# EVENT SHALL TOTAL COMPUTING & NETWORK DESIGN, INC.'S
# LIABILITY FOR ANY DAMAGES EVER EXCEED THE PRICE PAID FOR THE
# LICENSE TO USE THE SOFTWARE, REGARDLESS OF THE FORM OF CLAIM. THE
# PERSON USING THE SOFTWARE BEARS ALL RISK AS TO THE QUALITY
# AND PERFORMANCE OF THE SOFTWARE.
#
# Surface mail:
# =============
# TOTAL COMPUTING & NETWORK DESIGN, INC.
# P.O.BOX 680
# New York, NY 10156
# USA
#
# FAX: +1-212-208-3039
# PHONE: +1-212-203-4379
#
# Author: Bassem W. Jamaleddine
# Email: jamaleddineb@aol.com
###########################################################################

use strict;

my $ticks = 100;
{
	my $locatestring =  "locate POSIX.pm";
	eval "use POSIX; 1";
	if ($@ && ($@ =~ /$locatestring/)) {
	}
	elsif ($@) {
	}
	else {
		$ticks = POSIX::sysconf(2);
	}
}

my $BRIEF = <<BRIEF;
Usage:

  $0 [-help] [-info] [-notime] [-epotime] [-cache] [-fromcached] [-replay]
       [-iter] [-delay] [-transpose] [-stats] [-headonce] 
       [-sortby] [-keeporder] [-NVU] [-prune]


Use dcpus to view your system stats used per CPU in a multicore system.
This command be run in a terminal to iterate over the CPUs stats and
display them in an organized output.

dcpus command can cache the results in the directory /tmp/cached_sysstats
for replay at a later time. You can specify an alternate directory
by exporting the environment variable DCPUS_CACHEDIR:
  export DCPUS_CACHEDIR=/home/johndoe/cached

Take a quick look at your system stats:
  dcpus -info
this will print (and will vary on different system)
  user nice system idle iowait irq softirq steal guest guestnice

These are the same that can be seen on Linux by issuing:
  cat /proc/stat

To display the first three cores and the last four cores use the
option -prune:
  dcpus -prune 3,4
This will display the first 3 cores and the last 4 cores.

You can flip the columns and rows using -transpose option.
To display the CPUs per line and the stats per column use
the option -transpose:
  dcpus -prune 3,4 -transpose

To collect the CPUs and their stats in a loop iteration use the
options -iter and -delay:
  dcpus  -iter 3 -delay 1
  dcpus  -iter 3 -delay 1 -prune 2,3

To collect and cache the data of an iteration to be replayed later
on, use the -cache option:
  dcpus  -iter 3 -delay 1 -prune 2,3 -cache
At the end of the iteration you will see how to replay what has
been cached:


  dcpus  -iter 3 -delay 1 -prune 2,3 -replay 1718854605
The cached data can be replayed with any other option:
  dcpus  -iter 3 -delay 1 -prune 3,4 -replay 1718854605 -transpose
  dcpus  -iter 3 -delay 1 -replay 1718854605 -transpose
  dcpus  -replay 1718854605 -transpose
display only 
  dcpus  -iter 3 -delay 1 -replay 1718854605 -transpose -prune 2,5 -stats user,system,idle
display everything in the loop at once without delay:
  dcpus -replay 1718854605 -transpose -prune 2,5 -stats user,system,idle -iter 9999
  dcpus -replay 1718854605 -transpose -prune 2,5 -stats user,system,idle -iter 9999 -sortby user

When caching the data, all data is retained and replay can be used with any other option.

The following command iterate 30 times with delay of 1 second:
  dcpus -iter 30 -delay 1 -sortby iowait -transpose -stats system,user,irq,iowait -keeporder 
same as above but only gather 5 cores of importance of iowait:
  dcpus -iter 30 -delay 1 -NVU 5 -sortby iowait -transpose -stats system,user,irq,iowait -keeporder 
same as above but cache the data to be replayed later on:
  dcpus -iter 3a0 -delay 1 -NVU 5 -sortby iowait -transpose -stats system,user,irq,iowait -keeporder -cache

You can cache the iteration:
  dcpus -iter 3 -delay 1 -cache
then replay it with any other options:
  dcpus -iter 3 -delay 1 -NVU 5 -sortby iowait -transpose -stats system,user,irq,iowait -keeporder -replay 12345


BRIEF
my $USAGE = <<USAGE;
   5   10        20        30        40        50        60        70        80
|...+....1....+....2....+....3....+....4....+....5....+....6....+....7...
           |...+....1....+....2....+....3....+....4....+....5....+....6....+....7...

Usage:

  $0 [-help] [-info] [-notime] [-epotime] [-cache] [-fromcached] [-replay]
       [-iter] [-delay] [-transpose] [-stats] [-headonce] 
       [-sortby] [-keeporder] [-NVU] [-prune]

Where:

  -help                  Display this summary
  -info                  Print stats known in your system
  -notime                Don't display the time
  -epotime               Print using epock time
  -cache                 Cache result for later replay 
  -fromcached EPOTIME    Run command replaying from cache
  -replay EPOTIME        Run command replaying from cache
  -iter N                Iterate N times
  -delay D               Delay by D seconds between each iteration
  -transpose             Transpose CPUs and Interrupts
  -stats s1,s2,..   Specify the list of stats from the list
                           known to your system. Use -info to view them.
  -headonce              In an iteration print the header just once
  -sortby INT            You can specify one interrupt by name to have
                            it sorted by the order of CPUs
  -keeporder             Keep the order of the stats as being displayed
                            in /proc/stat. Use this option when specifying
                            the -stats option followed by any unorderd list
                            of stats  
  -NVU N                 Display only the top N cores this is useful when specifying
                            -sortby INT and you have lot of cores:
                           # dcpus -iter 3 -delay 1 -NVU 5 -sortby user -transpose -stats user,system,idle
  -prune n1,n2           Prune the cores showing only the first n1 cores 
                            and the last n2 cores; where n1 and n2 are numbers
  
USAGE
my $STLONG = {
 	user => 'Time spent with normal processing in user mode',
 	nice => 'Time spent with niced processes in user mode',
 	system => 'Time spent running in kernel mode',
 	idle => 'Time spent in vacations twiddling thumbs',
 	iowait => 'Time spent waiting for I/O to completed. This is considered idle time.',
 	irq => 'Time spent serving hardware interrupts',
 	softirq => 'Time spent serving software interrupts',
 	steal => 'Time stolen by other operating systems running in a virtual environment',
 	guest => 'Time spent for running a virtual CPU or guest OS under the control of the kernel',
 	guest_nice => 'time spent on processes running on a virtual CPU with niced priority',
};

local *OUT;

my $transpose = 0;

my $iter;
my $delay;
my $tranpose = 0;
my $stats;

my $headonce = 0;
my $sortby;
my $keeporder = 0;
my $NVU;
my $prune;
my $showtime = 1;
my $epotime = 0;

my $CACHED = 0;
my $FROMCACHED = 0;
my $TM;
my $INFO = 0;
my $CACHEDIR = (defined $ENV{DCPUS_CACHEDIR}) && (-d $ENV{DCPUS_CACHEDIR}) ? $ENV{DCPUS_CACHEDIR} : '/tmp/cached_sysstats';

# if0block deleted
while(@ARGV) {
	if ($ARGV[0] eq '-info') { #
		shift @ARGV;
		$INFO = 1;
	}
	elsif ($ARGV[0] eq '-notime') { #
		shift @ARGV;
		$showtime = 0;
	}
	elsif ($ARGV[0] eq '-epotime') { #
		shift @ARGV;
		$epotime = 1;
	}
	elsif ($ARGV[0] eq '-cache') { #
		shift @ARGV;
		$CACHED = 1;
		$TM = time;
	}
	elsif ($ARGV[0] eq '-fromcached') { #
		shift @ARGV;
		$TM = shift @ARGV;
		$FROMCACHED = 1;
	}
	elsif ($ARGV[0] eq '-replay') { #
		shift @ARGV;
		$TM = shift @ARGV;
		$FROMCACHED = 1;
	}
	elsif ($ARGV[0] eq '-iter') { #
		shift @ARGV;
		$iter = shift @ARGV;
	} elsif ($ARGV[0] eq '-delay') { #
		shift @ARGV;
		$delay = shift @ARGV;
	} elsif ($ARGV[0] eq '-transpose') { # transpose the displayable matrix
		shift @ARGV;
		$transpose = 1;
	} elsif ($ARGV[0] eq '-stats') {
		shift @ARGV;
		$stats = shift @ARGV;
	} elsif ($ARGV[0] eq '-headonce') {
		shift @ARGV;
		$headonce = 1;
	} elsif ($ARGV[0] eq '-sortby') {
		shift @ARGV;
		$sortby = shift @ARGV;
	} elsif ($ARGV[0] eq '-keeporder') {
		shift @ARGV;
		$keeporder = 1;
	} elsif ($ARGV[0] eq '-NVU') {
		shift @ARGV;
		$NVU = shift @ARGV;
	} elsif ($ARGV[0] eq '-prune') {
		shift @ARGV;
		$prune = shift @ARGV;
	} elsif ($ARGV[0] eq '-www?') {
		open(F, 'lynx -dump https://www.tcnd.com/tools/dcpus.html|');
		while(<F>) {print $_; }
		exit;
	} elsif (($ARGV[0] eq "-h") || ($ARGV[0] eq "-help") || ($ARGV[0] eq "--Help") || ($ARGV[0] eq '-?')) {
		usage();
	} elsif (($ARGV[0] eq "-V") || ($ARGV[0] eq "--Version")) {
		pversion();
	} else { die "unrecognized arguments <@ARGV>"; }
}

if (($sortby) && (!$transpose)) { # headonce not allowed on sorted unless transpose
	$headonce = 0;
}
if ($epotime) { $showtime = 0; } # cancel showtime if epotime is selected and set to 1

if ($CACHED && $FROMCACHED) {
	die "CANNOT CACHE INTERRUPTS AND READ FROMCACHED AT ONCE!\n";
}

if ($CACHED) {
	my $cachefile = "$CACHEDIR/" . "cpustats_" . $TM;
	open(OUT, ">$cachefile");
	cachestatspec($TM);
}

if ($FROMCACHED) {
	my $cachefile = "$CACHEDIR/" . "cpustats_$TM";
	die "CANNOT READ THE CACHED INTERRUPTS FILE $cachefile\n\n" unless -r $cachefile;
	open(FROMCACHED, "<$cachefile");
}

sub usage { print $USAGE; print $BRIEF; exit; }

my $CPUCNT;

my $ST = [];

my $A = [];
if ($FROMCACHED) { getstatspec($TM,$ST); } else { getstat1st($ST); }

if ($INFO) {
print "  SYSTEM TICKS $ticks\n\n";
print "  CPU COUNT $CPUCNT\n\n";
print "  STAT MNEMONICS: @$ST\n\n";
for (my $i=0; $i<@$ST; $i++) {
	printf "%13s    %s\n",$ST->[$i],$STLONG->{$ST->[$i]};
}
print "\n  THE CACHEDIR IS $CACHEDIR\n";
exit;
}

$transpose //= 0;
$iter //= 1;
$delay //= 0;

my $DAA = $iter > 1 ? 1 : 0;

if (defined $stats) {
	my $STx = join '|', @$ST;
	foreach (split(/\s*\,\s*/,$stats)) {
		die "UNKNOW STAT SPECIFIED: $_\n  MUST BE ONE OF:@$ST\n\n" unless $_ =~/^($STx)$/i;
	}
}

$NVU //= $CPUCNT;
$stats //= join '|', @$ST;
my $sMne = join '|', split(/\s*\,\s*/,$stats);

my @III;
my @IIINAME;
if ($keeporder) {
	for (my $i=0;$i<@$ST;$i++) {
		my $t = $ST->[$i];
		if ($t =~ /^($sMne)$/i) { push(@III,$i); push(@IIINAME,$ST->[$i]); }
	}
}
else {
	@IIINAME = split(/\|/,$sMne);
	foreach my $n (@IIINAME) {
		for (my $i=0;$i<@$ST;$i++) {
			my $t = $ST->[$i];
			if ($t =~ /^$n$/i) { push(@III,$i); }
		}
	}
}

my $ONCE = 1;

loopstat(
	{
		iter=>$iter, # 4,
		delay=>$delay, # 1,
		transpose=>$transpose, # 0,
		headonce=>$headonce,
		NVU=>$NVU,
		prune=>$prune,
		sortby => $sortby,
		showtime=>$showtime,
		epotime=>$epotime,
	}
);

if ($CACHED) {
	my $cachefile = "$CACHEDIR/" . "cpustats_" . $TM;
	print "INTERRUPTS CACHED IN $cachefile\n  YOU CAN REPLAY THE INTERRUPTS COLLECTION BY ADDING THE OPTION -replay $TM\n\n";
}

sub odl_cpus {
my($select,$C,$ST,$CPUs) = @_;
my %odl;
	foreach (my $i=0; $i <=$#{$C}; $i++) {
		my $t =  $ST->[$i];
		next unless $t =~ /^($select)$/i;
		map{ $odl{$_} = $C->[$i]->[$_] } (0..$#{$C->[0]});
	}
	foreach my $cpuid (reverse sort {$odl{$a}<=>$odl{$b}} (keys %odl)) {
		push(@$CPUs,$cpuid);
	}
}

sub loopstat {
my $args = shift;
my ($iter,$delay,$transpose,$headonce,$NVU,$prune,$sortby,$showtime,$epotime) =
		map{$args->{$_}}qw(iter delay transpose headonce NVU prune sortby showtime epotime);

my $cpusumL = 0;

$headonce //= 0;
my $sorted = defined $sortby ? 1 : 0;

my $A = [];
if ($FROMCACHED) { fromcached($A); } else { getstat($A); }

	foreach (my $i=0; $i <= $#{$A}; $i++) {
		foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
			$cpusumL += $A->[$i]->[$j];
		}
	}

my $C = [];

my ($fromJ,$toJ) = (9000000,0);
{
	my ($copruLT,$copruRT) = (99999999,0);
	if ((defined $prune) && ($prune =~ /(\d+),(\d+)/)) {
		my($ij1,$ij2) = ($1,$2);
		$copruLT = $ij1-1; # $mcST = $j1;
		$copruRT = $ij2-1; # $mcED = $j2;
	}
	my ($mcST,$mcED) = (0,$CPUCNT-1);
	($fromJ,$toJ) = ($mcST+$copruLT+1, $mcED-$copruRT); # columns skipper
}

my ($fromI,$toI) = (9000000,0); # from row to row, i is rowno
{
	my ($ropruTP,$ropruBT) = (99999999,0);
	if ((defined $prune) && ($prune =~ /(\d+),(\d+)/)) {
		my($ij1,$ij2) = ($1,$2);
		$ropruTP = $ij1-1; # $mrST = $i1;
		$ropruBT = $ij2-1; # $mrED = $i2;
	}
	my ($mrST,$mrED) = (0,$CPUCNT-1);
	($fromI,$toI) = ($mrST+$ropruTP+1, $mrED-$ropruBT); # rows skipper
}

my $HDR;
		{ # sorted or not sorted will always rebuild the header
my $CPUs = [];
$CPUs = [0..$CPUCNT-1];
my @CCC = @$CPUs[0..$NVU-1];
			my $tt = sprintf "%19s  %9s ", " ", " ";
			my $once = 1;
			my $cnt = -1;
			foreach my $j (@CCC) {
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
				if ($once && ($cnt >= $fromJ)) { $once = 0; $tt .= sprintf("%9s ","..."); }
				$tt .= sprintf("%9s ","CPU$j");
			}
$tt .= sprintf("%9s ","summed");
$HDR = $tt;
		}
my $HDRt;
{
				my $tt = sprintf "%19s  %9s ", " ", " ";
				$tt .= sprintf("%9s ","$_") foreach (@IIINAME);
		$HDRt = $tt;
}
for (my $it=1;$it<=$iter;$it++) {
	if ($it == 1) { sleep $DAA; }
my $cpusum = 0;
	my $B = [];
	my $dummy = 0;
	my $tIm = \$dummy;
	if ($FROMCACHED) { fromcached($B,$tIm); } else { getstat($B,$tIm); }

	foreach (my $i=0; $i <= $#{$B}; $i++) {
		foreach (my $j=0; $j <= $#{$B->[0]}; $j++) {
			$cpusum += $B->[$i]->[$j];
		}
	}
	foreach (my $i=0; $i <= $#{$A}; $i++) {
		foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
			$C->[$i]->[$j] = $B->[$i]->[$j] - $A->[$i]->[$j];
		}
	}

	my $CPUs = [];
	if ($sorted) {
		odl_cpus($sortby, $C,$ST,$CPUs);
	}
	else {
		$CPUs = [0..$CPUCNT-1];
	}
	my @CCC = @$CPUs[0..$NVU-1];

	if (!$transpose) {
		if ($headonce && !$sorted) {
			if ($ONCE) { $ONCE = 0; print "$HDR\n"; }
		}
		elsif (!$sorted) {
			print "$HDR\n";
		}
		else { # sorted or not sorted will always rebuild the header
			my $tt = sprintf "%19s  %9s ", " ", " ";
			my $once = 1;
			my $cnt = -1;
			foreach my $j (@CCC) {
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
				if ($once && ($cnt >= $fromJ)) { $once = 0; $tt .= sprintf("%9s ","..."); }
				$tt .= sprintf("%9s ","CPU$j");
			}
$tt .= sprintf("%9s ","summed");
print "$tt\n";
		}
		foreach my $i (@III) {
			my $s = "";
			my $tm = $showtime ? iso_time($$tIm) : $epotime ? $$tIm : " ";
			$s = sprintf "%19s  %9s ", $tm, $ST->[$i];
	my $tot = 0;
			my $once = 1;
			my $cnt = -1;
			foreach my $cpuid (@CCC) {
$tot += $C->[$i]->[$cpuid];
				$cnt++;
				next if (($cnt >= $fromJ) && ($cnt < $toJ));
				if ($once && ($cnt >= $fromJ)) { $once = 0; $s .= sprintf("%9s ","..."); }
				$s .= sprintf("%9d ",$C->[$i]->[$cpuid]);
			}
			$s .= sprintf("%9d ",$tot); #$s .= sprintf "    %s", $ILONG->[$i];
			print "$s\n";
		}
		foreach (my $i=0; $i <=$#{$A}; $i++) {
			foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
				$A->[$i]->[$j] = $B->[$i]->[$j];
			}
		}
my $used = 0;
my $idle = 0;
foreach (my $i=0; $i <=$#{$C}; $i++) {
	foreach (my $j=0; $j <= $#{$C->[0]}; $j++) {
		$used += $C->[$i]->[$j];
	}
}
{
	foreach (my $i=3; $i <=4; $i++) { #  idle and iowait
	foreach (my $j=0; $j <= $#{$C->[0]}; $j++) {
		$idle += $C->[$i]->[$j];
	}
	}
}
$used -= $idle;
my $cpudelta = $cpusum - $cpusumL;
$cpusumL = $cpusum;
my $usage = sprintf "%6.2f", $used / ($cpudelta) * 100;

print "used=$used  idle=$idle  usage $usage%\n";
sleep $delay;

	} # !transpose

	else {
		my $Ct = [];
		push @$Ct, [] foreach @{$C->[0]};
		my $m;
		foreach my $row (@{$C}) { $m = 0; push(@{$Ct->[$m++]}, $_) foreach @{$row}; }

if ($headonce) {
if ($ONCE) { $ONCE = 0; print "$HDRt\n"; }
}
else {
print "$HDRt\n";
}

		my $tot = [];
		foreach (my $j=0; $j <= $#{$Ct->[0]}; $j++) { my $tot->[$j] = 0; }

		foreach my $cpuid (@CCC) {
			foreach my $j (@III) {
				$tot->[$j] += $Ct->[$cpuid]->[$j];
			}
		}
		my $once = 1;
		my $cnt = -1;

		foreach my $cpuid (@CCC) {
			$cnt++;
			next if (($cnt >= $fromI) && ($cnt < $toI));
			my $s = "";
			if (($once) && ($cnt >= $fromI)) { $once = 0; $s .= sprintf("%19s  %9s\n"," ","  ..."); }
			my $tm = $showtime ? iso_time($$tIm) : $epotime ? $$tIm : " ";
			$s .= sprintf "%19s  %9s ", $tm, "CPU$cpuid";
			foreach my $j (@III) {
				my $t =  $ST->[$j];
				$s .= sprintf("%9d ",$Ct->[$cpuid]->[$j]);
			}
			print "$s\n";
		}
		my $s = "";
		$s = sprintf "%19s  %9s ", " ", "summed";
		foreach my $j (@III) {
			my $t =  $ST->[$j];
			$s .= sprintf("%9d ",$tot->[$j]);
		}
		print "$s\n";

		foreach (my $i=0; $i <=$#{$A}; $i++) {
			foreach (my $j=0; $j <= $#{$A->[0]}; $j++) {
				$A->[$i]->[$j] = $B->[$i]->[$j];
			}
		}
my $used = 0;
my $idle = 0;
foreach (my $i=0; $i <= $#{$Ct}; $i++) {
	foreach (my $j=0; $j <= $#{$Ct->[0]}; $j++) {
		$used += $Ct->[$i]->[$j];
	}
}
{
	foreach (my $i=0; $i <= $#{$Ct}; $i++) {
		my $j = 3;
		$idle += $Ct->[$i]->[$j];
	}
}

$used -= $idle;
my $cpudelta = $cpusum - $cpusumL;
$cpusumL = $cpusum;

my $usage = ($idle < 0.00001) ? 'MAXED WITH NO idle TIME'  : $used / $cpudelta * 100;

print "used=$used  idle=$idle  usage $usage%\n";
sleep $delay; # $delay;

	} # else $tranpose
} # end iter

} # end sub
sub getcpustat {
	open(F,'cat /proc/stat|') || die "CANNOT READ /proc/stat\n";
	my $l1 = (<F>);	
	chomp $l1;
	close F;

	return (split/\s+/,$l1);
}

sub getstat2 {
my ($A) = @_;
	open(F,'cat /proc/stat|') || die "CANNOT READ /proc/stat\n";
	while (my $l = <F>) {
		next unless $l =~ /^cpu\d+/;
		chomp $l;
		my @p = split(/\s+/,$l);
		shift @p;
		push(@$A,\@p);
	}
	close F;
}

sub getstat {
my ($Atr,$tm) = @_;
my $A = [];
	open(F,'cat /proc/stat|') || die "CANNOT READ /proc/stat\n";
	while (my $l = <F>) {
		next unless $l =~ /^cpu\d+/;
		chomp $l;
		my @p = split(/\s+/,$l);
		shift @p;
		push(@$A,\@p);
	}
	push @$Atr, [] foreach @{$A->[0]};
	my $m;
foreach my $row (@{$A}) {
$m = 0;
push(@{$Atr->[$m++]}, $_) foreach @{$row};
}
	close F;

$$tm = time;
if ($CACHED) {
	foreach (my $i=0; $i <$#{$Atr}; $i++) {
		print OUT "@{$Atr->[$i]}";
		print OUT "\n";
	}
	print OUT "-- $$tm\n";
}

}

sub getstat1st {
my ($ST) = @_;
my @ST = qw(user nice system idle iowait irq softirq steal guest guestnice);
	my $cpucount = 0;	
	my $statcount = 0;	
	open(F,'cat /proc/stat|') || die "CANNOT READ /proc/stat\n";
	while (my $l = <F>) {
		next unless $l =~ /^cpu\d+/;
		$cpucount++;
		chomp $l;
		my @p = split(/\s+/,$l);
		shift @p;
		$statcount = scalar @p;	
	}
	close F;
	$CPUCNT = $cpucount;
	map{$ST->[$_]=$ST[$_]}(0..$statcount-1);
}

sub getstatspec {
my ($time,$ST) = @_;
my $cachefile = "$CACHEDIR/" . "cpustats_spec_$time";
my @ST = qw(user nice system idle iowait irq softirq steal guest guestnice);
	my $cpucount = 0;
	my $statcount = 0;
	open(F,"cat $cachefile|") || die "CANNOT READ $cachefile\n"; 
	while (my $l = <F>) {
		next unless $l =~ /^cpu\d+/;
		$cpucount++;
		chomp $l;
		my @p = split(/\s+/,$l);
		shift @p;
		$statcount = scalar @p;	
	}
	close F;
	$CPUCNT = $cpucount;
	map{$ST->[$_]=$ST[$_]}(0..$statcount-1);
}

sub cachestatspec {
my $time = shift;
my $cachefile = "$CACHEDIR/" . "cpustats_spec_$time";

open(F,"cat /proc/stat|");
open(OUT2, ">$cachefile");
while (my $l = <F>) {
	print OUT2 $l;
}
close F;
close OUT2;
}
sub fromcached {
my ($A,$tm) = @_;

{
	my $l;
	while (($l = <FROMCACHED>) && ($l !~ /^\-\-\s+(\d+)\s*$/)) {
		push(@$A,[split(/\s+/,$l)]);
	}
	unless (($$tm) = ($l =~ m/^\-\-\s+(\d+)\s*$/)) {
		print "DONE WITH REPLAY\n";
		exit;
	}
}
# if0block deleted
}

sub iso_time {
my $tm = @_ ? shift : time;
my($sec,$min,$hours,$mday,$mon,$year) = localtime($tm);
my $date = ($year + 1900).'-'._dd($mon + 1).'-'._dd($mday);
my $time = _dd($hours).':'._dd($min).':'._dd($sec);
return "$date $time";
}

sub _dd { my $_dd = $_[0]; $_dd = '0'.$_dd if( length($_dd) == 1 ); return $_dd; }

