
/****************************************************************************/
/*                                                                          */
/*      NNstat -- Internet Statistics Collection Package                    */
/*                                                                          */
/*            Written by: Bob Braden & Annette DeSchon                      */
/*            USC Information Sciences Institute                            */
/*            Marina del Rey, California                                    */
/*                                                                          */
/*      Copyright (c) 1991 University of Southern California.               */
/*      All rights reserved.                                                */
/*                                                                          */
/*      Redistribution and use in source and binary forms are permitted     */
/*      provided that the above copyright notice and this paragraph are     */
/*      duplicated in all such forms and that any documentation,            */
/*      advertising materials, and other materials related to such          */
/*      distribution and use acknowledge that the software was              */
/*      developed by the University of Southern California, Information     */
/*      Sciences Institute.  The name of the University may not be used     */
/*      to endorse or promote products derived from this software           */
/*      without specific prior written permission.                          */
/*      THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR        */
/*      IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED      */
/*      WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR          */
/*      PURPOSE.                                                            */
/*                                                                          */
/****************************************************************************/

static char rcsid[]=
  "$Header: statio.c,v 1.9 93/10/11 18:39:55 mogul Exp $";

/*                           statio.c
 * 
 *     This file contains the common network I/O and display routines
 *     used by statspy, rspy, and collect.
 */

/* CHANGES:
 *   19Nov88 ISI:  tweak SHOW format.
 *    5Sep89 LANL:  Fix SPARC problem in print_val.
 *   13Oct89 Merit: Add option to print time in GMT.
 *      Rel 2.4:
 *    1Nov89 ISI: Add code for new objects FAB, FOB, MAB, MSB.
 *                Modify calling seqs of MA_list, FAO_list, MFB_list, ReadFO.
 *   30Nov89 Merit/ISI: Terse mode changes. 
 *      Rel 3.0:
 *   ISI: Changes for language extensions: add xdr_invoke() calling sequence, 
 *        add xdr_case(), new parm in SHOWinv(), changes in SHOWsublist().
 *   ISI: Move most list and read I/O routines to corresponding objects:
 *        MA_list(), MFB_list(), ReadWS(), ReadP2(), ReadBP(), ReadFO(),
 *        ReadHI(), readhist().
 *   SGI: Support 1-column mode, a subset of terse mode.
 *   Aug93/DECWRL:
 *	Alpha/OSF port; XDR is a pointer type; support for PRINT_NETLOAD
 */
 
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <rpc/rpc.h>
#include <netdb.h>
#include "stat.h"
#include "sobj.h"

extern int asock ; 
char *Get() ;
extern XDR W_XDR;

#define MAXCOLS 80
int   MaxItemSize = 0, LineUsed = 0, prevW = 0;
int  aligncnt;

int print_utime = 0;
boolean tersemode = FALSE, onecolmode = FALSE;

char *IfaceType = "Ethernet";

extern char *class_name[];
extern char *ether_ntoa();

void MakeCols();
void print_delta();
void print_time();
void hex_dump();
void hex_print();
void print_val();
void print_percent();
void print_dbl();
#ifdef PRINT_NETLOAD
void print_netload_percent();
#endif

/********************************************************************
 *
 *                      Read I/O ROUTINES
 *
 *   These routines are used by both statspy and collect to implement
 *   Read operations on particular object classes.  Each routine may be 
 *   called to perform any of the following functions:
 *
 *     In collect -- decode the data from XDR and format a display of it.
 *     In statspy (local read) -- format a display of the given data. 
 *     In statspy (remote read) -- encode the given data into XDR. 
 *
 *   Each routine returns FALSE if any XDR routine returns FALSE,
 *   otherwise it returns TRUE.
 *
 *
 ********************************************************************/
 
    /*
     *  ReadStd: Encode/decode and/or display the standard contents of
     *  a statistical object.
     *   */

