
sub add_process {
    my $procs = shift;
    my $cmds = shift;
    my $pid = shift;
    my $start = shift;
    my $exe = shift;
    my $parent = shift || -1;

    $procs->{$pid} = {
	start => $start,
	end => -1,
	parent => $parent,
	cmd => [$exe],
	sys => 0,
	user => 0,
	read => {},
	write => {},
	kids => {},
    };
    $cmds->{$exe} = {} unless $cmds->{$exe};
    $cmds->{$exe}->{$kid} = 1;
}

sub parse_data {
    my $procs = shift;
    my $cmds = shift;

    my $lasttime = 0;
    while (<STDIN>) {
	if (/^(\d+)\s+(\d+)\s+\((.*?)\)\s+(.*)\s+$/) {
	    my $time = $1;
	    my $pid = $2;
	    my $exe = $3;
	    my $op = $4;
	    $lasttime = $time;

	    if (! exists $procs->{$pid}) {
		&add_process($procs, $cmds, $pid, $time, $exe);
	    }

	    if ($op =~ /exit\suser:\s(\d+)\ssys:\s(\d+)/) {
		my $user = $1;
		my $sys = $2;
		$procs->{$pid}->{end} = $time;
		$procs->{$pid}->{sys} = $sys;
		$procs->{$pid}->{user} = $user;

		# hwclock warps system time, so adjust, pretending it takes
		# no time to execute
		if ($exe =~ /hwclock/) {
		    my $duration = $procs->{$pid}->{end} - $procs->{$pid}->{start};
		    my $tmp = $procs->{$pid};
		    $tmp->{start} += $duration;
		    while ($tmp->{parent}) {
			$tmp = $procs->{$tmp->{parent}};
			$tmp->{start} += $duration;
			if ($tmp->{end}) {
			    $tmp->{end} += $duration;
			}
		    }
		}
	    } elsif ($op =~ /fork\s(\d+)/) {
		my $kid = $1;

		&add_process($procs, $cmds, $kid, $time, $exe, $pid);
		$procs->{$pid}->{kids}->{$kid} = 1;
	    } elsif ($op =~ /exec\s(.*)/) {
		my $path = $1;
		my $bin = (splitpath($path))[2];
		push @{$procs->{$pid}->{cmd}}, $bin;
		$cmds->{$bin} = {} unless $cmds->{$bin};
		$cmds->{$bin}->{$pid} = 1;
	    } elsif ($op =~ /access\s(.*?)\sread:\s(\d+)\swrite:\s(\d+)/) {
		my $file = $1;
		my $read = $2;
		my $write = $3;
		if ($read) {
		    $procs->{$pid}->{read}->{$file} = []
			unless $procs->{$pid}->{read}->{$file};
		    push @{$procs->{$pid}->{read}->{$file}}, {
			time => $time,
			bytes => $read,
		    };
		}
		if ($write) {
		    $procs->{$pid}->{write}->{$file} = []
			unless $procs->{$pid}->{write}->{$file};
		    push @{$procs->{$pid}->{write}->{$file}}, {
			time => $time,
			bytes => $write,
		    };
		}
	    } else {
		print "cannot grok '$_'\n";
	    }
	}
    }

    return $lasttime;
}


sub accumulate_stats {
    my $procs = shift;
    my $stats = shift;

    foreach my $pid (keys %{$procs}) {
	if ($procs->{$pid}->{parent} == -1) {
	    $stats->{$pid} = {};
	    &do_accumulate_stats($procs, $stats->{$pid}, $pid);
	}
    }
}

sub do_accumulate_stats {
    my $procs = shift;
    my $stats = shift;
    my $pid = shift;
    
    my $proc = $procs->{$pid};

    my $nread = 0;
    my $nwrite = 0;
    foreach my $file (keys %{$proc->{read}}) {
	$nread += $proc->{read}->{$file};
    }
    foreach my $file (keys %{$proc->{write}}) {
	$nwrite += $proc->{write}->{$file};
    }

    my $cnread = $nread;
    my $cnwrite = $nwrite;
    my $csys = $proc->{sys};
    my $cuser = $proc->{user};
    
    foreach my $kid (keys %{$proc->{kids}}) {
	&do_accumulate_stats($procs, $stats, $kid);
	
	my $cstats = $stats->{$kid};
	
	$cnread+= $cstats->[0];
	$cnwrite+= $cstats->[1];
	$csys+= $cstats->[2];
	$cuser+= $cstats->[3];
    }
    
    $stats->{$pid} = [
		    $nread,
		    $nwrite,
		    $proc->{sys},
		    $proc->{user},
		    $cnread,
		    $cnwrite,
		    $csys,
		    $cuser
		    ];
}

