]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/sa/main.c
daemon(8): handle case of waitpid() returning without exited child
[FreeBSD/FreeBSD.git] / usr.sbin / sa / main.c
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
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
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 *
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
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
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
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
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
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
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
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
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 }