6992112349
Extend the ino_t, dev_t, nlink_t types to 64-bit ints. Modify struct dirent layout to add d_off, increase the size of d_fileno to 64-bits, increase the size of d_namlen to 16-bits, and change the required alignment. Increase struct statfs f_mntfromname[] and f_mntonname[] array length MNAMELEN to 1024. ABI breakage is mitigated by providing compatibility using versioned symbols, ingenious use of the existing padding in structures, and by employing other tricks. Unfortunately, not everything can be fixed, especially outside the base system. For instance, third-party APIs which pass struct stat around are broken in backward and forward incompatible ways. Kinfo sysctl MIBs ABI is changed in backward-compatible way, but there is no general mechanism to handle other sysctl MIBS which return structures where the layout has changed. It was considered that the breakage is either in the management interfaces, where we usually allow ABI slip, or is not important. Struct xvnode changed layout, no compat shims are provided. For struct xtty, dev_t tty device member was reduced to uint32_t. It was decided that keeping ABI compat in this case is more useful than reporting 64-bit dev_t, for the sake of pstat. Update note: strictly follow the instructions in UPDATING. Build and install the new kernel with COMPAT_FREEBSD11 option enabled, then reboot, and only then install new world. Credits: The 64-bit inode project, also known as ino64, started life many years ago as a project by Gleb Kurtsou (gleb). Kirk McKusick (mckusick) then picked up and updated the patch, and acted as a flag-waver. Feedback, suggestions, and discussions were carried by Ed Maste (emaste), John Baldwin (jhb), Jilles Tjoelker (jilles), and Rick Macklem (rmacklem). Kris Moore (kris) performed an initial ports investigation followed by an exp-run by Antoine Brodin (antoine). Essential and all-embracing testing was done by Peter Holm (pho). The heavy lifting of coordinating all these efforts and bringing the project to completion were done by Konstantin Belousov (kib). Sponsored by: The FreeBSD Foundation (emaste, kib) Differential revision: https://reviews.freebsd.org/D10439
534 lines
12 KiB
C
534 lines
12 KiB
C
/*
|
|
* Copyright (c) 1994 Christopher G. Demetriou
|
|
* 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.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by Christopher G. Demetriou.
|
|
* 4. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
|
|
*/
|
|
|
|
#if 0
|
|
#ifndef lint
|
|
static const char copyright[] =
|
|
"@(#) Copyright (c) 1994 Christopher G. Demetriou\n\
|
|
All rights reserved.\n";
|
|
#endif
|
|
#endif
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
/*
|
|
* sa: system accounting
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/acct.h>
|
|
#include <ctype.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "extern.h"
|
|
#include "pathnames.h"
|
|
|
|
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;
|
|
u_quad_t cutoff = 1;
|
|
const char *pdb_file = _PATH_SAVACCT;
|
|
const char *usrdb_file = _PATH_USRACCT;
|
|
|
|
static char *dfltargv[] = { NULL };
|
|
static int dfltargc = (sizeof dfltargv/sizeof(char *));
|
|
|
|
/* default to comparing by sum of user + system time */
|
|
cmpf_t sa_cmp = cmp_usrsys;
|
|
|
|
int
|
|
main(int argc, char **argv)
|
|
{
|
|
FILE *f;
|
|
char pathacct[] = _PATH_ACCT;
|
|
int ch, error = 0;
|
|
|
|
dfltargv[0] = pathacct;
|
|
|
|
while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1)
|
|
switch (ch) {
|
|
case 'a':
|
|
/* print all commands */
|
|
aflag = 1;
|
|
break;
|
|
case 'b':
|
|
/* sort by per-call user/system time average */
|
|
bflag = 1;
|
|
sa_cmp = cmp_avgusrsys;
|
|
break;
|
|
case 'c':
|
|
/* print percentage total time */
|
|
cflag = 1;
|
|
break;
|
|
case 'd':
|
|
/* sort by averge number of disk I/O ops */
|
|
dflag = 1;
|
|
sa_cmp = cmp_avgdkio;
|
|
break;
|
|
case 'D':
|
|
/* print and sort by total disk I/O ops */
|
|
Dflag = 1;
|
|
sa_cmp = cmp_dkio;
|
|
break;
|
|
case 'f':
|
|
/* force no interactive threshold comprison */
|
|
fflag = 1;
|
|
break;
|
|
case 'i':
|
|
/* do not read in summary file */
|
|
iflag = 1;
|
|
break;
|
|
case 'j':
|
|
/* instead of total minutes, give sec/call */
|
|
jflag = 1;
|
|
break;
|
|
case 'k':
|
|
/* sort by cpu-time average memory usage */
|
|
kflag = 1;
|
|
sa_cmp = cmp_avgcpumem;
|
|
break;
|
|
case 'K':
|
|
/* print and sort by cpu-storage integral */
|
|
sa_cmp = cmp_cpumem;
|
|
Kflag = 1;
|
|
break;
|
|
case 'l':
|
|
/* separate system and user time */
|
|
lflag = 1;
|
|
break;
|
|
case 'm':
|
|
/* print procs and time per-user */
|
|
mflag = 1;
|
|
break;
|
|
case 'n':
|
|
/* sort by number of calls */
|
|
sa_cmp = cmp_calls;
|
|
break;
|
|
case 'P':
|
|
/* specify program database summary file */
|
|
pdb_file = optarg;
|
|
break;
|
|
case 'q':
|
|
/* quiet; error messages only */
|
|
qflag = 1;
|
|
break;
|
|
case 'r':
|
|
/* reverse order of sort */
|
|
rflag = 1;
|
|
break;
|
|
case 's':
|
|
/* merge accounting file into summaries */
|
|
sflag = 1;
|
|
break;
|
|
case 't':
|
|
/* report ratio of user and system times */
|
|
tflag = 1;
|
|
break;
|
|
case 'u':
|
|
/* first, print uid and command name */
|
|
uflag = 1;
|
|
break;
|
|
case 'U':
|
|
/* specify user database summary file */
|
|
usrdb_file = optarg;
|
|
break;
|
|
case 'v':
|
|
/* cull junk */
|
|
vflag = 1;
|
|
cutoff = atoi(optarg);
|
|
break;
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
/* various argument checking */
|
|
if (fflag && !vflag)
|
|
errx(1, "only one of -f requires -v");
|
|
if (fflag && aflag)
|
|
errx(1, "only one of -a and -v may be specified");
|
|
/* XXX need more argument checking */
|
|
|
|
if (!uflag) {
|
|
/* initialize tables */
|
|
if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
|
|
errx(1, "process accounting initialization failed");
|
|
if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
|
|
errx(1, "user accounting initialization failed");
|
|
}
|
|
|
|
if (argc == 0) {
|
|
argc = dfltargc;
|
|
argv = dfltargv;
|
|
}
|
|
|
|
/* for each file specified */
|
|
for (; argc > 0; argc--, argv++) {
|
|
/*
|
|
* load the accounting data from the file.
|
|
* if it fails, go on to the next file.
|
|
*/
|
|
f = acct_load(argv[0], sflag);
|
|
if (f == NULL)
|
|
continue;
|
|
|
|
if (!uflag && sflag) {
|
|
#ifndef DEBUG
|
|
sigset_t nmask, omask;
|
|
int unmask = 1;
|
|
|
|
/*
|
|
* block most signals so we aren't interrupted during
|
|
* the update.
|
|
*/
|
|
if (sigfillset(&nmask) == -1) {
|
|
warn("sigfillset");
|
|
unmask = 0;
|
|
error = 1;
|
|
}
|
|
if (unmask &&
|
|
(sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
|
|
warn("couldn't set signal mask");
|
|
unmask = 0;
|
|
error = 1;
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
/*
|
|
* truncate the accounting data file ASAP, to avoid
|
|
* losing data. don't worry about errors in updating
|
|
* the saved stats; better to underbill than overbill,
|
|
* but we want every accounting record intact.
|
|
*/
|
|
if (ftruncate(fileno(f), 0) == -1) {
|
|
warn("couldn't truncate %s", *argv);
|
|
error = 1;
|
|
}
|
|
|
|
/*
|
|
* update saved user and process accounting data.
|
|
* note errors for later.
|
|
*/
|
|
if (pacct_update() != 0 || usracct_update() != 0)
|
|
error = 1;
|
|
|
|
#ifndef DEBUG
|
|
/*
|
|
* restore signals
|
|
*/
|
|
if (unmask &&
|
|
(sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
|
|
warn("couldn't restore signal mask");
|
|
error = 1;
|
|
}
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
/*
|
|
* close the opened accounting file
|
|
*/
|
|
if (fclose(f) == EOF) {
|
|
warn("fclose %s", *argv);
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (!uflag && !qflag) {
|
|
/* print any results we may have obtained. */
|
|
if (!mflag)
|
|
pacct_print();
|
|
else
|
|
usracct_print();
|
|
}
|
|
|
|
if (!uflag) {
|
|
/* finally, deallocate databases */
|
|
if (sflag || (!mflag && !qflag))
|
|
pacct_destroy();
|
|
if (sflag || (mflag && !qflag))
|
|
usracct_destroy();
|
|
}
|
|
|
|
exit(error);
|
|
}
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
(void)fprintf(stderr,
|
|
"usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
|
|
exit(1);
|
|
}
|
|
|
|
static FILE *
|
|
acct_load(const char *pn, int wr)
|
|
{
|
|
struct acctv3 ac;
|
|
struct cmdinfo ci;
|
|
ssize_t rv;
|
|
FILE *f;
|
|
int i;
|
|
|
|
/*
|
|
* open the file
|
|
*/
|
|
f = fopen(pn, wr ? "r+" : "r");
|
|
if (f == NULL) {
|
|
warn("open %s %s", pn, wr ? "for read/write" : "read-only");
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* read all we can; don't stat and open because more processes
|
|
* could exit, and we'd miss them
|
|
*/
|
|
while (1) {
|
|
/* get one accounting entry and punt if there's an error */
|
|
rv = readrec_forward(f, &ac);
|
|
if (rv != 1) {
|
|
if (rv == EOF)
|
|
warn("error reading %s", pn);
|
|
break;
|
|
}
|
|
|
|
/* decode it */
|
|
ci.ci_calls = 1;
|
|
for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
|
|
i++) {
|
|
char c = ac.ac_comm[i];
|
|
|
|
if (!isascii(c) || iscntrl(c)) {
|
|
ci.ci_comm[i] = '?';
|
|
ci.ci_flags |= CI_UNPRINTABLE;
|
|
} else
|
|
ci.ci_comm[i] = c;
|
|
}
|
|
if (ac.ac_flagx & AFORK)
|
|
ci.ci_comm[i++] = '*';
|
|
ci.ci_comm[i++] = '\0';
|
|
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 = ac.ac_io;
|
|
|
|
if (!uflag) {
|
|
/* and enter it into the usracct and pacct databases */
|
|
if (sflag || (!mflag && !qflag))
|
|
pacct_add(&ci);
|
|
if (sflag || (mflag && !qflag))
|
|
usracct_add(&ci);
|
|
} else if (!qflag)
|
|
printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n",
|
|
ci.ci_uid,
|
|
(ci.ci_utime + ci.ci_stime) / 1000000,
|
|
ci.ci_mem, ci.ci_io,
|
|
ci.ci_comm);
|
|
}
|
|
|
|
/* Finally, return the file stream for possible truncation. */
|
|
return (f);
|
|
}
|
|
|
|
/* sort commands, doing the right thing in terms of reversals */
|
|
static int
|
|
cmp_comm(const char *s1, const char *s2)
|
|
{
|
|
int rv;
|
|
|
|
rv = strcmp(s1, s2);
|
|
if (rv == 0)
|
|
rv = -1;
|
|
return (rflag ? rv : -rv);
|
|
}
|
|
|
|
/* sort by total user and system time */
|
|
static int
|
|
cmp_usrsys(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
double t1, t2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
t1 = c1.ci_utime + c1.ci_stime;
|
|
t2 = c2.ci_utime + c2.ci_stime;
|
|
|
|
if (t1 < t2)
|
|
return -1;
|
|
else if (t1 == t2)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by average user and system time */
|
|
static int
|
|
cmp_avgusrsys(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
double t1, t2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
t1 = c1.ci_utime + c1.ci_stime;
|
|
t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
|
|
|
|
t2 = c2.ci_utime + c2.ci_stime;
|
|
t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
|
|
|
|
if (t1 < t2)
|
|
return -1;
|
|
else if (t1 == t2)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by total number of disk I/O operations */
|
|
static int
|
|
cmp_dkio(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
if (c1.ci_io < c2.ci_io)
|
|
return -1;
|
|
else if (c1.ci_io == c2.ci_io)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by average number of disk I/O operations */
|
|
static int
|
|
cmp_avgdkio(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
double n1, n2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
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;
|
|
else if (n1 == n2)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by the cpu-storage integral */
|
|
static int
|
|
cmp_cpumem(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
if (c1.ci_mem < c2.ci_mem)
|
|
return -1;
|
|
else if (c1.ci_mem == c2.ci_mem)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by the cpu-time average memory usage */
|
|
static int
|
|
cmp_avgcpumem(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
double t1, t2;
|
|
double n1, n2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
t1 = c1.ci_utime + c1.ci_stime;
|
|
t2 = c2.ci_utime + c2.ci_stime;
|
|
|
|
n1 = c1.ci_mem / (t1 ? t1 : 1);
|
|
n2 = c2.ci_mem / (t2 ? t2 : 1);
|
|
|
|
if (n1 < n2)
|
|
return -1;
|
|
else if (n1 == n2)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
/* sort by the number of invocations */
|
|
static int
|
|
cmp_calls(const DBT *d1, const DBT *d2)
|
|
{
|
|
struct cmdinfo c1, c2;
|
|
|
|
memcpy(&c1, d1->data, sizeof(c1));
|
|
memcpy(&c2, d2->data, sizeof(c2));
|
|
|
|
if (c1.ci_calls < c2.ci_calls)
|
|
return -1;
|
|
else if (c1.ci_calls == c2.ci_calls)
|
|
return (cmp_comm(c1.ci_comm, c2.ci_comm));
|
|
else
|
|
return 1;
|
|
}
|