From 9ed27d10382c7a5e19ef98f5201d68ed00734006 Mon Sep 17 00:00:00 2001 From: dds Date: Tue, 22 May 2007 06:51:38 +0000 Subject: Increase precision of time values in the process accounting structure, while maintaining backward compatibility with legacy file and record formats. --- share/man/man5/acct.5 | 78 +++++++++------ sys/kern/kern_acct.c | 144 ++++++++++++++++++++------- sys/sys/acct.h | 71 +++++++++++--- usr.bin/lastcomm/Makefile | 1 + usr.bin/lastcomm/lastcomm.c | 95 ++++++------------ usr.bin/lastcomm/readrec.c | 231 ++++++++++++++++++++++++++++++++++++++++++++ usr.sbin/sa/Makefile | 4 +- usr.sbin/sa/db.c | 207 +++++++++++++++++++++++++++++++++++++++ usr.sbin/sa/extern.h | 34 ++++--- usr.sbin/sa/main.c | 110 +++++++++------------ usr.sbin/sa/pdb.c | 170 ++++++++++++-------------------- usr.sbin/sa/usrdb.c | 177 +++++++++++++-------------------- 12 files changed, 881 insertions(+), 441 deletions(-) create mode 100644 usr.bin/lastcomm/readrec.c create mode 100644 usr.sbin/sa/db.c diff --git a/share/man/man5/acct.5 b/share/man/man5/acct.5 index 8541081..48283a5 100644 --- a/share/man/man5/acct.5 +++ b/share/man/man5/acct.5 @@ -32,7 +32,7 @@ .\" @(#)acct.5 8.1 (Berkeley) 6/5/93 .\" $FreeBSD$ .\" -.Dd June 5, 1993 +.Dd May 15, 2007 .Dt ACCT 5 .Os .Sh NAME @@ -52,38 +52,47 @@ the kernel calls the function call to prepare and append the record to the accounting file. .Bd -literal -/* - * Accounting structures; these use a comp_t type which is a 3 bits base 8 - * exponent, 13 bit fraction ``floating point'' number. Units are 1/AHZ - * seconds. - */ -typedef u_short comp_t; - #define AC_COMM_LEN 16 -struct acct { - char ac_comm[AC_COMM_LEN]; /* command name */ - comp_t ac_utime; /* user time */ - comp_t ac_stime; /* system time */ - comp_t ac_etime; /* elapsed time */ - time_t ac_btime; /* starting time */ - uid_t ac_uid; /* user id */ - gid_t ac_gid; /* group id */ - short ac_mem; /* average memory usage */ - comp_t ac_io; /* count of IO blocks */ - dev_t ac_tty; /* controlling tty */ -#define AFORK 0x01 /* forked but not exec'ed */ -#define ASU 0x02 /* used super-user permissions */ -#define ACOMPAT 0x04 /* used compatibility mode */ -#define ACORE 0x08 /* dumped core */ -#define AXSIG 0x10 /* killed by a signal */ - char ac_flag; /* accounting flags */ -}; /* - * 1/AHZ is the granularity of the data encoded in the comp_t fields. - * This is not necessarily equal to hz. + * Accounting structure version 2 (current). + * The first byte is always zero. + * Time units are microseconds. */ -#define AHZ 64 + +struct acctv2 { + uint8_t ac_zero; /* zero identifies new version */ + uint8_t ac_version; /* record version number */ + uint16_t ac_len; /* record length */ + + char ac_comm[AC_COMM_LEN]; /* command name */ + float ac_utime; /* user time */ + float ac_stime; /* system time */ + float ac_etime; /* elapsed time */ + time_t ac_btime; /* starting time */ + uid_t ac_uid; /* user id */ + gid_t ac_gid; /* group id */ + float ac_mem; /* average memory usage */ + float ac_io; /* count of IO blocks */ + __dev_t ac_tty; /* controlling tty */ + + uint16_t ac_len2; /* record length */ + union { + __dev_t ac_align; /* force v1 compatible alignment */ + +#define AFORK 0x01 /* forked but not exec'ed */ +/* ASU is no longer supported */ +#define ASU 0x02 /* used super-user permissions */ +#define ACOMPAT 0x04 /* used compatibility mode */ +#define ACORE 0x08 /* dumped core */ +#define AXSIG 0x10 /* killed by a signal */ +#define ANVER 0x20 /* new record version */ + + uint8_t ac_flag; /* accounting flags */ + } ac_trailer; + +#define ac_flagx ac_trailer.ac_flag +}; .Ed .Pp If a terminated process was created by an @@ -100,7 +109,10 @@ and .Dv ASIG . .Dv ASU is no longer supported. +.Dv ANVER +is always set in the above structure. .Sh SEE ALSO +.Xr lastcomm 1 , .Xr acct 2 , .Xr execve 2 , .Xr sa 8 @@ -109,3 +121,11 @@ A .Nm file format appeared in .At v7 . +The current record format was introduced on May 2007. +It is backwards compatible with the previous format, +which is still documented in +.In sys/acct.h +and supported by +.Xr lastcomm 1 +and +.Xr sa 8 . diff --git a/sys/kern/kern_acct.c b/sys/kern/kern_acct.c index 39c430f..6783fba 100644 --- a/sys/kern/kern_acct.c +++ b/sys/kern/kern_acct.c @@ -101,18 +101,26 @@ __FBSDID("$FreeBSD$"); * Leffler, et al.: The Design and Implementation of the 4.3BSD * UNIX Operating System (Addison Welley, 1989) * on pages 62-63. + * On May 2007 the historic 3 bits base 8 exponent, 13 bit fraction + * compt_t representation described in the above reference was replaced + * with that of IEEE-754 floats. * * Arguably, to simplify accounting operations, this mechanism should * be replaced by one in which an accounting log file (similar to /dev/klog) * is read by a user process, etc. However, that has its own problems. */ +/* Floating point definitions from . */ +#define FLT_MANT_DIG 24 /* p */ +#define FLT_MAX_EXP 128 /* emax */ + /* * Internal accounting functions. * The former's operation is described in Leffler, et al., and the latter * was provided by UCB with the 4.4BSD-Lite release */ -static comp_t encode_comp_t(u_long, u_long); +static uint32_t encode_timeval(struct timeval); +static uint32_t encode_long(long); static void acctwatch(void); static void acct_thread(void *); static int acct_disable(struct thread *); @@ -325,7 +333,7 @@ acct_disable(struct thread *td) int acct_process(struct thread *td) { - struct acct acct; + struct acctv2 acct; struct timeval ut, st, tmp; struct plimit *newlim, *oldlim; struct proc *p; @@ -363,8 +371,8 @@ acct_process(struct thread *td) /* (2) The amount of user and system time that was used */ calcru(p, &ut, &st); - acct.ac_utime = encode_comp_t(ut.tv_sec, ut.tv_usec); - acct.ac_stime = encode_comp_t(st.tv_sec, st.tv_usec); + acct.ac_utime = encode_timeval(ut); + acct.ac_stime = encode_timeval(st); /* (3) The elapsed time the command ran (and its starting time) */ tmp = boottime; @@ -372,20 +380,22 @@ acct_process(struct thread *td) acct.ac_btime = tmp.tv_sec; microuptime(&tmp); timevalsub(&tmp, &p->p_stats->p_start); - acct.ac_etime = encode_comp_t(tmp.tv_sec, tmp.tv_usec); + acct.ac_etime = encode_timeval(tmp); /* (4) The average amount of memory used */ r = &p->p_stats->p_ru; tmp = ut; timevaladd(&tmp, &st); + /* Convert tmp (i.e. u + s) into hz units to match ru_i*. */ t = tmp.tv_sec * hz + tmp.tv_usec / tick; if (t) - acct.ac_mem = (r->ru_ixrss + r->ru_idrss + r->ru_isrss) / t; + acct.ac_mem = encode_long((r->ru_ixrss + r->ru_idrss + + + r->ru_isrss) / t); else acct.ac_mem = 0; /* (5) The number of disk I/O operations done */ - acct.ac_io = encode_comp_t(r->ru_inblock + r->ru_oublock, 0); + acct.ac_io = encode_long(r->ru_inblock + r->ru_oublock); /* (6) The UID and GID of the process */ acct.ac_uid = p->p_ucred->cr_ruid; @@ -400,9 +410,15 @@ acct_process(struct thread *td) SESS_UNLOCK(p->p_session); /* (8) The boolean flags that tell how the process terminated, etc. */ - acct.ac_flag = p->p_acflag; + acct.ac_flagx = p->p_acflag; PROC_UNLOCK(p); + /* Setup ancillary structure fields. */ + acct.ac_flagx |= ANVER; + acct.ac_zero = 0; + acct.ac_version = 2; + acct.ac_len = acct.ac_len2 = sizeof(acct); + /* * Eliminate any file size rlimit. */ @@ -428,44 +444,102 @@ acct_process(struct thread *td) return (ret); } +/* FLOAT_CONVERSION_START (Regression testing; don't remove this line.) */ + +/* Convert timevals and longs into IEEE-754 bit patterns. */ + +/* Mantissa mask (MSB is implied, so subtract 1). */ +#define MANT_MASK ((1 << (FLT_MANT_DIG - 1)) - 1) + /* - * Encode_comp_t converts from ticks in seconds and microseconds - * to ticks in 1/AHZ seconds. The encoding is described in - * Leffler, et al., on page 63. + * We calculate integer values to a precision of approximately + * 28 bits. + * This is high-enough precision to fill the 24 float bits + * and low-enough to avoid overflowing the 32 int bits. */ +#define CALC_BITS 28 -#define MANTSIZE 13 /* 13 bit mantissa. */ -#define EXPSIZE 3 /* Base 8 (3 bit) exponent. */ -#define MAXFRACT ((1 << MANTSIZE) - 1) /* Maximum fractional value. */ +/* log_2(1000000). */ +#define LOG2_1M 20 -static comp_t -encode_comp_t(u_long s, u_long us) +/* + * Convert the elements of a timeval into a 32-bit word holding + * the bits of a IEEE-754 float. + * The float value represents the timeval's value in microsecond units. + */ +static uint32_t +encode_timeval(struct timeval tv) { - int exp, rnd; - - exp = 0; - rnd = 0; - s *= AHZ; - s += us / (1000000 / AHZ); /* Maximize precision. */ + int log2_s; + int val, exp; /* Unnormalized value and exponent */ + int norm_exp; /* Normalized exponent */ + int shift; - while (s > MAXFRACT) { - rnd = s & (1 << (EXPSIZE - 1)); /* Round up? */ - s >>= EXPSIZE; /* Base 8 exponent == 3 bit shift. */ - exp++; + /* + * First calculate value and exponent to about CALC_BITS precision. + * Note that the following conditionals have been ordered so that + * the most common cases appear first. + */ + if (tv.tv_sec == 0) { + if (tv.tv_usec == 0) + return (0); + exp = 0; + val = tv.tv_usec; + } else { + /* + * Calculate the value to a precision of approximately + * CALC_BITS. + */ + log2_s = fls(tv.tv_sec) - 1; + if (log2_s + LOG2_1M < CALC_BITS) { + exp = 0; + val = 1000000 * tv.tv_sec + tv.tv_usec; + } else { + exp = log2_s + LOG2_1M - CALC_BITS; + val = (unsigned int)(((u_int64_t)1000000 * tv.tv_sec + + tv.tv_usec) >> exp); + } } + /* Now normalize and pack the value into an IEEE-754 float. */ + norm_exp = fls(val) - 1; + shift = FLT_MANT_DIG - norm_exp - 1; +#ifdef ACCT_DEBUG + printf("val=%d exp=%d shift=%d log2(val)=%d\n", + val, exp, shift, norm_exp); + printf("exp=%x mant=%x\n", FLT_MAX_EXP - 1 + exp + norm_exp, + ((shift > 0 ? (val << shift) : (val >> -shift)) & MANT_MASK)); +#endif + return (((FLT_MAX_EXP - 1 + exp + norm_exp) << (FLT_MANT_DIG - 1)) | + ((shift > 0 ? val << shift : val >> -shift) & MANT_MASK)); +} - /* If we need to round up, do it (and handle overflow correctly). */ - if (rnd && (++s > MAXFRACT)) { - s >>= EXPSIZE; - exp++; - } +/* + * Convert a non-negative long value into the bit pattern of + * an IEEE-754 float value. + */ +static uint32_t +encode_long(long val) +{ + int norm_exp; /* Normalized exponent */ + int shift; - /* Clean it up and polish it off. */ - exp <<= MANTSIZE; /* Shift the exponent into place */ - exp += s; /* and add on the mantissa. */ - return (exp); + KASSERT(val >= 0, ("encode_long: -ve value %ld", val)); + if (val == 0) + return (0); + norm_exp = fls(val) - 1; + shift = FLT_MANT_DIG - norm_exp - 1; +#ifdef ACCT_DEBUG + printf("val=%d shift=%d log2(val)=%d\n", + val, shift, norm_exp); + printf("exp=%x mant=%x\n", FLT_MAX_EXP - 1 + exp + norm_exp, + ((shift > 0 ? (val << shift) : (val >> -shift)) & MANT_MASK)); +#endif + return (((FLT_MAX_EXP - 1 + norm_exp) << (FLT_MANT_DIG - 1)) | + ((shift > 0 ? val << shift : val >> -shift) & MANT_MASK)); } +/* FLOAT_CONVERSION_END (Regression testing; don't remove this line.) */ + /* * Periodically check the filesystem to see if accounting * should be turned on or off. Beware the case where the vnode diff --git a/sys/sys/acct.h b/sys/sys/acct.h index 167a90c..7c118ed 100644 --- a/sys/sys/acct.h +++ b/sys/sys/acct.h @@ -38,45 +38,88 @@ #ifndef _SYS_ACCT_H_ #define _SYS_ACCT_H_ +#ifdef _KERNEL +#define float uint32_t +#endif + +#define AC_COMM_LEN 16 + /* - * Accounting structures; these use a comp_t type which is a 3 bits base 8 - * exponent, 13 bit fraction ``floating point'' number. Units are 1/AHZ - * seconds. + * Accounting structure version 2 (current). + * The first byte is always zero. + * Time units are microseconds. */ -typedef u_int16_t comp_t; -#define AC_COMM_LEN 16 -struct acct { +struct acctv2 { + uint8_t ac_zero; /* zero identifies new version */ + uint8_t ac_version; /* record version number */ + uint16_t ac_len; /* record length */ + char ac_comm[AC_COMM_LEN]; /* command name */ - comp_t ac_utime; /* user time */ - comp_t ac_stime; /* system time */ - comp_t ac_etime; /* elapsed time */ + float ac_utime; /* user time */ + float ac_stime; /* system time */ + float ac_etime; /* elapsed time */ time_t ac_btime; /* starting time */ uid_t ac_uid; /* user id */ gid_t ac_gid; /* group id */ - u_int16_t ac_mem; /* average memory usage */ - comp_t ac_io; /* count of IO blocks */ + float ac_mem; /* average memory usage */ + float ac_io; /* count of IO blocks */ __dev_t ac_tty; /* controlling tty */ + uint16_t ac_len2; /* record length */ + union { + __dev_t ac_align; /* force v1 compatible alignment */ + #define AFORK 0x01 /* forked but not exec'ed */ /* ASU is no longer supported */ #define ASU 0x02 /* used super-user permissions */ #define ACOMPAT 0x04 /* used compatibility mode */ #define ACORE 0x08 /* dumped core */ #define AXSIG 0x10 /* killed by a signal */ - u_int8_t ac_flag; /* accounting flags */ +#define ANVER 0x20 /* new record version */ + + uint8_t ac_flag; /* accounting flags */ + } ac_trailer; + +#define ac_flagx ac_trailer.ac_flag +}; + + +/* + * Legacy accounting structure (rev. 1.5-1.18). + * The first byte is always non-zero. + * Some fields use a comp_t type which is a 3 bits base 8 + * exponent, 13 bit fraction ``floating point'' number. + * Units are 1/AHZV1 seconds. + */ + +typedef uint16_t comp_t; + +struct acctv1 { + char ac_comm[AC_COMM_LEN]; /* command name */ + comp_t ac_utime; /* user time */ + comp_t ac_stime; /* system time */ + comp_t ac_etime; /* elapsed time */ + time_t ac_btime; /* starting time */ + uid_t ac_uid; /* user id */ + gid_t ac_gid; /* group id */ + uint16_t ac_mem; /* average memory usage */ + comp_t ac_io; /* count of IO blocks */ + __dev_t ac_tty; /* controlling tty */ + uint8_t ac_flag; /* accounting flags */ }; /* - * 1/AHZ is the granularity of the data encoded in the comp_t fields. + * 1/AHZV1 is the granularity of the data encoded in the comp_t fields. * This is not necessarily equal to hz. */ -#define AHZ 64 +#define AHZV1 64 #ifdef _KERNEL struct thread; int acct_process(struct thread *td); +#undef float #endif #endif /* !_SYS_ACCT_H_ */ diff --git a/usr.bin/lastcomm/Makefile b/usr.bin/lastcomm/Makefile index be75109..c79cd74 100644 --- a/usr.bin/lastcomm/Makefile +++ b/usr.bin/lastcomm/Makefile @@ -2,6 +2,7 @@ # $FreeBSD$ PROG= lastcomm +SRCS= lastcomm.c readrec.c WARNS?= 6 .include diff --git a/usr.bin/lastcomm/lastcomm.c b/usr.bin/lastcomm/lastcomm.c index 4b0a40f..8c55123 100644 --- a/usr.bin/lastcomm/lastcomm.c +++ b/usr.bin/lastcomm/lastcomm.c @@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$"); #include #include +#include #include #include #include @@ -66,7 +67,9 @@ __FBSDID("$FreeBSD$"); time_t expand(u_int); char *flagbits(int); const char *getdev(dev_t); -int requested(char *[], struct acct *); +int readrec_forward(FILE *f, struct acctv2 *av2); +int readrec_backward(FILE *f, struct acctv2 *av2); +int requested(char *[], struct acctv2 *); static void usage(void); #define AC_UTIME 1 /* user */ @@ -77,18 +80,15 @@ static void usage(void); #define AC_BTIME 16 /* starting time */ #define AC_FTIME 32 /* exit time (starting time + elapsed time )*/ -#define AC_HZ ((double)AHZ) - int main(int argc, char *argv[]) { + struct acctv2 ab; char *p; - struct acct ab; - struct stat sb; FILE *fp; - off_t size; + int (*readrec)(FILE *f, struct acctv2 *av2); time_t t; - int ch; + int ch, rv; const char *acctfile; int flags = 0; @@ -135,78 +135,51 @@ main(int argc, char *argv[]) if (strcmp(acctfile, "-") == 0) { fp = stdin; - size = sizeof(struct acct); /* Always one more to read. */ + readrec = readrec_forward; } else { /* Open the file. */ - if ((fp = fopen(acctfile, "r")) == NULL || - fstat(fileno(fp), &sb)) + if ((fp = fopen(acctfile, "r")) == NULL) err(1, "could not open %s", acctfile); - - /* - * Round off to integral number of accounting records, - * probably not necessary, but it doesn't hurt. - */ - size = sb.st_size - sb.st_size % sizeof(struct acct); - - /* Check if any records to display. */ - if ((unsigned)size < sizeof(struct acct)) - exit(0); + if (fseek(fp, 0l, SEEK_END) == -1) + err(1, "seek to end of %s failed", acctfile); + readrec = readrec_backward; } - do { - int rv; + while ((rv = readrec(fp, &ab)) == 1) { + for (p = &ab.ac_comm[0]; + p < &ab.ac_comm[AC_COMM_LEN] && *p; ++p) + if (!isprint(*p)) + *p = '?'; - if (fp != stdin) { - size -= sizeof(struct acct); - if (fseeko(fp, size, SEEK_SET) == -1) - err(1, "seek %s failed", acctfile); - } - - if ((rv = fread(&ab, sizeof(struct acct), 1, fp)) != 1) { - if (feof(fp)) - break; - else - err(1, "read %s returned %d", acctfile, rv); - } - - if (ab.ac_comm[0] == '\0') { - ab.ac_comm[0] = '?'; - ab.ac_comm[1] = '\0'; - } else - for (p = &ab.ac_comm[0]; - p < &ab.ac_comm[AC_COMM_LEN] && *p; ++p) - if (!isprint(*p)) - *p = '?'; if (*argv && !requested(argv, &ab)) continue; (void)printf("%-*.*s %-7s %-*s %-*s", AC_COMM_LEN, AC_COMM_LEN, ab.ac_comm, - flagbits(ab.ac_flag), + flagbits(ab.ac_flagx), UT_NAMESIZE, user_from_uid(ab.ac_uid, 0), UT_LINESIZE, getdev(ab.ac_tty)); /* user + system time */ if (flags & AC_CTIME) { - (void)printf(" %6.2f secs", - (expand(ab.ac_utime) + - expand(ab.ac_stime))/AC_HZ); + (void)printf(" %6.3f secs", + (ab.ac_utime + ab.ac_stime) / 1000000); } /* usr time */ if (flags & AC_UTIME) { - (void)printf(" %6.2f us", expand(ab.ac_utime)/AC_HZ); + (void)printf(" %6.3f us", ab.ac_utime / 1000000); } /* system time */ if (flags & AC_STIME) { - (void)printf(" %6.2f sy", expand(ab.ac_stime)/AC_HZ); + (void)printf(" %6.3f sy", ab.ac_stime / 1000000); } /* elapsed time */ if (flags & AC_ETIME) { - (void)printf(" %8.2f es", expand(ab.ac_etime)/AC_HZ); + (void)printf(" %8.3f es", ab.ac_etime / 1000000); } /* starting time */ @@ -217,31 +190,19 @@ main(int argc, char *argv[]) /* exit time (starting time + elapsed time )*/ if (flags & AC_FTIME) { t = ab.ac_btime; - t += (time_t)(expand(ab.ac_etime)/AC_HZ); + t += (time_t)(ab.ac_etime / 1000000); (void)printf(" %.16s", ctime(&t)); } printf("\n"); + } + if (rv == EOF) + err(1, "read record from %s failed", acctfile); - } while (size > 0); if (fflush(stdout)) err(1, "stdout"); exit(0); } -time_t -expand(u_int t) -{ - time_t nt; - - nt = t & 017777; - t >>= 13; - while (t) { - t--; - nt <<= 3; - } - return (nt); -} - char * flagbits(int f) { @@ -261,7 +222,7 @@ flagbits(int f) } int -requested(char *argv[], struct acct *acp) +requested(char *argv[], struct acctv2 *acp) { const char *p; diff --git a/usr.bin/lastcomm/readrec.c b/usr.bin/lastcomm/readrec.c new file mode 100644 index 0000000..4883b2a --- /dev/null +++ b/usr.bin/lastcomm/readrec.c @@ -0,0 +1,231 @@ +/*- + * Copyright (c) 2007 Diomidis Spinellis + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int readrec_forward(FILE *f, struct acctv2 *av2); +int readrec_backward(FILE *f, struct acctv2 *av2); + +/* + * Reverse offsetof: return the offset of field f + * from the end of the structure s. + */ +#define roffsetof(s, f) (sizeof(s) - offsetof(s, f)) + +/* + * Read exactly one record of size size from stream f into ptr. + * Failure to read the complete record is considered a file format error, + * and will set errno to EFTYPE. + * Return 0 on success, EOF on end of file or error. + */ +static int +fread_record(void *ptr, size_t size, FILE *f) +{ + size_t rv; + + if ((rv = fread(ptr, 1, size, f)) == size) + return (0); + else if (ferror(f) || rv == 0) + return (EOF); + else { + /* Short read. */ + errno = EFTYPE; + return (EOF); + } +} + +/* + * Return the value of a comp_t field. + */ +static float +decode_comp(comp_t v) +{ + int result, exp; + + result = v & 017777; + for (exp = v >> 13; exp; exp--) + result <<= 3; + return ((double)result / AHZV1); +} + +/* + * Read a v1 accounting record stored at the current + * position of stream f. + * Convert the data to the current record format. + * Return EOF on error or end-of-file. + */ +static int +readrec_v1(FILE *f, struct acctv2 *av2) +{ + struct acctv1 av1; + int rv; + + if ((rv = fread_record(&av1, sizeof(av1), f)) == EOF) + return (EOF); + av2->ac_zero = 0; + av2->ac_version = 2; + av2->ac_len = av2->ac_len2 = sizeof(*av2); + memcpy(av2->ac_comm, av1.ac_comm, AC_COMM_LEN); + av2->ac_utime = decode_comp(av1.ac_utime) * 1000000; + av2->ac_stime = decode_comp(av1.ac_stime) * 1000000; + av2->ac_etime = decode_comp(av1.ac_etime) * 1000000; + av2->ac_btime = av1.ac_btime; + av2->ac_uid = av1.ac_uid; + av2->ac_gid = av1.ac_gid; + av2->ac_mem = av1.ac_mem; + av2->ac_io = decode_comp(av1.ac_io); + av2->ac_tty = av1.ac_tty; + av2->ac_flagx = av1.ac_flag | ANVER; + return (0); +} + +/* + * Read an v2 accounting record stored at the current + * position of stream f. + * Return EOF on error or end-of-file. + */ +static int +readrec_v2(FILE *f, struct acctv2 *av2) +{ + return (fread_record(av2, sizeof(*av2), f)); +} + +/* + * Read a new-style (post-v1) accounting record stored at + * the current position of stream f. + * Convert the data to the current record format. + * Return EOF on error or end-of-file. + */ +static int +readrec_vx(FILE *f, struct acctv2 *av2) +{ + uint8_t magic, version; + + if (fread_record(&magic, sizeof(magic), f) == EOF || + fread_record(&version, sizeof(version), f) == EOF || + ungetc(version, f) == EOF || + ungetc(magic, f) == EOF) + return (EOF); + switch (version) { + case 2: + return (readrec_v2(f, av2)); + + /* Add handling for more versions here. */ + + default: + errno = EFTYPE; + return (EOF); + } +} + +/* + * Read an accounting record stored at the current + * position of stream f. + * Old-format records are converted to the current record + * format. + * Return the number of records read (1 or 0 at the end-of-file), + * or EOF on error. + */ +int +readrec_forward(FILE *f, struct acctv2 *av2) +{ + int magic, rv; + + if ((magic = getc(f)) == EOF) + return (ferror(f) ? EOF : 0); + if (ungetc(magic, f) == EOF) + return (EOF); + if (magic != 0) + /* Old record format. */ + rv = readrec_v1(f, av2); + else + /* New record formats. */ + rv = readrec_vx(f, av2); + return (rv == EOF ? EOF : 1); +} + +/* + * Read an accounting record ending at the current + * position of stream f. + * Old-format records are converted to the current record + * format. + * The file pointer is positioned at the beginning of the + * record read. + * Return the number of records read (1 or 0 at the end-of-file), + * or EOF on error. + */ +int +readrec_backward(FILE *f, struct acctv2 *av2) +{ + off_t pos; + int c; + uint16_t len; + + if ((pos = ftell(f)) == -1) + return (EOF); + if (pos == 0) + return (0); + if (fseek(f, -roffsetof(struct acctv2, ac_trailer), + SEEK_CUR) == EOF || + (c = getc(f)) == EOF) + return (EOF); + if (c & ANVER) { + /* New record formats. */ + if (fseeko(f, pos - roffsetof(struct acctv2, ac_len2), + SEEK_SET) == EOF || + fread_record(&len, sizeof(len), f) == EOF || + fseeko(f, pos - len, SEEK_SET) == EOF || + readrec_vx(f, av2) == EOF || + fseeko(f, pos - len, SEEK_SET) == EOF) + return (EOF); + else + return (1); + } else { + /* Old record format. */ + if (fseeko(f, pos - sizeof(struct acctv1), SEEK_SET) == EOF || + readrec_v1(f, av2) == EOF || + fseeko(f, pos - sizeof(struct acctv1), SEEK_SET) == EOF) + return (EOF); + else + return (1); + } +} diff --git a/usr.sbin/sa/Makefile b/usr.sbin/sa/Makefile index 994e0dd..ae6af5a 100644 --- a/usr.sbin/sa/Makefile +++ b/usr.sbin/sa/Makefile @@ -2,8 +2,10 @@ PROG= sa MAN= sa.8 -SRCS= main.c pdb.c usrdb.c +SRCS= main.c db.c pdb.c usrdb.c readrec.c WARNS?= 6 +.PATH: ${.CURDIR}/../../usr.bin/lastcomm + .include diff --git a/usr.sbin/sa/db.c b/usr.sbin/sa/db.c new file mode 100644 index 0000000..d31cd7c --- /dev/null +++ b/usr.sbin/sa/db.c @@ -0,0 +1,207 @@ +/*- + * Copyright (c) 2007 Diomidis Spinellis + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +/* Key used to store the version of the database data elements. */ +#define VERSION_KEY "\0VERSION" + +/* + * Create the in-memory database, *mdb. + * If iflag is not set, fill-in mdb with the records of the disk-based + * database dbname. + * Upgrade old-version records by calling v1_to_v2. + * Return 0 if OK, -1 on error. + */ +int +db_copy_in(DB **mdb, const char *dbname, const char *uname, BTREEINFO *bti, + int (*v1_to_v2)(DBT *key, DBT *data)) +{ + DBT key, data; + DB *ddb; + int error, rv, version; + + if ((*mdb = dbopen(NULL, O_RDWR, 0, DB_BTREE, bti)) == NULL) + return (-1); + + if (iflag) + return (0); + + if ((ddb = dbopen(dbname, O_RDONLY, 0, DB_BTREE, NULL)) == NULL) { + if (errno == ENOENT) + return (0); + warn("retrieving %s summary", uname); + db_destroy(*mdb, uname); + return (-1); + } + + error = 0; + + /* Obtain/set version. */ + version = 1; + key.data = &VERSION_KEY; + key.size = sizeof(VERSION_KEY); + + rv = DB_GET(ddb, &key, &data, 0); + if (rv < 0) { + warn("get version key from %s stats", uname); + error = -1; + goto closeout; + } else if (rv == 0) { /* It's there; verify version. */ + if (data.size != sizeof(version)) { + warnx("invalid version size %d in %s", + data.size, uname); + error = -1; + goto closeout; + } + memcpy(&version, data.data, data.size); + if (version != 2) { + warnx("unsupported version %d in %s", + version, uname); + error = -1; + goto closeout; + } + } + + for (rv = DB_SEQ(ddb, &key, &data, R_FIRST); rv == 0; + rv = DB_SEQ(ddb, &key, &data, R_NEXT)) { + + /* See if this is a version record. */ + if (key.size == sizeof(VERSION_KEY) && + memcmp(key.data, VERSION_KEY, sizeof(VERSION_KEY)) == 0) + continue; + + /* Convert record from v1, if needed. */ + if (version == 1 && v1_to_v2(&key, &data) < 0) { + warn("converting %s stats", uname); + error = -1; + goto closeout; + } + + /* Copy record to the in-memory database. */ + if ((rv = DB_PUT(*mdb, &key, &data, 0)) < 0) { + warn("initializing %s stats", uname); + error = -1; + goto closeout; + } + } + if (rv < 0) { + warn("retrieving %s summary", uname); + error = -1; + } + +closeout: + if (DB_CLOSE(ddb) < 0) { + warn("closing %s summary", uname); + error = -1; + } + + if (error) + db_destroy(*mdb, uname); + return (error); +} + +/* + * Save the in-memory database mdb to the disk database dbname. + * Return 0 if OK, -1 on error. + */ +int +db_copy_out(DB *mdb, const char *dbname, const char *uname, BTREEINFO *bti) +{ + DB *ddb; + DBT key, data; + int error, rv, version; + + if ((ddb = dbopen(dbname, O_RDWR|O_CREAT|O_TRUNC, 0644, + DB_BTREE, bti)) == NULL) { + warn("creating %s summary", uname); + return (-1); + } + + error = 0; + + for (rv = DB_SEQ(mdb, &key, &data, R_FIRST); + rv == 0; rv = DB_SEQ(mdb, &key, &data, R_NEXT)) { + if ((rv = DB_PUT(ddb, &key, &data, 0)) < 0) { + warn("saving %s summary", uname); + error = -1; + goto out; + } + } + if (rv < 0) { + warn("retrieving %s stats", uname); + error = -1; + } + +out: + /* Add a version record. */ + key.data = &VERSION_KEY; + key.size = sizeof(VERSION_KEY); + version = 2; + data.data = &version; + data.size = sizeof(version); + if ((rv = DB_PUT(ddb, &key, &data, 0)) < 0) { + warn("add version record to %s stats", uname); + error = -1; + } else if (rv == 1) { + warnx("duplicate version record in %s stats", uname); + error = -1; + } + + if (DB_SYNC(ddb, 0) < 0) { + warn("syncing %s summary", uname); + error = -1; + } + if (DB_CLOSE(ddb) < 0) { + warn("closing %s summary", uname); + error = -1; + } + return error; +} + +void +db_destroy(DB *db, const char *uname) +{ + if (DB_CLOSE(db) < 0) + warn("destroying %s stats", uname); +} diff --git a/usr.sbin/sa/extern.h b/usr.sbin/sa/extern.h index ccdd763..70f97a7 100644 --- a/usr.sbin/sa/extern.h +++ b/usr.sbin/sa/extern.h @@ -36,32 +36,41 @@ /* structures */ +/* All times are stored in 1e-6s units. */ + struct cmdinfo { char ci_comm[MAXCOMLEN+2]; /* command name (+ '*') */ - u_long ci_uid; /* user id */ + uid_t ci_uid; /* user id */ u_quad_t ci_calls; /* number of calls */ - u_quad_t ci_etime; /* elapsed time */ - u_quad_t ci_utime; /* user time */ - u_quad_t ci_stime; /* system time */ - u_quad_t ci_mem; /* memory use */ - u_quad_t ci_io; /* number of disk i/o ops */ + double ci_etime; /* elapsed time */ + double ci_utime; /* user time */ + double ci_stime; /* system time */ + double ci_mem; /* memory use */ + double ci_io; /* number of disk i/o ops */ u_int ci_flags; /* flags; see below */ }; #define CI_UNPRINTABLE 0x0001 /* unprintable chars in name */ struct userinfo { - u_long ui_uid; /* user id; for consistency */ + uid_t ui_uid; /* user id; for consistency */ u_quad_t ui_calls; /* number of invocations */ - u_quad_t ui_utime; /* user time */ - u_quad_t ui_stime; /* system time */ - u_quad_t ui_mem; /* memory use */ - u_quad_t ui_io; /* number of disk i/o ops */ + double ui_utime; /* user time */ + double ui_stime; /* system time */ + double ui_mem; /* memory use */ + double ui_io; /* number of disk i/o ops */ }; /* typedefs */ typedef int (*cmpf_t)(const DBT *, const DBT *); +/* external functions in db.c */ +int db_copy_in(DB **mdb, const char *dbname, const char *name, + BTREEINFO *bti, int (*v1_to_v2)(DBT *key, DBT *data)); +int db_copy_out(DB *mdb, const char *dbname, const char *name, + BTREEINFO *bti); +void db_destroy(DB *db, const char *uname); + /* external functions in pdb.c */ int pacct_init(void); void pacct_destroy(void); @@ -69,6 +78,9 @@ int pacct_add(const struct cmdinfo *); int pacct_update(void); void pacct_print(void); +/* external functions in readrec.c */ +int readrec_forward(FILE *f, struct acctv2 *av2); + /* external functions in usrdb.c */ int usracct_init(void); void usracct_destroy(void); diff --git a/usr.sbin/sa/main.c b/usr.sbin/sa/main.c index 3e10c1b..7be13e5 100644 --- a/usr.sbin/sa/main.c +++ b/usr.sbin/sa/main.c @@ -46,6 +46,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -56,17 +57,16 @@ __FBSDID("$FreeBSD$"); #include "extern.h" #include "pathnames.h" -static int acct_load(const char *, int); -static u_quad_t decode_comp_t(comp_t); -static int cmp_comm(const char *, const char *); -static int cmp_usrsys(const DBT *, const DBT *); -static int cmp_avgusrsys(const DBT *, const DBT *); -static int cmp_dkio(const DBT *, const DBT *); -static int cmp_avgdkio(const DBT *, const DBT *); -static int cmp_cpumem(const DBT *, const DBT *); -static int cmp_avgcpumem(const DBT *, const DBT *); -static int cmp_calls(const DBT *, const DBT *); -static void usage(void); +static FILE *acct_load(const char *, int); +static int cmp_comm(const char *, const char *); +static int cmp_usrsys(const DBT *, const DBT *); +static int cmp_avgusrsys(const DBT *, const DBT *); +static int cmp_dkio(const DBT *, const DBT *); +static int cmp_avgdkio(const DBT *, const DBT *); +static int cmp_cpumem(const DBT *, const DBT *); +static int cmp_avgcpumem(const DBT *, const DBT *); +static int cmp_calls(const DBT *, const DBT *); +static void usage(void); int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag; int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag; @@ -83,6 +83,7 @@ cmpf_t sa_cmp = cmp_usrsys; int main(int argc, char **argv) { + FILE *f; char pathacct[] = _PATH_ACCT; int ch, error = 0; @@ -210,14 +211,12 @@ main(int argc, char **argv) /* for each file specified */ for (; argc > 0; argc--, argv++) { - int fd; - /* * load the accounting data from the file. * if it fails, go on to the next file. */ - fd = acct_load(argv[0], sflag); - if (fd < 0) + f = acct_load(argv[0], sflag); + if (f == NULL) continue; if (!uflag && sflag) { @@ -248,7 +247,7 @@ main(int argc, char **argv) * the saved stats; better to underbill than overbill, * but we want every accounting record intact. */ - if (ftruncate(fd, 0) == -1) { + if (ftruncate(fileno(f), 0) == -1) { warn("couldn't truncate %s", *argv); error = 1; } @@ -275,8 +274,8 @@ main(int argc, char **argv) /* * close the opened accounting file */ - if (close(fd) == -1) { - warn("close %s", *argv); + if (fclose(f) == EOF) { + warn("fclose %s", *argv); error = 1; } } @@ -308,21 +307,22 @@ usage() exit(1); } -static int +static FILE * acct_load(const char *pn, int wr) { - struct acct ac; + struct acctv2 ac; struct cmdinfo ci; ssize_t rv; - int fd, i; + FILE *f; + int i; /* * open the file */ - fd = open(pn, wr ? O_RDWR : O_RDONLY, 0); - if (fd == -1) { + f = fopen(pn, wr ? "r+" : "r"); + if (f == NULL) { warn("open %s %s", pn, wr ? "for read/write" : "read-only"); - return (-1); + return (NULL); } /* @@ -331,13 +331,12 @@ acct_load(const char *pn, int wr) */ while (1) { /* get one accounting entry and punt if there's an error */ - rv = read(fd, &ac, sizeof(struct acct)); - if (rv == -1) - warn("error reading %s", pn); - else if (rv > 0 && rv < (int)sizeof(struct acct)) - warnx("short read of accounting data in %s", pn); - if (rv != sizeof(struct acct)) + rv = readrec_forward(f, &ac); + if (rv != 1) { + if (rv == EOF) + warn("error reading %s", pn); break; + } /* decode it */ ci.ci_calls = 1; @@ -351,15 +350,15 @@ acct_load(const char *pn, int wr) } else ci.ci_comm[i] = c; } - if (ac.ac_flag & AFORK) + if (ac.ac_flagx & AFORK) ci.ci_comm[i++] = '*'; ci.ci_comm[i++] = '\0'; - ci.ci_etime = decode_comp_t(ac.ac_etime); - ci.ci_utime = decode_comp_t(ac.ac_utime); - ci.ci_stime = decode_comp_t(ac.ac_stime); + ci.ci_etime = ac.ac_etime; + ci.ci_utime = ac.ac_utime; + ci.ci_stime = ac.ac_stime; ci.ci_uid = ac.ac_uid; ci.ci_mem = ac.ac_mem; - ci.ci_io = decode_comp_t(ac.ac_io) / AHZ; + ci.ci_io = ac.ac_io; if (!uflag) { /* and enter it into the usracct and pacct databases */ @@ -368,34 +367,15 @@ acct_load(const char *pn, int wr) if (sflag || (mflag && !qflag)) usracct_add(&ci); } else if (!qflag) - printf("%6lu %12.2f cpu %12juk mem %12ju io %s\n", + printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n", ci.ci_uid, - (ci.ci_utime + ci.ci_stime) / (double) AHZ, - (uintmax_t)ci.ci_mem, (uintmax_t)ci.ci_io, + (ci.ci_utime + ci.ci_stime) / 1000000, + ci.ci_mem, ci.ci_io, ci.ci_comm); } - /* finally, return the file descriptor for possible truncation */ - return (fd); -} - -static u_quad_t -decode_comp_t(comp_t comp) -{ - u_quad_t rv; - - /* - * for more info on the comp_t format, see: - * /usr/src/sys/kern/kern_acct.c - * /usr/src/sys/sys/acct.h - * /usr/src/usr.bin/lastcomm/lastcomm.c - */ - rv = comp & 0x1fff; /* 13 bit fraction */ - comp >>= 13; /* 3 bit base-8 exponent */ - while (comp--) - rv <<= 3; - - return (rv); + /* Finally, return the file stream for possible truncation. */ + return (f); } /* sort commands, doing the right thing in terms of reversals */ @@ -415,7 +395,7 @@ static int cmp_usrsys(const DBT *d1, const DBT *d2) { struct cmdinfo c1, c2; - u_quad_t t1, t2; + double t1, t2; memcpy(&c1, d1->data, sizeof(c1)); memcpy(&c2, d2->data, sizeof(c2)); @@ -482,8 +462,8 @@ cmp_avgdkio(const DBT *d1, const DBT *d2) memcpy(&c1, d1->data, sizeof(c1)); memcpy(&c2, d2->data, sizeof(c2)); - n1 = (double) c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1); - n2 = (double) c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1); + n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1); + n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1); if (n1 < n2) return -1; @@ -515,7 +495,7 @@ static int cmp_avgcpumem(const DBT *d1, const DBT *d2) { struct cmdinfo c1, c2; - u_quad_t t1, t2; + double t1, t2; double n1, n2; memcpy(&c1, d1->data, sizeof(c1)); @@ -524,8 +504,8 @@ cmp_avgcpumem(const DBT *d1, const DBT *d2) t1 = c1.ci_utime + c1.ci_stime; t2 = c2.ci_utime + c2.ci_stime; - n1 = (double) c1.ci_mem / (double) (t1 ? t1 : 1); - n2 = (double) c2.ci_mem / (double) (t2 ? t2 : 1); + n1 = c1.ci_mem / (t1 ? t1 : 1); + n2 = c2.ci_mem / (t2 ? t2 : 1); if (n1 < n2) return -1; diff --git a/usr.sbin/sa/pdb.c b/usr.sbin/sa/pdb.c index 0c92d34..38beb55 100644 --- a/usr.sbin/sa/pdb.c +++ b/usr.sbin/sa/pdb.c @@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -48,68 +49,61 @@ static void print_ci(const struct cmdinfo *, const struct cmdinfo *); static DB *pacct_db; -int -pacct_init() +/* Legacy format in AHZV1 units. */ +struct cmdinfov1 { + char ci_comm[MAXCOMLEN+2]; /* command name (+ '*') */ + uid_t ci_uid; /* user id */ + u_quad_t ci_calls; /* number of calls */ + u_quad_t ci_etime; /* elapsed time */ + u_quad_t ci_utime; /* user time */ + u_quad_t ci_stime; /* system time */ + u_quad_t ci_mem; /* memory use */ + u_quad_t ci_io; /* number of disk i/o ops */ + u_int ci_flags; /* flags; see below */ +}; + +/* + * Convert a v1 data record into the current version. + * Return 0 if OK, -1 on error, setting errno. + */ +static int +v1_to_v2(DBT *key __unused, DBT *data) { - DB *saved_pacct_db; - int error; + struct cmdinfov1 civ1; + static struct cmdinfo civ2; - pacct_db = dbopen(NULL, O_RDWR, 0, DB_BTREE, NULL); - if (pacct_db == NULL) + if (data->size != sizeof(civ1)) { + errno = EFTYPE; return (-1); - - error = 0; - if (!iflag) { - DBT key, data; - int serr, nerr; - - saved_pacct_db = dbopen(pdb_file, O_RDONLY, 0, DB_BTREE, - NULL); - if (saved_pacct_db == NULL) { - error = errno == ENOENT ? 0 : -1; - if (error) - warn("retrieving process accounting summary"); - goto out; - } - - serr = DB_SEQ(saved_pacct_db, &key, &data, R_FIRST); - if (serr < 0) { - warn("retrieving process accounting summary"); - error = -1; - goto closeout; - } - while (serr == 0) { - nerr = DB_PUT(pacct_db, &key, &data, 0); - if (nerr < 0) { - warn("initializing process accounting stats"); - error = -1; - break; - } - - serr = DB_SEQ(saved_pacct_db, &key, &data, R_NEXT); - if (serr < 0) { - warn("retrieving process accounting summary"); - error = -1; - break; - } - } - -closeout: if (DB_CLOSE(saved_pacct_db) < 0) { - warn("closing process accounting summary"); - error = -1; - } } + memcpy(&civ1, data->data, data->size); + memset(&civ2, 0, sizeof(civ2)); + memcpy(civ2.ci_comm, civ1.ci_comm, sizeof(civ2.ci_comm)); + civ2.ci_uid = civ1.ci_uid; + civ2.ci_calls = civ1.ci_calls; + civ2.ci_etime = ((double)civ1.ci_etime / AHZV1) * 1000000; + civ2.ci_utime = ((double)civ1.ci_utime / AHZV1) * 1000000; + civ2.ci_stime = ((double)civ1.ci_stime / AHZV1) * 1000000; + civ2.ci_mem = civ1.ci_mem; + civ2.ci_io = civ1.ci_io; + civ2.ci_flags = civ1.ci_flags; + data->size = sizeof(civ2); + data->data = &civ2; + return (0); +} -out: if (error != 0) - pacct_destroy(); - return (error); +/* Copy pdb_file to in-memory pacct_db. */ +int +pacct_init() +{ + return (db_copy_in(&pacct_db, pdb_file, "process accounting", + NULL, v1_to_v2)); } void pacct_destroy() { - if (DB_CLOSE(pacct_db) < 0) - warn("destroying process accounting stats"); + db_destroy(pacct_db, "process accounting"); } int @@ -154,52 +148,12 @@ pacct_add(const struct cmdinfo *ci) return (0); } +/* Copy in-memory pacct_db to pdb_file. */ int pacct_update() { - DB *saved_pacct_db; - DBT key, data; - int error, serr, nerr; - - saved_pacct_db = dbopen(pdb_file, O_RDWR|O_CREAT|O_TRUNC, 0644, - DB_BTREE, NULL); - if (saved_pacct_db == NULL) { - warn("creating process accounting summary"); - return (-1); - } - - error = 0; - - serr = DB_SEQ(pacct_db, &key, &data, R_FIRST); - if (serr < 0) { - warn("retrieving process accounting stats"); - error = -1; - } - while (serr == 0) { - nerr = DB_PUT(saved_pacct_db, &key, &data, 0); - if (nerr < 0) { - warn("saving process accounting summary"); - error = -1; - break; - } - - serr = DB_SEQ(pacct_db, &key, &data, R_NEXT); - if (serr < 0) { - warn("retrieving process accounting stats"); - error = -1; - break; - } - } - - if (DB_SYNC(saved_pacct_db, 0) < 0) { - warn("syncing process accounting summary"); - error = -1; - } - if (DB_CLOSE(saved_pacct_db) < 0) { - warn("closing process accounting summary"); - error = -1; - } - return error; + return (db_copy_out(pacct_db, pdb_file, "process accounting", + NULL)); } void @@ -330,7 +284,7 @@ print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip) int uflow; c = cip->ci_calls ? cip->ci_calls : 1; - t = (cip->ci_utime + cip->ci_stime) / (double) AHZ; + t = (cip->ci_utime + cip->ci_stime) / 1000000; if (t < 0.01) { t = 0.01; uflow = 1; @@ -347,26 +301,26 @@ print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip) } if (jflag) - printf("%11.2fre ", cip->ci_etime / (double) (AHZ * c)); + printf("%11.3fre ", cip->ci_etime / (1000000 * c)); else - printf("%11.2fre ", cip->ci_etime / (60.0 * AHZ)); + printf("%11.3fre ", cip->ci_etime / (60.0 * 1000000)); if (cflag) { if (cip != totalcip) printf(" %4.1f%% ", cip->ci_etime / - (double)totalcip->ci_etime * 100); + totalcip->ci_etime * 100); else printf(" %4s ", ""); } if (!lflag) { if (jflag) - printf("%11.2fcp ", t / (double) cip->ci_calls); + printf("%11.3fcp ", t / (double) cip->ci_calls); else printf("%11.2fcp ", t / 60.0); if (cflag) { if (cip != totalcip) printf(" %4.1f%% ", - (double)(cip->ci_utime + cip->ci_stime) / + (cip->ci_utime + cip->ci_stime) / (totalcip->ci_utime + totalcip->ci_stime) * 100); else @@ -374,9 +328,9 @@ print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip) } } else { if (jflag) - printf("%11.2fu ", cip->ci_utime / (double) (AHZ * c)); + printf("%11.3fu ", cip->ci_utime / (1000000 * c)); else - printf("%11.2fu ", cip->ci_utime / (60.0 * AHZ)); + printf("%11.2fu ", cip->ci_utime / (60.0 * 1000000)); if (cflag) { if (cip != totalcip) printf(" %4.1f%% ", cip->ci_utime / @@ -385,9 +339,9 @@ print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip) printf(" %4s ", ""); } if (jflag) - printf("%11.2fs ", cip->ci_stime / (double) (AHZ * c)); + printf("%11.3fs ", cip->ci_stime / (1000000 * c)); else - printf("%11.2fs ", cip->ci_stime / (60.0 * AHZ)); + printf("%11.2fs ", cip->ci_stime / (60.0 * 1000000)); if (cflag) { if (cip != totalcip) printf(" %4.1f%% ", cip->ci_stime / @@ -401,18 +355,18 @@ print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip) if (!uflow) printf("%8.2fre/cp ", cip->ci_etime / - (double) (cip->ci_utime + cip->ci_stime)); + (cip->ci_utime + cip->ci_stime)); else printf("*ignore* "); } if (Dflag) - printf("%10jutio ", (uintmax_t)cip->ci_io); + printf("%10.0fio ", cip->ci_io); else printf("%8.0favio ", cip->ci_io / c); if (Kflag) - printf("%10juk*sec ", (uintmax_t)cip->ci_mem); + printf("%10.0fk*sec ", cip->ci_mem); else printf("%8.0fk ", cip->ci_mem / t); diff --git a/usr.sbin/sa/usrdb.c b/usr.sbin/sa/usrdb.c index 2f76d65..07a5033 100644 --- a/usr.sbin/sa/usrdb.c +++ b/usr.sbin/sa/usrdb.c @@ -49,74 +49,69 @@ static int uid_compare(const DBT *, const DBT *); static DB *usracct_db; +/* Legacy format in AHZV1 units. */ +struct userinfov1 { + uid_t ui_uid; /* user id; for consistency */ + u_quad_t ui_calls; /* number of invocations */ + u_quad_t ui_utime; /* user time */ + u_quad_t ui_stime; /* system time */ + u_quad_t ui_mem; /* memory use */ + u_quad_t ui_io; /* number of disk i/o ops */ +}; + +/* + * Convert a v1 data record into the current version. + * Return 0 if OK, -1 on error, setting errno. + */ +static int +v1_to_v2(DBT *key, DBT *data) +{ + struct userinfov1 uiv1; + static struct userinfo uiv2; + static uid_t uid; + + if (key->size != sizeof(u_long) || data->size != sizeof(uiv1)) { + errno = EFTYPE; + return (-1); + } + + /* Convert key. */ + key->size = sizeof(uid_t); + uid = (uid_t)*(u_long *)(key->data); + key->data = &uid; + + /* Convert data. */ + memcpy(&uiv1, data->data, data->size); + memset(&uiv2, 0, sizeof(uiv2)); + uiv2.ui_uid = uiv1.ui_uid; + uiv2.ui_calls = uiv1.ui_calls; + uiv2.ui_utime = ((double)uiv1.ui_utime / AHZV1) * 1000000; + uiv2.ui_stime = ((double)uiv1.ui_stime / AHZV1) * 1000000; + uiv2.ui_mem = uiv1.ui_mem; + uiv2.ui_io = uiv1.ui_io; + data->size = sizeof(uiv2); + data->data = &uiv2; + + return (0); +} + +/* Copy usrdb_file to in-memory usracct_db. */ int usracct_init() { - DB *saved_usracct_db; BTREEINFO bti; - int error; bzero(&bti, sizeof bti); bti.compare = uid_compare; - usracct_db = dbopen(NULL, O_RDWR, 0, DB_BTREE, &bti); - if (usracct_db == NULL) - return (-1); - - error = 0; - if (!iflag) { - DBT key, data; - int serr, nerr; - - saved_usracct_db = dbopen(usrdb_file, O_RDONLY, 0, DB_BTREE, - &bti); - if (saved_usracct_db == NULL) { - error = (errno == ENOENT) ? 0 : -1; - if (error) - warn("retrieving user accounting summary"); - goto out; - } - - serr = DB_SEQ(saved_usracct_db, &key, &data, R_FIRST); - if (serr < 0) { - warn("retrieving user accounting summary"); - error = -1; - goto closeout; - } - while (serr == 0) { - nerr = DB_PUT(usracct_db, &key, &data, 0); - if (nerr < 0) { - warn("initializing user accounting stats"); - error = -1; - break; - } - - serr = DB_SEQ(saved_usracct_db, &key, &data, R_NEXT); - if (serr < 0) { - warn("retrieving user accounting summary"); - error = -1; - break; - } - } - -closeout: - if (DB_CLOSE(saved_usracct_db) < 0) { - warn("closing user accounting summary"); - error = -1; - } - } - -out: - if (error != 0) - usracct_destroy(); - return (error); + return (db_copy_in(&usracct_db, usrdb_file, "user accounting", + &bti, v1_to_v2)); } void usracct_destroy() { - if (DB_CLOSE(usracct_db) < 0) - warn("destroying user accounting stats"); + db_destroy(usracct_db, "user accounting"); } int @@ -124,7 +119,7 @@ usracct_add(const struct cmdinfo *ci) { DBT key, data; struct userinfo newui; - u_long uid; + uid_t uid; int rv; uid = ci->ci_uid; @@ -133,13 +128,13 @@ usracct_add(const struct cmdinfo *ci) rv = DB_GET(usracct_db, &key, &data, 0); if (rv < 0) { - warn("get key %lu from user accounting stats", uid); + warn("get key %u from user accounting stats", uid); return (-1); } else if (rv == 0) { /* it's there; copy whole thing */ /* add the old data to the new data */ bcopy(data.data, &newui, data.size); if (newui.ui_uid != uid) { - warnx("key %lu != expected record number %lu", + warnx("key %u != expected record number %u", newui.ui_uid, uid); warnx("inconsistent user accounting stats"); return (-1); @@ -159,7 +154,7 @@ usracct_add(const struct cmdinfo *ci) data.size = sizeof newui; rv = DB_PUT(usracct_db, &key, &data, 0); if (rv < 0) { - warn("add key %lu to user accounting stats", uid); + warn("add key %u to user accounting stats", uid); return (-1); } else if (rv != 0) { warnx("DB_PUT returned 1"); @@ -169,56 +164,17 @@ usracct_add(const struct cmdinfo *ci) return (0); } +/* Copy in-memory usracct_db to usrdb_file. */ int usracct_update() { - DB *saved_usracct_db; - DBT key, data; BTREEINFO bti; - int error, serr, nerr; bzero(&bti, sizeof bti); bti.compare = uid_compare; - saved_usracct_db = dbopen(usrdb_file, O_RDWR|O_CREAT|O_TRUNC, 0644, - DB_BTREE, &bti); - if (saved_usracct_db == NULL) { - warn("creating user accounting summary"); - return (-1); - } - - error = 0; - - serr = DB_SEQ(usracct_db, &key, &data, R_FIRST); - if (serr < 0) { - warn("retrieving user accounting stats"); - error = -1; - } - while (serr == 0) { - nerr = DB_PUT(saved_usracct_db, &key, &data, 0); - if (nerr < 0) { - warn("saving user accounting summary"); - error = -1; - break; - } - - serr = DB_SEQ(usracct_db, &key, &data, R_NEXT); - if (serr < 0) { - warn("retrieving user accounting stats"); - error = -1; - break; - } - } - - if (DB_SYNC(saved_usracct_db, 0) < 0) { - warn("syncing process accounting summary"); - error = -1; - } - if (DB_CLOSE(saved_usracct_db) < 0) { - warn("closing process accounting summary"); - error = -1; - } - return error; + return (db_copy_out(usracct_db, usrdb_file, "user accounting", + &bti)); } void @@ -239,25 +195,24 @@ usracct_print() printf("%-*s %9ju ", MAXLOGNAME - 1, user_from_uid(ui->ui_uid, 0), (uintmax_t)ui->ui_calls); - t = (double) (ui->ui_utime + ui->ui_stime) / - (double) AHZ; - if (t < 0.0001) /* kill divide by zero */ - t = 0.0001; + t = (ui->ui_utime + ui->ui_stime) / 1000000; + if (t < 0.000001) /* kill divide by zero */ + t = 0.000001; printf("%12.2f%s ", t / 60.0, "cpu"); /* ui->ui_calls is always != 0 */ if (dflag) - printf("%12ju%s", - (uintmax_t)(ui->ui_io / ui->ui_calls), "avio"); + printf("%12.0f%s", + ui->ui_io / ui->ui_calls, "avio"); else - printf("%12ju%s", (uintmax_t)ui->ui_io, "tio"); + printf("%12.0f%s", ui->ui_io, "tio"); - /* t is always >= 0.0001; see above */ + /* t is always >= 0.000001; see above. */ if (kflag) printf("%12.0f%s", ui->ui_mem / t, "k"); else - printf("%12ju%s", (uintmax_t)ui->ui_mem, "k*sec"); + printf("%12.0f%s", ui->ui_mem, "k*sec"); printf("\n"); @@ -270,7 +225,7 @@ usracct_print() static int uid_compare(const DBT *k1, const DBT *k2) { - u_long d1, d2; + uid_t d1, d2; bcopy(k1->data, &d1, sizeof d1); bcopy(k2->data, &d2, sizeof d2); -- cgit v1.1