boolean ReadStd(xdrs, outp, outl, objp)
    register XDR *xdrs;
    register char *outp;
    int outl;
    register OBJ *objp;
{
    int n;
    char *cp = objp->obj_name;
    
    if (xdrs) {
       if (!xdr_string(xdrs, &cp, MAX_OBJNAME) ||
           !xdr_int(xdrs, &objp->obj_totlength) ||
           !xdr_int(xdrs, &objp->obj_state.currtime) ||
           !xdr_int(xdrs, &objp->obj_state.maketime) ||
           !xdr_int(xdrs, &objp->obj_state.cleartime) ||
           !xdr_u_int(xdrs, &objp->obj_state.totalno) ||
           !xdr_u_int(xdrs, &objp->obj_state.orphans) ||
           !xdr_int(xdrs, &objp->obj_state.datatype) ||
           !xdr_int(xdrs, &objp->obj_state.dleng) ||
           !xdr_int(xdrs, &objp->obj_state.datatyp2) ||
           !xdr_int(xdrs, &objp->obj_state.dleng2) ||
           !xdr_int(xdrs, &objp->obj_class) 
          )
          return(FALSE);
          }
    if (outl) {
        if (outl < 140) return(FALSE);
        sprintf(outp, "\nOBJECT: %s  Class= %s  [Created: ",
            objp->obj_name,
            class_name[objp->obj_class]);
        print_time(endof(outp), objp->obj_state.maketime) ;
        strcat(outp, "]\n  ReadTime: ");
        print_time(endof(outp), objp->obj_state.currtime);
        strcat(outp, ", ClearTime: ");
        print_time(endof(outp), objp->obj_state.cleartime);
        print_delta(endof(outp), objp->obj_state.cleartime, 
                     objp->obj_state.currtime) ;
        sprintf(endof(outp), "\n  Total Count= %d (+%d orphans)\n",
            objp->obj_state.totalno - objp->obj_state.orphans, /*31Dec*/
            objp->obj_state.orphans) ;
        outp += (n = strlen(outp));
        outl -= n;
    }
    return(TRUE);
} /* ReadStd */


    /*
     *  FAO_list() --  I/O routine for single entry of freq-all or 
     *                      freq-only read data.
     *
     *     objstp: contains parameters of object
     *     mfbep:  pts to element that may be sent or received (determined by
     *             direction of xdrs), and perhaps (if outl > 0)
     *             displayed into buffer: char *outp.
     *
     *     class:  FAOBJ | FOOBJ | FOBOBJ
     *
     *      Note: assumes FODE is subset of FADE structure.
     */
boolean FAO_list(xdrs, outp, outl, faep, objstp, enumh, class)
    register XDR  *xdrs;
    char *outp;
    int outl;
    FADE  *faep;
    struct Obj_state *objstp;
    EnumHandle   enumh;
    int class;
    {
    register char *cp = outp;
    char *Enump, *FindEnum();
    int i;
    char bytbuf[20];
    FOBDE *fobep = (FOBDE *) faep;
    u_char *valp = (class == FOBOBJ)? fobep->fob_value: 
                                       faep->fad_value;
    
    if (faep == NULL) return(FALSE);
    if (xdrs) {
        if (class == FOBOBJ) {
            if (!xdr_u_int(xdrs, &fobep->fob_count) ||
                !xdr_opaque(xdrs, &fobep->fob_Hbyte, sizeof(struct dblword)) ||
                !xdr_opaque(xdrs, fobep->fob_value, objstp->dleng) )
                     return(FALSE);
        }
        else { 
            if (!xdr_u_int(xdrs, &faep->fad_count) ||
                !xdr_opaque(xdrs, faep->fad_value, objstp->dleng))
                    return(FALSE);
            if (class == FAOBJ)  {
                if (!xdr_int(xdrs, &faep->fad_time))
                    return(FALSE);
            }
        }
    }
        
    if (outl) {
        if (outl < 80) return(FALSE);
        *cp++ = '[';
        print_val(cp, valp, objstp->datatype, objstp->dleng);
        if (Enump = FindEnum(enumh, valp, objstp->dleng)) {
            strcat(cp, " \"");
            strcat(cp,  Enump);
            strcat(cp, "\"");
            cp = endof(cp);
        }
        sprintf(endof(cp), "]= %d ", faep->fad_count); /* (same for either) */
        if (class == FOBOBJ) {
            print_dbl(bytbuf, fobep->fob_Hbyte, fobep->fob_Lbyte);
            sprintf(endof(cp), "&%sB ", bytbuf);
        }
        if ((i = Align(outp)) != 0)
           sprintf(endof(cp), "%*c", i, ' ');
        print_percent(endof(cp), objstp->totalno, faep->fad_count);
#ifdef PRINT_NETLOAD
        if (class == FOBOBJ)
            print_netload_percent(endof(outp),
                fobep->fob_Hbyte, fobep->fob_Lbyte, faep->fad_count,
                objstp->currtime - objstp->cleartime);
#endif
        if (class == FAOBJ)   /* FA -- add "@- kkksecs" */
           sprintf(endof(cp), "@-%dsec ", objstp->currtime - faep->fad_time);
        
        MakeCols(outp);
    }
    return(TRUE);
} /* FAO_list */
    
    
/*       
 *       boolean xdr_invoke() --
 *
 *   Routine to do XDR encode/decode for invocation request
 *             within ATTACH command.
 */
