summaryrefslogtreecommitdiff
path: root/src/sar2pcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sar2pcp')
-rw-r--r--src/sar2pcp/GNUmakefile43
-rw-r--r--src/sar2pcp/README81
-rwxr-xr-xsrc/sar2pcp/sar2pcp756
3 files changed, 880 insertions, 0 deletions
diff --git a/src/sar2pcp/GNUmakefile b/src/sar2pcp/GNUmakefile
new file mode 100644
index 0000000..be49a5c
--- /dev/null
+++ b/src/sar2pcp/GNUmakefile
@@ -0,0 +1,43 @@
+#!gmake
+#
+# Copyright (c) 2010 Ken McDonell. All Rights Reserved.
+# Copyright (c) 2009 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+#
+# 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.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/src/include/builddefs
+
+SCRIPT = sar2pcp
+LDIRT = $(MAN_PAGES) $(MAN_PAGES).tmp
+LSRCFILES = $(SCRIPT) README
+
+ifneq ($(POD2MAN),)
+MAN_SECTION = 1
+MAN_PAGES = $(SCRIPT).$(MAN_SECTION)
+MAN_DEST = $(PCP_MAN_DIR)/man$(MAN_SECTION)
+endif
+
+default: $(MAN_PAGES)
+
+$(SCRIPT).$(MAN_SECTION): $(SCRIPT)
+ $(POD_MAKERULE)
+
+include $(BUILDRULES)
+
+install: default
+ $(INSTALL) -m 755 $(SCRIPT) $(PCP_BIN_DIR)/$(SCRIPT)
+ @$(INSTALL_MAN)
+
+default_pcp : default
+
+install_pcp : install
diff --git a/src/sar2pcp/README b/src/sar2pcp/README
new file mode 100644
index 0000000..276c25b
--- /dev/null
+++ b/src/sar2pcp/README
@@ -0,0 +1,81 @@
+Converting a sadc data file to a PCP archive
+
+This example uses the PCP::LogImport Perl wrapper around the libpcp_import
+library to convert a sadc datafile into a PCP archive.
+
+The version of sar that is supported here is the one from
+http://freshmeat.net/projects/sysstat/ which includes the sadf utility
+to translate the sadc datafile into an XML file. The Perl script
+sar2pcp runs sadf and translates the XML into a PCP archive.
+
+Usage: sar2pcp sadcfile archive
+
+The translation currently supports the following PCP metrics:
+ disk.all.read
+ disk.all.read_bytes
+ disk.all.total
+ disk.all.total_bytes
+ disk.all.write
+ disk.all.write_bytes
+ disk.dev.avactive
+ disk.dev.read_bytes
+ disk.dev.total
+ disk.dev.total_bytes
+ disk.dev.write_bytes
+ kernel.all.cpu.idle
+ kernel.all.cpu.intr
+ kernel.all.cpu.nice
+ kernel.all.cpu.sys
+ kernel.all.cpu.user
+ kernel.all.cpu.wait.total
+ kernel.all.intr
+ kernel.all.load
+ kernel.all.pswitch
+ kernel.percpu.cpu.idle
+ kernel.percpu.cpu.intr
+ kernel.percpu.cpu.nice
+ kernel.percpu.cpu.sys
+ kernel.percpu.cpu.user
+ kernel.percpu.cpu.wait.total
+ mem.util.bufmem
+ mem.util.cached
+ mem.util.free
+ mem.util.swapCached
+ mem.util.swapFree
+ mem.util.used
+ mem.vmstat.pgfault
+ mem.vmstat.pgfree
+ mem.vmstat.pgmajfault
+ mem.vmstat.pgpgin
+ mem.vmstat.pgpgout
+ network.interface.collisions
+ network.interface.in.bytes
+ network.interface.in.drops
+ network.interface.in.errors
+ network.interface.in.fifo
+ network.interface.in.frame
+ network.interface.in.packets
+ network.interface.out.bytes
+ network.interface.out.carrier
+ network.interface.out.drops
+ network.interface.out.errors
+ network.interface.out.fifo
+ network.interface.out.packets
+ proc.runq.runnable
+ swap.pagesin
+ swap.pagesout
+ vfs.dentry.count
+ vfs.files.count
+ vfs.inodes.count
+
+This is sufficient to support the following standard pmchart views:
+ CPU
+ Disk
+ Diskbytes
+ Loadavg
+ Memory
+ Netbytes
+ Netpackets
+ Overview (except for a lesser memory stats)
+ Paging
+
diff --git a/src/sar2pcp/sar2pcp b/src/sar2pcp/sar2pcp
new file mode 100755
index 0000000..15dc332
--- /dev/null
+++ b/src/sar2pcp/sar2pcp
@@ -0,0 +1,756 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2012-2013 Red Hat.
+# 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.
+#
+
+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 by default
+my $parser;
+my %handlemap; # pmi* handles, one per metric-instance pair
+my %instmap; # ensures we don't add duplicate indom/instance pairs
+my $putsts = 0; # pmiPutValue() errors are only checked @ end of loop
+my $in_cpu_load = 0;
+my $snarf_text = 0;
+my $snarf_name;
+my $save_text;
+my $interface;
+my $disk_all_total;
+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];
+}
+
+sub dotime($)
+{
+ # convert time format HH-MM-SS from a few 10.x sysstat versions
+ # into the format HH:MM:SS that Date::Parse (reasonably) expects
+ #
+ my ($daytime) = @_;
+ $daytime =~ s/-/:/g;
+ return $daytime;
+}
+
+sub dovalue($)
+{
+ # convert string to floating point value: "0,00" / "0.00" / undef
+ # (unusual LC_NUMERIC settings put comma instead of decimal point)
+ # fail to convert (or attempt arithmetic on unconverted values) =>
+ # e.g. 'Argument "\x{31}\x{2c}..." isn't numeric in addition (+)'
+ #
+ my ($value) = @_;
+ return $value unless defined($value);
+ $value =~ s/,/./g;
+ return $value;
+}
+
+my %warning_tags; # XML tags we do not know and will issue warnings about
+my %skipped_tags = ( # XML tags we know about, but have no metric to map yet
+ 'sysstat' => 1, 'sysdata-version' => 1,
+ 'sysname' => 1, 'release' => 1,
+ 'machine' => 1, 'comments' => 1,
+ 'restarts' => 1, 'boot' => 1,
+ 'file-date' => 1, 'statistics' => 1,
+ 'interrupts' => 1, 'int-global' => 1,
+ 'int-proc' => 1, 'irqcpu' => 1,
+ 'serial' => 1, 'pty-nr' => 1,
+ 'tty' => 1,
+ 'super-sz' => 1, 'super-sz-percent' => 1,
+ 'dquot-sz' => 1, 'dquot-sz-percent' => 1,
+ 'rtsig-sz' => 1, 'rtsig-sz-percent' => 1,
+ 'memory' => 1, 'memused-percent' => 1,
+ 'commit' => 1, 'commit-percent' => 1,
+ 'swpused' => 1, 'swpused-percent' => 1,
+ 'swpcad-percent' => 1,
+ 'frmpg' => 1, 'bufpg' => 1, 'campg' => 1,
+ 'usb-devices' => 1,
+ 'disk' => 1, 'network' => 1,
+ 'power-management' => 1,
+ 'cpu-frequency' => 1, 'cpu-weighted-frequency' => 1,
+ 'cpufreq' => 1, 'cpuwfreq' => 1,
+ 'cpu' => 1,
+ 'hugepages' => 1, 'hugfree' => 1,
+ 'hugused' => 1, 'hugused-percent' => 1,
+ 'active' => 1, 'inactive' => 1,
+ 'temperature' => 1, 'temp' => 1,
+ 'usb' => 1,
+ 'filesystems' => 1,
+);
+
+# Register metrics with a singular value.
+#
+sub register_single($)
+{
+ my ($name) = @_;
+ my $units = pmiUnits(0,0,0,0,0,0);
+ my $type = PM_TYPE_FLOAT;
+ my $sts;
+
+ 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;
+ }
+ $sts = pmiAddMetric($name, PM_ID_NULL, $type, PM_INDOM_NULL, PM_SEM_INSTANT, $units);
+ die "pmiAddMetric($name, ...): " . pmiErrStr($sts) . "\n" unless ($sts == 0);
+}
+
+# Register metrics with multiple values.
+#
+sub register_multi($$)
+{
+ my ($name,$indom) = @_;
+ my $units = pmiUnits(0,0,0,0,0,0);
+ my $type = PM_TYPE_FLOAT;
+ my $sts;
+
+ if ($name eq 'proc.runq.runnable' || $name eq 'proc.nprocs') {
+ $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);
+ }
+ elsif ($name =~ /filesys\..*files/) {
+ $units = pmiUnits(0,0,1,0,0,PM_COUNT_ONE);
+ $type = PM_TYPE_U64;
+ }
+ elsif ($name =~ /filesys\./) {
+ $units = pmiUnits(1,0,0,PM_SPACE_MBYTE,0,0);
+ $type = PM_TYPE_U32;
+ }
+ $sts = pmiAddMetric($name, PM_ID_NULL, $type, $indom, PM_SEM_INSTANT, $units);
+ die "pmiAddMetric($name, ...): " . pmiErrStr(-1) . "\n" unless ($sts == 0);
+}
+
+# Wrapper for pmiPutValueHandle() - used for non-indom-null metrics.
+# If first time this instance has been seen for this indom, add it to
+# the instance domain, and get a handle and add it to the handlemap.
+# Finally add the given value into the current result for the archive.
+#
+sub putinst($$$$)
+{
+ my ($name,$indom,$instance,$value) = @_;
+ my $handlekey = $name . '/' . $instance;
+ my $instkey = $indom . '-' . $instance;
+ my ($handle,$sts);
+
+ return unless defined($value);
+
+ # 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{$instkey})) {
+ my $inst;
+ if (defined($instmap{$indom})) {
+ $instmap{$indom}++;
+ $inst = $instmap{$indom};
+ }
+ else {
+ $instmap{$indom} = 0;
+ $inst = 0;
+ }
+ $sts = pmiAddInstance($indom, $instance, $inst);
+ die "pmiAddInstance([$name], $instance, $inst): " . pmiErrStr($sts) . "\n" unless ($sts >= 0);
+ $instmap{$instkey} = $inst;
+ }
+ $handle = $handlemap{$handlekey};
+ if (!defined($handle)) {
+ if (!defined($handlemap{$name})) {
+ register_multi($name, $indom);
+ $handlemap{$name} = -1; # invalid handle for no-instance lookup
+ }
+ $sts = pmiGetHandle($name, $instance);
+ die "pmiGetHandle($name, $instance): " . pmiErrStr($sts) . "\n" unless ($sts >= 0);
+ $handlemap{$handlekey} = $handle = $sts;
+ }
+ $sts = pmiPutValueHandle($handle, dovalue($value));
+ if ($sts < 0 && $putsts == 0) { $putsts = $sts };
+}
+
+# Wrapper for pmiPutValueHandle() - used for indom-null metrics only.
+#
+sub put($$)
+{
+ my ($name,$value) = @_;
+ my ($handle,$sts);
+
+ return unless defined($value);
+
+ $handle = $handlemap{$name};
+ if (!defined($handle)) {
+ register_single($name);
+ $sts = pmiGetHandle($name, '');
+ $sts >= 0 or die "pmiGetHandle($name, ...): " . pmiErrStr($sts) . "\n";
+ $handlemap{$name} = $handle = $sts;
+ }
+ $sts = pmiPutValueHandle($handle, dovalue($value));
+ if ($sts < 0 && $putsts == 0) { $putsts = $sts };
+}
+
+if ($#ARGV != 1) {
+ print STDERR "Usage: sar2pcp infile outfile\n";
+ exit(1);
+}
+
+if ($ARGV[0] =~ /\.xml$/i) {
+ my $sts = open(XMLDATA, '<', $ARGV[0]);
+ if (!defined($sts)) {
+ print STDERR "sar2pcp: " .
+ "Failed to open sadf XML file \"$ARGV[0]\": $!\n";
+ exit(1);
+ }
+} else {
+ my $sadf = 'sadf';
+ $sadf = $ENV{SADF} if defined($ENV{SADF});
+ my $pid = open(XMLDATA, '-|', "$sadf -x $ARGV[0] -- -A");
+ if (!defined($pid)) {
+ print STDERR "sar2pcp: " .
+ "Failed to launch $sadf to convert \"$ARGV[0]\" to XML\n";
+ exit(1);
+ }
+}
+if (eof(XMLDATA)) {
+ print STDERR "sar2pcp: XML document from $ARGV[0] contains no data\n";
+ exit(1);
+}
+
+$parser = XML::TokeParser->new(\*XMLDATA, Noempty => 1);
+if (!defined($parser)) {
+ print STDERR "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'}) . ' ' . dotime($token->attr->{'time'});
+ $sample++;
+ $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" .../>
+ # Take care: some are missing, some attribute names change!
+ #
+ my ($usr, $sys, $nice, $wait, $irq, $soft, $intr, $guest, $steal, $idle);
+
+ $usr = $token->attr->{'usr'};
+ $usr = $token->attr->{'user'} unless defined($usr); # name change
+ $usr = dovalue($usr) / 100 if defined($usr);
+ $sys = $token->attr->{'sys'};
+ $sys = $token->attr->{'system'} unless defined($sys); # name change
+ $sys = dovalue($sys) / 100 if defined($sys);
+ $idle = $token->attr->{'idle'};
+ $idle = dovalue($idle) / 100 if defined($idle);
+ $nice = $token->attr->{'nice'};
+ $nice = dovalue($nice) / 100 if defined($nice);
+ $wait = $token->attr->{'iowait'};
+ $wait = dovalue($wait) / 100 if defined($wait);
+
+ $irq = dovalue($token->attr->{'irq'});
+ $soft = dovalue($token->attr->{'soft'});
+ $intr = ($irq + $soft) / 100 unless (!defined($irq) || !defined($soft));
+
+ $guest = $token->attr->{'guest'};
+ $guest = dovalue($guest) / 100 if defined($guest);
+ $steal = $token->attr->{'steal'};
+ $steal = dovalue($steal) / 100 if defined($steal);
+
+ if ($token->attr->{'number'} eq 'all') {
+ put('kernel.all.cpu.user', $usr);
+ put('kernel.all.cpu.nice', $nice);
+ put('kernel.all.cpu.sys', $sys);
+ put('kernel.all.cpu.wait.total', $wait);
+ put('kernel.all.cpu.steal', $steal);
+ put('kernel.all.cpu.intr', $intr);
+ put('kernel.all.cpu.guest', $guest);
+ put('kernel.all.cpu.idle', $idle);
+ }
+ else {
+ my $instance = 'cpu' . $token->attr->{'number'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 0);
+
+ putinst('kernel.percpu.cpu.user', $indom, $instance, $usr);
+ putinst('kernel.percpu.cpu.nice', $indom, $instance, $nice);
+ putinst('kernel.percpu.cpu.sys', $indom, $instance, $sys);
+ putinst('kernel.percpu.cpu.wait.total', $indom, $instance, $wait);
+ putinst('kernel.percpu.cpu.steal', $indom, $instance, $steal);
+ putinst('kernel.percpu.cpu.intr', $indom, $instance, $intr);
+ putinst('kernel.percpu.cpu.guest', $indom, $instance, $guest);
+ putinst('kernel.percpu.cpu.idle', $indom, $instance, $idle);
+ }
+ }
+ elsif ($token->tag eq 'process-and-context-switch' ||
+ $token->tag eq 'processes' || $token->tag eq 'context-switch') {
+ # <process-and-context-switch per="second" proc="0.00"
+ # cswch="652.10"/>
+ put('kernel.all.pswitch', $token->attr->{'cswch'});
+ # pro tem skip proc
+ }
+ elsif ($token->tag eq 'irq') {
+ # <irq intr="sum" value="230.46"/>
+ if ($token->attr->{'intr'} eq 'sum') {
+ put('kernel.all.intr', $token->attr->{'value'});
+ }
+ # skip all other intr counts for now
+ }
+ elsif ($token->tag eq 'swap-pages') {
+ # <swap-pages per="second" pswpin="0.00" pswpout="0.00"/>
+ put('swap.pagesin', $token->attr->{'pswpin'});
+ put('swap.pagesout', $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"/>
+ put('mem.vmstat.pgpgin', $token->attr->{'pgpgin'});
+ put('mem.vmstat.pgpgout', $token->attr->{'pgpgout'});
+ put('mem.vmstat.pgfault', $token->attr->{'fault'});
+ put('mem.vmstat.pgmajfault', $token->attr->{'majflt'});
+ put('mem.vmstat.pgfree', $token->attr->{'pgfree'});
+ # pro tem skip pgscank, pgscand, pgsteal and vmeff-percent
+ }
+ elsif ($token->tag eq 'io') {
+ # <io per="second">
+ # no metric values from attribute data, all tags
+ }
+ elsif ($token->tag eq 'tps') {
+ # <tps>0.80</tps>
+ # no metric values from attribute data, all tags
+ }
+ elsif ($token->tag eq 'io-reads') {
+ # <io-reads rtps="0.00" bread="0.00"/>
+ # assume blocks are 512 bytes
+ my $iops = dovalue($token->attr->{'rtps'});
+ if (defined($iops)) {
+ put('disk.all.read', $iops);
+ $disk_all_total = $iops;
+ }
+ my $bytes = dovalue($token->attr->{'bread'});
+ if (defined($bytes)) {
+ put('disk.all.read_bytes', $bytes / 2);
+ $disk_all_total_bytes = $bytes;
+ }
+ }
+ elsif ($token->tag eq 'io-writes') {
+ # <io-writes wtps="0.80" bwrtn="17.64"/>
+ # assume blocks are 512 bytes
+ my $iops = dovalue($token->attr->{'wtps'});
+ if (defined($iops)) {
+ $disk_all_total += $iops;
+ put('disk.all.write', $iops);
+ put('disk.all.total', $disk_all_total);
+ }
+ my $bytes = dovalue($token->attr->{'bwrtn'});
+ if (defined($bytes)) {
+ put('disk.all.write_bytes', $bytes / 2);
+ $disk_all_total_bytes += $bytes;
+ put('disk.all.total_bytes', $disk_all_total_bytes / 2);
+ }
+ }
+ elsif ($token->tag eq 'memfree') {
+ # <memfree>78896</memfree>
+ $snarf_name = 'mem.util.free';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'memused') {
+ # <memused>947232</memused>
+ $snarf_name = 'mem.util.used';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'buffers') {
+ # <buffers>165296</buffers>
+ $snarf_name = 'mem.util.bufmem';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'cached') {
+ # <cached>368644</cached>
+ $snarf_name = 'mem.util.cached';
+ $snarf_text = 1;
+ }
+ # pro tem skip <commit>
+ # pro tem skip <commit-percent>
+ # pro tem skip <active>
+ # pro tem skip <inactive>
+ elsif ($token->tag eq 'dirty') {
+ # <dirty>208</dirty>
+ $snarf_name = 'mem.util.dirty';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'swpfree') {
+ # <swpfree>1920808</swpfree>
+ $snarf_name = 'mem.util.swapFree';
+ $snarf_text = 1;
+ }
+ # pro tem skip <swpused>
+ elsif ($token->tag eq 'swpcad') {
+ # <swpcad>19208</swpcad>
+ $snarf_name = '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"/>
+ # depending on sadc version, these may be attributes
+ # or separate tags (below).
+ put('vfs.dentry.count', $token->attr->{'dentunusd'});
+ put('vfs.files.count', $token->attr->{'file-nr'});
+ put('vfs.inodes.count', $token->attr->{'inode-nr'});
+ # pro tem skip pty-nr
+ }
+ elsif ($token->tag eq 'dentunusd') {
+ # <dentunusd>75415</dentunusd>
+ $snarf_name = 'vfs.dentry.count';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'file-nr' || $token->tag eq 'file-sz') {
+ # <file-nr>4608</file-nr>
+ # metric defined already (above - different XML output versions!)
+ $snarf_name = 'vfs.files.count';
+ $snarf_text = 1;
+ }
+ elsif ($token->tag eq 'inode-nr' || $token->tag eq 'inode-sz') {
+ # <inode-nr>72802</inode-nr>
+ # metric defined already (above - different XML output versions!)
+ $snarf_name = 'vfs.inodes.count';
+ $snarf_text = 1;
+ }
+ # pro tem skip pty-nr
+ elsif ($token->tag eq 'queue') {
+ my $indom = pmInDom_build(PMI_DOMAIN, 1);
+ put('proc.runq.runnable', $token->attr->{'runq-sz'});
+ put('proc.nprocs', $token->attr->{'plist-sz'});
+ putinst('kernel.all.load', $indom, '1 minute', $token->attr->{'ldavg-1'});
+ putinst('kernel.all.load', $indom, '5 minute', $token->attr->{'ldavg-5'});
+ putinst('kernel.all.load', $indom, '15 minute', $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"/>
+ my $instance = 'disk' . $token->attr->{'dev'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 2);
+ my ($read_bytes, $write_bytes, $percent);
+
+ putinst('disk.dev.total', $indom, $instance, $token->attr->{'tps'});
+ # 512-byte sectors/sec
+ $percent = dovalue($token->attr->{'util-percent'});
+ $read_bytes = dovalue($token->attr->{'rd_sec'});
+ $write_bytes = dovalue($token->attr->{'wr_sec'});
+ if (defined($read_bytes)) {
+ putinst('disk.dev.read_bytes', $indom, $instance, $read_bytes / 2);
+ }
+ if (defined($write_bytes)) {
+ putinst('disk.dev.write_bytes', $indom, $instance, $write_bytes / 2);
+ }
+ if (defined($read_bytes) && defined($write_bytes)) {
+ putinst('disk.dev.total_bytes', $indom, $instance,
+ ($read_bytes + $write_bytes) / 2);
+ }
+ if (defined($percent)) {
+ putinst('disk.dev.avactive', $indom, $instance, $percent / 100);
+ }
+ putinst('disk.dev.avrqsz', $indom, $instance, dovalue($token->attr->{'avgrq-sz'}));
+ putinst('disk.dev.avqsz', $indom, $instance, dovalue($token->attr->{'avgqu-sz'}));
+ putinst('disk.dev.await', $indom, $instance, dovalue($token->attr->{'await'}));
+ putinst('disk.dev.svctm', $indom, $instance, dovalue($token->attr->{'svctm'}));
+ # TODO disk.all aggregation?
+ }
+ elsif ($token->tag eq 'net-device') {
+ # <net-device iface="eth0">
+ $interface = $token->attr->{'iface'};
+ }
+ 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 $indom = pmInDom_build(PMI_DOMAIN, 4);
+ my $iface = $token->attr->{'iface'};
+ $interface = $iface if (defined($iface)); # else it comes from net-device
+
+ putinst('network.interface.in.packets', $indom, $interface, $token->attr->{'rxpck'});
+ putinst('network.interface.out.packets', $indom, $interface, $token->attr->{'txpck'});
+ putinst('network.interface.in.bytes', $indom, $interface, $token->attr->{'rxkB'});
+ putinst('network.interface.out.bytes', $indom, $interface, $token->attr->{'txkB'});
+ # pro tem skip rxcmp, txcmp, rxmcst
+ }
+ 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 $indom = pmInDom_build(PMI_DOMAIN, 4);
+ my $iface = $token->attr->{'iface'};
+ $interface = $iface if (defined($iface)); # else it comes from net-device
+
+ putinst('network.interface.in.errors', $indom, $interface, $token->attr->{'rxerr'});
+ putinst('network.interface.out.errors', $indom, $interface, $token->attr->{'txerr'});
+ putinst('network.interface.collisions', $indom, $interface, $token->attr->{'coll'});
+ putinst('network.interface.in.drops', $indom, $interface, $token->attr->{'rxdrop'});
+ putinst('network.interface.out.drops', $indom, $interface, $token->attr->{'txdrop'});
+ putinst('network.interface.out.carrier', $indom, $interface, $token->attr->{'txcarr'});
+ putinst('network.interface.in.frame', $indom, $interface, $token->attr->{'rxfram'});
+ putinst('network.interface.in.fifo', $indom, $interface, $token->attr->{'rxfifo'});
+ putinst('network.interface.out.fifo', $indom, $interface, $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 for now
+ 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_name = 'hinv.ncpu';
+ $snarf_text = 2;
+ }
+ elsif ($token->tag eq 'filesystem') {
+ # <filesystem fsname="/dev/sda8" MBfsfree="429418" MBfsused="39806" fsused-percent="8.48" ufsused-percent="13.57" Ifree="30318480" Iused="204912" Iused-percent="0.67"/>
+ my $instance = $token->attr->{'fsname'};
+ my $indom = pmInDom_build(PMI_DOMAIN, 5);
+ my ($free, $used);
+ $free = dovalue($token->attr->{'MBfsfree'});
+ $used = dovalue($token->attr->{'MBfsused'});
+ if (defined($free) && defined($used)) {
+ putinst('filesys.capacity', $indom, $instance, $used+$free);
+ }
+ putinst('filesys.free', $indom, $instance, $free);
+ putinst('filesys.used', $indom, $instance, $used);
+ $free = dovalue($token->attr->{'Ifree'});
+ $used = dovalue($token->attr->{'Iused'});
+ if (defined($free) && defined($used)) {
+ putinst('filesys.maxfiles', $indom, $instance, $used+$free);
+ }
+ putinst('filesys.freefiles', $indom, $instance, $free);
+ putinst('filesys.usedfiles', $indom, $instance, $used);
+ }
+ elsif (!defined($skipped_tags{$token->tag})) {
+ $warning_tags{$token->tag} = 1;
+ }
+ if ($putsts != 0) {
+ my $tag = $token->tag;
+ die "pmiPutValue: Failed @ $stamp on $tag: " . 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($snarf_name, 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($snarf_name, '', $save_text) >= 0
+ or die "pmiPutValue(hinv.ncpu,,$save_text): " . pmiErrStr(-1) . "\n";
+ }
+ }
+ elsif ($token->is_text) {
+ if ($snarf_text == 1) {
+ put($snarf_name, $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) {
+ #debug# print "comment: " . $token->text . "\n";
+ next;
+ }
+ elsif ($token->is_pi) {
+ #debug# print "process instruction: " . $token->target . "\n";
+ next;
+ }
+}
+
+if (%warning_tags) {
+ my @warned = keys %warning_tags;
+ my $nwarns = $#warned + 1;
+ print STDERR
+ "PCP archive produced, but $nwarns unrecognised tag(s) detected in ".
+ "$sample samples:\n\t";
+ print STDERR '"', pop(@warned), '"';
+ while (@warned) { print STDERR ', "', pop(@warned), '"'; }
+ print STDERR "\n";
+}
+
+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>.
+
+However, if I<infile> has the suffix ".xml", then it will be considered
+already in XML format and B<sar2pcp> will operate directly on it.
+
+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.
+A Python wrapper module is also available.
+
+=head1 CAVEATS
+
+When not using the XML input option, B<sar2pcp> requires I<infile> to
+have been created by a version of B<sadc>(1) from
+L<http://sebastien.godard.pagesperso-orange.fr/>
+which includes the B<sadf>(1) utility
+to translate I<infile> into an XML stream (any since version 6);
+B<sar2pcp> will automatically run B<sadf>(1) and translate the resultant
+XML into a PCP archive.
+
+When using binary B<sadc> files
+it is important to ensure the installed B<sadf> is compatible with the
+version of B<sadc> that originally generated the binary files. Simply
+assuming a newer installed version will work is unfortunately far too
+optimistic, and nor should one assume that binary data from different
+platforms (e.g. different endianness) will work - these issues are due
+to limitations in B<sadc> and B<sadf>, and not in B<sar2pcp> itself.
+
+Fortunately, the B<sadf> message indicating that an incompatibility has
+been detected is consistent across versions, and is always prefixed
+
+Invalid system activity file
+
+Using an XML I<infile> has the advantage that the installed version
+of B<sadf> is completely bypassed. B<sar2pcp> undertakes to transform
+any valid XML produced by any of the different variations of B<sadf>
+into a valid PCP archive. Any version of PCP will be able to interpret
+the archive files produced by any version of B<sar2pcp>, and you are
+also free to move the binary PCP archive between different platforms,
+different hardware, even different operating systems - it Just Works (TM).
+
+=head1 SEE ALSO
+
+B<pmie>(1),
+B<pmchart>(1),
+B<pmlogger>(1),
+B<pmlogextract>(1),
+B<pmlogsummary>(1),
+B<sadc>(1),
+B<sadf>(1),
+B<sar>(1),
+B<Date::Parse>(3pm),
+B<Date::Format>(3pm),
+B<PCP::LogImport>(3pm),
+B<XML::TokeParser>(3pm) and
+B<LOGIMPORT>(3).