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