boolean
xdr_invoke(xdrs, invp)
    XDR *xdrs;
    struct invokes *invp;
    {
    char *cpf1 = invp->inv_field1,
         *cpf2 = invp->inv_field2,
         *cpobj= invp->inv_objname;
    u_int32 *valp = invp->inv_parmp;
    int parmbytes = 4*(invp->inv_nparms);

    if( !xdr_string(xdrs, &cpf1, MAX_FLDNAME) ||
        !xdr_string(xdrs, &cpf2, MAX_FLDNAME) ||
        !xdr_string(xdrs, &cpobj, MAX_OBJNAME) ||
        !xdr_int(xdrs, &invp->inv_classno))
        return(FALSE);
    if (valp) {
        if (!xdr_bytes(xdrs, &valp, &parmbytes,
                                     MAXPARMS*LONGPERVAL*sizeof(u_int32))) 
            return(FALSE);
        invp->inv_nparms = parmbytes/4;
        invp->inv_parmp = valp;
    }
    return(TRUE);
} /* xdr_invoke() */


/*
 *      boolean xdr_case() --
 *
 *   Routine to do XDR encode/decode for case of SELECT command
 */
boolean
xdr_case(xdrs, casep, invp)
    XDR *xdrs;
    int *casep;
    struct invokes *invp;
    {
    u_int32 *valp = invp->inv_parmp;
    int parmbytes = 4*(invp->inv_nparms);

    if (!xdr_int(xdrs, casep)) return(FALSE);
    if (valp) {
        if (!xdr_bytes(xdrs, &valp, &parmbytes,
                                     MAXPARMS*LONGPERVAL*sizeof(u_int32))) 
            return(FALSE);
        invp->inv_nparms = parmbytes/4;
        invp->inv_parmp = valp;
    }
    return(TRUE);
} /* xdr_case() */

 
    /*--------------------------------------------------------------
     *  
     *               I/O Routines for SHOW command
     *
     *   Routines to do XDR encode/decode and/or display for SHOW command.
     *
     *          SHOWfield()
     *          SHOWsublist()
     *          SHOWinv()
     */
         
#define OUTSIZE 256
/*
 *  SHOWfield() -- Outer loop I/O routine for SHOW command. 
 *      Called for each new AT field
 *          char *fname -- Name of field at which invocation will occur
 *          int   fno   -- Corresponding field number
 */
boolean
SHOWfield(xdrs, fname, fno, islog)
    XDR   *xdrs;  /* (XDR *) */
    char  *fname;
    int    fno, islog;
    {
    char  out[OUTSIZE];
    
    if (xdrs)
        if (!xdr_string(xdrs, &fname, MAX_FLDNAME)||
            !xdr_int(xdrs, &fno))
                return(FALSE);
    if (islog) {
        sprintf(out, "INVOKED AT %.32s (FIELD #%d):\n", fname, fno);
        log(out, logfp);
    }
    
    return(TRUE);
}  /* SHOWfield() */

