Exporting Custom Output

Introduction

With Version 2.6.0 comes a redesign of the mechanism for producing custom output through the use of --export. In fact enough flexibility has been added that code for producing s-expressions as well as vmstat output have now been moved to 2 external files named sexpr.ph and vmstat.ph both of which can now be found in /opt/hp/collectl along with a new format called list-oriented which can be delivered via lexpr.ph. The reason anyone should care is that now if you want to produce your own exportable form of output and be able to print it locally, make it available to another program over a socket or even write to a local file and still be able to log to raw and/or plot formats, you get that all that functionality for free.

How It Works

The interface to all this is really quite simple. At the command line the user types collectl --export name[,options] where: There are currently 5 diffent custom exports that are part of a standard collectl distribution: The first three are the most interesting because they have been built to take advantage of collectl's capability of sending their output over a socket and are therefore the ideal vehicle for interfacing with other tools and environments. All three share a common set of options as described in the following table:

These apply to gexpr, lexpr and sexpr
ddebugging value, see beginning of the actual export file for details
foutput snapshot file name for lexp and sexpr. If this option is not used, -f must be and the snapshot file names are then set to the single character L and S respectively and written into the directory associated with -f
sspecifies a subset of those subsystems specified with -s in the collectl command line and only data collected for that subset will be sent. The default to report everything.
These apply to gexpr and lexpr only
avgused in conjunction with i=, send the average of the data over the associated set of intervals. If neither avg, min or max is used, the values the the more recent monitoring interval will be sent.
cothis does not take a value and indicates changes only to a particulate data element are to be reported. If not specified samples for all reporting intervals will be sent.
ispecifies the reporting interval in seconds. In other words, if you specify i=60, a sample will be sent every 60 seconds independent of collectl's monitoring interval. The default is to send every sample. NOTE - this interval must be a multiple of the base collectl interval.
maxused in conjunction with i=, send the maximum data value over the associated set of intervals. If neither avg, min or max is used, the values the the more recent monitoring interval will be sent.
minused in conjunction with i=, send the minimum data value over the associated set of intervals. If neither avg, min or max is used, the values the the more recent monitoring interval will be sent.
ttl is the time to live in intervals for each piece of performance data. If more than this number of intervals passes data will be sent regardless of whether it changed or not and the ttl countdown timer reset. The default is 5. This actually has a second use for gexpr and that is to set the gmond ttl to double this number multiplied by the interval

Logging
Collectl can actually create up to 3 different type of log files and it's worth spending a little more time enumerating how collectl decides where and when to create them.

Example

Perhaps the best way to see how all this works is with a simple example and it turns out that vmstat.ph is small enough to meet that need. You can also refer to the others as well.

This first section gets called almost immediately by collectl after reading in the various user switches. This is the place to catch switch errors and since this routine always requires -scm we'll just hardcode it to that and reject any user entered ones. This initialization subrouting must be named for our module followed by Init.

sub vmstatInit
{
  error("-s not allowed with 'vmstat'")          if $userSubsys ne '';
  error("-f requires either --rawtoo or -P")     if $filename ne '' && !$rawtooFlag && !$plotFlag;
  error("-P or --rawtoo require -f")             if $filename eq '' && ($rawtooFlag || $plotFlag);
  $subsys=$userSubsys='cm';
}
Next we define the output routine, with the same base name as that of our included file.

The if statement uses collectl's standard idiom for printing headers based on the number of lines printed and whether or not the user wants only a singleheader, no header or even to clear the screen between headers. If you do not want/need all these features it's perfectly fine to use a more simplifed header printing mechanism, such as the one is sexpr.ph.

sub vmstat
{
  my $line;
  if (($headerRepeat==0 && !$headersPrinted) || ($headerRepeat>0 && ($totalCounter % $headerRepeat)==1))
  {
    $line= "${cls}#${miniBlanks}procs ---------------memory (KB)--------------- --swaps-- -----io---- --system-- ----cpu-----\n";
    $line.="#$miniDateTime r  b   swpd   free   buff  cache  inact active   si   so    bi    bo   in    cs us sy  id wa\n";
  }
Next comes the handling of optional date/time prefixes that I stole from printTerm() in formatit.ph which can be controlled by various switch options. Again, if you have no intent of supporting these you can even put in error handling in your initialization routine or simply ignore the switches.
  my $datetime='';
  if ($options=~/[dDTm]/)
  {
    ($ss, $mm, $hh, $mday, $mon, $year)=localtime($lastSecs);
    $datetime=sprintf("%02d:%02d:%02d", $hh, $mm, $ss);
    $datetime=sprintf("%02d/%02d %s", $mon+1, $mday, $datetime)                  if $options=~/d/;
    $datetime=sprintf("%04d%02d%02d %s", $year+1900, $mon+1, $mday, $datetime)   if $options=~/D/;
    $datetime.=".$usecs"                                                         if ($options=~/m/);
    $datetime.=" ";
  }
Here we build the actual output, noting that we're not really printing anything yet, but rather building up a string (which may contain the header) that we will print in one shot.
  my $i=$NumCpus;
  my $usr=$userP[$i]+$niceP[$i];
  my $sys=$sysP[$i]+$irqP[$i]+$softP[$i]+$stealP[$i];
  $line.=sprintf("%s %2d %2d %6s %6s %6s %6s %6s %6s %4d %4d %5d %5d %4d %5d %2d %2d %3d %2d\n",
                $datetime, $procsRun, $procsBlock,
                cvt($swapUsed,6,1,1),  cvt($memFree,6,1,1),  cvt($memBuf,6,1,1),
                cvt($memCached,6,1,1), cvt($inactive,6,1,1), cvt($active,6,1,1),
                $swapin/$intSecs, $swapout/$intSecs, $pagein/$intSecs, $pageout/$intSecs,
                $intrpt/$intSecs, $ctxt/$intSecs,
                $usr, $sys, $idleP[$i], $waitP[$i]);
Finally comes the output. There is actually a lot of latitude here and in this case we're caling printText() which will send the output to the terminal or over a socket. It will not write to a local file as does sexpr or lexpr, but if you want to see how to do that, refer to them. As with all perl require files, they must return true and therefore the final statement that consists of the digit 1.
  printText($line);
}
1;
Try running it and you'll see all the pagination and time formats work just as they do with standard output formats.