1 /*-
2 * SPDX-License-Identifier: BSD-4-Clause
3 *
4 * Copyright (c) 1994 Christopher G. Demetriou
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by Christopher G. Demetriou.
18 * 4. The name of the author may not be used to endorse or promote products
19 * derived from this software without specific prior written permission
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /*
34 * sa: system accounting
35 */
36
37 #include <sys/types.h>
38 #include <sys/acct.h>
39 #include <ctype.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <fcntl.h>
43 #include <signal.h>
44 #include <stdint.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <unistd.h>
49 #include "extern.h"
50 #include "pathnames.h"
51
52 static FILE *acct_load(const char *, int);
53 static int cmp_comm(const char *, const char *);
54 static int cmp_usrsys(const DBT *, const DBT *);
55 static int cmp_avgusrsys(const DBT *, const DBT *);
56 static int cmp_dkio(const DBT *, const DBT *);
57 static int cmp_avgdkio(const DBT *, const DBT *);
58 static int cmp_cpumem(const DBT *, const DBT *);
59 static int cmp_avgcpumem(const DBT *, const DBT *);
60 static int cmp_calls(const DBT *, const DBT *);
61 static void usage(void);
62
63 int aflag, bflag, cflag, dflag, Dflag, fflag, iflag, jflag, kflag;
64 int Kflag, lflag, mflag, qflag, rflag, sflag, tflag, uflag, vflag;
65 u_quad_t cutoff = 1;
66 const char *pdb_file = _PATH_SAVACCT;
67 const char *usrdb_file = _PATH_USRACCT;
68
69 static char *dfltargv[] = { NULL };
70 static int dfltargc = (sizeof dfltargv/sizeof(char *));
71
72 /* default to comparing by sum of user + system time */
73 cmpf_t sa_cmp = cmp_usrsys;
74
75 int
main(int argc,char ** argv)76 main(int argc, char **argv)
77 {
78 FILE *f;
79 char pathacct[] = _PATH_ACCT;
80 int ch, error = 0;
81
82 dfltargv[0] = pathacct;
83
84 while ((ch = getopt(argc, argv, "abcdDfijkKlmnP:qrstuU:v:")) != -1)
85 switch (ch) {
86 case 'a':
87 /* print all commands */
88 aflag = 1;
89 break;
90 case 'b':
91 /* sort by per-call user/system time average */
92 bflag = 1;
93 sa_cmp = cmp_avgusrsys;
94 break;
95 case 'c':
96 /* print percentage total time */
97 cflag = 1;
98 break;
99 case 'd':
100 /* sort by averge number of disk I/O ops */
101 dflag = 1;
102 sa_cmp = cmp_avgdkio;
103 break;
104 case 'D':
105 /* print and sort by total disk I/O ops */
106 Dflag = 1;
107 sa_cmp = cmp_dkio;
108 break;
109 case 'f':
110 /* force no interactive threshold comprison */
111 fflag = 1;
112 break;
113 case 'i':
114 /* do not read in summary file */
115 iflag = 1;
116 break;
117 case 'j':
118 /* instead of total minutes, give sec/call */
119 jflag = 1;
120 break;
121 case 'k':
122 /* sort by cpu-time average memory usage */
123 kflag = 1;
124 sa_cmp = cmp_avgcpumem;
125 break;
126 case 'K':
127 /* print and sort by cpu-storage integral */
128 sa_cmp = cmp_cpumem;
129 Kflag = 1;
130 break;
131 case 'l':
132 /* separate system and user time */
133 lflag = 1;
134 break;
135 case 'm':
136 /* print procs and time per-user */
137 mflag = 1;
138 break;
139 case 'n':
140 /* sort by number of calls */
141 sa_cmp = cmp_calls;
142 break;
143 case 'P':
144 /* specify program database summary file */
145 pdb_file = optarg;
146 break;
147 case 'q':
148 /* quiet; error messages only */
149 qflag = 1;
150 break;
151 case 'r':
152 /* reverse order of sort */
153 rflag = 1;
154 break;
155 case 's':
156 /* merge accounting file into summaries */
157 sflag = 1;
158 break;
159 case 't':
160 /* report ratio of user and system times */
161 tflag = 1;
162 break;
163 case 'u':
164 /* first, print uid and command name */
165 uflag = 1;
166 break;
167 case 'U':
168 /* specify user database summary file */
169 usrdb_file = optarg;
170 break;
171 case 'v':
172 /* cull junk */
173 vflag = 1;
174 cutoff = atoi(optarg);
175 break;
176 case '?':
177 default:
178 usage();
179 }
180
181 argc -= optind;
182 argv += optind;
183
184 /* various argument checking */
185 if (fflag && !vflag)
186 errx(1, "only one of -f requires -v");
187 if (fflag && aflag)
188 errx(1, "only one of -a and -v may be specified");
189 /* XXX need more argument checking */
190
191 if (!uflag) {
192 /* initialize tables */
193 if ((sflag || (!mflag && !qflag)) && pacct_init() != 0)
194 errx(1, "process accounting initialization failed");
195 if ((sflag || (mflag && !qflag)) && usracct_init() != 0)
196 errx(1, "user accounting initialization failed");
197 }
198
199 if (argc == 0) {
200 argc = dfltargc;
201 argv = dfltargv;
202 }
203
204 /* for each file specified */
205 for (; argc > 0; argc--, argv++) {
206 /*
207 * load the accounting data from the file.
208 * if it fails, go on to the next file.
209 */
210 f = acct_load(argv[0], sflag);
211 if (f == NULL)
212 continue;
213
214 if (!uflag && sflag) {
215 #ifndef DEBUG
216 sigset_t nmask, omask;
217 int unmask = 1;
218
219 /*
220 * block most signals so we aren't interrupted during
221 * the update.
222 */
223 if (sigfillset(&nmask) == -1) {
224 warn("sigfillset");
225 unmask = 0;
226 error = 1;
227 }
228 if (unmask &&
229 (sigprocmask(SIG_BLOCK, &nmask, &omask) == -1)) {
230 warn("couldn't set signal mask");
231 unmask = 0;
232 error = 1;
233 }
234 #endif /* DEBUG */
235
236 /*
237 * truncate the accounting data file ASAP, to avoid
238 * losing data. don't worry about errors in updating
239 * the saved stats; better to underbill than overbill,
240 * but we want every accounting record intact.
241 */
242 if (ftruncate(fileno(f), 0) == -1) {
243 warn("couldn't truncate %s", *argv);
244 error = 1;
245 }
246
247 /*
248 * update saved user and process accounting data.
249 * note errors for later.
250 */
251 if (pacct_update() != 0 || usracct_update() != 0)
252 error = 1;
253
254 #ifndef DEBUG
255 /*
256 * restore signals
257 */
258 if (unmask &&
259 (sigprocmask(SIG_SETMASK, &omask, NULL) == -1)) {
260 warn("couldn't restore signal mask");
261 error = 1;
262 }
263 #endif /* DEBUG */
264 }
265
266 /*
267 * close the opened accounting file
268 */
269 if (fclose(f) == EOF) {
270 warn("fclose %s", *argv);
271 error = 1;
272 }
273 }
274
275 if (!uflag && !qflag) {
276 /* print any results we may have obtained. */
277 if (!mflag)
278 pacct_print();
279 else
280 usracct_print();
281 }
282
283 if (!uflag) {
284 /* finally, deallocate databases */
285 if (sflag || (!mflag && !qflag))
286 pacct_destroy();
287 if (sflag || (mflag && !qflag))
288 usracct_destroy();
289 }
290
291 exit(error);
292 }
293
294 static void
usage(void)295 usage(void)
296 {
297 (void)fprintf(stderr,
298 "usage: sa [-abcdDfijkKlmnqrstu] [-P file] [-U file] [-v cutoff] [file ...]\n");
299 exit(1);
300 }
301
302 static FILE *
acct_load(const char * pn,int wr)303 acct_load(const char *pn, int wr)
304 {
305 struct acctv3 ac;
306 struct cmdinfo ci;
307 ssize_t rv;
308 FILE *f;
309 int i;
310
311 /*
312 * open the file
313 */
314 f = fopen(pn, wr ? "r+" : "r");
315 if (f == NULL) {
316 warn("open %s %s", pn, wr ? "for read/write" : "read-only");
317 return (NULL);
318 }
319
320 /*
321 * read all we can; don't stat and open because more processes
322 * could exit, and we'd miss them
323 */
324 while (1) {
325 /* get one accounting entry and punt if there's an error */
326 rv = readrec_forward(f, &ac);
327 if (rv != 1) {
328 if (rv == EOF)
329 warn("error reading %s", pn);
330 break;
331 }
332
333 /* decode it */
334 ci.ci_calls = 1;
335 for (i = 0; i < (int)sizeof ac.ac_comm && ac.ac_comm[i] != '\0';
336 i++) {
337 char c = ac.ac_comm[i];
338
339 if (!isascii(c) || iscntrl(c)) {
340 ci.ci_comm[i] = '?';
341 ci.ci_flags |= CI_UNPRINTABLE;
342 } else
343 ci.ci_comm[i] = c;
344 }
345 if (ac.ac_flagx & AFORK)
346 ci.ci_comm[i++] = '*';
347 ci.ci_comm[i++] = '\0';
348 ci.ci_etime = ac.ac_etime;
349 ci.ci_utime = ac.ac_utime;
350 ci.ci_stime = ac.ac_stime;
351 ci.ci_uid = ac.ac_uid;
352 ci.ci_mem = ac.ac_mem;
353 ci.ci_io = ac.ac_io;
354
355 if (!uflag) {
356 /* and enter it into the usracct and pacct databases */
357 if (sflag || (!mflag && !qflag))
358 pacct_add(&ci);
359 if (sflag || (mflag && !qflag))
360 usracct_add(&ci);
361 } else if (!qflag)
362 printf("%6u %12.3lf cpu %12.0lfk mem %12.0lf io %s\n",
363 ci.ci_uid,
364 (ci.ci_utime + ci.ci_stime) / 1000000,
365 ci.ci_mem, ci.ci_io,
366 ci.ci_comm);
367 }
368
369 /* Finally, return the file stream for possible truncation. */
370 return (f);
371 }
372
373 /* sort commands, doing the right thing in terms of reversals */
374 static int
cmp_comm(const char * s1,const char * s2)375 cmp_comm(const char *s1, const char *s2)
376 {
377 int rv;
378
379 rv = strcmp(s1, s2);
380 if (rv == 0)
381 rv = -1;
382 return (rflag ? rv : -rv);
383 }
384
385 /* sort by total user and system time */
386 static int
cmp_usrsys(const DBT * d1,const DBT * d2)387 cmp_usrsys(const DBT *d1, const DBT *d2)
388 {
389 struct cmdinfo c1, c2;
390 double t1, t2;
391
392 memcpy(&c1, d1->data, sizeof(c1));
393 memcpy(&c2, d2->data, sizeof(c2));
394
395 t1 = c1.ci_utime + c1.ci_stime;
396 t2 = c2.ci_utime + c2.ci_stime;
397
398 if (t1 < t2)
399 return -1;
400 else if (t1 == t2)
401 return (cmp_comm(c1.ci_comm, c2.ci_comm));
402 else
403 return 1;
404 }
405
406 /* sort by average user and system time */
407 static int
cmp_avgusrsys(const DBT * d1,const DBT * d2)408 cmp_avgusrsys(const DBT *d1, const DBT *d2)
409 {
410 struct cmdinfo c1, c2;
411 double t1, t2;
412
413 memcpy(&c1, d1->data, sizeof(c1));
414 memcpy(&c2, d2->data, sizeof(c2));
415
416 t1 = c1.ci_utime + c1.ci_stime;
417 t1 /= (double) (c1.ci_calls ? c1.ci_calls : 1);
418
419 t2 = c2.ci_utime + c2.ci_stime;
420 t2 /= (double) (c2.ci_calls ? c2.ci_calls : 1);
421
422 if (t1 < t2)
423 return -1;
424 else if (t1 == t2)
425 return (cmp_comm(c1.ci_comm, c2.ci_comm));
426 else
427 return 1;
428 }
429
430 /* sort by total number of disk I/O operations */
431 static int
cmp_dkio(const DBT * d1,const DBT * d2)432 cmp_dkio(const DBT *d1, const DBT *d2)
433 {
434 struct cmdinfo c1, c2;
435
436 memcpy(&c1, d1->data, sizeof(c1));
437 memcpy(&c2, d2->data, sizeof(c2));
438
439 if (c1.ci_io < c2.ci_io)
440 return -1;
441 else if (c1.ci_io == c2.ci_io)
442 return (cmp_comm(c1.ci_comm, c2.ci_comm));
443 else
444 return 1;
445 }
446
447 /* sort by average number of disk I/O operations */
448 static int
cmp_avgdkio(const DBT * d1,const DBT * d2)449 cmp_avgdkio(const DBT *d1, const DBT *d2)
450 {
451 struct cmdinfo c1, c2;
452 double n1, n2;
453
454 memcpy(&c1, d1->data, sizeof(c1));
455 memcpy(&c2, d2->data, sizeof(c2));
456
457 n1 = c1.ci_io / (double) (c1.ci_calls ? c1.ci_calls : 1);
458 n2 = c2.ci_io / (double) (c2.ci_calls ? c2.ci_calls : 1);
459
460 if (n1 < n2)
461 return -1;
462 else if (n1 == n2)
463 return (cmp_comm(c1.ci_comm, c2.ci_comm));
464 else
465 return 1;
466 }
467
468 /* sort by the cpu-storage integral */
469 static int
cmp_cpumem(const DBT * d1,const DBT * d2)470 cmp_cpumem(const DBT *d1, const DBT *d2)
471 {
472 struct cmdinfo c1, c2;
473
474 memcpy(&c1, d1->data, sizeof(c1));
475 memcpy(&c2, d2->data, sizeof(c2));
476
477 if (c1.ci_mem < c2.ci_mem)
478 return -1;
479 else if (c1.ci_mem == c2.ci_mem)
480 return (cmp_comm(c1.ci_comm, c2.ci_comm));
481 else
482 return 1;
483 }
484
485 /* sort by the cpu-time average memory usage */
486 static int
cmp_avgcpumem(const DBT * d1,const DBT * d2)487 cmp_avgcpumem(const DBT *d1, const DBT *d2)
488 {
489 struct cmdinfo c1, c2;
490 double t1, t2;
491 double n1, n2;
492
493 memcpy(&c1, d1->data, sizeof(c1));
494 memcpy(&c2, d2->data, sizeof(c2));
495
496 t1 = c1.ci_utime + c1.ci_stime;
497 t2 = c2.ci_utime + c2.ci_stime;
498
499 n1 = c1.ci_mem / (t1 ? t1 : 1);
500 n2 = c2.ci_mem / (t2 ? t2 : 1);
501
502 if (n1 < n2)
503 return -1;
504 else if (n1 == n2)
505 return (cmp_comm(c1.ci_comm, c2.ci_comm));
506 else
507 return 1;
508 }
509
510 /* sort by the number of invocations */
511 static int
cmp_calls(const DBT * d1,const DBT * d2)512 cmp_calls(const DBT *d1, const DBT *d2)
513 {
514 struct cmdinfo c1, c2;
515
516 memcpy(&c1, d1->data, sizeof(c1));
517 memcpy(&c2, d2->data, sizeof(c2));
518
519 if (c1.ci_calls < c2.ci_calls)
520 return -1;
521 else if (c1.ci_calls == c2.ci_calls)
522 return (cmp_comm(c1.ci_comm, c2.ci_comm));
523 else
524 return 1;
525 }
526