xref: /dragonfly/usr.bin/kcollect/kcollect.c (revision d50f9ae3448247db98eb135b85b2a32e6e4187f4)
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
main(int ac,char ** av)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
format_output(uintmax_t value,char fmt,uintmax_t scale,char * ret)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 *
get_influx_series(const char * val)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
dump_influxdb(kcollect_t * ary,size_t count,size_t total_count,__unused const char * display_fmt)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
dump_text(kcollect_t * ary,size_t count,size_t total_count,const char * display_fmt)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
restore_headers(kcollect_t * ary,const char * datafile)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
dump_dbm(kcollect_t * ary,size_t count,const char * datafile)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
str2unix(const char * str,const char * fmt)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
rec_comparator(const void * c1,const void * c2)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 *
load_kernel(kcollect_t * scaleid,kcollect_t * ary,size_t * counter)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
load_dbm(const char * datafile,kcollect_t ** ret_ary,size_t * counter)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
dump_fields(kcollect_t * ary)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
adjust_fields(kcollect_t * ent,const char * fields)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