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