drm/linux: Port kfifo.h to DragonFly BSD
[dragonfly.git] / usr.bin / kcollect / kcollect.c
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 }