/*
 *  SHOWsublist() -- TRUE/FALSE Sublist I/O routine for SHOW command .
 *      Called at start of each IF sublist
 *         int level      -- integer level in tree
 */
boolean
SHOWsublist(xdrs, level, code, islog) 
    XDR *xdrs;  /* (XDR *) */
    int code;
    int   level, islog;
    {
    char  out[OUTSIZE];

    if (xdrs)       
        if (!xdr_int(xdrs, &code))  
            return(FALSE);
        
    if (islog) {
        sprintf(out, "%*c %s\n", 3*level-2, ' ',
            (code == StartTsubl)? "True=>" : 
            (code == StartFsubl)? "False=>":
            (code == Case)? "Case ():" : "Default:"         
             );
        log(out, logfp);
    }
    return(TRUE);
} /* SHOWsublist() */
        
    
/*
 *  SHOWinv() -- Invocation I/O routine for SHOW command.
 *      Called for each invocation
 *          struct invokes *invokesp -- ptr to invocation parm structure
 *          int level --  invocation level number
 *          code -- One of Record, If, or Select
 */
boolean
SHOWinv(xdrs, invokesp, level, code, islog)
    XDR *xdrs;  /* (XDR *) */
    register struct invokes *invokesp;
    int level, code, islog;
    {
    char  out[OUTSIZE];
    int indent = 3*level+2;
    
    invokesp->inv_nparms = 0;  /* No value list */

    if (xdrs && (xdrs->x_op == XDR_DECODE))
        invokesp->inv_parmp = NULL;
                   
    if (xdrs) 
        if (!xdr_invoke(xdrs, invokesp))
                return(FALSE);
                
    if (islog) {
        sprintf(out, "%*c", indent, ' ');
        sprintf(&out[indent], 
              (code == If) ? "IF %.32s IS (FILTER_OBJECT)" :
              (code == Record) ? "RECORD %.32s %.32s IN (OBJECT)" :
                                 "SELECT %.32s {\n",
                invokesp->inv_field1, invokesp->inv_field2);
        if (code != Select)
            sprintf(endof(out), " %.32s (CLASS) %s\n",
                invokesp->inv_objname, class_name[invokesp->inv_classno]);      
        log(out, logfp);
    }

    if (invokesp->inv_parmp && xdrs && (xdrs->x_op == XDR_DECODE)) {
	free(invokesp->inv_parmp);	/* XXX perhaps use XDR_FREE? */
	invokesp->inv_parmp = NULL;
    }

    return(TRUE);
} /* SHOWinv() */


u_int32
get_long(xdrs, out_str, label)
   XDR *xdrs;
   char *out_str, *label;
{
   u_int32 i;

   if (!xdr_int(xdrs, &i))  {
      sprintf(out_str,"Bad xdr_int call in get_long");
      i = 0;
      }
   else {
      sprintf(out_str,"%s = %u\n", label, i);
      }
   return(i);
} /* get_long */

#ifdef PRINT_NETLOAD
/* get_long, with no newline */
u_int32
get_long_nonl(xdrs, out_str, label)
   XDR *xdrs;
   char *out_str, *label;
{
   u_int32 i;

   if (!xdr_int(xdrs, &i))  {
      sprintf(out_str,"Bad xdr_int call in get_long_nonl");
      i = 0;
      }
   else {
      sprintf(out_str,"%s = %u ", label, i);
      }
   return(i);
} /* get_long_nonl */
#endif


    /*
     *  Standard preamble in response to Read command
     *
     */            
