xref: /freebsd/usr.sbin/sa/pdb.c (revision 06c3fb27)
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 #include <sys/types.h>
34 #include <sys/acct.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <stdbool.h>
39 #include <stdint.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include "extern.h"
43 #include "pathnames.h"
44 
45 static int check_junk(const struct cmdinfo *);
46 static void add_ci(const struct cmdinfo *, struct cmdinfo *);
47 static void print_ci(const struct cmdinfo *, const struct cmdinfo *);
48 
49 static DB	*pacct_db;
50 
51 /* Legacy format in AHZV1 units. */
52 struct cmdinfov1 {
53 	char		ci_comm[MAXCOMLEN+2];	/* command name (+ '*') */
54 	uid_t		ci_uid;			/* user id */
55 	u_quad_t	ci_calls;		/* number of calls */
56 	u_quad_t	ci_etime;		/* elapsed time */
57 	u_quad_t	ci_utime;		/* user time */
58 	u_quad_t	ci_stime;		/* system time */
59 	u_quad_t	ci_mem;			/* memory use */
60 	u_quad_t	ci_io;			/* number of disk i/o ops */
61 	u_int		ci_flags;		/* flags; see below */
62 };
63 
64 /*
65  * Convert a v1 data record into the current version.
66  * Return 0 if OK, -1 on error, setting errno.
67  */
68 static int
69 v1_to_v2(DBT *key __unused, DBT *data)
70 {
71 	struct cmdinfov1 civ1;
72 	static struct cmdinfo civ2;
73 
74 	if (data->size != sizeof(civ1)) {
75 		errno = EFTYPE;
76 		return (-1);
77 	}
78 	memcpy(&civ1, data->data, data->size);
79 	memset(&civ2, 0, sizeof(civ2));
80 	memcpy(civ2.ci_comm, civ1.ci_comm, sizeof(civ2.ci_comm));
81 	civ2.ci_uid = civ1.ci_uid;
82 	civ2.ci_calls = civ1.ci_calls;
83 	civ2.ci_etime = ((double)civ1.ci_etime / AHZV1) * 1000000;
84 	civ2.ci_utime = ((double)civ1.ci_utime / AHZV1) * 1000000;
85 	civ2.ci_stime = ((double)civ1.ci_stime / AHZV1) * 1000000;
86 	civ2.ci_mem = civ1.ci_mem;
87 	civ2.ci_io = civ1.ci_io;
88 	civ2.ci_flags = civ1.ci_flags;
89 	data->size = sizeof(civ2);
90 	data->data = &civ2;
91 	return (0);
92 }
93 
94 /* Copy pdb_file to in-memory pacct_db. */
95 int
96 pacct_init(void)
97 {
98 	return (db_copy_in(&pacct_db, pdb_file, "process accounting",
99 	    NULL, v1_to_v2));
100 }
101 
102 void
103 pacct_destroy(void)
104 {
105 	db_destroy(pacct_db, "process accounting");
106 }
107 
108 int
109 pacct_add(const struct cmdinfo *ci)
110 {
111 	DBT key, data;
112 	struct cmdinfo newci;
113 	char keydata[sizeof ci->ci_comm];
114 	int rv;
115 
116 	bcopy(ci->ci_comm, &keydata, sizeof keydata);
117 	key.data = &keydata;
118 	key.size = strlen(keydata);
119 
120 	rv = DB_GET(pacct_db, &key, &data, 0);
121 	if (rv < 0) {
122 		warn("get key %s from process accounting stats", ci->ci_comm);
123 		return (-1);
124 	} else if (rv == 0) {	/* it's there; copy whole thing */
125 		/* XXX compare size if paranoid */
126 		/* add the old data to the new data */
127 		bcopy(data.data, &newci, data.size);
128 	} else {		/* it's not there; zero it and copy the key */
129 		bzero(&newci, sizeof newci);
130 		bcopy(key.data, newci.ci_comm, key.size);
131 	}
132 
133 	add_ci(ci, &newci);
134 
135 	data.data = &newci;
136 	data.size = sizeof newci;
137 	rv = DB_PUT(pacct_db, &key, &data, 0);
138 	if (rv < 0) {
139 		warn("add key %s to process accounting stats", ci->ci_comm);
140 		return (-1);
141 	} else if (rv == 1) {
142 		warnx("duplicate key %s in process accounting stats",
143 		    ci->ci_comm);
144 		return (-1);
145 	}
146 
147 	return (0);
148 }
149 
150 /* Copy in-memory pacct_db to pdb_file. */
151 int
152 pacct_update(void)
153 {
154 	return (db_copy_out(pacct_db, pdb_file, "process accounting",
155 	    NULL));
156 }
157 
158 void
159 pacct_print(void)
160 {
161 	BTREEINFO bti;
162 	DBT key, data, ndata;
163 	DB *output_pacct_db;
164 	struct cmdinfo *cip, ci, ci_total, ci_other, ci_junk;
165 	int rv;
166 
167 	bzero(&ci_total, sizeof ci_total);
168 	strcpy(ci_total.ci_comm, "");
169 	bzero(&ci_other, sizeof ci_other);
170 	strcpy(ci_other.ci_comm, "***other");
171 	bzero(&ci_junk, sizeof ci_junk);
172 	strcpy(ci_junk.ci_comm, "**junk**");
173 
174 	/*
175 	 * Retrieve them into new DB, sorted by appropriate key.
176 	 * At the same time, cull 'other' and 'junk'
177 	 */
178 	bzero(&bti, sizeof bti);
179 	bti.compare = sa_cmp;
180 	output_pacct_db = dbopen(NULL, O_RDWR, 0, DB_BTREE, &bti);
181 	if (output_pacct_db == NULL) {
182 		warn("couldn't sort process accounting stats");
183 		return;
184 	}
185 
186 	ndata.data = NULL;
187 	ndata.size = 0;
188 	rv = DB_SEQ(pacct_db, &key, &data, R_FIRST);
189 	if (rv < 0)
190 		warn("retrieving process accounting stats");
191 	while (rv == 0) {
192 		cip = (struct cmdinfo *) data.data;
193 		bcopy(cip, &ci, sizeof ci);
194 
195 		/* add to total */
196 		add_ci(&ci, &ci_total);
197 
198 		if (vflag && ci.ci_calls <= cutoff &&
199 		    (fflag || check_junk(&ci))) {
200 			/* put it into **junk** */
201 			add_ci(&ci, &ci_junk);
202 			goto next;
203 		}
204 		if (!aflag &&
205 		    ((ci.ci_flags & CI_UNPRINTABLE) != 0 || ci.ci_calls <= 1)) {
206 			/* put into ***other */
207 			add_ci(&ci, &ci_other);
208 			goto next;
209 		}
210 		rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
211 		if (rv < 0)
212 			warn("sorting process accounting stats");
213 
214 next:		rv = DB_SEQ(pacct_db, &key, &data, R_NEXT);
215 		if (rv < 0)
216 			warn("retrieving process accounting stats");
217 	}
218 
219 	/* insert **junk** and ***other */
220 	if (ci_junk.ci_calls != 0) {
221 		data.data = &ci_junk;
222 		data.size = sizeof ci_junk;
223 		rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
224 		if (rv < 0)
225 			warn("sorting process accounting stats");
226 	}
227 	if (ci_other.ci_calls != 0) {
228 		data.data = &ci_other;
229 		data.size = sizeof ci_other;
230 		rv = DB_PUT(output_pacct_db, &data, &ndata, 0);
231 		if (rv < 0)
232 			warn("sorting process accounting stats");
233 	}
234 
235 	/* print out the total */
236 	print_ci(&ci_total, &ci_total);
237 
238 	/* print out; if reversed, print first (smallest) first */
239 	rv = DB_SEQ(output_pacct_db, &data, &ndata, rflag ? R_FIRST : R_LAST);
240 	if (rv < 0)
241 		warn("retrieving process accounting report");
242 	while (rv == 0) {
243 		cip = (struct cmdinfo *) data.data;
244 		bcopy(cip, &ci, sizeof ci);
245 
246 		print_ci(&ci, &ci_total);
247 
248 		rv = DB_SEQ(output_pacct_db, &data, &ndata,
249 		    rflag ? R_NEXT : R_PREV);
250 		if (rv < 0)
251 			warn("retrieving process accounting report");
252 	}
253 	DB_CLOSE(output_pacct_db);
254 }
255 
256 static int
257 check_junk(const struct cmdinfo *cip)
258 {
259 	char *cp;
260 	size_t len;
261 
262 	fprintf(stderr, "%s (%ju) -- ", cip->ci_comm, (uintmax_t)cip->ci_calls);
263 	cp = fgetln(stdin, &len);
264 
265 	return (cp && (cp[0] == 'y' || cp[0] == 'Y')) ? 1 : 0;
266 }
267 
268 static void
269 add_ci(const struct cmdinfo *fromcip, struct cmdinfo *tocip)
270 {
271 	tocip->ci_calls += fromcip->ci_calls;
272 	tocip->ci_etime += fromcip->ci_etime;
273 	tocip->ci_utime += fromcip->ci_utime;
274 	tocip->ci_stime += fromcip->ci_stime;
275 	tocip->ci_mem += fromcip->ci_mem;
276 	tocip->ci_io += fromcip->ci_io;
277 }
278 
279 static void
280 print_ci(const struct cmdinfo *cip, const struct cmdinfo *totalcip)
281 {
282 	double t, c;
283 	int uflow;
284 
285 	c = cip->ci_calls ? cip->ci_calls : 1;
286 	t = (cip->ci_utime + cip->ci_stime) / 1000000;
287 	if (t < 0.01) {
288 		t = 0.01;
289 		uflow = 1;
290 	} else
291 		uflow = 0;
292 
293 	printf("%8ju ", (uintmax_t)cip->ci_calls);
294 	if (cflag) {
295 		if (cip != totalcip)
296 			printf(" %4.1f%%  ", cip->ci_calls /
297 			    (double)totalcip->ci_calls * 100);
298 		else
299 			printf(" %4s   ", "");
300 	}
301 
302 	if (jflag)
303 		printf("%11.3fre ", cip->ci_etime / (1000000 * c));
304 	else
305 		printf("%11.3fre ", cip->ci_etime / (60.0 * 1000000));
306 	if (cflag) {
307 		if (cip != totalcip)
308 			printf(" %4.1f%%  ", cip->ci_etime /
309 			    totalcip->ci_etime * 100);
310 		else
311 			printf(" %4s   ", "");
312 	}
313 
314 	if (!lflag) {
315 		if (jflag)
316 			printf("%11.3fcp ", t / (double) cip->ci_calls);
317 		else
318 			printf("%11.2fcp ", t / 60.0);
319 		if (cflag) {
320 			if (cip != totalcip)
321 				printf(" %4.1f%%  ",
322 				    (cip->ci_utime + cip->ci_stime) /
323 				    (totalcip->ci_utime + totalcip->ci_stime) *
324 				    100);
325 			else
326 				printf(" %4s   ", "");
327 		}
328 	} else {
329 		if (jflag)
330 			printf("%11.3fu ", cip->ci_utime / (1000000 * c));
331 		else
332 			printf("%11.2fu ", cip->ci_utime / (60.0 * 1000000));
333 		if (cflag) {
334 			if (cip != totalcip)
335 				printf(" %4.1f%%  ", cip->ci_utime /
336 				    (double)totalcip->ci_utime * 100);
337 			else
338 				printf(" %4s   ", "");
339 		}
340 		if (jflag)
341 			printf("%11.3fs ", cip->ci_stime / (1000000 * c));
342 		else
343 			printf("%11.2fs ", cip->ci_stime / (60.0 * 1000000));
344 		if (cflag) {
345 			if (cip != totalcip)
346 				printf(" %4.1f%%  ", cip->ci_stime /
347 				    (double)totalcip->ci_stime * 100);
348 			else
349 				printf(" %4s   ", "");
350 		}
351 	}
352 
353 	if (tflag) {
354 		if (!uflow)
355 			printf("%8.2fre/cp ",
356 			    cip->ci_etime /
357 			    (cip->ci_utime + cip->ci_stime));
358 		else
359 			printf("*ignore*      ");
360 	}
361 
362 	if (Dflag)
363 		printf("%10.0fio ", cip->ci_io);
364 	else
365 		printf("%8.0favio ", cip->ci_io / c);
366 
367 	if (Kflag)
368 		printf("%10.0fk*sec ", cip->ci_mem);
369 	else
370 		printf("%8.0fk ", cip->ci_mem / t);
371 
372 	printf("  %s\n", cip->ci_comm);
373 }
374