#!/usr/bin/perl
#
# Copyright (c) 2010 Ken McDonell.  All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# Assumptions:
# 	Output from sadf is completely deterministic, with the 
# 	number and order of tags identical for each sample interval.
#
#	Since we don't know what sort of system the data came from
#	there is no point trying to match the PMIDs with those from
#	any particular OS PMDA.  For the names we'll use the common
#	names where possible, else the Linux names (as the most likely
#	case).
#

use strict;
use warnings;

use XML::TokeParser;
use Date::Parse;
use Date::Format;
use PCP::LogImport;

my $sample = 0;
my $stamp;
my $zone = "UTC";	# timestamps from sadf are in UTC
my $parser;
my @handle = ();	# pmi* handles, one per metric-instance pair
my $h;			# index into handle[]
my $putsts = 0;		# pmiPutValue() errors are only checked @ end of loop
my $seen_disk_dev = 0;	# one trip guard to define disk.dev.* metrics
my $seen_net_iface = 0;	# one trip guard to define network.interface.* metrics
my $in_cpu_load = 0;
my %instmap = ();
my $snarf_text = 0;
my $save_text;
my $disk_all_total_bytes;

sub dodate($)
{
    # convert datetime format YYYY-MM-DD from sadf into the format
    # DD-Mmm-YYYY that Date::Parse seems to be able to parse correctly
    #
    my ($datetime) = @_;
    my @fields = split(/-/, $datetime);
    my $mm;
    my %mm_map = (
	"01" => "Jan", "02" => "Feb", "03" => "Mar", "04" => "Apr",
	"05" => "May", "06" => "Jun", "07" => "Jul", "08" => "Aug",
	"09" => "Sep", "10" => "Oct", "11" => "Nov", "12" => "Dec",
    );
    $#fields == 2 or die "dodate: bad datetime format: \"$datetime\"\n";
    $mm = $fields[1];
    if ($mm < 10 && $mm !~ /^0/) { $mm = "0" . $mm };	# add leading zero
    defined($mm_map{$mm}) or die "dodate: bad month in datetime: \"$datetime\"\n";
    return $fields[2] . "-" . $mm_map{$mm} . "-" . $fields[0];
}

# Handle metrics with the a singular value, calling pmiAddMetric() and
# pmiGetHandle()
#
sub def_single($)
{
    my ($name) = @_;
    my $sts;
    my $units = pmiUnits(0,0,0,0,0,0);
    my $type = PM_TYPE_FLOAT;
    if ($name eq "kernel.all.pswitch" || $name eq "kernel.all.intr" ||
        $name eq "swap.pagesin" || $name eq "swap.pagesout" ||
	$name eq "mem.vmstat.pgpgin" || $name eq "mem.vmstat.pgpgout" ||
	$name eq "mem.vmstat.pgfault" || $name eq "mem.vmstat.pgmajfault" ||
	$name eq "mem.vmstat.pgfree" || $name eq "disk.all.total" ||
	$name eq "disk.all.read" || $name eq "disk.all.write") {
	$units = pmiUnits(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE);
    }
    elsif ($name =~ /^mem\.util\./) {
	$units = pmiUnits(1,0,0,PM_SPACE_KBYTE,0,0);
	$type = PM_TYPE_U64;
    }
    elsif ($name =~ /disk\.all\..*bytes/) {
	$units = pmiUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0);
    }
    elsif ($name =~ /^vfs\./) {
	$type = PM_TYPE_U32;
    }
    pmiAddMetric($name, PM_ID_NULL, $type, PM_INDOM_NULL, PM_SEM_INSTANT, $units) == 0
	or die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n";
    $sts = pmiGetHandle($name, "");
    $sts >= 0 or die "pmiGetHandle($name, ...): " . pmiErrStr($sts) . "\n";
    push(@handle, $sts);
}

# Handle metrics with multiple values, calling pmiAddMetric().
# Defer to pmiGetHandle() to def_metric_inst().
#
sub def_multi($$)
{
    my ($name,$indom) = @_;
    my $units = pmiUnits(0,0,0,0,0,0);
    my $type = PM_TYPE_FLOAT;
    if ($name eq "proc.runq.runnable") {
	$units = pmiUnits(0,0,1,0,0,PM_COUNT_ONE);
	$type = PM_TYPE_U32;
    }
    elsif ($name =~ /disk\.dev\..*bytes/ ||
	   $name =~ /network\.interface\..*\.bytes/) {
	$units = pmiUnits(1,-1,0,PM_SPACE_KBYTE,PM_TIME_SEC,0);
    }
    elsif ($name eq "disk.dev.total" || $name eq "disk.dev.read" ||
           $name eq "disk.dev.write" || $name =~ /network\.interface\./) {
	$units = pmiUnits(0,-1,1,0,PM_TIME_SEC,PM_COUNT_ONE);
    }
    pmiAddMetric($name, PM_ID_NULL, $type, $indom, PM_SEM_INSTANT, $units) == 0
	or die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n";
}

# Deal with metric-instance pairs.
# If first time this instance has been seen for this indom, add it to
# the instance domain.
# Get a handle and add it to handle[].
#
sub def_metric_inst($$$)
{
    my ($name,$indom,$instance) = @_;
    my $sts;
    # instmap{} holds the last allocated inst number with $indom as the
    # key, and marks the instance as known with $indom . $instance as the
    # key
    if (!defined($instmap{$indom . $instance})) {
	my $inst;
	if (defined($instmap{$indom})) {
	    $instmap{$indom}++;
	    $inst = $instmap{$indom};
	}
	else {
	    $instmap{$indom} = 0;
	    $inst = 0;
	}
	pmiAddInstance($indom, $instance, $inst) >= 0
	    or die "pmiAddInstance([$name], $instance, $inst): " . pmiErrStr(-1) . "\n";
	$instmap{$indom . $instance} = $inst;
    }
    $sts = pmiGetHandle($name, $instance);
    $sts >= 0 or die "pmiGetHandle($name, $instance): " . pmiErrStr($sts) . "\n";
    push(@handle, $sts);
}

# wrapper for pmiPutValueHandle()
#
sub put($)
{
    my ($value) = @_;
    my $sts;
    if (!defined($handle[$h])) {
	pmiDump();
	die <<EOF
put($value): No handle[] entry for index $h.
Check Handles in dump above.
EOF
    }
    $sts = pmiPutValueHandle($handle[$h], $value);
    if ($sts < 0 && $putsts == 0) { $putsts = $sts };
    $h++;
}

if ($#ARGV != 1) {
    print "Usage: sar2pcp infile outfile\n";
    exit(1);
}

my $pid = open(PIPEIN, '-|', "sadf -x $ARGV[0] -- -A");
if (!defined($pid)) {
    print "sar2pcp: Failed to launch sadf to convert \"$ARGV[0]\" to XML\n";
    exit(1);
}

$parser = XML::TokeParser->new(\*PIPEIN, Noempty => 1);
if (!defined($parser)) {
    print "sar2pcp: Failed to open input stream for \"$ARGV[0]\"\n";
    exit(1);
}

pmiStart($ARGV[1], 0);

while (defined(my $token = $parser->get_token())) {
    if ($token->is_start_tag) {
	if ($token->tag eq "timestamp") {
	    $stamp = dodate($token->attr->{'date'}) . " " . $token->attr->{'time'};
	    $sample++;
	    $h = 0;
	    $putsts = 0;
	}
	elsif ($token->tag eq "cpu-load" || $token->tag eq "cpu-load-all") {
	    $in_cpu_load = 1;
	}
	elsif ($token->tag eq "cpu" && $in_cpu_load) {
	    # <cpu number="all" usr="2.00" nice="0.00" sys="1.20"
	    #  iowait="0.00" steal="0.00" irq="0.00" soft="0.00"
	    #  guest="0.00" idle="96.79"/>
	    # <cpu number="0" usr="2.00" .../>
	    if ($token->attr->{'number'} eq "all") {
		if ($sample == 1) {
		    def_single("kernel.all.cpu.user");
		    def_single("kernel.all.cpu.nice");
		    def_single("kernel.all.cpu.sys");
		    def_single("kernel.all.cpu.wait.total");
		    def_single("kernel.all.cpu.steal");
		    # intr is irq + soft
		    def_single("kernel.all.cpu.intr");
		    # pro tem, skip guest
		    def_single("kernel.all.cpu.idle");
		}
		put($token->attr->{'usr'}/100);
		put($token->attr->{'nice'}/100);
		put($token->attr->{'sys'}/100);
		put($token->attr->{'iowait'}/100);
		put($token->attr->{'steal'}/100);
		put(($token->attr->{'irq'}+$token->attr->{'soft'})/100);
		put($token->attr->{'idle'}/100);
	    }
	    else {
		if ($sample == 1) {
		    my $instance = "cpu" . $token->attr->{'number'};
		    my $indom = pmInDom_build(PMI_DOMAIN, 0);
		    if ($token->attr->{'number'} eq "0") {
			def_multi("kernel.percpu.cpu.user", $indom);
			def_multi("kernel.percpu.cpu.nice", $indom);
			def_multi("kernel.percpu.cpu.sys", $indom);
			def_multi("kernel.percpu.cpu.wait.total", $indom);
			def_multi("kernel.percpu.cpu.steal", $indom);
			# intr is irq + soft
			def_multi("kernel.percpu.cpu.intr", $indom);
			def_multi("kernel.percpu.cpu.idle", $indom);
		    }
		    def_metric_inst("kernel.percpu.cpu.user", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.nice", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.sys", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.wait.total", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.steal", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.intr", $indom, $instance);
		    def_metric_inst("kernel.percpu.cpu.idle", $indom, $instance);
		}
		put($token->attr->{'usr'}/100);
		put($token->attr->{'nice'}/100);
		put($token->attr->{'sys'}/100);
		put($token->attr->{'iowait'}/100);
		put($token->attr->{'steal'}/100);
		put(($token->attr->{'irq'}+$token->attr->{'soft'})/100);
		put($token->attr->{'idle'}/100);
	    }
	}
	elsif ($token->tag eq "process-and-context-switch") {
	    # <process-and-context-switch per="second" proc="0.00"
	    #  cswch="652.10"/>
	    if ($sample == 1) {
		def_single("kernel.all.pswitch");
		# pro tem skip cswch
	    }
	    put($token->attr->{'proc'});
	}
	elsif ($token->tag eq "irq") {
	    # <irq intr="sum" value="230.46"/>
	    if ($token->attr->{'intr'} eq "sum") {
		if ($sample == 1) {
		    def_single("kernel.all.intr");
		}
		put($token->attr->{'value'});
	    }
	    # skip all other intr counts
	}
	elsif ($token->tag eq "swap-pages") {
	    # <swap-pages per="second" pswpin="0.00" pswpout="0.00"/>
	    if ($sample == 1) {
		def_single("swap.pagesin");
		def_single("swap.pagesout");
	    }
	    put($token->attr->{'pswpin'});
	    put($token->attr->{'pswpout'});
	}
	elsif ($token->tag eq "paging") {
	    # <paging per="second" pgpgin="0.00" pgpgout="8.82" fault="49.50"
	    #  majflt="0.00" pgfree="94.19" pgscank="0.00" pgscand="0.00"
	    #  pgsteal="0.00" vmeff-percent="0.00"/>
	    if ($sample == 1) {
		def_single("mem.vmstat.pgpgin");
		def_single("mem.vmstat.pgpgout");
		def_single("mem.vmstat.pgfault");
		def_single("mem.vmstat.pgmajfault");
		def_single("mem.vmstat.pgfree");
		# pro tem skip pgscank, pgscand, pgsteal and vmeff-percent
	    }
	    put($token->attr->{'pgpgin'});
	    put($token->attr->{'pgpgout'});
	    put($token->attr->{'fault'});
	    put($token->attr->{'majflt'});
	    put($token->attr->{'pgfree'});
	}
	elsif ($token->tag eq "io") {
	    # <io per="second">
	    # no data, set up metrics the first time
	    if ($sample == 1) {
		def_single("disk.all.total");
		def_single("disk.all.read");
		def_single("disk.all.read_bytes");
		def_single("disk.all.write");
		def_single("disk.all.write_bytes");
		def_single("disk.all.total_bytes");
	    }
	}
	elsif ($token->tag eq "tps") {
	    # <tps>0.80</tps>
	    $snarf_text = 1;
	}
	elsif ($token->tag eq "io-reads") {
	    # <io-reads rtps="0.00" bread="0.00"/>
	    # assume blocks are 512 bytes
	    put($token->attr->{'rtps'});
	    put($token->attr->{'bread'}/2);
	    $disk_all_total_bytes = $token->attr->{'bread'}/2;
	}
	elsif ($token->tag eq "io-writes") {
	    # <io-writes wtps="0.80" bwrtn="17.64"/>
	    # assume blocks are 512 bytes
	    put($token->attr->{'wtps'});
	    put($token->attr->{'bwrtn'}/2);
	    $disk_all_total_bytes += $token->attr->{'bwrtn'}/2;
	    put($disk_all_total_bytes);
	}
	elsif ($token->tag eq "memfree") {
	    # <memfree>78896</memfree>
	    if ($sample == 1) {
		def_single("mem.util.free");
	    }
	    $snarf_text = 1;
	}
	elsif ($token->tag eq "memused") {
	    # <memused>947232</memused>
	    if ($sample == 1) {
		def_single("mem.util.used");
	    }
	    $snarf_text = 1;
	}
	elsif ($token->tag eq "buffers") {
	    # <buffers>165296</buffers>`
	    if ($sample == 1) {
		def_single("mem.util.bufmem");
	    }
	    $snarf_text = 1;
	}
	elsif ($token->tag eq "cached") {
	    # <cached>368644</cached>
	    if ($sample == 1) {
		def_single("mem.util.cached");
	    }
	    $snarf_text = 1;
	}
	# pro tem skip <commit>
	elsif ($token->tag eq "swpfree") {
	    # <swpfree>1920808</swpfree>
	    if ($sample == 1) {
		def_single("mem.util.swapFree");
	    }
	    $snarf_text = 1;
	}
	# pro tem skip <swpused>
	elsif ($token->tag eq "swpcad") {
	    # <swpcad>19208</swpcad>
	    if ($sample == 1) {
		def_single("mem.util.swapCached");
	    }
	    $snarf_text = 1;
	}
	# pro tem skip <frmpg>, <bufpg> and <campg>
	elsif ($token->tag eq "kernel") {
	    # <kernel dentunusd="86251" file-nr="9696" inode-nr="86841"
	    #  pty-nr="123"/>
	    if ($sample == 1) {
		def_single("vfs.dentry.count");
		def_single("vfs.files.count");
		def_single("vfs.inodes.count");
		# pro tem skip pty-nr
	    }
	    put($token->attr->{'dentunusd'});
	    put($token->attr->{'file-nr'});
	    put($token->attr->{'inode-nr'});
	}
	elsif ($token->tag eq "queue") {
	    if ($sample == 1) {
		my $indom = pmInDom_build(PMI_DOMAIN, 1);
		def_single("proc.runq.runnable");
		# pro tem skip plist-sz
		def_multi("kernel.all.load", $indom);
		def_metric_inst("kernel.all.load", $indom, "1 minute");
		def_metric_inst("kernel.all.load", $indom, "5 minute");
		def_metric_inst("kernel.all.load", $indom, "15 minute");
	    }
	    put($token->attr->{'runq-sz'});
	    put($token->attr->{'ldavg-1'});
	    put($token->attr->{'ldavg-5'});
	    put($token->attr->{'ldavg-15'});
	}
	elsif ($token->tag eq "disk-device") {
	    # <disk-device dev="dev8-0" tps="0.00" rd_sec="0.00" wr_sec="0.00"
	    #  avgrq-sz="0.00" avgqu-sz="0.00" await="0.00" svctm="0.00"
	    #  util-percent="0.00"/>
	    if ($sample == 1) {
		my $instance = "disk" . $token->attr->{'dev'};
		my $indom = pmInDom_build(PMI_DOMAIN, 2);
		if ($seen_disk_dev == 0) {
		    def_multi("disk.dev.total", $indom);
		    def_multi("disk.dev.read_bytes", $indom);
		    def_multi("disk.dev.write_bytes", $indom);
		    def_multi("disk.dev.total_bytes", $indom);
		    # pro tem skip avgrq-sz, avgqu-sz, await, svctm
		    def_multi("disk.dev.avactive", $indom);
		    $seen_disk_dev = 1;
		}
		def_metric_inst("disk.dev.total", $indom, $instance);
		def_metric_inst("disk.dev.read_bytes", $indom, $instance);
		def_metric_inst("disk.dev.write_bytes", $indom, $instance);
		def_metric_inst("disk.dev.total_bytes", $indom, $instance);
		def_metric_inst("disk.dev.avactive", $indom, $instance);
	    }
	    put($token->attr->{'tps'});
	    put($token->attr->{'rd_sec'}/2);	# 512-byte sectors/sec
	    put($token->attr->{'wr_sec'}/2);	# 512-byte sectors/sec
	    put($token->attr->{'rd_sec'}/2 + $token->attr->{'wr_sec'}/2);
	    put($token->attr->{'util-percent'}/100);
	    # TODO disk.all aggregation?
	}
	elsif ($token->tag eq "net-dev") {
	    # <net-dev iface="eth0" rxpck="0.00" txpck="0.00" rxkB="0.00"
	    #  txkB="0.00" rxcmp="0.00" txcmp="0.00" rxmcst="0.00"/>
	    my $instance = $token->attr->{'iface'};
	    if ($instance eq 'lo' || $instance =~ /tun[0-9]/) {
		# some network interfaces are not interesting
		next;
	    }
	    if ($sample == 1) {
		my $indom = pmInDom_build(PMI_DOMAIN, 4);
		if ($seen_net_iface == 0) {
		    def_multi("network.interface.in.packets", $indom);
		    def_multi("network.interface.out.packets", $indom);
		    def_multi("network.interface.in.bytes", $indom);
		    def_multi("network.interface.out.bytes", $indom);
		    # pro tem skip rxcmp, txcmp, rxmcst
		    $seen_net_iface = 1;
		}
		def_metric_inst("network.interface.in.packets", $indom, $instance);
		def_metric_inst("network.interface.out.packets", $indom, $instance);
		def_metric_inst("network.interface.in.bytes", $indom, $instance);
		def_metric_inst("network.interface.out.bytes", $indom, $instance);
	    }
	    put($token->attr->{'rxpck'});
	    put($token->attr->{'txpck'});
	    put($token->attr->{'rxkB'});
	    put($token->attr->{'txkB'});
	}
	elsif ($token->tag eq "net-edev") {
	    # <net-edev iface="eth0" rxerr="0.00" txerr="0.00" coll="0.00"
	    #  rxdrop="0.00" txdrop="0.00" txcarr="0.00" rxfram="0.00"
	    #  rxfifo="0.00" txfifo="0.00"/>
	    my $instance = $token->attr->{'iface'};
	    if ($instance eq 'lo' || $instance =~ /tun[0-9]/) {
		# some network interfaces are not interesting
		next;
	    }
	    if ($sample == 1) {
		my $indom = pmInDom_build(PMI_DOMAIN, 4);
		if ($seen_net_iface == 1) {
		    def_multi("network.interface.in.errors", $indom);
		    def_multi("network.interface.out.errors", $indom);
		    def_multi("network.interface.collisions", $indom);
		    def_multi("network.interface.in.drops", $indom);
		    def_multi("network.interface.out.drops", $indom);
		    def_multi("network.interface.out.carrier", $indom);
		    def_multi("network.interface.in.frame", $indom);
		    def_multi("network.interface.in.fifo", $indom);
		    def_multi("network.interface.out.fifo", $indom);
		    $seen_net_iface = 2;
		}
		def_metric_inst("network.interface.in.errors", $indom, $instance);
		def_metric_inst("network.interface.out.errors", $indom, $instance);
		def_metric_inst("network.interface.collisions", $indom, $instance);
		def_metric_inst("network.interface.in.drops", $indom, $instance);
		def_metric_inst("network.interface.out.drops", $indom, $instance);
		def_metric_inst("network.interface.out.carrier", $indom, $instance);
		def_metric_inst("network.interface.in.frame", $indom, $instance);
		def_metric_inst("network.interface.in.fifo", $indom, $instance);
		def_metric_inst("network.interface.out.fifo", $indom, $instance);
	    }
	    put($token->attr->{'rxerr'});
	    put($token->attr->{'txerr'});
	    put($token->attr->{'coll'});
	    put($token->attr->{'rxdrop'});
	    put($token->attr->{'txdrop'});
	    put($token->attr->{'txcarr'});
	    put($token->attr->{'rxfram'});
	    put($token->attr->{'rxfifo'});
	    put($token->attr->{'txfifo'});
	}
	elsif ($token->tag eq "net-nfs" || $token->tag eq "net-nfsd" ||
	       $token->tag eq "net-sock" || $token->tag eq "net-sock6" ||
	       $token->tag eq "net-ip" || $token->tag eq "net-eip" ||
	       $token->tag eq "net-ip6" || $token->tag eq "net-eip6" ||
	       $token->tag eq "net-icmp" || $token->tag eq "net-eicmp" ||
	       $token->tag eq "net-icmp6" || $token->tag eq "net-eicmp6" ||
	       $token->tag eq "net-tcp" || $token->tag eq "net-etcp" ||
	       $token->tag eq "net-udp" || $token->tag eq "net-udp6") {
	    # skip all the network protocol stats
	    next;
	}
	elsif ($token->tag eq "host") {
	    # <host nodename="bozo">
	    pmiSetHostname($token->attr->{'nodename'}) >= 0
		or die "pmiSetHostname($token->attr->{'nodename'}): " . pmiErrStr(-1) . "\n";
	    pmiSetTimezone($zone) >= 0
		or die "pmiSetTimezone($zone): " . pmiErrStr(-1) . "\n";
	}
	elsif ($token->tag eq "number-of-cpus") {
	    # <number-of-cpus>1</number-of-cpus>
	    $snarf_text = 2;
	}
	elsif ($token->tag eq "sysstat" ||
	       $token->tag eq "sysdata-version" ||
	       $token->tag eq "sysname" ||
	       $token->tag eq "release" ||
	       $token->tag eq "machine" ||
	       $token->tag eq "file-date" ||
	       $token->tag eq "statistics" ||
	       $token->tag eq "interrupts" ||
	       $token->tag eq "int-global" ||
	       $token->tag eq "memory" ||
	       $token->tag eq "memused-percent" ||
	       $token->tag eq "commit" ||
	       $token->tag eq "commit-percent" ||
	       $token->tag eq "swpused" ||
	       $token->tag eq "swpused-percent" ||
	       $token->tag eq "swpcad-percent" ||
	       $token->tag eq "frmpg" ||
	       $token->tag eq "bufpg" ||
	       $token->tag eq "campg" ||
	       $token->tag eq "disk" ||
	       $token->tag eq "network" ||
	       $token->tag eq "power-management" ||
	       $token->tag eq "cpu-frequency" ||
	       $token->tag eq "cpu-weighted-frequency" ||
	       $token->tag eq "cpufreq" ||
	       $token->tag eq "cpuwfreq" ||
	       $token->tag eq "cpu" ||
	       $token->tag eq "hugepages" ||
	       $token->tag eq "hugfree" ||
	       $token->tag eq "hugused" ||
	       $token->tag eq "hugused-percent" ||
	       $token->tag eq "active" ||
	       $token->tag eq "inactive") {
	    # ones we know about, but don't care about
	    next;
	}
	else {
	    if ($sample <= 1) {
		print "Warning: XML tag <" . $token->tag . "> not handled\n";
	    }
	}
	$putsts == 0 or die "pmiPutValue: Failed @ $stamp: " . pmiErrStr($putsts) . "\n";
	next;
    }
    elsif ($token->is_end_tag) {
	#debug# print "end: " . $token->tag . "\n";
	if ($token->tag eq "timestamp") {
	    #debug# my ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($stamp, "+1000");
	    #debug# print "stamp: $stamp time: " . str2time($stamp) . " pieces: ss=$ss mm=$mm hh=$hh dd=$day month=$month year=$year";
	    #debug# if (defined($zone)) { print " zone=$zone"; }
	    #debug# print "\n";
	    pmiWrite(str2time($stamp, $zone), 0) >= 0
		or die "pmiWrite: @ $stamp: " . pmiErrStr(-1) . "\n";
	}
	elsif ($token->tag eq "cpu-load" || $token->tag eq "cpu-load-all") {
	    $in_cpu_load = 0;
	}
	elsif ($token->tag eq "number-of-cpus") {
	    # log metric once ... don't create or use handle
	    # value snarfed in $save_text
	    pmiAddMetric("hinv.ncpu", PM_ID_NULL, PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE, pmiUnits(0,0,0,0,0,0)) == 0
		or die "pmiAddMetric(hinv.ncpu, ...): " . pmiErrStr(-1) . "\n";
	    pmiPutValue("hinv.ncpu", "", $save_text) >= 0
		or die "pmiPutValue(hinv.ncpu,,$save_text): " . pmiErrStr(-1) . "\n";
	}
    }
    elsif ($token->is_text) {
	if ($snarf_text == 1) {
	    put($token->text);
	    $snarf_text = 0;
	}
	elsif ($snarf_text == 2) {
	    $save_text = $token->text;
	    $snarf_text = 0;
	}
	else {
	    #debug# print "text: " . $token->text . "\n";
	    ;
	}
	next;
    }
    elsif ($token->is_comment) {
	print "comment: " . $token->text . "\n";
	next;
    }
    elsif ($token->is_pi) {
	print "process instruction: " . $token->target . "\n";
	next;
    }
}

pmiEnd();

=pod

=head1 NAME

sar2pcp - Import sar data and create a PCP archive

=head1 SYNOPSIS

B<sar2pcp> I<infile> I<outfile>

=head1 DESCRIPTION

B<sar2pcp> is intended to read a binary System Activity Reporting
(sar) data file
as created by B<sadc>(1) (I<infile>) and translate this into a Performance
Co-Pilot (PCP) archive with the basename I<outfile>.

The resultant PCP achive may be used with all the PCP client tools
to graph subsets of the data using B<pmchart>(1),
perform data reduction and reporting, filter with
the PCP inference engine B<pmie>(1), etc.

A series of physical files will be created with the prefix I<outfile>.
These are I<outfile>B<.0> (the performance data),
I<outfile>B<.meta> (the metadata that describes the performance data) and
I<outfile>B<.index> (a temporal index to improve efficiency of replay
operations for the archive).  If any of these files exists already,
then B<sar2pcp> will B<not> overwrite them and will exit with an error
message of the form

__pmLogNewFile: "blah.0" already exists, not over-written

B<sar2pcp> is a Perl script that uses the PCP::LogImport Perl wrapper
around the PCP I<libpcp_import>
library, and as such could be used as an example to develop new
tools to import other types of performance data and create PCP archives.

=head1 CAVEAT

B<sar2pcp> requires I<infile> to have been created by the version
of B<sadc>(1) from
L<http://freshmeat.net/projects/sysstat>
which includes the B<sadf>(1) utility
to translate I<infile> into an XML stream;
B<sar2pcp> will automatically run B<sadf>(1) and translate the resultant
XML into a PCP archive.

=head1 SEE ALSO

B<Date::Parse>(3pm),
B<Date::Format>(3pm),
B<LOGIMPORT>(3),
B<PCP::LogImport>(3pm),
B<pmchart>(1),
B<pmie>(1)
B<pmlogger>(1),
B<sadc>(1),
B<sadf>(1),
B<sar>(1) and
B<XML::TokeParser>(3pm).