boolean PutReadResp(fh, totalen, sobp )
    XDR *fh;
    int totalen;
    SOBJ *sobp;
    {
    int op = READ_op;
    char outbuf[OUTSIZE];
    OBJ  xobj;
    
    if (!ISTTY(fh)) 
        if (!xdr_int(fh, &op)) return(FALSE);   
    
    SetupXRep(&xobj, sobp);
    xobj.obj_totlength = totalen+sizeof(struct Obj_state)+sizeof(int);
    if (sobp->sob_class == WSOBJ) {
            /* Working set object: type and length of read values differ
             *   from what is shown in SOBJ.
             */      
        xobj.obj_state.datatype = xobj.obj_state.datatyp2 = DTYP_int;
        xobj.obj_state.dleng = sizeof(int);
    }
    
    if (!ReadStd(fh, outbuf, (ISTTY(fh))? OUTSIZE:0, &xobj))
        return(FALSE);
    if (ISTTY(fh))
        printf(outbuf);
    return(TRUE);
} /* boolean PutReadResp() */


    /*
     *  Routine to reformat the SOBJ for an object into an OBJ, the
     *  parameter area needed to encode/display it. Also supply total
     *  length field.
     */  
SetupXRep(xobjp, sobp)
    OBJ  *xobjp;
    SOBJ *sobp;
    {
    bcopy(&sobp->sob_object, &xobjp->obj_state, sizeof(struct Obj_state));
    strcpy(xobjp->obj_name, sobp->sob_name);
    xobjp->obj_class = sobp->sob_class;
    xobjp->obj_state.currtime = CurrTime;
} /* SetupXRep() */


/********************************************************************
 *
 *                 UTILITY ROUTINES
 *
 ********************************************************************/
                     
static char *IPformat[] = {"%d.%d.%d.%d", "%d.%d.%d", "%d.%d", "%d"};
 
     /*
     *  Print value, given ptr to first byte and length and data type.
     *    Note: bytes are in big-endian (network) order.
     */
void     
print_val(val_str, valuep, dtype, dleng)
   char   *val_str;
   u_char *valuep;
   int     dtype, dleng;
{
   u_int32 v_wd;
   register int i;
   register u_char *cp;
    
   switch (dtype) {
         
      case DTYP_IP:  /* IP address ... assume length == 4 */
         if (tersemode) 
             {  /* Terse ... set i # of trailing zero bytes */
             cp = valuep+3;
             for (i=0; i<3; i++) if (*cp--) break;
         }
         else i = 0;
         sprintf(val_str, IPformat[i], 
                 0xff&*valuep, 0xff&*(valuep+1), 
                 0xff&*(valuep+2), 0xff&*(valuep+3));
         break;
              
      case DTYP_EtherA:  /* Ethernet Address ...   */
         strcpy(val_str, ether_ntoa(valuep)) ;
         break;
         
      case DTYP_int:  /* Integer... length <= 4 */
         if (dleng <= 4) {
             bcopy(valuep, &v_wd, dleng);  /* align on fullword boundary... */
             sprintf(val_str, "%d", 
                    (dleng==1)?  * (u_char *) &v_wd :
                    (dleng==2)? ntohs(*(u_short *) &v_wd) : 
                                ntohl(v_wd) );
             break;
         }  /* else print in hex... */

      case DTYP_IPsock:
         sprintf(val_str, "%d.%d.%d.%d/%d",
                 0xff&valuep[0], 0xff&valuep[1],
                 0xff&valuep[2], 0xff&valuep[3],
                 ntohs(*(u_short *)(valuep+4)));
                 /* XXX above ntohs() possible alignment trouble? XXX */
         break;
      case DTYP_bits:
      case DTYP_unknown: 
      default:  /* Print in hex */
         strcpy(val_str--, "x");
         while (dleng-- > 0)   
            sprintf(val_str += 2, "%2.2x", *valuep++);
         break;         
    }  /* end switch */
} /* print_val */
 
 /*
  *  print_dbl(buffer, High-order4, Low-order3);
  *
  */
