xref: /dragonfly/usr.bin/kcollect/kcollect.c (revision d33ae0a9)
1 /*
2  * Copyright (c) 2017-2019 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 #include <ndbm.h>
35 #include <fcntl.h>
36 #include <errno.h>
37 
38 #define SLEEP_INTERVAL	60	/* minimum is KCOLLECT_INTERVAL */
39 #define DATA_BASE_INDEX	8	/* up to 8 headers */
40 
41 #define DISPLAY_TIME_ONLY "%H:%M:%S"
42 #define DISPLAY_FULL_DATE "%F %H:%M:%S"
43 #define HDR_BASE	"HEADER"
44 #define HDR_STRLEN	6
45 
46 #define HDR_FMT_INDEX	0
47 #define HDR_FMT_TITLE	1
48 #define HDR_FMT_HOST	2
49 
50 #define HOST_NAME_MAX sysconf(_SC_HOST_NAME_MAX)
51 
52 static void format_output(uintmax_t value,char fmt,uintmax_t scale, char* ret);
53 static void dump_text(kcollect_t *ary, size_t count,
54 			size_t total_count, const char* display_fmt);
55 static void dump_influxdb(kcollect_t *, size_t, size_t, const char*);
56 
57 static void dump_dbm(kcollect_t *ary, size_t count, const char *datafile);
58 static int str2unix(const char* str, const char* fmt);
59 static int rec_comparator(const void *c1, const void *c2);
60 static void load_dbm(const char *datafile,
61 			kcollect_t **ret_ary, size_t *counter);
62 static kcollect_t *load_kernel(kcollect_t *scaleid, kcollect_t *ary,
63 			size_t *counter);
64 static void dump_fields(kcollect_t *ary);
65 static void adjust_fields(kcollect_t *ent, const char *fields);
66 static void restore_headers(kcollect_t *ary, const char *datafile);
67 
68 static void (*dumpfn)(kcollect_t *, size_t, size_t, const char*);
69 
70 FILE *OutFP;
71 int UseGMT;
72 int OutputWidth = 1024;
73 int OutputHeight = 1024;
74 int SmoothOpt;
75 int LoadedFromDB = 0;
76 int Fflag = 0;
77 
78 int
79 main(int ac, char **av)
80 {
81 	kcollect_t *ary_base;
82 	kcollect_t *ary;
83 	size_t bytes = 0;
84 	size_t count;
85 	size_t total_count;
86 	const char *datafile = NULL;
87 	const char *fields = NULL;
88 	int cmd = 't';
89 	int ch;
90 	int keepalive = 0;
91 	int last_ticks;
92 	int loops = 0;
93 	int maxtime = 0;
94 	const char *dbmFile = NULL;
95 	int fromFile = 0;
96 
97 	OutFP = stdout;
98 	dumpfn = dump_text;
99 
100 	sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
101 	if (bytes == 0) {
102 		fprintf(stderr, "kern.collect_data not available\n");
103 		exit(1);
104 	}
105 
106 	while ((ch = getopt(ac, av, "o:O:b:d:r:fFlsgt:xw:GW:H:")) != -1) {
107 		char *suffix;
108 
109 		switch(ch) {
110 		case 'o':
111 			fields = optarg;
112 			break;
113 		case 'O':
114 			if ((strncasecmp("influxdb", optarg, 16) == 0)) {
115 				dumpfn = dump_influxdb;
116 			} else if (strncasecmp("text", optarg, 16) == 0) {
117 				dumpfn = dump_text;
118 			} else {
119 				fprintf(stderr, "Bad output text format %s\n", optarg);
120 				exit(1);
121 			}
122 			break;
123 		case 'b':
124 			datafile = optarg;
125 			cmd = 'b';
126 			break;
127 		case 'd':
128 			dbmFile = optarg;
129 			fromFile = 1;
130 			break;
131 		case 'r':
132 			datafile = optarg;
133 			cmd = 'r';
134 			break;
135 		case 'f':
136 			keepalive = 1;
137 			break;
138 		case 'F':
139 			Fflag = 1;
140 			keepalive = 1;
141 			break;
142 		case 'l':
143 			cmd = 'l';
144 			break;
145 		case 's':
146 			SmoothOpt = 1;
147 			break;
148 		case 'w':
149 			datafile = optarg;
150 			cmd = 'w';
151 			break;
152 		case 'g':
153 			cmd = 'g';
154 			break;
155 		case 'x':
156 			cmd = 'x';
157 			break;
158 		case 't':
159 			maxtime = strtol(optarg, &suffix, 0);
160 			switch(*suffix) {
161 			case 'd':
162 				maxtime *= 24;
163 				/* fall through */
164 			case 'h':
165 				maxtime *= 60;
166 				/* fall through */
167 			case 'm':
168 				maxtime *= 60;
169 				break;
170 			case 0:
171 				break;
172 			default:
173 				fprintf(stderr,
174 					"Illegal suffix in -t option\n");
175 				exit(1);
176 			}
177 			break;
178 		case 'G':
179 			UseGMT = 1;
180 			break;
181 		case 'W':
182 			OutputWidth = strtol(optarg, NULL, 0);
183 			break;
184 		case 'H':
185 			OutputHeight = strtol(optarg, NULL, 0);
186 			break;
187 		default:
188 			fprintf(stderr, "Unknown option %c\n", ch);
189 			exit(1);
190 			/* NOT REACHED */
191 		}
192 	}
193 	if (cmd != 'x' && ac != optind) {
194 		fprintf(stderr, "Unknown argument %s\n", av[optind]);
195 		exit(1);
196 		/* NOT REACHED */
197 	}
198 
199 	total_count = 0;
200 	last_ticks = 0;
201 
202 	if (cmd == 'x' || cmd == 'w')
203 		start_gnuplot(ac - optind, av + optind, datafile);
204 
205 	do {
206 		/*
207 		 * Snarf as much data as we can.  If we are looping,
208 		 * snarf less (no point snarfing stuff we already have).
209 		 */
210 		bytes = 0;
211 		sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
212 		if (cmd == 'l')
213 			bytes = sizeof(kcollect_t) * 2;
214 
215 		if (Fflag && loops == 0)
216 			loops++;
217 
218 		if (loops) {
219 			size_t loop_bytes;
220 
221 			loop_bytes = sizeof(kcollect_t) *
222 				     (4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
223 			if (bytes > loop_bytes)
224 				bytes = loop_bytes;
225 		}
226 
227 		/*
228 		 * If we got specified a file to load from: replace the data
229 		 * array and counter
230 		 */
231 		if (fromFile) {
232 			kcollect_t *dbmAry = NULL;
233 
234 			load_dbm(dbmFile, &dbmAry, &count);
235 			ary = ary_base = dbmAry;
236 		} else {
237 			kcollect_t scaleid[2];
238 
239 			ary_base = malloc(bytes +
240 					  DATA_BASE_INDEX * sizeof(kcollect_t));
241 			ary = ary_base + DATA_BASE_INDEX;
242 			sysctlbyname("kern.collect_data", ary, &bytes, NULL, 0);
243 			count = bytes / sizeof(kcollect_t);
244 			if (count < 2) {
245 				fprintf(stderr,
246 					"[ERR] kern.collect_data failed\n");
247 				exit(1);
248 			}
249 			scaleid[0] = ary[0];
250 			scaleid[1] = ary[1];
251 			count -= 2;
252 			ary = load_kernel(scaleid, ary + 2, &count);
253 		}
254 		if (fields)
255 			adjust_fields(&ary[1], fields);
256 
257 
258 		/*
259 		 * Delete duplicate entries when looping
260 		 */
261 		if (loops) {
262 			while (count > DATA_BASE_INDEX) {
263 				if ((int)(ary[count-1].ticks - last_ticks) > 0)
264 					break;
265 				--count;
266 			}
267 		}
268 
269 		/*
270 		 * Delete any entries beyond the time limit
271 		 */
272 		if (maxtime) {
273 			maxtime *= ary[0].hz;
274 			while (count > DATA_BASE_INDEX) {
275 				if ((int)(ary[0].ticks - ary[count-1].ticks) <
276 				    maxtime) {
277 					break;
278 				}
279 				--count;
280 			}
281 		}
282 
283 		switch(cmd) {
284 		case 't':
285 			if (count > DATA_BASE_INDEX) {
286 				dumpfn(ary, count, total_count,
287 					  (fromFile ? DISPLAY_FULL_DATE :
288 						      DISPLAY_TIME_ONLY));
289 			}
290 			break;
291 		case 'b':
292 			if (count > DATA_BASE_INDEX)
293 				dump_dbm(ary, count, datafile);
294 			break;
295 		case 'r':
296 			if (count >= DATA_BASE_INDEX)
297 				restore_headers(ary, datafile);
298 			break;
299 		case 'l':
300 			dump_fields(ary);
301 			exit(0);
302 			break;		/* NOT REACHED */
303 		case 'g':
304 			if (count > DATA_BASE_INDEX)
305 				dump_gnuplot(ary, count);
306 			break;
307 		case 'w':
308 			if (count >= DATA_BASE_INDEX)
309 				dump_gnuplot(ary, count);
310 			break;
311 		case 'x':
312 			if (count > DATA_BASE_INDEX)
313 				dump_gnuplot(ary, count);
314 			break;
315 		}
316 		if (keepalive && !fromFile) {
317 			fflush(OutFP);
318 			fflush(stdout);
319 			switch(cmd) {
320 			case 't':
321 				sleep(1);
322 				break;
323 			case 'x':
324 			case 'g':
325 			case 'w':
326 				sleep(60);
327 				break;
328 			default:
329 				sleep(10);
330 				break;
331 			}
332 		}
333 		last_ticks = ary[DATA_BASE_INDEX].ticks;
334 		if (count >= DATA_BASE_INDEX)
335 			total_count += count - DATA_BASE_INDEX;
336 
337 		/*
338 		 * Loop for incremental aquisition.  When outputting to
339 		 * gunplot, we have to send the whole data-set again so
340 		 * do not increment loops in that case.
341 		 */
342 		if (cmd != 'g' && cmd != 'x' && cmd != 'w')
343 			++loops;
344 
345 		free(ary_base);
346 	} while (keepalive);
347 
348 	if (cmd == 'x')
349 		pclose(OutFP);
350 }
351 
352 static
353 void
354 format_output(uintmax_t value,char fmt,uintmax_t scale, char* ret)
355 {
356 	char buf[9];
357 
358 	switch(fmt) {
359 	case '2':
360 		/*
361 		 * fractional x100
362 		 */
363 		sprintf(ret, "%5ju.%02ju",
364 			value / 100, value % 100);
365 		break;
366 	case 'p':
367 		/*
368 		 * Percentage fractional x100 (100% = 10000)
369 		 */
370 		sprintf(ret,"%4ju.%02ju%%",
371 			value / 100, value % 100);
372 		break;
373 	case 'm':
374 		/*
375 		 * Megabytes
376 		 */
377 		humanize_number(buf, sizeof(buf), value, "",
378 				2,
379 				HN_FRACTIONAL |
380 				HN_NOSPACE);
381 		sprintf(ret,"%8.8s", buf);
382 		break;
383 	case 'c':
384 		/*
385 		 * Raw count over period (this is not total)
386 		 */
387 		humanize_number(buf, sizeof(buf), value, "",
388 				HN_AUTOSCALE,
389 				HN_FRACTIONAL |
390 				HN_NOSPACE |
391 				HN_DIVISOR_1000);
392 		sprintf(ret,"%8.8s", buf);
393 		break;
394 	case 'b':
395 		/*
396 		 * Total bytes (this is a total), output
397 		 * in megabytes.
398 		 */
399 		if (scale > 100000000) {
400 			humanize_number(buf, sizeof(buf),
401 					value, "",
402 					3,
403 					HN_FRACTIONAL |
404 					HN_NOSPACE);
405 		} else {
406 			humanize_number(buf, sizeof(buf),
407 					value, "",
408 					2,
409 					HN_FRACTIONAL |
410 					HN_NOSPACE);
411 		}
412 		sprintf(ret,"%8.8s", buf);
413 		break;
414 	default:
415 		sprintf(ret,"%s","        ");
416 		break;
417 	}
418 }
419 
420 static
421 const char *
422 get_influx_series(const char *val)
423 {
424 	/* cpu values (user, idle, syst) */
425 	if ((strncmp("user", val, 8) == 0) ||
426 	    (strncmp("idle", val, 8) == 0 ) ||
427 	    (strncmp("syst", val, 8) == 0))
428 		return "cpu_value";
429 
430 	/* load value (load) */
431 	if (strncmp("load", val, 8) == 0)
432 		return "load_value";
433 
434 	/* swap values (swapuse, swapano, swapcac) */
435 	if ((strncmp("swapuse", val, 8) == 0) ||
436 	    (strncmp("swapano", val, 8) == 0 ) ||
437 	    (strncmp("swapcac", val, 8) == 0))
438 		return "swap_value";
439 
440 	/* vm values (fault, cow, zfill) */
441 	if ((strncmp("fault", val, 8) == 0) ||
442 	    (strncmp("cow", val, 8) == 0 ) ||
443 	    (strncmp("zfill", val, 8) == 0))
444 		return "vm_value";
445 
446 	/* memory values (fault, cow, zfill) */
447 	if ((strncmp("cache", val, 8) == 0) ||
448 	    (strncmp("inact", val, 8) == 0 ) ||
449 	    (strncmp("act", val, 8) == 0) ||
450 	    (strncmp("wired", val, 8) == 0) ||
451 	    (strncmp("free", val, 8) == 0))
452 		return "memory_value";
453 
454 	/* misc values (syscalls, nlookup, intr, ipi, timer) */
455 	if ((strncmp("syscalls", val, 8) == 0) ||
456 	    (strncmp("nlookup", val, 8) == 0 ) ||
457 	    (strncmp("intr", val, 8) == 0) ||
458 	    (strncmp("ipi", val, 8) == 0) ||
459 	    (strncmp("timer", val, 8) == 0))
460 		return "misc_value";
461 
462 	return "misc_value";
463 
464 }
465 
466 static
467 void
468 dump_influxdb(kcollect_t *ary, size_t count, size_t total_count,
469 	  __unused const char* display_fmt)
470 {
471 	int j;
472 	int i;
473 	uintmax_t value;
474 	size_t ts_nsec;
475 	char hostname[HOST_NAME_MAX];
476 	char *colname;
477 
478 	if (gethostname(hostname, HOST_NAME_MAX) != 0) {
479 		fprintf(stderr, "Failed to get hostname\n");
480 		exit(1);
481 	}
482 
483 	for (i = count - 1; i >= DATA_BASE_INDEX; --i) {
484 		/*
485 		 * Timestamp
486 		 */
487 		ts_nsec = (ary[i].realtime.tv_sec
488 		    * 1000 /* ms */
489 		    * 1000 /* usec */
490 		    * 1000 /* nsec */
491 		    + 123  /* add a few ns since due to missing precision */);
492 		ts_nsec += (ary[i].realtime.tv_usec * 1000);
493 
494 		for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
495 			if (ary[1].data[j] == 0)
496 				continue;
497 
498 			/*
499 			 * NOTE: kernel does not have to provide the scale
500 			 *	 (that is, the highest likely value), nor
501 			 *	 does it make sense in all cases.
502 			 *
503 			 *       But should we since we're using raw values?
504 			 */
505 			value = ary[i].data[j];
506 			colname = (char *)&ary[1].data[j];
507 
508 			printf("%s,host=%s,type=%.8s value=%jd %jd\n",
509 			    get_influx_series(colname),
510 			    hostname, colname, value, ts_nsec);
511 		}
512 		printf("\n");
513 		++total_count;
514 	}
515 
516 }
517 
518 static
519 void
520 dump_text(kcollect_t *ary, size_t count, size_t total_count,
521 	  const char* display_fmt)
522 {
523 	int j;
524 	int i;
525 	uintmax_t scale;
526 	uintmax_t value;
527 	char fmt;
528 	char sbuf[20];
529 	struct tm *tmv;
530 	time_t t;
531 
532 	for (i = count - 1; i >= DATA_BASE_INDEX; --i) {
533 		if ((total_count & 15) == 0) {
534 			if (!strcmp(display_fmt, DISPLAY_FULL_DATE)) {
535 				printf("%20s", "timestamp ");
536 			} else {
537 				printf("%8.8s", "time");
538 			}
539 			for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
540 				if (ary[1].data[j]) {
541 					printf(" %8.8s",
542 						(char *)&ary[1].data[j]);
543 				}
544 			}
545 			printf("\n");
546 		}
547 
548 		/*
549 		 * Timestamp
550 		 */
551 		t = ary[i].realtime.tv_sec;
552 		if (UseGMT)
553 			tmv = gmtime(&t);
554 		else
555 			tmv = localtime(&t);
556 		strftime(sbuf, sizeof(sbuf), display_fmt, tmv);
557 		printf("%8s", sbuf);
558 
559 		for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
560 			if (ary[1].data[j] == 0)
561 				continue;
562 
563 			/*
564 			 * NOTE: kernel does not have to provide the scale
565 			 *	 (that is, the highest likely value), nor
566 			 *	 does it make sense in all cases.
567 			 *
568 			 *	 Example scale - kernel provides total amount
569 			 *	 of memory available for memory related
570 			 *	 statistics in the scale field.
571 			 */
572 			value = ary[i].data[j];
573 			scale = KCOLLECT_GETSCALE(ary[0].data[j]);
574 			fmt = KCOLLECT_GETFMT(ary[0].data[j]);
575 
576 			printf(" ");
577 
578 			format_output(value, fmt, scale, sbuf);
579 			printf("%s",sbuf);
580 		}
581 
582 		printf("\n");
583 		++total_count;
584 	}
585 }
586 
587 /* restores the DBM database header records to current machine */
588 static
589 void
590 restore_headers(kcollect_t *ary, const char *datafile)
591 {
592 	DBM *db;
593 	char hdr[32];
594 	datum key, value;
595 	int i;
596 
597 	db = dbm_open(datafile, (O_RDWR),
598 		      (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
599 
600 	if (db == NULL) {
601 		switch (errno) {
602 		case EACCES:
603 			fprintf(stderr,
604 				"[ERR] database file \"%s\" is read-only, "
605 				"check permissions. (%i)\n",
606 				datafile, errno);
607 			break;
608 		default:
609 			fprintf(stderr,
610 				"[ERR] opening our database file \"%s\" "
611 				"produced an error. (%i)\n",
612 				datafile, errno);
613 		}
614 		exit(EXIT_FAILURE);
615 	} else {
616 		for (i = 0; i < DATA_BASE_INDEX; ++i) {
617 			snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
618 			key.dptr = hdr;
619 			key.dsize = strlen(key.dptr) + 1;
620 			value.dptr = &ary[i].data;
621 			value.dsize = sizeof(uint64_t) * KCOLLECT_ENTRIES;
622 			if (dbm_store(db,key,value,DBM_REPLACE) == -1) {
623 				fprintf(stderr,
624 					"[ERR] error storing the value in "
625 					"the database file \"%s\" (%i)\n",
626 					datafile, errno);
627 				dbm_close(db);
628 				exit(EXIT_FAILURE);
629 			}
630 		}
631 	}
632 	dbm_close(db);
633 }
634 
635 
636 /*
637  * Store the array of kcollect_t records in a dbm db database,
638  * path passed in datafile
639  */
640 static
641 void
642 dump_dbm(kcollect_t *ary, size_t count, const char *datafile)
643 {
644 	DBM * db;
645 
646 	db = dbm_open(datafile, (O_RDWR | O_CREAT),
647 		      (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
648 	if (db == NULL) {
649 		switch (errno) {
650 		case EACCES:
651 			fprintf(stderr,
652 				"[ERR] database file \"%s\" is read-only, "
653 				"check permissions. (%i)\n",
654 				datafile, errno);
655 			break;
656 		default:
657 			fprintf(stderr,
658 				"[ERR] opening our database file \"%s\" "
659 				"produced an error. (%i)\n",
660 				datafile, errno);
661 		}
662 		exit(EXIT_FAILURE);
663 	} else {
664 		struct tm *tmv;
665 		char buf[20];
666 		datum key;
667 		datum value;
668 		time_t t;
669 		uint i;
670 		int cmd;
671 		char hdr[32];
672 
673 		for (i = 0; i < count; ++i) {
674 			/*
675 			 * The first DATA_BASE_INDEX records are special.
676 			 */
677 			cmd = DBM_INSERT;
678 			if (i < DATA_BASE_INDEX) {
679 				snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
680 				key.dptr = hdr;
681 				key.dsize = strlen(hdr) + 1;
682 
683 				value = dbm_fetch(db, key);
684 				if (value.dptr == NULL ||
685 				    bcmp(ary[i].data, value.dptr,
686 					 sizeof(uint64_t) * KCOLLECT_ENTRIES)) {
687 					cmd = DBM_REPLACE;
688 					if (value.dptr != NULL) {
689 						fprintf(stderr,
690 							"Header %d changed "
691 							"in database, "
692 							"updating\n",
693 							i);
694 					}
695 				}
696 			} else {
697 				t = ary[i].realtime.tv_sec;
698 				if (LoadedFromDB)
699 					tmv = localtime(&t);
700 				else
701 					tmv = gmtime(&t);
702 				strftime(buf, sizeof(buf),
703 					 DISPLAY_FULL_DATE, tmv);
704 				key.dptr = buf;
705 				key.dsize = sizeof(buf);
706 			}
707 			value.dptr = ary[i].data;
708 			value.dsize = sizeof(uint64_t) * KCOLLECT_ENTRIES;
709 			if (dbm_store(db, key, value, cmd) == -1) {
710 				fprintf(stderr,
711 					"[ERR] error storing the value in "
712 					"the database file \"%s\" (%i)\n",
713 					datafile, errno);
714 				dbm_close(db);
715 				exit(EXIT_FAILURE);
716 			}
717 
718 		}
719 		dbm_close(db);
720 	}
721 }
722 
723 /*
724  * Transform a string (str) matching a format string (fmt) into a unix
725  * timestamp and return it used by load_dbm()
726  */
727 static
728 int
729 str2unix(const char* str, const char* fmt){
730 	struct tm tm;
731 	time_t ts;
732 
733 	/*
734 	 * Reset all the fields because strptime only sets what it
735 	 * finds, which may lead to undefined members
736 	 */
737 	memset(&tm, 0, sizeof(struct tm));
738 	strptime(str, fmt, &tm);
739 	ts = timegm(&tm);
740 
741 	return (int)ts;
742 }
743 
744 /*
745  * Sorts the ckollect_t records by time, to put youngest first,
746  * so desc by timestamp used by load_dbm()
747  */
748 static
749 int
750 rec_comparator(const void *c1, const void *c2)
751 {
752 	const kcollect_t *k1 = (const kcollect_t*)c1;
753 	const kcollect_t *k2 = (const kcollect_t*)c2;
754 
755 	if (k1->realtime.tv_sec < k2->realtime.tv_sec)
756 		return -1;
757 	if (k1->realtime.tv_sec > k2->realtime.tv_sec)
758 		return 1;
759 	return 0;
760 }
761 
762 /*
763  * Normalizes kcollect records from the kernel.  We reserve the first
764  * DATA_BASE_INDEX elements for specific headers.
765  */
766 static
767 kcollect_t *
768 load_kernel(kcollect_t *scaleid, kcollect_t *ary, size_t *counter)
769 {
770 	ary -= DATA_BASE_INDEX;
771 	bzero(ary, sizeof(*ary) * DATA_BASE_INDEX);
772 	ary[0] = scaleid[0];
773 	ary[1] = scaleid[1];
774 
775 	/*
776 	 * Add host field
777 	 */
778 	gethostname((char *)ary[2].data,
779 		    sizeof(uint64_t) * KCOLLECT_ENTRIES - 1);
780 
781 	*counter += DATA_BASE_INDEX;
782 
783 	return ary;
784 }
785 
786 /*
787  * Loads the kcollect records from a dbm DB database specified in datafile.
788  * returns the resulting array in ret_ary and the array counter in counter
789  */
790 static
791 void
792 load_dbm(const char* datafile, kcollect_t **ret_ary,
793 	 size_t *counter)
794 {
795 	char hostname[sizeof(uint64_t) * KCOLLECT_ENTRIES];
796 	DBM *db = dbm_open(datafile,(O_RDONLY),(S_IRUSR|S_IRGRP));
797 	datum key;
798 	datum value;
799 	size_t recCounter = DATA_BASE_INDEX;
800 	int headersFound = 0;
801 	uint c;
802 	uint sc;
803 
804 	if (db == NULL) {
805 		fprintf(stderr,
806 			"[ERR] opening our database \"%s\" produced "
807 			"an error! (%i)\n",
808 			datafile, errno);
809 		exit(EXIT_FAILURE);
810 	}
811 
812 	/* counting loop */
813 	for (key = dbm_firstkey(db); key.dptr; key = dbm_nextkey(db)) {
814 		value = dbm_fetch(db, key);
815 		if (value.dptr == NULL)
816 			continue;
817 		if (strncmp(key.dptr, HDR_BASE, HDR_STRLEN) == 0)
818 			continue;
819 		recCounter++;
820 	}
821 
822 	/* with the count allocate enough memory */
823 	if (*ret_ary)
824 		free(*ret_ary);
825 
826 	*ret_ary = malloc(sizeof(kcollect_t) * recCounter);
827 
828 	if (*ret_ary == NULL) {
829 		fprintf(stderr,
830 			"[ERR] failed to allocate enough memory to "
831 			"hold the database! Aborting.\n");
832 		dbm_close(db);
833 		exit(EXIT_FAILURE);
834 	}
835 	bzero(*ret_ary, sizeof(kcollect_t) * recCounter);
836 
837 	/*
838 	 * Actual data retrieval  but only of recCounter
839 	 * records
840 	 */
841 	c = DATA_BASE_INDEX;
842 	key = dbm_firstkey(db);
843 	while (key.dptr && c < recCounter) {
844 		value = dbm_fetch(db, key);
845 		if (value.dptr == NULL) {
846 			key = dbm_nextkey(db);
847 			continue;
848 		}
849 		if (!strncmp(key.dptr, HDR_BASE, HDR_STRLEN)) {
850 			/*
851 			 * Ignore unsupported header indices
852 			 */
853 			sc = strtoul((char *)key.dptr +
854 				     HDR_STRLEN, NULL, 10);
855 			if (sc >= DATA_BASE_INDEX) {
856 				key = dbm_nextkey(db);
857 				continue;
858 			}
859 			headersFound |= 1 << sc;
860 		} else {
861 			sc = c++;
862 			(*ret_ary)[sc].realtime.tv_sec =
863 				str2unix(key.dptr,
864 					 DISPLAY_FULL_DATE);
865 		}
866 		memcpy((*ret_ary)[sc].data,
867 		       value.dptr,
868 		       sizeof(uint64_t) * KCOLLECT_ENTRIES);
869 
870 		key = dbm_nextkey(db);
871 	}
872 
873 	/*
874 	 * HEADER2 - hostname (must match)
875 	 */
876 	if ((headersFound & 4) && *(char *)(*ret_ary)[2].data == 0)
877 		headersFound &= ~4;
878 
879 	bzero(hostname, sizeof(hostname));
880 	gethostname(hostname, sizeof(hostname) - 1);
881 
882 	if (headersFound & 0x0004) {
883 		if (*(char *)(*ret_ary)[2].data &&
884 		    strcmp(hostname, (char *)(*ret_ary)[2].data) != 0) {
885 			fprintf(stderr,
886 				"Cannot load database %s, hostname mismatch\n",
887 				datafile);
888 			exit(1);
889 		}
890 	}
891 
892 	/*
893 	 * Set the counter,
894 	 * and sort the non-header records.
895 	 */
896 	*counter = recCounter;
897         qsort(&(*ret_ary)[DATA_BASE_INDEX], recCounter - DATA_BASE_INDEX,
898 	      sizeof(kcollect_t), rec_comparator);
899 	dbm_close(db);
900 
901 	if ((headersFound & 3) != 3) {
902 		fprintf(stderr, "We could not retrieve all necessary headers, "
903 			"might be the database file is corrupted? (%i)\n",
904 			headersFound);
905 		exit(EXIT_FAILURE);
906 	}
907 	LoadedFromDB = 1;
908 }
909 
910 static void
911 dump_fields(kcollect_t *ary)
912 {
913 	int j;
914 
915 	for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
916 		if (ary[1].data[j] == 0)
917 			continue;
918 		printf("%8.8s %c\n",
919 		       (char *)&ary[1].data[j],
920 		       KCOLLECT_GETFMT(ary[0].data[j]));
921 	}
922 }
923 
924 static void
925 adjust_fields(kcollect_t *ent, const char *fields)
926 {
927 	char *copy = strdup(fields);
928 	char *word;
929 	int selected[KCOLLECT_ENTRIES];
930 	int j;
931 
932 	bzero(selected, sizeof(selected));
933 
934 	word = strtok(copy, ", \t");
935 	while (word) {
936 		for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
937 			if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
938 				selected[j] = 1;
939 				break;
940 			}
941 		}
942 		word = strtok(NULL, ", \t");
943 	}
944 	free(copy);
945 	for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
946 		if (!selected[j])
947 			ent->data[j] = 0;
948 	}
949 }
950