xref: /dragonfly/usr.bin/kcollect/kcollect.c (revision fae225dc)
1 /*
2  * Copyright (c) 2017 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
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  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include "kcollect.h"
33 
34 #define SLEEP_INTERVAL	60	/* minimum is KCOLLECT_INTERVAL */
35 
36 static void dump_text(kcollect_t *ary, size_t count, size_t total_count);
37 static void dump_dbm(kcollect_t *ary, size_t count, const char *datafile);
38 static void dump_fields(kcollect_t *ary);
39 static void adjust_fields(kcollect_t *ent, const char *fields);
40 
41 FILE *OutFP;
42 int UseGMT;
43 int OutputWidth = 1024;
44 int OutputHeight = 1024;
45 int SmoothOpt;
46 
47 int
48 main(int ac, char **av)
49 {
50 	kcollect_t *ary;
51 	size_t bytes = 0;
52 	size_t count;
53 	size_t total_count;
54 	const char *datafile = NULL;
55 	const char *fields = NULL;
56 	int cmd = 't';
57 	int ch;
58 	int keepalive = 0;
59 	int last_ticks;
60 	int loops = 0;
61 	int maxtime = 0;
62 
63 	OutFP = stdout;
64 
65 	sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
66 	if (bytes == 0) {
67 		fprintf(stderr, "kern.collect_data not available\n");
68 		exit(1);
69 	}
70 
71 	while ((ch = getopt(ac, av, "o:b:flsgt:xw:GW:H:")) != -1) {
72 		char *suffix;
73 
74 		switch(ch) {
75 		case 'o':
76 			fields = optarg;
77 			break;
78 		case 'b':
79 			datafile = optarg;
80 			cmd = 'b';
81 			break;
82 		case 'f':
83 			keepalive = 1;
84 			break;
85 		case 'l':
86 			cmd = 'l';
87 			break;
88 		case 's':
89 			SmoothOpt = 1;
90 			break;
91 		case 'w':
92 			datafile = optarg;
93 			cmd = 'w';
94 			break;
95 		case 'g':
96 			cmd = 'g';
97 			break;
98 		case 'x':
99 			cmd = 'x';
100 			break;
101 		case 't':
102 			maxtime = strtol(optarg, &suffix, 0);
103 			switch(*suffix) {
104 			case 'd':
105 				maxtime *= 24;
106 				/* fall through */
107 			case 'h':
108 				maxtime *= 60;
109 				/* fall through */
110 			case 'm':
111 				maxtime *= 60;
112 				break;
113 			case 0:
114 				break;
115 			default:
116 				fprintf(stderr,
117 					"Illegal suffix in -t option\n");
118 				exit(1);
119 			}
120 			break;
121 		case 'G':
122 			UseGMT = 1;
123 			break;
124 		case 'W':
125 			OutputWidth = strtol(optarg, NULL, 0);
126 			break;
127 		case 'H':
128 			OutputHeight = strtol(optarg, NULL, 0);
129 			break;
130 		default:
131 			fprintf(stderr, "Unknown option %c\n", ch);
132 			exit(1);
133 			/* NOT REACHED */
134 		}
135 	}
136 	if (cmd != 'x' && ac != optind) {
137 		fprintf(stderr, "Unknown argument %s\n", av[optind]);
138 		exit(1);
139 		/* NOT REACHED */
140 	}
141 
142 	total_count = 0;
143 	last_ticks = 0;
144 
145 	if (cmd == 'x' || cmd == 'w')
146 		start_gnuplot(ac - optind, av + optind, datafile);
147 
148 	do {
149 		/*
150 		 * Snarf as much data as we can.  If we are looping,
151 		 * snarf less (no point snarfing stuff we already have).
152 		 */
153 		bytes = 0;
154 		sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
155 		if (cmd == 'l')
156 			bytes = sizeof(kcollect_t) * 2;
157 
158 		if (loops) {
159 			size_t loop_bytes;
160 
161 			loop_bytes = sizeof(kcollect_t) *
162 				     (4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
163 			if (bytes > loop_bytes)
164 				bytes = loop_bytes;
165 		}
166 
167 		ary = malloc(bytes);
168 		sysctlbyname("kern.collect_data", ary, &bytes, NULL, 0);
169 
170 		if (fields)
171 			adjust_fields(&ary[1], fields);
172 
173 		count = bytes / sizeof(kcollect_t);
174 
175 		/*
176 		 * Delete duplicate entries when looping
177 		 */
178 		if (loops) {
179 			while (count > 2) {
180 				if ((int)(ary[count-1].ticks - last_ticks) > 0)
181 					break;
182 				--count;
183 			}
184 		}
185 
186 		/*
187 		 * Delete any entries beyond the time limit
188 		 */
189 		if (maxtime) {
190 			maxtime *= ary[0].hz;
191 			while (count > 2) {
192 				if ((int)(ary[0].ticks - ary[count-1].ticks) <
193 				    maxtime) {
194 					break;
195 				}
196 				--count;
197 			}
198 		}
199 
200 		switch(cmd) {
201 		case 't':
202 			if (count > 2)
203 				dump_text(ary, count, total_count);
204 			break;
205 		case 'b':
206 			if (count > 2)
207 				dump_dbm(ary, count, datafile);
208 			break;
209 		case 'l':
210 			dump_fields(ary);
211 			exit(0);
212 			break;		/* NOT REACHED */
213 		case 'g':
214 			if (count > 2)
215 				dump_gnuplot(ary, count);
216 			break;
217 		case 'w':
218 			if (count >= 2)
219 				dump_gnuplot(ary, count);
220 			break;
221 		case 'x':
222 			if (count > 2)
223 				dump_gnuplot(ary, count);
224 			break;
225 		}
226 		if (keepalive) {
227 			fflush(OutFP);
228 			fflush(stdout);
229 			switch(cmd) {
230 			case 't':
231 				sleep(1);
232 				break;
233 			case 'x':
234 			case 'g':
235 			case 'w':
236 				sleep(60);
237 				break;
238 			default:
239 				sleep(10);
240 				break;
241 			}
242 		}
243 		last_ticks = ary[2].ticks;
244 		if (count >= 2)
245 			total_count += count - 2;
246 
247 		/*
248 		 * Loop for incremental aquisition.  When outputting to
249 		 * gunplot, we have to send the whole data-set again so
250 		 * do not increment loops in that case.
251 		 */
252 		if (cmd != 'g' && cmd != 'x' && cmd != 'w')
253 			++loops;
254 
255 		free(ary);
256 	} while (keepalive);
257 
258 	if (cmd == 'x')
259 		pclose(OutFP);
260 }
261 
262 static
263 void
264 dump_text(kcollect_t *ary, size_t count, size_t total_count)
265 {
266 	int j;
267 	int i;
268 	uintmax_t scale;
269 	uintmax_t value;
270 	char fmt;
271 	char buf[9];
272 	struct tm *tmv;
273 	time_t t;
274 
275 	for (i = count - 1; i >= 2; --i) {
276 		if ((total_count & 15) == 0) {
277 			printf("%8.8s", "time");
278 			for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
279 				if (ary[1].data[j]) {
280 					printf(" %8.8s",
281 						(char *)&ary[1].data[j]);
282 				}
283 			}
284 			printf("\n");
285 		}
286 
287 		/*
288 		 * Timestamp
289 		 */
290 		t = ary[i].realtime.tv_sec;
291 		if (UseGMT)
292 			tmv = gmtime(&t);
293 		else
294 			tmv = localtime(&t);
295 		strftime(buf, sizeof(buf), "%H:%M:%S", tmv);
296 		printf("%8.8s", buf);
297 
298 		for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
299 			if (ary[1].data[j] == 0)
300 				continue;
301 
302 			/*
303 			 * NOTE: kernel does not have to provide the scale
304 			 *	 (that is, the highest likely value), nor
305 			 *	 does it make sense in all cases.
306 			 *
307 			 *	 Example scale - kernel provides total amount
308 			 *	 of memory available for memory related
309 			 *	 statistics in the scale field.
310 			 */
311 			value = ary[i].data[j];
312 			scale = KCOLLECT_GETSCALE(ary[0].data[j]);
313 			fmt = KCOLLECT_GETFMT(ary[0].data[j]);
314 
315 			printf(" ");
316 
317 			switch(fmt) {
318 			case '2':
319 				/*
320 				 * fractional x100
321 				 */
322 				printf("%5ju.%02ju", value / 100, value % 100);
323 				break;
324 			case 'p':
325 				/*
326 				 * Percentage fractional x100 (100% = 10000)
327 				 */
328 				printf("%4ju.%02ju%%",
329 					value / 100, value % 100);
330 				break;
331 			case 'm':
332 				/*
333 				 * Megabytes
334 				 */
335 				humanize_number(buf, sizeof(buf), value, "",
336 						2,
337 						HN_FRACTIONAL |
338 						HN_NOSPACE);
339 				printf("%8.8s", buf);
340 				break;
341 			case 'c':
342 				/*
343 				 * Raw count over period (this is not total)
344 				 */
345 				humanize_number(buf, sizeof(buf), value, "",
346 						HN_AUTOSCALE,
347 						HN_FRACTIONAL |
348 						HN_NOSPACE |
349 						HN_DIVISOR_1000);
350 				printf("%8.8s", buf);
351 				break;
352 			case 'b':
353 				/*
354 				 * Total bytes (this is a total), output
355 				 * in megabytes.
356 				 */
357 				if (scale > 100000000) {
358 					humanize_number(buf, sizeof(buf),
359 							value, "",
360 							3,
361 							HN_FRACTIONAL |
362 							HN_NOSPACE);
363 				} else {
364 					humanize_number(buf, sizeof(buf),
365 							value, "",
366 							2,
367 							HN_FRACTIONAL |
368 							HN_NOSPACE);
369 				}
370 				printf("%8.8s", buf);
371 				break;
372 			default:
373 				printf("        ");
374 				break;
375 			}
376 		}
377 		printf("\n");
378 		++total_count;
379 	}
380 }
381 
382 static void
383 dump_dbm(kcollect_t *ary __unused, size_t count __unused, const char *datafile __unused)
384 {
385 }
386 
387 static void
388 dump_fields(kcollect_t *ary)
389 {
390 	int j;
391 
392 	for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
393 		if (ary[1].data[j] == 0)
394 			continue;
395 		printf("%8.8s %c\n",
396 		       (char *)&ary[1].data[j],
397 		       KCOLLECT_GETFMT(ary[0].data[j]));
398 	}
399 }
400 
401 static void
402 adjust_fields(kcollect_t *ent, const char *fields)
403 {
404 	char *copy = strdup(fields);
405 	char *word;
406 	int selected[KCOLLECT_ENTRIES];
407 	int j;
408 
409 	bzero(selected, sizeof(selected));
410 
411 	word = strtok(copy, ", \t");
412 	while (word) {
413 		for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
414 			if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
415 				selected[j] = 1;
416 				break;
417 			}
418 		}
419 		word = strtok(NULL, ", \t");
420 	}
421 	free(copy);
422 	for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
423 		if (!selected[j])
424 			ent->data[j] = 0;
425 	}
426 }
427