void
print_dbl(p, H, L)
    char *p;
    register u_int32 H, L;
    {
    register char *cp = p;
    u_int32 N;
        
    if (L == 0 && H == 0) {
        *p++ = '0';
        *p = '\0';
        return;
    }
    p += 18;   
    *p = '\0';    
    while ((H)||(L)){
        N = ((H%10)<<24)+L; 
        H = H/10;
        L = N/10;
        *--p = "0123456789"[N%10];
    }
    while (*cp++ = *p++);
       
}  /* print_dbl() */

    
void
hex_print(fp, p, len)
   FILE *fp;
   register char *p;
   register int len;
{
   register int i = 0, j ;
   char out[128];
         
   while (len > 0) {
      for (j = 0; (j<4) && (len > 0); j++)  {
         if (!j) 
           sprintf(out, "x%4.4x: ", i );
           strcat(out, " ");

           print_val(endof(out), p, DTYP_bits, 
                 (len < sizeof(u_int32)?len:sizeof(u_int32)));
           p += sizeof(u_int32) ;
           i += sizeof(u_int32) ;
           len -= sizeof(u_int32) ;
      }
      strcat(out, "\n");
      fprintf(fp, out);
   }
   
} /* hex_print */

void
hex_dump(xdrs, fp, outp, outl, max, disp_type)
   XDR *xdrs;
   FILE *fp;
   char *outp;
   int outl,          /* number of characters */
       max,           /* number of words */
       disp_type;
{
   register int i = 0, j ;
   u_int32 temp ;
   boolean exit = FALSE; 
         
   while (! exit) {
      for (j = 0; j<4; j++)  {
         if (max && (((i*4)+j) >= max)) {
            exit = TRUE;
            break;
            }
         if (xdr_u_int(xdrs, &temp)) {
            if (!j) 
               sprintf(outp,(disp_type == DTYP_int)? "%3d: ":
                           (disp_type == DTYP_IP)?  "%3d: ":
                                                    "x%2.2x: ", i );
            strcat(outp, " ");
            print_val(endof(outp), &temp, disp_type, sizeof(u_int32));
            }
         else {
            exit = TRUE;
            break;
            }
         }
      if (fp) fprintf(fp,"%s\n", outp);
      *outp = '\0';
      i++;
      }
} /* hex_dump */

void
print_time(out_str, i)
   char *out_str;
   time_t i;
{
/*  
     struct tm *localtime(clock)
          time_t *clock;
     char *asctime(tm)
          struct tm *tm;
    
     struct tm {
        int tm_sec;           -- seconds (0 - 59) --
        int tm_min;           -- minutes (0 - 59) --
        int tm_hour;          -- hours (0 - 23) --
        int tm_mday;          -- day of month (1 - 31) --
        int tm_mon;           -- month of year (0 - 11) --
        int tm_year;          -- year - 1900 --
        int tm_wday;          -- day of week (Sunday = 0) --
        int tm_yday;          -- day of year (0 - 365) --
        int tm_isdst;
            };

    struct timezone {
        int  tz_minuteswest;     -- of Greenwich --
        int  tz_dsttime;    -- type of dst correction to apply --
        };
        
*/  
   struct tm *t;

   if (print_utime)
      t = gmtime(&i);
   else
      t = localtime(&i);
   sprintf(out_str, "%2.2d:%2.2d:%2.2d %2.2d-%2.2d-%2.2d", 
      t->tm_hour, t->tm_min, t->tm_sec,
      t->tm_mon+1, t->tm_mday, t->tm_year);
      
} /* print_time */   

void
print_delta(out_str, time1, time2)
   char *out_str;
   time_t time1, time2;
{
   sprintf(out_str, " (@-%dsec)", time2 - time1);
} /* print_delta */
    
    /*
     *  Given ptr to null-terminated string containing new item in 
     *  output stream, append enough blanks to make output
     *  line up in nice columns.  If there is not room on current line
     *  for another max-sized line, output NL, instead.  Global
     *  variables MaxItemSize and LineUsed are needed.
     */
