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, 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 *);
57 static void (*dumpfn
)(kcollect_t
*, size_t, size_t, const char *);
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 *);
70 int OutputWidth
= 1024;
71 int OutputHeight
= 1024;
73 static int LoadedFromDB
;
74 static int HostnameMismatch
;
78 main(int ac
, char **av
)
85 const char *datafile
= NULL
;
86 const char *fields
= NULL
;
93 const char *dbmFile
= NULL
;
99 sysctlbyname("kern.collect_data", NULL
, &bytes
, NULL
, 0);
101 fprintf(stderr
, "kern.collect_data not available\n");
105 while ((ch
= getopt(ac
, av
, "o:O:b:d:r:fFlsgt:xw:GW:H:")) != -1) {
113 if ((strncasecmp("influxdb", optarg
, 16) == 0)) {
114 dumpfn
= dump_influxdb
;
115 } else if (strncasecmp("text", optarg
, 16) == 0) {
118 fprintf(stderr
, "Bad output text format %s\n", optarg
);
158 maxtime
= strtol(optarg
, &suffix
, 0);
173 "Illegal suffix in -t option\n");
181 OutputWidth
= strtol(optarg
, NULL
, 0);
184 OutputHeight
= strtol(optarg
, NULL
, 0);
187 fprintf(stderr
, "Unknown option %c\n", ch
);
192 if (cmd
!= 'x' && ac
!= optind
) {
193 fprintf(stderr
, "Unknown argument %s\n", av
[optind
]);
201 if (cmd
== 'x' || cmd
== 'w')
202 start_gnuplot(ac
- optind
, av
+ optind
, datafile
);
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.
210 if (HostnameMismatch
) {
212 "Hostname mismatch, can't show live data\n");
217 * Snarf as much data as we can. If we are looping,
218 * snarf less (no point snarfing stuff we already have).
221 sysctlbyname("kern.collect_data", NULL
, &bytes
, NULL
, 0);
223 bytes
= sizeof(kcollect_t
) * 2;
225 /* Skip to the newest entries */
226 if (Fflag
&& loops
== 0)
232 loop_bytes
= sizeof(kcollect_t
) *
233 (4 + SLEEP_INTERVAL
/ KCOLLECT_INTERVAL
);
234 if (bytes
> loop_bytes
)
239 * If we got specified a file to load from: replace the data
243 kcollect_t
*dbmAry
= NULL
;
245 load_dbm(dbmFile
, &dbmAry
, &count
);
246 ary
= ary_base
= dbmAry
;
248 kcollect_t scaleid
[2];
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
);
257 "[ERR] kern.collect_data failed\n");
263 ary
= load_kernel(scaleid
, ary
+ 2, &count
);
266 adjust_fields(&ary
[1], fields
);
270 * Delete duplicate entries when looping
273 while (count
> DATA_BASE_INDEX
) {
274 if ((int)(ary
[count
-1].ticks
- last_ticks
) > 0)
281 * Delete any entries beyond the time limit
284 maxtime
*= ary
[0].hz
;
285 while (count
> DATA_BASE_INDEX
) {
286 if ((int)(ary
[0].ticks
- ary
[count
-1].ticks
) <
296 if (count
> DATA_BASE_INDEX
) {
297 dumpfn(ary
, count
, total_count
,
298 (fromFile
? DISPLAY_FULL_DATE
:
303 if (HostnameMismatch
) {
305 "Hostname mismatch, cannot save to DB\n");
308 if (count
> DATA_BASE_INDEX
)
309 dump_dbm(ary
, count
, datafile
);
313 if (count
>= DATA_BASE_INDEX
)
314 restore_headers(ary
, datafile
);
319 break; /* NOT REACHED */
321 if (count
> DATA_BASE_INDEX
)
322 dump_gnuplot(ary
, count
);
325 if (count
>= DATA_BASE_INDEX
)
326 dump_gnuplot(ary
, count
);
329 if (count
> DATA_BASE_INDEX
)
330 dump_gnuplot(ary
, count
);
333 if (keepalive
&& !fromFile
) {
350 last_ticks
= ary
[DATA_BASE_INDEX
].ticks
;
351 if (count
>= DATA_BASE_INDEX
)
352 total_count
+= count
- DATA_BASE_INDEX
;
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.
359 if (cmd
!= 'g' && cmd
!= 'x' && cmd
!= 'w')
371 format_output(uintmax_t value
,char fmt
,uintmax_t scale
, char* ret
)
380 sprintf(ret
, "%5ju.%02ju",
381 value
/ 100, value
% 100);
385 * Percentage fractional x100 (100% = 10000)
387 sprintf(ret
,"%4ju.%02ju%%",
388 value
/ 100, value
% 100);
394 humanize_number(buf
, sizeof(buf
), value
, "",
398 sprintf(ret
,"%8.8s", buf
);
402 * Raw count over period (this is not total)
404 humanize_number(buf
, sizeof(buf
), value
, "",
409 sprintf(ret
,"%8.8s", buf
);
413 * Total bytes (this is a total), output
416 if (scale
> 100000000) {
417 humanize_number(buf
, sizeof(buf
),
423 humanize_number(buf
, sizeof(buf
),
429 sprintf(ret
,"%8.8s", buf
);
432 sprintf(ret
,"%s"," ");
439 get_influx_series(const char *val
)
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))
447 /* load value (load) */
448 if (strncmp("load", val
, 8) == 0)
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))
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))
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";
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))
485 dump_influxdb(kcollect_t
*ary
, size_t count
, size_t total_count
,
486 __unused
const char* display_fmt
)
492 char hostname
[HOST_NAME_MAX
];
496 snprintf(hostname
, HOST_NAME_MAX
, "%s", (char *)ary
[2].data
);
498 if (gethostname(hostname
, HOST_NAME_MAX
) != 0) {
499 fprintf(stderr
, "Failed to get hostname\n");
504 for (i
= count
- 1; i
>= DATA_BASE_INDEX
; --i
) {
508 ts_nsec
= (ary
[i
].realtime
.tv_sec
512 + 123 /* add a few ns since due to missing precision */);
513 ts_nsec
+= (ary
[i
].realtime
.tv_usec
* 1000);
515 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
516 if (ary
[1].data
[j
] == 0)
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.
524 * But should we since we're using raw values?
526 value
= ary
[i
].data
[j
];
527 colname
= (char *)&ary
[1].data
[j
];
529 printf("%s,host=%s,type=%.8s value=%jd %jd\n",
530 get_influx_series(colname
),
531 hostname
, colname
, value
, ts_nsec
);
541 dump_text(kcollect_t
*ary
, size_t count
, size_t total_count
,
542 const char* display_fmt
)
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 ");
558 printf("%8.8s", "time");
560 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
561 if (ary
[1].data
[j
]) {
563 (char *)&ary
[1].data
[j
]);
572 t
= ary
[i
].realtime
.tv_sec
;
577 strftime(sbuf
, sizeof(sbuf
), display_fmt
, tmv
);
580 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
581 if (ary
[1].data
[j
] == 0)
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.
589 * Example scale - kernel provides total amount
590 * of memory available for memory related
591 * statistics in the scale field.
593 value
= ary
[i
].data
[j
];
594 scale
= KCOLLECT_GETSCALE(ary
[0].data
[j
]);
595 fmt
= KCOLLECT_GETFMT(ary
[0].data
[j
]);
599 format_output(value
, fmt
, scale
, sbuf
);
608 /* restores the DBM database header records to current machine */
611 restore_headers(kcollect_t
*ary
, const char *datafile
)
618 db
= dbm_open(datafile
, (O_RDWR
),
619 (S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
));
625 "[ERR] database file \"%s\" is read-only, "
626 "check permissions. (%i)\n",
631 "[ERR] opening our database file \"%s\" "
632 "produced an error. (%i)\n",
637 for (i
= 0; i
< DATA_BASE_INDEX
; ++i
) {
638 snprintf(hdr
, sizeof(hdr
), "%s%d", HDR_BASE
, i
);
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) {
645 "[ERR] error storing the value in "
646 "the database file \"%s\" (%i)\n",
658 * Store the array of kcollect_t records in a dbm db database,
659 * path passed in datafile
663 dump_dbm(kcollect_t
*ary
, size_t count
, const char *datafile
)
667 db
= dbm_open(datafile
, (O_RDWR
| O_CREAT
),
668 (S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
));
673 "[ERR] database file \"%s\" is read-only, "
674 "check permissions. (%i)\n",
679 "[ERR] opening our database file \"%s\" "
680 "produced an error. (%i)\n",
694 for (i
= 0; i
< count
; ++i
) {
696 * The first DATA_BASE_INDEX records are special.
699 if (i
< DATA_BASE_INDEX
) {
700 snprintf(hdr
, sizeof(hdr
), "%s%d", HDR_BASE
, i
);
702 key
.dsize
= strlen(hdr
) + 1;
704 value
= dbm_fetch(db
, key
);
705 if (value
.dptr
== NULL
||
706 bcmp(ary
[i
].data
, value
.dptr
,
707 sizeof(uint64_t) * KCOLLECT_ENTRIES
)) {
709 if (value
.dptr
!= NULL
) {
718 t
= ary
[i
].realtime
.tv_sec
;
723 strftime(buf
, sizeof(buf
),
724 DISPLAY_FULL_DATE
, tmv
);
726 key
.dsize
= sizeof(buf
);
728 value
.dptr
= ary
[i
].data
;
729 value
.dsize
= sizeof(uint64_t) * KCOLLECT_ENTRIES
;
730 if (dbm_store(db
, key
, value
, cmd
) == -1) {
732 "[ERR] error storing the value in "
733 "the database file \"%s\" (%i)\n",
745 * Transform a string (str) matching a format string (fmt) into a unix
746 * timestamp and return it used by load_dbm()
750 str2unix(const char* str
, const char* fmt
){
755 * Reset all the fields because strptime only sets what it
756 * finds, which may lead to undefined members
758 memset(&tm
, 0, sizeof(struct tm
));
759 strptime(str
, fmt
, &tm
);
766 * Sorts the ckollect_t records by time, to put youngest first,
767 * so desc by timestamp used by load_dbm()
771 rec_comparator(const void *c1
, const void *c2
)
773 const kcollect_t
*k1
= (const kcollect_t
*)c1
;
774 const kcollect_t
*k2
= (const kcollect_t
*)c2
;
776 if (k1
->realtime
.tv_sec
< k2
->realtime
.tv_sec
)
778 if (k1
->realtime
.tv_sec
> k2
->realtime
.tv_sec
)
784 * Normalizes kcollect records from the kernel. We reserve the first
785 * DATA_BASE_INDEX elements for specific headers.
789 load_kernel(kcollect_t
*scaleid
, kcollect_t
*ary
, size_t *counter
)
791 ary
-= DATA_BASE_INDEX
;
792 bzero(ary
, sizeof(*ary
) * DATA_BASE_INDEX
);
799 gethostname((char *)ary
[2].data
,
800 sizeof(uint64_t) * KCOLLECT_ENTRIES
- 1);
802 *counter
+= DATA_BASE_INDEX
;
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
813 load_dbm(const char* datafile
, kcollect_t
**ret_ary
,
816 char hostname
[sizeof(uint64_t) * KCOLLECT_ENTRIES
];
817 DBM
*db
= dbm_open(datafile
,(O_RDONLY
),(S_IRUSR
|S_IRGRP
));
820 size_t recCounter
= DATA_BASE_INDEX
;
821 int headersFound
= 0;
827 "[ERR] opening our database \"%s\" produced "
834 for (key
= dbm_firstkey(db
); key
.dptr
; key
= dbm_nextkey(db
)) {
835 value
= dbm_fetch(db
, key
);
836 if (value
.dptr
== NULL
)
838 if (strncmp(key
.dptr
, HDR_BASE
, HDR_STRLEN
) == 0)
843 /* with the count allocate enough memory */
847 *ret_ary
= malloc(sizeof(kcollect_t
) * recCounter
);
849 if (*ret_ary
== NULL
) {
851 "[ERR] failed to allocate enough memory to "
852 "hold the database! Aborting.\n");
856 bzero(*ret_ary
, sizeof(kcollect_t
) * recCounter
);
859 * Actual data retrieval but only of recCounter
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
);
870 if (!strncmp(key
.dptr
, HDR_BASE
, HDR_STRLEN
)) {
872 * Ignore unsupported header indices
874 sc
= strtoul((char *)key
.dptr
+
875 HDR_STRLEN
, NULL
, 10);
876 if (sc
>= DATA_BASE_INDEX
) {
877 key
= dbm_nextkey(db
);
880 headersFound
|= 1 << sc
;
883 (*ret_ary
)[sc
].realtime
.tv_sec
=
887 memcpy((*ret_ary
)[sc
].data
,
889 sizeof(uint64_t) * KCOLLECT_ENTRIES
);
891 key
= dbm_nextkey(db
);
895 * HEADER2 - hostname (must match)
897 if ((headersFound
& 4) && *(char *)(*ret_ary
)[2].data
== 0)
900 bzero(hostname
, sizeof(hostname
));
901 gethostname(hostname
, sizeof(hostname
) - 1);
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 */
912 * and sort the non-header records.
914 *counter
= recCounter
;
915 qsort(&(*ret_ary
)[DATA_BASE_INDEX
], recCounter
- DATA_BASE_INDEX
,
916 sizeof(kcollect_t
), rec_comparator
);
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",
929 dump_fields(kcollect_t
*ary
)
933 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
934 if (ary
[1].data
[j
] == 0)
937 (char *)&ary
[1].data
[j
],
938 KCOLLECT_GETFMT(ary
[0].data
[j
]));
943 adjust_fields(kcollect_t
*ent
, const char *fields
)
945 char *copy
= strdup(fields
);
947 int selected
[KCOLLECT_ENTRIES
];
950 bzero(selected
, sizeof(selected
));
952 word
= strtok(copy
, ", \t");
954 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
955 if (strncmp(word
, (char *)&ent
->data
[j
], 8) == 0) {
960 word
= strtok(NULL
, ", \t");
963 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {