2 * Copyright (c) 2017-2019 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@backplane.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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
38 #define SLEEP_INTERVAL 60 /* minimum is KCOLLECT_INTERVAL */
39 #define DATA_BASE_INDEX 8 /* up to 8 headers */
41 #define DISPLAY_TIME_ONLY "%H:%M:%S"
42 #define DISPLAY_FULL_DATE "%F %H:%M:%S"
43 #define HDR_BASE "HEADER"
46 #define HDR_FMT_INDEX 0
47 #define HDR_FMT_TITLE 1
48 #define HDR_FMT_HOST 2
50 #define HOST_NAME_MAX sysconf(_SC_HOST_NAME_MAX)
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*);
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,
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);
68 static void (*dumpfn)(kcollect_t *, size_t, size_t, const char*);
72 int OutputWidth = 1024;
73 int OutputHeight = 1024;
79 main(int ac, char **av)
86 const char *datafile = NULL;
87 const char *fields = NULL;
94 const char *dbmFile = NULL;
100 sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
102 fprintf(stderr, "kern.collect_data not available\n");
106 while ((ch = getopt(ac, av, "o:O:b:d:r:fFlsgt:xw:GW:H:")) != -1) {
114 if ((strncasecmp("influxdb", optarg, 16) == 0)) {
115 dumpfn = dump_influxdb;
116 } else if (strncasecmp("text", optarg, 16) == 0) {
119 fprintf(stderr, "Bad output text format %s\n", optarg);
159 maxtime = strtol(optarg, &suffix, 0);
174 "Illegal suffix in -t option\n");
182 OutputWidth = strtol(optarg, NULL, 0);
185 OutputHeight = strtol(optarg, NULL, 0);
188 fprintf(stderr, "Unknown option %c\n", ch);
193 if (cmd != 'x' && ac != optind) {
194 fprintf(stderr, "Unknown argument %s\n", av[optind]);
202 if (cmd == 'x' || cmd == 'w')
203 start_gnuplot(ac - optind, av + optind, datafile);
207 * Snarf as much data as we can. If we are looping,
208 * snarf less (no point snarfing stuff we already have).
211 sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
213 bytes = sizeof(kcollect_t) * 2;
215 if (Fflag && loops == 0)
221 loop_bytes = sizeof(kcollect_t) *
222 (4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
223 if (bytes > loop_bytes)
228 * If we got specified a file to load from: replace the data
232 kcollect_t *dbmAry = NULL;
234 load_dbm(dbmFile, &dbmAry, &count);
235 ary = ary_base = dbmAry;
237 kcollect_t scaleid[2];
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);
246 "[ERR] kern.collect_data failed\n");
252 ary = load_kernel(scaleid, ary + 2, &count);
255 adjust_fields(&ary[1], fields);
259 * Delete duplicate entries when looping
262 while (count > DATA_BASE_INDEX) {
263 if ((int)(ary[count-1].ticks - last_ticks) > 0)
270 * Delete any entries beyond the time limit
273 maxtime *= ary[0].hz;
274 while (count > DATA_BASE_INDEX) {
275 if ((int)(ary[0].ticks - ary[count-1].ticks) <
285 if (count > DATA_BASE_INDEX) {
286 dumpfn(ary, count, total_count,
287 (fromFile ? DISPLAY_FULL_DATE :
292 if (count > DATA_BASE_INDEX)
293 dump_dbm(ary, count, datafile);
296 if (count >= DATA_BASE_INDEX)
297 restore_headers(ary, datafile);
302 break; /* NOT REACHED */
304 if (count > DATA_BASE_INDEX)
305 dump_gnuplot(ary, count);
308 if (count >= DATA_BASE_INDEX)
309 dump_gnuplot(ary, count);
312 if (count > DATA_BASE_INDEX)
313 dump_gnuplot(ary, count);
316 if (keepalive && !fromFile) {
333 last_ticks = ary[DATA_BASE_INDEX].ticks;
334 if (count >= DATA_BASE_INDEX)
335 total_count += count - DATA_BASE_INDEX;
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.
342 if (cmd != 'g' && cmd != 'x' && cmd != 'w')
354 format_output(uintmax_t value,char fmt,uintmax_t scale, char* ret)
363 sprintf(ret, "%5ju.%02ju",
364 value / 100, value % 100);
368 * Percentage fractional x100 (100% = 10000)
370 sprintf(ret,"%4ju.%02ju%%",
371 value / 100, value % 100);
377 humanize_number(buf, sizeof(buf), value, "",
381 sprintf(ret,"%8.8s", buf);
385 * Raw count over period (this is not total)
387 humanize_number(buf, sizeof(buf), value, "",
392 sprintf(ret,"%8.8s", buf);
396 * Total bytes (this is a total), output
399 if (scale > 100000000) {
400 humanize_number(buf, sizeof(buf),
406 humanize_number(buf, sizeof(buf),
412 sprintf(ret,"%8.8s", buf);
415 sprintf(ret,"%s"," ");
422 get_influx_series(const char *val)
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))
430 /* load value (load) */
431 if (strncmp("load", val, 8) == 0)
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))
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))
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";
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))
468 dump_influxdb(kcollect_t *ary, size_t count, size_t total_count,
469 __unused const char* display_fmt)
475 char hostname[HOST_NAME_MAX];
478 if (gethostname(hostname, HOST_NAME_MAX) != 0) {
479 fprintf(stderr, "Failed to get hostname\n");
483 for (i = count - 1; i >= DATA_BASE_INDEX; --i) {
487 ts_nsec = (ary[i].realtime.tv_sec
491 + 123 /* add a few ns since due to missing precision */);
492 ts_nsec += (ary[i].realtime.tv_usec * 1000);
494 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
495 if (ary[1].data[j] == 0)
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.
503 * But should we since we're using raw values?
505 value = ary[i].data[j];
506 colname = (char *)&ary[1].data[j];
508 printf("%s,host=%s,type=%.8s value=%jd %jd\n",
509 get_influx_series(colname),
510 hostname, colname, value, ts_nsec);
520 dump_text(kcollect_t *ary, size_t count, size_t total_count,
521 const char* display_fmt)
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 ");
537 printf("%8.8s", "time");
539 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
540 if (ary[1].data[j]) {
542 (char *)&ary[1].data[j]);
551 t = ary[i].realtime.tv_sec;
556 strftime(sbuf, sizeof(sbuf), display_fmt, tmv);
559 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
560 if (ary[1].data[j] == 0)
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.
568 * Example scale - kernel provides total amount
569 * of memory available for memory related
570 * statistics in the scale field.
572 value = ary[i].data[j];
573 scale = KCOLLECT_GETSCALE(ary[0].data[j]);
574 fmt = KCOLLECT_GETFMT(ary[0].data[j]);
578 format_output(value, fmt, scale, sbuf);
587 /* restores the DBM database header records to current machine */
590 restore_headers(kcollect_t *ary, const char *datafile)
597 db = dbm_open(datafile, (O_RDWR),
598 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
604 "[ERR] database file \"%s\" is read-only, "
605 "check permissions. (%i)\n",
610 "[ERR] opening our database file \"%s\" "
611 "produced an error. (%i)\n",
616 for (i = 0; i < DATA_BASE_INDEX; ++i) {
617 snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
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) {
624 "[ERR] error storing the value in "
625 "the database file \"%s\" (%i)\n",
637 * Store the array of kcollect_t records in a dbm db database,
638 * path passed in datafile
642 dump_dbm(kcollect_t *ary, size_t count, const char *datafile)
646 db = dbm_open(datafile, (O_RDWR | O_CREAT),
647 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
652 "[ERR] database file \"%s\" is read-only, "
653 "check permissions. (%i)\n",
658 "[ERR] opening our database file \"%s\" "
659 "produced an error. (%i)\n",
673 for (i = 0; i < count; ++i) {
675 * The first DATA_BASE_INDEX records are special.
678 if (i < DATA_BASE_INDEX) {
679 snprintf(hdr, sizeof(hdr), "%s%d", HDR_BASE, i);
681 key.dsize = strlen(hdr) + 1;
683 value = dbm_fetch(db, key);
684 if (value.dptr == NULL ||
685 bcmp(ary[i].data, value.dptr,
686 sizeof(uint64_t) * KCOLLECT_ENTRIES)) {
688 if (value.dptr != NULL) {
697 t = ary[i].realtime.tv_sec;
702 strftime(buf, sizeof(buf),
703 DISPLAY_FULL_DATE, tmv);
705 key.dsize = sizeof(buf);
707 value.dptr = ary[i].data;
708 value.dsize = sizeof(uint64_t) * KCOLLECT_ENTRIES;
709 if (dbm_store(db, key, value, cmd) == -1) {
711 "[ERR] error storing the value in "
712 "the database file \"%s\" (%i)\n",
724 * Transform a string (str) matching a format string (fmt) into a unix
725 * timestamp and return it used by load_dbm()
729 str2unix(const char* str, const char* fmt){
734 * Reset all the fields because strptime only sets what it
735 * finds, which may lead to undefined members
737 memset(&tm, 0, sizeof(struct tm));
738 strptime(str, fmt, &tm);
745 * Sorts the ckollect_t records by time, to put youngest first,
746 * so desc by timestamp used by load_dbm()
750 rec_comparator(const void *c1, const void *c2)
752 const kcollect_t *k1 = (const kcollect_t*)c1;
753 const kcollect_t *k2 = (const kcollect_t*)c2;
755 if (k1->realtime.tv_sec < k2->realtime.tv_sec)
757 if (k1->realtime.tv_sec > k2->realtime.tv_sec)
763 * Normalizes kcollect records from the kernel. We reserve the first
764 * DATA_BASE_INDEX elements for specific headers.
768 load_kernel(kcollect_t *scaleid, kcollect_t *ary, size_t *counter)
770 ary -= DATA_BASE_INDEX;
771 bzero(ary, sizeof(*ary) * DATA_BASE_INDEX);
778 gethostname((char *)ary[2].data,
779 sizeof(uint64_t) * KCOLLECT_ENTRIES - 1);
781 *counter += DATA_BASE_INDEX;
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
792 load_dbm(const char* datafile, kcollect_t **ret_ary,
795 char hostname[sizeof(uint64_t) * KCOLLECT_ENTRIES];
796 DBM *db = dbm_open(datafile,(O_RDONLY),(S_IRUSR|S_IRGRP));
799 size_t recCounter = DATA_BASE_INDEX;
800 int headersFound = 0;
806 "[ERR] opening our database \"%s\" produced "
813 for (key = dbm_firstkey(db); key.dptr; key = dbm_nextkey(db)) {
814 value = dbm_fetch(db, key);
815 if (value.dptr == NULL)
817 if (strncmp(key.dptr, HDR_BASE, HDR_STRLEN) == 0)
822 /* with the count allocate enough memory */
826 *ret_ary = malloc(sizeof(kcollect_t) * recCounter);
828 if (*ret_ary == NULL) {
830 "[ERR] failed to allocate enough memory to "
831 "hold the database! Aborting.\n");
835 bzero(*ret_ary, sizeof(kcollect_t) * recCounter);
838 * Actual data retrieval but only of recCounter
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);
849 if (!strncmp(key.dptr, HDR_BASE, HDR_STRLEN)) {
851 * Ignore unsupported header indices
853 sc = strtoul((char *)key.dptr +
854 HDR_STRLEN, NULL, 10);
855 if (sc >= DATA_BASE_INDEX) {
856 key = dbm_nextkey(db);
859 headersFound |= 1 << sc;
862 (*ret_ary)[sc].realtime.tv_sec =
866 memcpy((*ret_ary)[sc].data,
868 sizeof(uint64_t) * KCOLLECT_ENTRIES);
870 key = dbm_nextkey(db);
874 * HEADER2 - hostname (must match)
876 if ((headersFound & 4) && *(char *)(*ret_ary)[2].data == 0)
879 bzero(hostname, sizeof(hostname));
880 gethostname(hostname, sizeof(hostname) - 1);
882 if (headersFound & 0x0004) {
883 if (*(char *)(*ret_ary)[2].data &&
884 strcmp(hostname, (char *)(*ret_ary)[2].data) != 0) {
886 "Cannot load database %s, hostname mismatch\n",
894 * and sort the non-header records.
896 *counter = recCounter;
897 qsort(&(*ret_ary)[DATA_BASE_INDEX], recCounter - DATA_BASE_INDEX,
898 sizeof(kcollect_t), rec_comparator);
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",
911 dump_fields(kcollect_t *ary)
915 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
916 if (ary[1].data[j] == 0)
919 (char *)&ary[1].data[j],
920 KCOLLECT_GETFMT(ary[0].data[j]));
925 adjust_fields(kcollect_t *ent, const char *fields)
927 char *copy = strdup(fields);
929 int selected[KCOLLECT_ENTRIES];
932 bzero(selected, sizeof(selected));
934 word = strtok(copy, ", \t");
936 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
937 if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
942 word = strtok(NULL, ", \t");
945 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {