/*
 * Portions Copyright (c) 1999-2003 Apple Computer, Inc. All Rights
 *  Reserved.
 *
 *  This file contains Original Code and/or Modifications of Original Code
 *  as defined in and that are subject to the Apple Public Source License
 *  Version 2.0 (the 'License'). You may not use this file except in
 *  compliance with the License. Please obtain a copy of the License at
 *  http://www.opensource.apple.com/apsl/ and read it before using this
 *  file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
*/

/*
  cc -Wall -Wno-long-double -I. -I ../sadc.tproj -O -o  sar sar.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <mach/mach.h>
#include <sys/param.h>
#include <sys/sysctl.h>

#include <sadc.h>
#include <sar.h>


#define IFNET_32_BIT_COUNTERS 1

/* Options used only for launching sadc */
int t_interval = 5; 	        /* in seconds              */
char * t_intervalp = "5";
int n_samples  = 1;	        /* number of sample loops  */
char * n_samplesp = "1";

/* Used only for storing the binary output after launching sadc */
char *outfile  = NULL;          /* output file             */
int ofd        = 0;		/* output file descriptor  */

/*
 * When launching sadc, this file descriptor reads sadc's stdout
 *    via pipe.
 * When not launching sadc, this file descriptor will be either
 *    the input file passed in with the -f flag
 *    or the standard input file /var/log/sa/saXX
 */
int ifd        = 0;		/* input file descriptor   */   
char *infile   = NULL;          /* input file              */



/* Used when we have to luanch sadc */
pid_t pid;
int fd[2];  /* read from fd[0], write to fd[1] */

char *optionstring1 =  "Adgn:puo:";
char *optionstring1_usage = "/usr/bin/sar [-Adgpu] [-n { DEV | EDEV | PPP } ] [-o filename] t [n]";
char *optionstring2 = "Adgn:pue:f:i:s:";
char *optionstring2_usage = "/usr/bin/sar [-Adgpu] [-n { DEV | EDEV | PPP }] [-e time] [-f filename] [-i sec] [-s time]";


/* option flags */
int aflag = 0;
int Aflag = 0;
int bflag = 0;
int cflag = 0;
int dflag = 0;  /* drive statistics */
int gflag = 0;  /* page-out activity */
int kflag = 0;
int mflag = 0;

int nflag = 0;  /* network statistics */
int network_mode = 0;
char *sadc_mflagp = "-m";
char *sadc_ppp_modep = "PPP";

int pflag = 0;  /* page-in activity */
int qflag = 0;
int rflag = 0;
int uflag = 0;   /* cpu utilization - this is the only default */
int vflag = 0;
int wflag = 0;
int yflag = 0;
int set_default_flag = 1;
int flag_count = 0;

/*
 *  To get the current time of day in seconds
 *  based on a 24 hour clock, pass in the time_t from time()
 *  the remainder is the current time in seconds
*/
#define HOURS_PER_DAY 24
#define MINS_PER_HOUR 60
#define SECS_PER_MIN 60
#define SECS_PER_DAY (SECS_PER_MIN * MINS_PER_HOUR * HOURS_PER_DAY)

/* end time delimiter -- converted from hh:mm:ss to seconds */
time_t end_time = 0;

int iflag = 0;
int iseconds = 0;  /* interval seconds, default = 0 implies all samples are
		    * printed */

/* start time delimiter -- converted from hh:mm:ss to seconds */
time_t start_time = 0;

int oflag = 0;
int fflag = 0;

/* stat records average and previous */
struct vm_statistics       prev_vmstat,  avg_vmstat, cur_vmstat;
host_cpu_load_info_data_t  prev_cpuload, avg_cpuload, cur_cpuload;
struct drivestats_report   *dr_head = NULL;

/* internal table of drive path mappings */
struct drivepath *dp_table = NULL;
int dp_count = 0;

/* internal table of network interface statistics */
struct netstats_report *nr_table = NULL;
int nr_count;
struct netstats *netstat_readbuf = NULL;
size_t netstat_readbuf_size = 0;

int avg_counter = 0;
int avg_interval = 0;

extern int errno;

/* Forward function declarations */
static void exit_usage();
static void open_output_file(char *path);
static void open_input_file(char *path);
static void read_record_hdr(struct record_hdr *hdr, int writeflag);
static void read_record_data(char *buf, size_t size, int writeflag);
static void write_record_hdr(struct record_hdr *hdr);
static void write_record_data(char *buf, size_t size);
static long convert_hms(char *string);
static char *get_hms_string(time_t, char *);
static int find_restart_header(struct record_hdr *);
static void print_all_column_headings (time_t timestamp);
static void print_column_heading (int type, char *timebufptr, int mode);
static void read_sample_set(int, time_t, struct record_hdr *);
static void do_main_workloop();
static int bypass_sample_set(struct record_hdr *, time_t);
static void skip_data(int);
static int get_cpu_sample(int flag, struct record_hdr *hdr);
static void print_cpu_sample(char *timebufptr);
static int get_vmstat_sample(int flag, struct record_hdr *hdr);
static void print_vmstat_sample(char *timebufptr);

static int get_drivestats_sample(int flag, struct record_hdr *hdr);
static void init_drivestats(struct drivestats_report *dr);
static void print_drivestats_sample(char *timebufptr);
static int get_drivepath_sample(int flag, struct record_hdr *hdr);

static void set_cur_netstats(struct netstats_report *nr, struct netstats *ns);
static void init_prev_netstats(struct netstats_report *nr);
static int get_netstats_sample(int flag, struct record_hdr *hdr);
static void print_netstats_sample(char *timebufptr);
    
static void exit_average();

int
main(argc, argv)
    int argc;
    char *argv[];
{

    char    ch;

    time_t curr_time;		/* current time in seconds */
    char timebuf[26];
    char filenamebuf[20];
    char *optstring = NULL;
    int optstringval;
    int i;
    
    /*
     * Detirmine which option string to use
     */

    optreset=0;
    optstringval=0;
    
    while((ch=getopt(argc, argv, "aAbcdgkmn:pqruvwyo:e:f:i:s:")) != EOF) {
	switch(ch) {
	case 'o':
	    if (optstringval == 2)
		exit_usage();
	    optstring=optionstring1;
	    optstringval=1;
	    break;
	case 'e':
	case 'f':
	case 'i':
	case 's':
	    if (optstringval == 1)
		exit_usage();
	    optstring=optionstring2;
	    optstringval=2;
	    break;
	default:
	    /* ignore for now */
	    break;
	}
    }

    if (!optstring)
    {
	/* still trying to determine which option string to use */
	if (argc - optind > 0)
	{
	    optstring=optionstring1;  /* we should have a t_second value */
	    optstringval=1;
	}
	else
	{
	    optstring=optionstring2;
	    optstringval=2;
	}
    }

    optreset = optind = 1;
    while ((ch=getopt(argc, argv, optstring)) != EOF) {
	switch (ch) {
	case 'a':
	    aflag = 1;
	    set_default_flag = 0;
	    flag_count++;
	    break;
	case 'A':
	    Aflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'b':
	    bflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'c':
	    cflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'd':
	    dflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'g':
	    gflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'k':
	    kflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'm':
	    mflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'n':
	    nflag= 1;
	    if (!strncmp(optarg, "PPP", 3))
		network_mode |= NET_PPP_MODE;
	    else if (!strncmp(optarg, "DEV", 3))
		network_mode |= NET_DEV_MODE;
	    else if (!strncmp(optarg, "EDEV", 4))
		network_mode |= NET_EDEV_MODE;
	    else
		exit_usage();
	    set_default_flag = 0;
	    flag_count++;
	    break;	    
	case 'p':
	    pflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'q':
	    qflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'r':
	    rflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'u':
	    uflag= 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'v':
	    vflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'w':
	    wflag = 1;
	    set_default_flag = 0;
	    flag_count++;
	    break;
	case 'y':
	    yflag = 1;
	    set_default_flag = 0;
	    flag_count++;	    
	    break;
	case 'o':
	    /* open the output file */
	    oflag = 1;
	    outfile=optarg;
	    (void)open_output_file(outfile);	    
	    break;
	case 'e':  /* eflag */
	    end_time = convert_hms(optarg);
	    break;
	case 'f':
	    fflag = 1;
	    infile=optarg;	    
	    break;
	case 'i':
	    iflag = 1;
	    iseconds=atoi(optarg);
	    break;
	case 's':
	    start_time = convert_hms(optarg);
	    break;
	default:
	    exit_usage();
	    break;
	}
    }

    /* setup default uflag option */
    if (Aflag)
    {
	dflag = gflag = pflag = uflag = 1;
	if (!nflag)
	{
	    /*
	     * Add network stats to the load
	     * but avoid PPP data by default.
	     */
	    nflag = 1;
	    network_mode = NET_DEV_MODE | NET_EDEV_MODE;;
	}
	flag_count = 2;	  /* triggers column headings */
    }
    else if (set_default_flag)
    {
	uflag=1;
	flag_count++;
    }

    if (nflag)
    {
	if (network_mode & NET_PPP_MODE)
	{
	    if (!(network_mode & NET_DEV_MODE) &&
	      !(network_mode & NET_EDEV_MODE))
	    {
		/* set defaults */
		network_mode |= NET_DEV_MODE;
		network_mode |= NET_EDEV_MODE;
		flag_count++;
	    }
	}
    }

    argc -= optind;
    argv += optind;

    /* set up signal handlers */
    signal(SIGINT,  exit_average);
    signal(SIGQUIT, exit_average);
    signal(SIGHUP,  exit_average);
    signal(SIGTERM, exit_average); 

    if (optstringval == 1)
    {
	/* expecting a time interval */
	
	char *p;

	if (argc >= 1)
	{
	    errno = 0;
	    t_interval = strtol(argv[0], &p, 0);
	    t_intervalp = argv[0];
	    if (errno || (*p != '\0') || t_interval <= 0 )
		exit_usage();
	    if (argc >= 2)
	    {
		errno=0;
		n_samples = strtol(argv[1], &p, 0);
		n_samplesp = argv[1];
		if (errno || (*p != '\0') || n_samples <= 0)
		    exit_usage();
	    }
	}
    }

    /* where does the input come from */
    if (fflag)
    {
	(void)open_input_file(infile);
    }
    else if (optstringval == 2)
    {
	/*
	 * Create a filename of the form /var/log/sa/sadd
	 * where "dd" is the date of the month
	 */
	curr_time = time((time_t *)0);        /* returns time in seconds */

	/*
	  timebuf will be a 26-character string of the form:
	  Thu Nov 24 18:22:48 1986\n\0
	*/

	ctime_r(&curr_time, timebuf);
	strncpy(filenamebuf, "/var/log/sa/sa", 14);
	strncpy(&filenamebuf[14], &timebuf[8], 2);
	if (filenamebuf[14] == ' ')
	    filenamebuf[14] = '0';
	filenamebuf[16]='\0';
	infile = filenamebuf;
	(void)open_input_file(infile);
    }
    else if (optstringval == 1)
    {
	/* launch sadc */
	if (pipe(fd) == -1)
	{
	    fprintf(stderr, "sar: pipe(2) failed, errno = (%d)\n",errno);
	    exit(1);
	}

	if ((pid=fork()) == 0)
	{
#if 0
	    int efd;
#endif
	    
            /* This is the child */
	    /* Close all file descriptors except the one we need */
	    
	    for (i=0; i <= KERN_MAXFILESPERPROC; i++) {
		if ((i != fd[0]) && (i != fd[1]))
		    (void)close(i);
	    }
#if 0
	    efd = open("/tmp/errlog", O_CREAT|O_APPEND|O_RDWR, 0666);
	    if (dup2(efd,2) == -1) {
		exit(1);
	    }
#endif
	    /* Dup the two file descriptors to stdin and stdout */
	    if (dup2(fd[0],0) == -1) {
		exit(1);
	    }
	    if (dup2(fd[1],1) == -1) {
		exit(1);
	    }
	    /* Exec the child process */
	    if (network_mode & NET_PPP_MODE)
		execl("/usr/lib/sa/sadc", "sadc", sadc_mflagp, sadc_ppp_modep, t_intervalp, n_samplesp, NULL);
	    else
		execl("/usr/lib/sa/sadc", "sadc", t_intervalp, n_samplesp, NULL);		    

	    perror("execlp sadc");
	    exit(2); /* This call of exit(2) should never be reached... */
	}
	else
	{	 /* This is the parent */
	    if (pid == -1) {
		fprintf(stderr, "sar: fork(2) failed, errno = (%d)\n",errno);
		exit(1);
	    }
	    close (fd[1]);  /* parent does not write to the pipe */
	    ifd = fd[0];    /* parent will read from the pipe */
	}	
    }
    else
    {
	/* we're confused about source of input data - bail out */
	fprintf(stderr, "sar: no input file recognized\n");
	exit_usage();
    }

    /* start reading input data and format the output */
    (void)do_main_workloop();
    (void)exit_average();
    exit(0);
}

static void
exit_usage()
{
    fprintf(stderr, "\n%s\n\n", optionstring1_usage);
    fprintf(stderr, "%s\n",   optionstring2_usage);
    exit(EXIT_FAILURE);
}

static void
open_output_file(char *path)
{
    if ((ofd = open(path, O_CREAT|O_APPEND|O_TRUNC|O_WRONLY, 0664)) == -1 )
    {
	/* failed to open path */
	fprintf(stderr, "sar: failed to open output file [%s]\n", path);
	exit_usage();
    }
}


static void
open_input_file(char *path)
{
    if ((ifd = open(path, O_RDONLY, 0)) == -1)
    {
	/* failed to open path */
	fprintf(stderr, "sar: failed to open input file [%d][%s]\n", ifd, path);
	exit_usage();
    }
}

static void
read_record_hdr(hdr, writeflag)
    struct record_hdr *hdr;
    int writeflag; 
{
    errno = 0;
    int num = 0;
    int n = 0;
    size_t size = 0;

    size = sizeof(struct record_hdr);

    while (size)
    {
	num = read(ifd, &hdr[n], size);	
	if (num > 0)
	{
	    n += num;
	    size -= num;
	}
	else if (num == 0)
	    exit_average();
	else
	{
	    fprintf(stderr, "sar: read_record_data failed, errno=%d num=%d, size=%d\n", (int)errno, (int)num, (int)size);
	    exit(EXIT_FAILURE);
	}
    }
    
    if (oflag && writeflag)
	write_record_hdr(hdr);
    
    return;	
}

static void
read_record_data(buf, size, writeflag)
    char *  buf;
    size_t  size;
    int     writeflag;
{
    errno = 0;
    size_t num = 0;
    size_t n = 0;

    while (size)
    {
	num = read(ifd, &buf[n], size);	
	if (num > 0)
	{
	    n += num;
	    size -= num;
	}
	else if (num == 0)   /* EOF */
	    exit_average();	
	else
	{
	    fprintf(stderr, "sar: read_record_data failed, errno=%d num=%d, size=%d\n", (int)errno, (int)num, (int)size);
	    exit(EXIT_FAILURE);
	}
    }

    if (oflag && writeflag)
	write_record_data(buf, n);
    
    return;
}

static void
write_record_hdr(hdr)
    struct record_hdr *hdr;
{
    errno = 0;
    int num;
    
    if ((num = write(ofd, hdr, sizeof(struct record_hdr))) == -1)
    {
	fprintf(stderr, "sar: write_record_hdr failed, errno=%d\n", errno);	
	exit(EXIT_FAILURE);
    }
    return;
}

static void
write_record_data(char *buf, size_t nbytes)
{
    errno = 0;
    int num;
    if ((num = write(ofd, buf, nbytes)) == -1)
    {
	fprintf(stderr, "sar: write_record_data failed, errno=%d\n", errno);
	exit(EXIT_FAILURE);
    }
    return;	
}

/*
 * Convert a string of one of the forms
 *      hh   hh:mm     hh:mm:ss
 * into the number of seconds.
 * exit on error
*/

static time_t
convert_hms(string)
    char *string;
{
    int hh = 0;   /* hours */
    int mm = 0;   /* minutes */
    int ss = 0;   /* seconds */
    time_t seconds;
    time_t timestamp;
    struct tm *tm;
    int i;

    if (string == NULL || *string == '\0')
	goto convert_err;

    for (i=0; string[i] != '\0'; i++)
    {
	if ((!isdigit(string[i])) && (string[i] != ':'))
	{
	    goto convert_err;
	}
    }

    if (sscanf(string, "%d:%d:%d", &hh, &mm, &ss) != 3)
    {
	if (sscanf(string, "%d:%d", &hh, &mm) != 2)
	{
	    if (sscanf(string, "%d", &hh) != 1)
	    {
		goto convert_err;
	    }
	}
    }

    if (hh < 0 || hh >= HOURS_PER_DAY ||
      mm < 0 || mm >= MINS_PER_HOUR ||
      ss < 0 || ss > SECS_PER_MIN)
    {
	goto convert_err;
    }

    seconds = ((((hh * MINS_PER_HOUR) + mm) * SECS_PER_MIN) + ss);
    timestamp = time((time_t *)0);
    tm=localtime(&timestamp);
    seconds -= tm->tm_gmtoff;
    
    return(seconds);   
    
    convert_err:
    fprintf(stderr, "sar: time format usage is hh[:mm[:ss]]\n");
    exit_usage();
    return(0);
}


/*
 * Use ctime_r to convert a time value into
 * a 26-character string of the form:
 *
 * Thu Nov 24 18:22:48 1986\n\0
 */

static char *
get_hms_string(tdata, tbuf)
    time_t tdata;
    char *tbuf;
{
    time_t t;
    char *p;

    t = tdata;
    ctime_r(&t, tbuf);
    p=&tbuf[11];
    tbuf[19] = 0;

    return(p);
}
    

/* sample set flags */
#define INIT_SET   0
#define PRINT_SET  1
#define PRINT_AVG  2

static void
do_main_workloop()
{
    struct record_hdr hdr;
    time_t cur_timestamp = 0;	/* seconds - Coordinated Universal Time */
    time_t next_timestamp = 0;  /* seconds - Coordinated Universal Time */

    if (!find_restart_header(&hdr))
	exit(1);

    cur_timestamp = hdr.rec_timestamp;

    /* convert sflag's start_time from 24 hour clock time to UTC seconds */
    if (start_time  < (cur_timestamp % SECS_PER_DAY))
	start_time = cur_timestamp;
    else
	start_time += cur_timestamp - (cur_timestamp % SECS_PER_DAY);

    /* convert end_time, from 24 hour clock time to UTC seconds */
    if (end_time != 0)
	end_time += cur_timestamp - (cur_timestamp % SECS_PER_DAY);

#if 0
	fprintf(stderr, "start = %ld, end = %ld, cur=%ld, [24hour - %ld]\n",
	  start_time, end_time, cur_timestamp,(cur_timestamp % SECS_PER_DAY));
#endif

    while (cur_timestamp < start_time)
    {
	bypass_sample_set(&hdr, cur_timestamp);
	cur_timestamp = hdr.rec_timestamp;
    }

    next_timestamp = cur_timestamp + iseconds;
    print_all_column_headings(cur_timestamp);
    read_sample_set(INIT_SET, cur_timestamp, &hdr);
    cur_timestamp = hdr.rec_timestamp;    

    while ((end_time == 0) || (next_timestamp < end_time))
    {
	if (cur_timestamp < next_timestamp)
	{
	    bypass_sample_set (&hdr, cur_timestamp);
	    cur_timestamp = hdr.rec_timestamp;
	}
	else
	{
	    /* need to know the seconds interval when printing averages */
	    if (avg_interval == 0)
	    {
		if (iseconds)
		    avg_interval = iseconds;
		else
		    avg_interval = cur_timestamp - next_timestamp;
	    }
	    next_timestamp = cur_timestamp + iseconds;	    
	    read_sample_set(PRINT_SET, cur_timestamp, &hdr);
	    cur_timestamp = hdr.rec_timestamp;	    
	}
    }
    exit_average();
}


/*
 * Find and fill in a restart header.  We don't write
 * the binary data when looking for SAR_RESTART.
 * Return:  1 on success
 *          0 on failure
 */
static int
find_restart_header (ret_hdr)
    struct record_hdr *ret_hdr;
{
    struct record_hdr hdr;
    int bufsize = 0;
    char *buf = NULL;

    errno = 0;
    
    restart_loop:
    read_record_hdr(&hdr, FALSE);   /* exits on error */
    
    if (hdr.rec_type == SAR_RESTART)
    {
	*ret_hdr = hdr;
	if (oflag)
	    write_record_hdr(&hdr);   /* writes the RESTART record */
	if (buf)
	    free(buf);
	return(1);
    }

    /*
     * not the record we want...
     * read past data and try again
     */
    if (hdr.rec_count)
    {
	if (fflag)
	{ /* seek past data in the file */
	    if ((lseek(ifd, (hdr.rec_count * hdr.rec_size), SEEK_CUR)) == -1)
	    {
		/*exit on error */
		fprintf(stderr, "sar: lseek failed, errno=%d\n", errno);
		exit(EXIT_FAILURE);
	    }
	    
	}	    
	/* compute data size - malloc a new buf if it's not big enough */
	else 
	{
	    /* have to read from the pipe */
	    if (bufsize < (hdr.rec_count * hdr.rec_size))
	    {
		if (buf)
		    free(buf);
		bufsize = hdr.rec_count * hdr.rec_size;
		if((buf = (char *)malloc(bufsize)) == NULL)
		{
		    fprintf(stderr, "sar: malloc failed\n");
		    return(0);
		}
	    }
	    /* exits on error */
	    read_record_data(buf, (hdr.rec_count * hdr.rec_size), FALSE);
	}
    }
    goto restart_loop;
}

static void
print_all_column_headings(timestamp)
    time_t timestamp;
{
    char timebuf[26];
    char *timebufp;

    timebufp = get_hms_string (timestamp, timebuf);

    if (uflag) /* print cpu headers */
	print_column_heading(SAR_CPU, timebufp, 0);

    if (gflag) 	/* print page-out activity */
	print_column_heading(SAR_VMSTAT, timebufp, 0);

    if (pflag ) /* print page-in activity */	
	print_column_heading(SAR_VMSTAT, timebufp, 1);

    if (dflag) /* print drive stats */
	print_column_heading(SAR_DRIVESTATS, timebufp, 0);

    if (nflag) /* print network stats */
    {
	if (network_mode & NET_DEV_MODE)
	    print_column_heading(SAR_NETSTATS, timebufp, NET_DEV_MODE);	    

	if (network_mode & NET_EDEV_MODE)
	    print_column_heading(SAR_NETSTATS, timebufp, NET_EDEV_MODE);	    	    
    }
}	


/*
 * Find and fill in a timestamp header.
 * Write the binary data when looking for SAR_TIMESTAMP
 * Don't do anything with the data, just read past it.
 * Return:  1 on success
 *          0 on failure
 */
static int
bypass_sample_set (ret_hdr, timestamp)
    struct record_hdr *ret_hdr;
    time_t timestamp;
{
    struct record_hdr hdr;
    int bufsize = 0;
    char *buf = NULL;

    bypass_loop:
    read_record_hdr(&hdr, TRUE);   /* exits on error */
    
    if (hdr.rec_type == SAR_TIMESTAMP)
    {
	*ret_hdr = hdr;
	if (buf)
	    free(buf);
	return(1);
    }

    /*
     * not the record we want...
     * read past data and try again
     */
    if (hdr.rec_count)
    {
	if (fflag && !oflag)
	{
	    /*
	     * we're reading from a file and we don't have to write the
	     * binary data so seek past data in the file
	     */
	    errno = 0;
	    if ((lseek(ifd, (hdr.rec_count * hdr.rec_size), SEEK_CUR)) == -1)
	    {
		/*exit on error */
		fprintf(stderr, "sar: lseek failed, errno=%d\n", errno);
		exit(EXIT_FAILURE);
	    }	    
	}
	else
	{
	    /*
	     * We end up here when reading from pipe.
	     * malloc a new buffer if current is not big enough
	     */
	    if (bufsize < (hdr.rec_count * hdr.rec_size))
	    {
		if (buf)
		    free(buf);
		bufsize = hdr.rec_count * hdr.rec_size;
		if((buf = (char *)malloc(bufsize)) == NULL)
		{
		    fprintf(stderr, "sar: malloc failed\n");
		    exit(EXIT_FAILURE);
		}
	    }

	    /* exits on error */
	    read_record_data(buf, (hdr.rec_count * hdr.rec_size), TRUE);
	}
    } /* end if hdr.rec_count */
    goto bypass_loop;
}


/*
 * INIT_SET: This initializes the first sample for each type.
 * PRINT_SET: This read, compute and print out sample data.
 */
static void
read_sample_set(flag, timestamp, ret_hdr)
    int flag;
    time_t timestamp;
    struct record_hdr *ret_hdr;
{
    struct record_hdr hdr;
    char timebuf[26];
    char *timebufp;
    char *indent_string;
    char *indent_string_wide;
    char *indent_string_narrow;
    int sar_cpu = 0;
    int sar_vmstat=0;
    int sar_drivestats=0;
    int sar_drivepath=0;
    int sar_netstats = 0;

    indent_string_wide = "          ";
    indent_string_narrow = "  ";    
    indent_string = indent_string_narrow;
    
    read_record_hdr(&hdr, TRUE);

    while (hdr.rec_type != SAR_TIMESTAMP)
    {
	switch (hdr.rec_type)
	{
	case SAR_CPU:
	    sar_cpu = get_cpu_sample(flag, &hdr);
	    break;
	case SAR_VMSTAT:
	    sar_vmstat=get_vmstat_sample(flag, &hdr);
	    break;
	case SAR_DRIVEPATH:
	  sar_drivepath = get_drivepath_sample(flag, &hdr);
	  if (sar_drivepath < 0)
	      fprintf(stderr, "sar: drivepath sync code error %d\n", sar_drivepath);
	  break;
	case SAR_DRIVESTATS:
	    sar_drivestats = get_drivestats_sample(flag, &hdr);
	    break;
	case SAR_NETSTATS:
	    sar_netstats = get_netstats_sample(flag, &hdr);
	    break;
	default:
	    break;
	}

	read_record_hdr(&hdr, TRUE);
    }

    /* return the timestamp header */
    *ret_hdr = hdr;

    if (flag == PRINT_SET)
    {
	avg_counter++;
	timebufp = get_hms_string(timestamp, timebuf);

	if (uflag && sar_cpu)
	    print_cpu_sample(timebufp);

	if((gflag || pflag) && sar_vmstat)
	    print_vmstat_sample(timebufp);

	if (dflag && sar_drivestats)
	    print_drivestats_sample(timebufp);

	if (nflag && sar_netstats)
	    print_netstats_sample(timebufp);
    }
}

static void
skip_data(bufsize)
    int bufsize;
{
    char *buf = NULL;
    
    if (fflag)
    {
        /* seek past data in the file */
	if ((lseek(ifd, bufsize, SEEK_CUR) == -1))
	{
	    /*exit on error */
	    fprintf(stderr, "sar: lseek failed, errno=%d\n", errno);
	    exit(EXIT_FAILURE);
	}
    }	    
    else 
    {
	/* have to read from the pipe */
	if((buf = (char *)malloc(bufsize)) == NULL)
	{
	    fprintf(stderr, "sar: malloc failed\n");
	    exit(EXIT_FAILURE);
	}
	/* even though we skip this data, we still write it if necessary */
	read_record_data(buf, bufsize, TRUE);
    }
    if (buf)
	free(buf);
    
    return;
}

static int
get_cpu_sample(flag, hdr)
    int flag;
    struct record_hdr *hdr;
{
    int  datasize;

    datasize = hdr->rec_count * hdr->rec_size;
    
    if (datasize != sizeof(host_cpu_load_info_data_t))
    {
	/* read past the data but don't do anything with it */
	skip_data(datasize);
	return(0);
    }

    read_record_data ((char *)&cur_cpuload, (int)sizeof(host_cpu_load_info_data_t), TRUE );

    if (flag == INIT_SET)
    {
	prev_cpuload = cur_cpuload;
	bzero(&avg_cpuload, sizeof(avg_cpuload));
    }
    return(1);
}

static void
print_cpu_sample(timebufptr)
    char * timebufptr;
{

    double time;

    time = 0.0;
    cur_cpuload.cpu_ticks[CPU_STATE_USER]
      -= prev_cpuload.cpu_ticks[CPU_STATE_USER];
    
    prev_cpuload.cpu_ticks[CPU_STATE_USER]
      += cur_cpuload.cpu_ticks[CPU_STATE_USER];
	
    time += cur_cpuload.cpu_ticks[CPU_STATE_USER];
	
    cur_cpuload.cpu_ticks[CPU_STATE_SYSTEM]
      -= prev_cpuload.cpu_ticks[CPU_STATE_SYSTEM];
	
    prev_cpuload.cpu_ticks[CPU_STATE_SYSTEM]
      += cur_cpuload.cpu_ticks[CPU_STATE_SYSTEM];
	
    time += cur_cpuload.cpu_ticks[CPU_STATE_SYSTEM];
	
    cur_cpuload.cpu_ticks[CPU_STATE_IDLE]
      -= prev_cpuload.cpu_ticks[CPU_STATE_IDLE];
	
    prev_cpuload.cpu_ticks[CPU_STATE_IDLE]
      += cur_cpuload.cpu_ticks[CPU_STATE_IDLE];
	
    time += cur_cpuload.cpu_ticks[CPU_STATE_IDLE];

    avg_cpuload.cpu_ticks[CPU_STATE_USER] += rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_USER]
      / (time ? time : 1));

    avg_cpuload.cpu_ticks[CPU_STATE_SYSTEM] += rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_SYSTEM]
      / (time ? time : 1));
    
    avg_cpuload.cpu_ticks[CPU_STATE_IDLE] += rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_IDLE]
      / (time ? time : 1));

    if(flag_count > 1)
	print_column_heading(SAR_CPU, timebufptr, 0);

    fprintf(stdout, "%s%5.0f   ", timebufptr,
      rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_USER]
      / (time ? time : 1)));
	
    fprintf(stdout, "%4.0f   ",
      rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_SYSTEM]
      / (time ? time : 1)));
	
    fprintf(stdout, "%4.0f\n",
      rint(100. * cur_cpuload.cpu_ticks[CPU_STATE_IDLE]
      / (time ? time : 1)));
}

static int
get_vmstat_sample(flag, hdr)
    int flag;
    struct record_hdr *hdr;
{
    int  datasize;

    datasize = hdr->rec_count * hdr->rec_size;
    
    if (datasize != sizeof(struct vm_statistics))
    {
	/* read past the data but don't do anything with it */
	skip_data(datasize);
	return(0);
    }

    read_record_data ((char *)&cur_vmstat, (int)sizeof(struct vm_statistics), TRUE );

    if (flag == INIT_SET)
    {
	prev_vmstat = cur_vmstat;
	bzero(&avg_vmstat, sizeof(avg_vmstat));
    }
    return(1);
}


static void
print_vmstat_sample(char *timebufptr)
{

    cur_vmstat.faults -= prev_vmstat.faults;
    prev_vmstat.faults += cur_vmstat.faults;
    avg_vmstat.faults += cur_vmstat.faults;	

    cur_vmstat.cow_faults -= prev_vmstat.cow_faults;
    prev_vmstat.cow_faults += cur_vmstat.cow_faults;
    avg_vmstat.cow_faults += cur_vmstat.cow_faults;	

    cur_vmstat.zero_fill_count -= prev_vmstat.zero_fill_count;
    prev_vmstat.zero_fill_count += cur_vmstat.zero_fill_count;
    avg_vmstat.zero_fill_count += cur_vmstat.zero_fill_count;

    cur_vmstat.reactivations -= prev_vmstat.reactivations;
    prev_vmstat.reactivations += cur_vmstat.reactivations;
    avg_vmstat.reactivations += cur_vmstat.reactivations;	
	
    cur_vmstat.pageins -= prev_vmstat.pageins;
    prev_vmstat.pageins += cur_vmstat.pageins;
    avg_vmstat.pageins += cur_vmstat.pageins;	
	
    cur_vmstat.pageouts -= prev_vmstat.pageouts;
    prev_vmstat.pageouts += cur_vmstat.pageouts;
    avg_vmstat.pageouts += cur_vmstat.pageouts;


    if (gflag)
    {
	if (flag_count > 1)
	    print_column_heading(SAR_VMSTAT, timebufptr, 0);
	fprintf(stdout, "%s   %8.1f   \n", timebufptr, (float)((float)cur_vmstat.pageouts/avg_interval));
    }
	
    if (pflag)
    {
	if (flag_count > 1)
	    print_column_heading(SAR_VMSTAT, timebufptr, 1);
	fprintf(stdout, "%s   %8.1f      %8.1f      %8.1f\n", timebufptr,
	  (float)((float)cur_vmstat.pageins / avg_interval),
	  (float)((float)cur_vmstat.cow_faults/avg_interval),
	  (float)((float)cur_vmstat.faults/avg_interval));
    }
    fflush(stdout);
}

static int
get_drivestats_sample(flag, hdr)
    int flag;
    struct record_hdr *hdr;
{
    struct drivestats *databuf;
    struct drivestats_report *dr;
    size_t  datasize;
    int     datacount;
    int     index;    
    int     i;
    
    datasize = hdr->rec_count * hdr->rec_size;
    datacount = hdr->rec_count;
    
    if (hdr->rec_size != sizeof(struct drivestats))
    {
	/* something isn't right... read past the data but don't analyze it */
	skip_data(datasize);
	return(0);
    }

    /* malloc read buffer */
    if ((databuf = (struct drivestats *)malloc(datasize)) == NULL)
    {
	fprintf(stderr, "sar: malloc failed\n");
	exit (EXIT_FAILURE);
    }

    bzero(databuf, datasize);

    read_record_data ((char *)databuf, datasize, TRUE );
    
    /* clear all global current fields */
    for(dr = dr_head; dr; dr=(struct drivestats_report *)dr->next)
    {
	dr->present = 0;
	dr->cur_Reads = 0;
	dr->cur_BytesRead = 0;
	dr->cur_Writes = 0;
	dr->cur_BytesWritten = 0;
	dr->cur_LatentReadTime = 0;
	dr->cur_LatentWriteTime = 0;
	dr->cur_ReadErrors = 0;
	dr->cur_WriteErrors = 0;
	dr->cur_ReadRetries = 0;
	dr->cur_WriteRetries = 0;
	dr->cur_TotalReadTime = 0;
	dr->cur_TotalWriteTime=0;
    }

    /* By this point, we have read in a complete set of diskstats from the sadc
     * data collector.
     * The order of the drives in not guaranteed.
     * The global report structure is a linked list, but may need initialization
     * We need to traverse this list  and transfer the current
     * read data.  If a disk entry isn't found, then we need to allocate one
     * initilize it.
    */
    for (i=0; i< datacount; i++)
    {
	struct drivestats_report *dr_last = NULL;

	index = databuf[i].drivepath_id;   /* use this as index into dp_table */
	
	/* find disk entry or allocate new one*/
	for(dr = dr_head; dr; dr=(struct drivestats_report *)dr->next)
	{
	    dr_last = dr;
	    if(index == dr->drivepath_id)
		break;
	} 
	
	if (dr == NULL)
	{
	    /* allocate new entry */
	    if((dr = (struct drivestats_report *)malloc(sizeof(struct drivestats_report))) == NULL)
	    {
		fprintf(stderr, "sar: malloc error\n");
		exit(EXIT_FAILURE);
	    }
	    bzero((char *)dr, sizeof(struct drivestats_report));
	    dr->blocksize = databuf[i].blocksize;
	    dr->drivepath_id = index;
	    dr->next = NULL;
	    dr->avg_count = 0;

	    /* get the BSDName which should be in the table by now */
	    if ((index < dp_count) && (dp_table[index].state != DPSTATE_UNINITIALIZED))
		strncpy(dr->name, dp_table[index].BSDName, MAXDRIVENAME+1);
	    else
		strcpy(dr->name, "disk??");

	    if (dr_head == NULL)
	    {
		dr_head = dr;
		dr_head->next = NULL;
	    }
	    else
	    {
		dr_last->next = (char *)dr;
	    }
	} /* end if dr == NULL */
	
	dr->present = TRUE;
	dr->cur_Reads = databuf[i].Reads;
	dr->cur_BytesRead = databuf[i].BytesRead;
	dr->cur_Writes = databuf[i].Writes;
	dr->cur_BytesWritten = databuf[i].BytesWritten;
	dr->cur_LatentReadTime = databuf[i].LatentReadTime;
	dr->cur_LatentWriteTime = databuf[i].LatentWriteTime;
	dr->cur_ReadErrors = databuf[i].ReadErrors;
	dr->cur_WriteErrors = databuf[i].WriteErrors;
	dr->cur_ReadRetries = databuf[i].ReadRetries;
	dr->cur_WriteRetries = databuf[i].WriteRetries;
	dr->cur_TotalReadTime = databuf[i].TotalReadTime;
	dr->cur_TotalWriteTime=databuf[i].TotalWriteTime;
    } /* end for loop */
	
    /* Reinitialize the prev and avg fields when
     * This is a new disk
     * This is a changed disk - name change implies disk swapping
     * This disk is not present in this sample
     */
    for(dr = dr_head; dr; dr=(struct drivestats_report *)dr->next)
    {
	if (dr->drivepath_id >= dp_count)
	{
	    /* something is amiss */
	    continue;
	}
	else
	{
	    index = dr->drivepath_id;   /* use this as index into dp_table */
	}
	
	if ((flag == INIT_SET) ||
	  (dp_table[index].state == DPSTATE_NEW) ||
	  (dp_table[index].state == DPSTATE_CHANGED) ||
	  (!dr->present))
	{
	    /*
	     * prev will be set to cur
	     * activate the state in dp_table
	     */
	    if (dr->present)
		dp_table[index].state = DPSTATE_ACTIVE;
	    
	    init_drivestats(dr);
	}
    }
    return(1);
}

static void
init_drivestats(struct drivestats_report *dr)
{
    dr->avg_count = 0;
    dr->prev_Reads = dr->cur_Reads;
    dr->avg_Reads = 0;
    dr->prev_BytesRead = dr->cur_BytesRead;
    dr->avg_BytesRead = 0;
    dr->prev_Writes = dr->cur_Writes;
    dr->avg_Writes = 0;
    dr->prev_BytesWritten = dr->cur_BytesWritten;
    dr->avg_BytesWritten = 0;
    dr->prev_LatentReadTime = dr->cur_LatentReadTime;
    dr->avg_LatentReadTime = 0;
    dr->prev_LatentWriteTime = dr->cur_LatentWriteTime ;
    dr->avg_LatentWriteTime = 0;
    dr->prev_ReadErrors = dr->cur_ReadErrors ;
    dr->avg_ReadErrors = 0;
    dr->prev_WriteErrors = dr->cur_WriteErrors ;
    dr->avg_WriteErrors = 0;
    dr->prev_ReadRetries = dr->cur_ReadRetries ;
    dr->avg_ReadRetries = 0;
    dr->prev_WriteRetries = dr->cur_WriteRetries ;
    dr->avg_WriteRetries = 0;
    dr->prev_TotalReadTime = dr->cur_TotalReadTime ;
    dr->avg_TotalReadTime = 0;
    dr->prev_TotalWriteTime = dr->cur_TotalWriteTime ;
    dr->avg_TotalWriteTime = 0;    
}


static void
print_drivestats_sample(char *timebufptr)
{
    struct drivestats_report *dr;
    long double transfers_per_second;
    long double kb_per_transfer, mb_per_second;
    u_int64_t interval_bytes, interval_transfers, interval_blocks;
    u_int64_t interval_time;
    long double blocks_per_second, ms_per_transaction;

    if (flag_count > 1)
	print_column_heading(SAR_DRIVESTATS, timebufptr, 0);
	
    for (dr=dr_head; dr; dr=(struct drivestats_report *)dr->next)
    {
	if(!dr->present)
	    continue;

	/*
	 * This sanity check is for drives that get removed and then
	 * returned during the sampling sleep interval.  If anything
	 * looks out of sync, reinit and skip this entry.  There is
	 * no way to guard against this entirely.
	 */
	if ((dr->cur_Reads < dr->prev_Reads) ||
	  (dr->cur_BytesRead < dr->prev_BytesRead) ||
	  (dr->cur_Writes < dr->prev_Writes) ||
	  (dr->cur_BytesWritten < dr->prev_BytesWritten))
	{
	    init_drivestats(dr);
	    continue;
	}

	dr->avg_count++;

	dr->cur_Reads -= dr->prev_Reads;
	dr->prev_Reads += dr->cur_Reads;
	dr->avg_Reads += dr->cur_Reads;
	  
        dr->cur_BytesRead -= dr->prev_BytesRead;
	dr->prev_BytesRead += dr->cur_BytesRead;
	dr->avg_BytesRead += dr->cur_BytesRead;
	
	dr->cur_Writes -= dr->prev_Writes ;
	dr->prev_Writes += dr->cur_Writes ;
	dr->avg_Writes += dr->cur_Writes ;

	dr->cur_BytesWritten -= dr->prev_BytesWritten ;
	dr->prev_BytesWritten += dr->cur_BytesWritten ;
	dr->avg_BytesWritten += dr->cur_BytesWritten ;

	dr->cur_LatentReadTime -= dr->prev_LatentReadTime ;
	dr->prev_LatentReadTime += dr->cur_LatentReadTime ;
	dr->avg_LatentReadTime += dr->cur_LatentReadTime ;

	dr->cur_LatentWriteTime -= dr->prev_LatentWriteTime ;
	dr->prev_LatentWriteTime += dr->cur_LatentWriteTime ;
	dr->avg_LatentWriteTime += dr->cur_LatentWriteTime ;	

	dr->cur_ReadErrors -= dr->prev_ReadErrors ;
	dr->prev_ReadErrors += dr->cur_ReadErrors ;
	dr->avg_ReadErrors += dr->cur_ReadErrors ;

	dr->cur_WriteErrors -= dr->prev_WriteErrors ;
	dr->prev_WriteErrors += dr->cur_WriteErrors ;
	dr->avg_WriteErrors += dr->cur_WriteErrors ;

	dr->cur_ReadRetries -= dr->prev_ReadRetries ;
	dr->prev_ReadRetries += dr->cur_ReadRetries ;
	dr->avg_ReadRetries += dr->cur_ReadRetries ;

	dr->cur_WriteRetries -= dr->prev_WriteRetries ;
	dr->prev_WriteRetries += dr->cur_WriteRetries;
	dr->avg_WriteRetries += dr->cur_WriteRetries;

	dr->cur_TotalReadTime -= dr->prev_TotalReadTime ;
	dr->prev_TotalReadTime += dr->cur_TotalReadTime ;
	dr->avg_TotalReadTime += dr->cur_TotalReadTime ;

	dr->cur_TotalWriteTime -= dr->prev_TotalWriteTime ;
	dr->prev_TotalWriteTime += dr->cur_TotalWriteTime ;
	dr->avg_TotalWriteTime += dr->cur_TotalWriteTime ;

	/* I/O volume */
	interval_bytes = dr->cur_BytesRead + dr->cur_BytesWritten;

	/* I/O counts */
	interval_transfers = dr->cur_Reads + dr->cur_Writes;

	/* I/O time */
	interval_time = dr->cur_LatentReadTime + dr->cur_LatentWriteTime;

	interval_blocks = interval_bytes / dr->blocksize;
	blocks_per_second = interval_blocks / avg_interval;
	transfers_per_second = interval_transfers / avg_interval;
	mb_per_second = (interval_bytes / avg_interval) / (1024 *1024);

	kb_per_transfer = (interval_transfers > 0) ?
	  ((long double)interval_bytes / interval_transfers)
	  / 1024 : 0;

	/* times are in nanoseconds, convert to milliseconds */
	ms_per_transaction = (interval_transfers > 0) ?
	  ((long double)interval_time / interval_transfers)
	  / 1000 : 0;

	/* print device name */
	fprintf(stdout, "%s   %-10s", timebufptr, dr->name);
	  
	/* print transfers per second */
	fprintf(stdout, "%4.0Lf       ", transfers_per_second);
	
	/* print blocks per second - in device blocksize */
	fprintf(stdout, "%4.0Lf\n", blocks_per_second);
    }
}

/*
 * Print averages before exiting.
 */
static void
exit_average()
{
    int i;
    
    if (avg_counter <= 0 )
	exit(0);

    if (oflag)
      {
	if (ofd)
	  close (ofd);
	ofd = 0;
      }

    if (uflag) /* print cpu averages */
    {
	if(flag_count > 1)
	    print_column_heading(SAR_CPU, 0, 0);
    
        fprintf(stdout, "Average:  %5d   ",
	   (int)avg_cpuload.cpu_ticks[CPU_STATE_USER]
	  / (avg_counter ? avg_counter : 1));

	fprintf(stdout, "%4d   ", 
	   (int)avg_cpuload.cpu_ticks[CPU_STATE_SYSTEM]
	  / (avg_counter ? avg_counter : 1));

	fprintf(stdout, "%4d   \n",
	   (int)avg_cpuload.cpu_ticks[CPU_STATE_IDLE]
	  / (avg_counter ? avg_counter : 1));
	
	fflush(stdout);	
    }    


    if (gflag) /* print page-out averages */
    {
	if (flag_count > 1)
	    print_column_heading(SAR_VMSTAT, 0, 0);
	
	fprintf(stdout, "Average:   %8.1f\n",
	(float)((avg_vmstat.pageouts / (avg_counter ? avg_counter : 1)) / avg_interval));
	fflush(stdout);	
    }

    if (pflag) /* print page-in averages */
    {
	if (flag_count > 1)
	    print_column_heading(SAR_VMSTAT, 0, 1);	    
	
	fprintf(stdout, "Average:   %8.1f      %8.1f      %8.1f\n",
	  (float)(((float)avg_vmstat.pageins / (avg_counter ? avg_counter : 1)) / avg_interval),
	  (float)(((float)avg_vmstat.cow_faults / (avg_counter ? avg_counter : 1)) / avg_interval),
	  (float)(((float)avg_vmstat.faults / (avg_counter ? avg_counter : 1)) / avg_interval));
	fflush(stdout);
    }

    if (dflag) /* print drivestats averages */
    {
	struct drivestats_report *dr;
	long double transfers_per_second;
	long double kb_per_transfer, mb_per_second;
	u_int64_t total_bytes, total_transfers, total_blocks;
	u_int64_t total_time;
	long double blocks_per_second, ms_per_transaction;
	int msdig;

	if (flag_count > 1)
	    print_column_heading(SAR_DRIVESTATS, 0, 0);

	for (dr=dr_head; dr; dr=(struct drivestats_report *)dr->next)
	{
	    /* don't bother to print out averages for disks that were removed */
	    if (!dr->present)
		continue;

	    fprintf(stdout, "           %s    %s\n",
	      dp_table[dr->drivepath_id].BSDName, dp_table[dr->drivepath_id].ioreg_path);	    
	    
	    /* I/O volume */
	    total_bytes = dr->avg_BytesRead + dr->avg_BytesWritten;

	    /* I/O counts */
	    total_transfers = dr->avg_Reads + dr->avg_Writes;

	    /* I/O time */
	    total_time = dr->avg_LatentReadTime + dr->avg_LatentWriteTime;

	    total_blocks = total_bytes / dr->blocksize;
	    blocks_per_second = total_blocks / avg_interval;
	    transfers_per_second = total_transfers / avg_interval;
	    mb_per_second = (total_bytes / avg_interval) / (1024 *1024);

	    kb_per_transfer = (total_transfers > 0) ?
	      ((long double)total_bytes / total_transfers)
	      / 1024 : 0;

	    /* times are in nanoseconds, convert to milliseconds */
	    ms_per_transaction = (total_transfers > 0) ?
	      ((long double)total_time / total_transfers)
	      / 1000 : 0;
	    msdig = (ms_per_transaction < 100.0) ? 1 : 0;
	    fprintf(stdout, "Average:   %-10s %4.0Lf      %4.0Lf\n",
	      dr->name,
	      (transfers_per_second / dr->avg_count),
	      (blocks_per_second / dr->avg_count));
	    
	    fflush(stdout);	
	}
    } /* end if dflag */

    if (nflag)
    {
	int avg_count;
	
	if (network_mode & NET_DEV_MODE)	    
	{
	    if (flag_count > 1)
		print_column_heading(SAR_NETSTATS, 0, NET_DEV_MODE);
	    for (i = 0; i < nr_count; i++)
	    {
		if (!nr_table[i].valid)
		    continue;

		if(nr_table[i].avg_count == 0)
		    avg_count = 1;
		else
		    avg_count = nr_table[i].avg_count;

		fprintf(stdout, "Average:   %-8.8s", nr_table[i].tname_unit);
	    
		fprintf (stdout, "%8llu    ",
		  ((nr_table[i].avg_ipackets / avg_count) / avg_interval));

		fprintf (stdout, "%10llu    ",
		  ((nr_table[i].avg_ibytes / avg_count) / avg_interval));

		fprintf (stdout, "%8llu    ",
		  ((nr_table[i].avg_opackets / avg_count) / avg_interval));
		
		fprintf (stdout, "%10llu\n",
		  ((nr_table[i].avg_obytes / avg_count) / avg_interval));
		
		fflush(stdout);
	    }
	}

	if (network_mode & NET_EDEV_MODE)	    
	{

	    if(flag_count > 1)
		print_column_heading(SAR_NETSTATS, 0, NET_EDEV_MODE);

	    for (i = 0; i < nr_count; i++)
	    {
		if (!nr_table[i].valid)
		    continue;

		if(nr_table[i].avg_count == 0)
		    avg_count = 1;
		else
		    avg_count = nr_table[i].avg_count;

		fprintf(stdout, "Average:   %-8.8s  ", nr_table[i].tname_unit);
	    
		fprintf (stdout, "%7llu    ",		  
		  ((nr_table[i].avg_ierrors / avg_count) / avg_interval));
		
		fprintf (stdout, "%7llu    ",
		  ((nr_table[i].avg_oerrors / avg_count) / avg_interval));

		fprintf (stdout, "%5llu    ",
		  ((nr_table[i].avg_collisions / avg_count) / avg_interval));

		fprintf (stdout, "   %5llu\n",
		  ((nr_table[i].avg_drops / avg_count) / avg_interval));
		
		fflush(stdout);
	    }
	}	

    } /* end if nflag */
    exit(0);
}


/*
 * Return < 0 failure, debugging purposes only
 * Return = 0 data skipped
 * Return > 0 success
 */
  
static int
get_drivepath_sample(flag, hdr)
    int flag;
    struct record_hdr *hdr;
{
    size_t datasize;
    struct drivepath dp;
    struct drivestats_report *dr;
    int i, n;

    datasize = hdr->rec_count * hdr->rec_size;

    if (datasize != sizeof(struct drivepath))
    {
	/* read past the data but don't do anything with it */
	skip_data(datasize);
	return(0);
    }

    read_record_data ((char *)&dp, (int)sizeof(struct drivepath), TRUE );

    /*
     * If state is new -- put a new entry in the dp_table.
     * If state is changed -- traverse the drivestats_report table
     * and copy new name.
     */
    if (dp.state == DPSTATE_NEW)
    {

	if (dp_table == NULL)
	{
	    if (dp.drivepath_id != 0)
		return(-1);
	    /* First setup of internal drivepath table */
	    dp_table = (struct drivepath *)malloc(sizeof(struct drivepath));
	    if (dp_table == NULL)
		return(-2);
	    dp_count = 1;
	}

	if (dflag)
	    fprintf(stdout, "New Disk: [%s] %s\n", dp.BSDName, dp.ioreg_path);

	/* traverse table and find next uninitialized entry */
	for (i = 0; i< dp_count; i++)
	{
	    if (dp_table[i].state == DPSTATE_UNINITIALIZED)
	    {
		if (dp.drivepath_id != i)
		{
		    /* the table is out of sync - this should not happen */
		    return (-3);
		}
		dp_table[i] = dp;
		return(1);
	    }
	}
	/*
	 * If we get here, we've run out of table entries.
	 * Double the size of the table, then assign the next entry.
	 */
	if (dp.drivepath_id != i)
	{
	    /* the table is out of sync - this should not happen */
	    return (-4);
	}
	n = dp_count * 2;
	dp_table = (struct drivepath *)realloc(dp_table, n * sizeof(struct drivepath));
	bzero(&dp_table[dp_count], dp_count * sizeof(struct drivepath));
	dp_table[dp_count] = dp;
	dp_count = n;
	return(1);

    }
    else if (dp.state == DPSTATE_CHANGED)
    {

	  /* Update the name in the table */
	if ((dp.drivepath_id < dp_count) && (dp_table[dp.drivepath_id].state != DPSTATE_UNINITIALIZED))
	{
	    if (strcmp(dp_table[dp.drivepath_id].ioreg_path, dp.ioreg_path) != 0)
	    {
		/* something is amiss */
		return (-5);
	    }
	    else
	    {
		if (dflag)
		{
		    fprintf(stdout, "Change: [%s] %s\n", dp.BSDName,
		      dp_table[dp.drivepath_id].ioreg_path);
		}
		strcpy(dp_table[dp.drivepath_id].BSDName, dp.BSDName);

		for(dr = dr_head; dr; dr=(struct drivestats_report *)dr->next)
		  {
		    if (dr->drivepath_id == dp.drivepath_id)
		      strcpy(dr->name, dp.BSDName);
		  }
		return(1);
	    }
	}
	else
	    return(-6);
    }
    return(-7);
}

/*
 * Bytes and packet counts are used to track
 * counter wraps.  So, don't enforce the
 * NET_DEV_MODE or NET_EDEV_MODE  in here.
 * Maintain all the stats.
 */
static void
set_cur_netstats(struct netstats_report *nr, struct netstats *ns)
{

    nr->cur_ipackets   = ns->net_ipackets;
    nr->cur_ibytes     = ns->net_ibytes;
    nr->cur_opackets   = ns->net_opackets;
    nr->cur_obytes     = ns->net_obytes;

    nr->cur_ierrors    = ns->net_ierrors;
    nr->cur_oerrors    = ns->net_oerrors;
    nr->cur_collisions = ns->net_collisions;
    nr->cur_drops      = ns->net_drops;

    nr->cur_imcasts    = ns->net_imcasts;
    nr->cur_omcasts    = ns->net_omcasts;

}

static void
init_prev_netstats(struct netstats_report *nr)
{
    nr->avg_count = 0;
    nr->valid = 1;
    nr->present = 1;

    nr->prev_ipackets = nr->cur_ipackets;
    nr->avg_ipackets  = 0;
    nr->prev_ibytes   = nr->cur_ibytes;
    nr->avg_ibytes    = 0;	
    nr->prev_opackets = nr->cur_opackets;
    nr->avg_opackets  = 0;
    nr->prev_obytes   = nr->cur_obytes;
    nr->avg_obytes    = 0;

    nr->prev_ierrors  = nr->cur_ierrors;
    nr->avg_ierrors   = 0;
    nr->prev_oerrors  = nr->cur_oerrors ;
    nr->avg_oerrors   = 0;
    nr->prev_collisions = nr->cur_collisions ;
    nr->avg_collisions  = 0;
    nr->prev_drops  = nr->cur_drops ;
    nr->avg_drops   = 0;

    /* track these, but never displayed */    
    nr->prev_imcasts  = nr->cur_imcasts;
    nr->avg_imcasts = 0;
    nr->prev_omcasts  = nr->cur_omcasts;
    nr->avg_omcasts = 0;    
}

/*
 * Success : 1
 * Failure : 0
 */
static int
get_netstats_sample(flag, hdr)
    int flag;
    struct record_hdr *hdr;
{
    struct netstats *databuf = NULL;
    size_t datasize;
    int    datacount;
    int    i, j;

    datasize = hdr->rec_count * hdr->rec_size;
    datacount = hdr->rec_count;

    if (hdr->rec_size != sizeof(struct netstats))
    {
	/* something isn't right... read past the data but don't analyze it */
	skip_data(datasize);
	return(0);
    }

    /* malloc new or bigger read buffer */
    if((netstat_readbuf == NULL) || (netstat_readbuf_size < datasize))
    {
	if (netstat_readbuf)
	    free (netstat_readbuf);
	
	if ((netstat_readbuf = (struct netstats *)malloc(datasize)) == NULL)
	{
	    fprintf(stderr, "sar: malloc failed\n");
	    exit (EXIT_FAILURE);
	}
	netstat_readbuf_size = datasize;
    }

    bzero(netstat_readbuf, netstat_readbuf_size);
    databuf = netstat_readbuf;

    read_record_data ((char *)databuf, datasize, TRUE );    

    if (nr_table == NULL)
    {
	/* initial internal table setup */
	nr_table = (struct netstats_report *)malloc(datacount * sizeof(struct netstats_report));
	nr_count = datacount;
	bzero(nr_table, (datacount * sizeof(struct netstats_report)));

	/* on first init, this is faster than finding our way to NEW_ENTRY */
	for (i = 0; i < datacount; i++)
	{
	    if (!(network_mode & NET_PPP_MODE))
	    {
		if (!strncmp(databuf[i].tname_unit, "ppp", 3))
		    continue;   /*
				 * Skip ppp interfaces.
				 * ie don't even put them in this internal table.
				 */
	    }
	    strncpy(nr_table[i].tname_unit, databuf[i].tname_unit, MAX_TNAME_UNIT_SIZE);
	    nr_table[i].tname_unit[MAX_TNAME_UNIT_SIZE] = '\0';
	    set_cur_netstats(&nr_table[i], &databuf[i]);
	    init_prev_netstats(&nr_table[i]);
	}
	return(1);
    }

    /*
     * clear all the present flags.
     * As we traverse the current sample set
     * and update the internal table, the flag
     * is reset.
     */
    for (i = 0; i < nr_count; i++)
    {
	nr_table[i].present = 0;
    }

    /*
     * Find and update table entries.
     * Init new entries.
     */
    for (i=0; i<datacount; i++)
    {
	int found;
	char *name;
	int nr_index;
	int n;

	name = databuf[i].tname_unit;
	found = 0;

	if (!(network_mode & NET_PPP_MODE))
	{
	    if (!strncmp(name, "ppp", 3))
		continue;   /* skip ppp interfaces */
	}

	/* Find the matching entry using the interface name */
	for (j=0; j < nr_count && !found; j++)
	{
	    if (nr_table[j].valid)
	    {
		if(!strcmp(nr_table[j].tname_unit, name))
		{
		    found = 1;
		    nr_table[j].present = 1;
		    set_cur_netstats(&nr_table[j], &databuf[i]);
		}
	    }
	} /* end for */

	if (!found)  /* this is a new entry */
	{
	    /* Find an invalid entry in the table and init it */
	    for (j=0; j < nr_count; j++)
	    {
		if (!nr_table[j].valid)
		{
		    nr_index = j;
		    goto NEW_ENTRY;
		}
	    }

	    /* we ran out of entries... grow the table */
	    n = nr_count * 2;
	    nr_table = (struct netstats_report *)realloc(nr_table, n * sizeof(struct netstats_report));
	    bzero(&nr_table[nr_count], nr_count * sizeof (struct netstats_report));
	    nr_index = nr_count;
	    nr_count = n;
	    
	    NEW_ENTRY:
	    strncpy(nr_table[nr_index].tname_unit, databuf[i].tname_unit, MAX_TNAME_UNIT_SIZE);
	    nr_table[nr_index].tname_unit[MAX_TNAME_UNIT_SIZE] = '\0';
	    set_cur_netstats(&nr_table[nr_index], &databuf[i]);
	    init_prev_netstats(&nr_table[nr_index]);
	}
	
    } /* end for */

    /*
     * Traverse the internal table.  Any valid entry that wasn't
     * present in this sample is cleared for reuse.
     */
    for (i = 0; i < nr_count; i++)
    {
	if (nr_table[i].valid)
	{
	    if (nr_table[i].present == 0)
		bzero(&nr_table[i], sizeof(struct netstats_report));
	}
    }
    return (1);
}

static void
print_netstats_sample(char *timebufptr)
{
    int i;

    for (i=0; i < nr_count; i++)
    {
	if (!nr_table[i].valid)
	    continue;

	/*
	 * This is where we attempt to handle counters that
	 * might wrap ... the kernel netstats are only 32 bits.
	 *
	 * Interfaces may go away and then return within the
	 * sampling period.  This can't be detected and it
	 * may look like a counter wrap.  An interface generation
	 * counter will help... but isn't implemented at this time.
	 */

	/* 
	 * The ppp interfaces are very likely to come and go during
	 * a sampling period.  During the normal life of a ppp interface,
	 * it's less likely that the packet counter will wrap, so if
	 * it appears to have done so, is probably because the
	 * interface unit number has been reused. 
	 * We reinitialize that interface in that case.
	 */
	if (network_mode & NET_PPP_MODE)
	{
	    /*
	     * ppp interfaces won't even make it into this table
	     * when NET_PPP_MODE isn't set
	    */
	    if (!strncmp(nr_table[i].tname_unit, "ppp", 3))
	    {
		/*
		 * Both ipackets and opackets have to be less
		 * than the previous counter to cause us to reinit.
		 */

		if ((nr_table[i].cur_ipackets < nr_table[i].prev_ipackets)
		  && (nr_table[i].cur_opackets < nr_table[i].prev_opackets))
		{
		    init_prev_netstats(&nr_table[i]);
		    continue;
		}
	    }
	}

	nr_table[i].avg_count ++;

#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_ipackets < nr_table[i].prev_ipackets)
	    nr_table[i].cur_ipackets += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_ipackets -= nr_table[i].prev_ipackets;
	nr_table[i].prev_ipackets += nr_table[i].cur_ipackets;
	nr_table[i].avg_ipackets += nr_table[i].cur_ipackets;
	

#ifdef IFNET_32_BIT_COUNTERS	
	while (nr_table[i].cur_ibytes < nr_table[i].prev_ibytes)
	    nr_table[i].cur_ibytes += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_ibytes -= nr_table[i].prev_ibytes;
	nr_table[i].prev_ibytes += nr_table[i].cur_ibytes;
	nr_table[i].avg_ibytes += nr_table[i].cur_ibytes;


#ifdef IFNET_32_BIT_COUNTERS	
	while (nr_table[i].cur_opackets < nr_table[i].prev_opackets)
	    nr_table[i].cur_opackets += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_opackets -= nr_table[i].prev_opackets;
	nr_table[i].prev_opackets += nr_table[i].cur_opackets;
	nr_table[i].avg_opackets += nr_table[i].cur_opackets;

#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_obytes < nr_table[i].prev_obytes)
	    nr_table[i].cur_obytes += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_obytes -= nr_table[i].prev_obytes;
	nr_table[i].prev_obytes += nr_table[i].cur_obytes;
	nr_table[i].avg_obytes += nr_table[i].cur_obytes;


#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_ierrors < nr_table[i].prev_ierrors)
	    nr_table[i].cur_ierrors += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_ierrors -= nr_table[i].prev_ierrors;
	nr_table[i].prev_ierrors += nr_table[i].cur_ierrors;
	nr_table[i].avg_ierrors += nr_table[i].cur_ierrors;

#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_oerrors < nr_table[i].prev_oerrors)
	    nr_table[i].cur_oerrors += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_oerrors -= nr_table[i].prev_oerrors;
	nr_table[i].prev_oerrors += nr_table[i].cur_oerrors;
	nr_table[i].avg_oerrors += nr_table[i].cur_oerrors;

#ifdef IFNET_32_BIT_COUNTERS	
	while (nr_table[i].cur_collisions < nr_table[i].prev_collisions)
	    nr_table[i].cur_collisions += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_collisions -= nr_table[i].prev_collisions;
	nr_table[i].prev_collisions += nr_table[i].cur_collisions;
	nr_table[i].avg_collisions += nr_table[i].cur_collisions;

#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_drops < nr_table[i].prev_drops)
	    nr_table[i].cur_drops += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_drops -= nr_table[i].prev_drops;
	nr_table[i].prev_drops += nr_table[i].cur_drops;
	nr_table[i].avg_drops += nr_table[i].cur_drops;

	
#ifdef IFNET_32_BIT_COUNTERS	
	while (nr_table[i].cur_imcasts < nr_table[i].prev_imcasts)
	    nr_table[i].cur_imcasts += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_imcasts -= nr_table[i].prev_imcasts;
	nr_table[i].prev_imcasts += nr_table[i].cur_imcasts;
	nr_table[i].avg_imcasts += nr_table[i].cur_imcasts;

#ifdef IFNET_32_BIT_COUNTERS
	while (nr_table[i].cur_omcasts < nr_table[i].prev_omcasts)
	    nr_table[i].cur_omcasts += 0x100000000LL;
#endif /* IFNET_32_BIT_COUNTERS */
	nr_table[i].cur_omcasts -= nr_table[i].prev_omcasts;
	nr_table[i].prev_omcasts += nr_table[i].cur_omcasts;
	nr_table[i].avg_omcasts += nr_table[i].cur_omcasts;
    }


    if (!(flag_count > 1))
	fprintf(stdout, "\n");
    
    if (network_mode & NET_DEV_MODE)
    {
	if (flag_count > 1)
	    print_column_heading(SAR_NETSTATS, timebufptr, NET_DEV_MODE);
	
	for (i=0; i < nr_count; i++)
	{
	    if (!nr_table[i].valid)
		continue;

	    if (!(network_mode & NET_PPP_MODE))
	    {
		if (!strncmp(nr_table[i].tname_unit, "ppp", 3))
		{
		    continue;  /* skip any ppp interfaces */
		}
	    }
	
	    /* print the interface name */
	    fprintf(stdout, "%s    %-8.8s", timebufptr, nr_table[i].tname_unit);

	    fprintf (stdout, "%8llu    ",
	      (nr_table[i].cur_ipackets / avg_interval));

	    fprintf (stdout, "%10llu    ",
	      (nr_table[i].cur_ibytes / avg_interval));

	    fprintf (stdout, "%8llu    ",
	      (nr_table[i].cur_opackets / avg_interval));

	    fprintf (stdout, "%10llu\n",
	      (nr_table[i].cur_obytes / avg_interval));
	}
    }

    
    if (network_mode & NET_EDEV_MODE)
    {
	if(flag_count > 1)
	{
	    print_column_heading(SAR_NETSTATS, timebufptr, NET_EDEV_MODE);
	}
    
	for (i=0; i < nr_count; i++)
	{
	    if (!nr_table[i].valid)
		continue;
	    
	    if (!(network_mode & NET_PPP_MODE))
	    {
		if (!strncmp(nr_table[i].tname_unit, "ppp", 3))
		{
		    continue;  /* skip any ppp interfaces */
		}
	    }	

	    /* print the interface name */
	    fprintf(stdout, "%s    %-8.8s  ", timebufptr, nr_table[i].tname_unit);
	    
	    fprintf (stdout, "%7llu    ",
	      (nr_table[i].cur_ierrors / avg_interval));
	    
	    fprintf (stdout, "%7llu    ",
	      (nr_table[i].cur_oerrors / avg_interval));
		
	    fprintf (stdout, "%5llu    ",
	      (nr_table[i].cur_collisions / avg_interval));
	    
	    fprintf (stdout, "   %5llu\n",
	      (nr_table[i].cur_drops / avg_interval));
	}
	fflush(stdout);
    }
}

static void
print_column_heading(int type, char *timebufptr, int mode)
{
    char *p;

    p = timebufptr;
    
    if (p == NULL)
	p = "Average:";

    if (!(flag_count > 1))
      fprintf(stdout, "\n");

    switch (type)
    {
    case SAR_CPU:
	fprintf (stdout, "\n%s  %%usr   %%sys   %%idle\n", p);
	break;
	
    case SAR_VMSTAT:
	if (mode == 0)  /* gflag */
	    fprintf(stdout, "\n%s    pgout/s\n", p);
	else if (mode == 1)  /* pflag */
	    fprintf(stdout, "\n%s     pgin/s        pflt/s        vflt/s\n", p);	    
	break;	
    case SAR_DRIVESTATS:
	fprintf(stdout, "\n%s   device    r+w/s    blks/s\n", p);	
	break;
    case SAR_NETSTATS:
	if (mode == NET_DEV_MODE)
	{	    
	    fprintf(stdout, "\n%s %-8.8s   %8.8s    %10.10s    %8.8s    %10.10s\n", p,
	      "   IFACE", "Ipkts/s", "Ibytes/s", "Opkts/s", "Obytes/s");
	}
	else if (mode == NET_EDEV_MODE)
	{
	    fprintf(stdout, "\n%s %-8.8s     %7.7s     %7.7s    %5s      %s\n", p,
	      "   IFACE", "Ierrs/s", "Oerrs/s", "Coll/s", "Drop/s");
	}
	break;	
    default:
	break;
    }
}