void     
MakeCols(cp)
    register char *cp;
{
    register int W, i, MIS;
    char temp[120];
    
    if (cp == NULL) {
        /* Initialize */
        MaxItemSize = LineUsed = aligncnt = prevW = 0;
        return;
    }
    
    W = strlen(cp);
    if (tersemode||onecolmode) 
        LineUsed = MAXCOLS+1;  /* One column => Force newline always */
    else if (!LineUsed) {
       /* line is empty */
       prevW = LineUsed = W;
       return;
       }

    MIS = ( ((prevW > MaxItemSize)? prevW: MaxItemSize)+7)&~7;
    strcpy(temp, cp);

    i = MIS - prevW;
    LineUsed += i+W;
    if (LineUsed > MAXCOLS) {
       /* it wouldn't fit */
       sprintf(cp,"\n%s",temp);
       prevW = LineUsed = W;
       }
    else {
       /* another column fit */
       MaxItemSize = MIS-7;
       if (i > 0) {
          sprintf(cp, "%*c", i, ' ');
          strcat(cp, temp);
          }
       prevW = W;
       }
       
} /* MakeCols */

int Align(cp)
    char *cp;
    {
    register int n = strlen(cp)+1;
    register int k;
    
    aligncnt = (aligncnt) ? (aligncnt + (n - (aligncnt>>3))) :
        /* That is 8*(exponential avg of n), decay factor 1/8 */
                       (n<<3);
        /* Initial value  = 8*n */
        
    k = ((((n > (k = (aligncnt>>3)))? n : k)+3)&~3);
    return(k-n);
} /* Align */

void
print_percent(cp, total, part)
   char *cp;
   int total, part;
{
   float rate;

   rate = (total)? ((((float) part)/total)*100): 0;

   if (rate == 0)
      sprintf(cp, "(0%%%%) ");   
   else if (rate < .1)
      sprintf(cp, "( <.1%%%%) ");
   else if (rate < 10)
      sprintf(cp, "( %.1f%%%%) ", rate); 
   else
      sprintf(cp, "(%.1f%%%%) ", rate);
} /* print_percent */

#ifdef PRINT_NETLOAD
/*
 * print_netload_percent:
 *   print network utilization percentage of given quantity
 *      cp: ptr to string for output data
 *      partH: high part of data
 *      partL: low  part of data
 *      obj:   object pointer
 *
 * notes:
 *  - times are in seconds, which is too coarse
 *  - percentage of network capacity since last clear can be
 *    a small, unstable quantity
 */
void
print_netload_percent(cp, partH, partL, count, deltaT)
   char *cp;
   u_int32 partH, partL;
   u_int32 count, deltaT;
{
   int overhead;
   double nmax;
   double rate;
   double nbytes;
   
   /* find total bytes since last clear */
   nbytes = (((double)partH * MAX_LBYTE) + partL);

   /* add in per-packet costs that nnstat doesn't keep: */
   if (strcmp(IfaceType, "Ethernet") == 0) {
       /*   8 byte preamble */
       /*  12 byte inter-frame-gap */
       /*   4 byte frame check seq */
       /*  14 byte ethernet header */
	nbytes += ((double)count * 38.0);
       /* # bytes that Ethernet could carry in given time */
       nmax = (double)deltaT * 1250000.0;
   }
   else if (strcmp(IfaceType, "FDDI") == 0) {
       /*   8 byte (or more) preamble */
       /*   1 byte start delimiter */
       /*   4 byte frame check seq */
       /*  21 byte FDDI+LLC header */
       /*  3? byte end-of-frame sequence */
	nbytes += ((double)count * 37.0);
       /* # bytes that FDDI could carry in given time */
       nmax = (double)deltaT * 12500000.0;
   }
   else {
	fprintf(stderr, "print_netload_percent: unknown interface type %s\n",
		IfaceType);
	exit(1);
   }

   rate = (nmax) ? ((((double) nbytes)/nmax)*100): 0;
   
   if (rate == 0)
      sprintf(cp, "(0%%%% %s load) ", IfaceType);
   else if (rate < .1)
      sprintf(cp, "( <.1%%%% %s load) ", IfaceType);
   else if (rate < 10)
      sprintf(cp, "( %.1f%%%% %s load) ", rate, IfaceType);
   else
      sprintf(cp, "(%.1f%%%% %s load) ", rate, IfaceType);
} /* print_netload_percent */
#endif
