2 * Copyright (c) 2017 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 */
40 #define DISPLAY_TIME_ONLY "%H:%M:%S"
41 #define DISPLAY_FULL_DATE "%F %H:%M:%S"
42 #define HDR_FMT "HEADER0"
43 #define HDR_TITLE "HEADER1"
45 static void format_output(uintmax_t value
,char fmt
,uintmax_t scale
, char* ret
);
46 static void dump_text(kcollect_t
*ary
, size_t count
,
47 size_t total_count
, const char* display_fmt
);
48 static void dump_dbm(kcollect_t
*ary
, size_t count
, const char *datafile
);
49 static int str2unix(const char* str
, const char* fmt
);
50 static int rec_comparator(const void *c1
, const void *c2
);
51 static void load_dbm(const char *datafile
,
52 kcollect_t
**ret_ary
, size_t *counter
);
53 static void dump_fields(kcollect_t
*ary
);
54 static void adjust_fields(kcollect_t
*ent
, const char *fields
);
55 static void restore_headers(kcollect_t
*ary
, const char *datafile
);
59 int OutputWidth
= 1024;
60 int OutputHeight
= 1024;
65 main(int ac
, char **av
)
71 const char *datafile
= NULL
;
72 const char *fields
= NULL
;
80 kcollect_t
*dbmAry
= NULL
;
81 const char *dbmFile
= NULL
;
86 sysctlbyname("kern.collect_data", NULL
, &bytes
, NULL
, 0);
88 fprintf(stderr
, "kern.collect_data not available\n");
92 while ((ch
= getopt(ac
, av
, "o:b:d:r:flsgt:xw:GW:H:")) != -1) {
131 maxtime
= strtol(optarg
, &suffix
, 0);
146 "Illegal suffix in -t option\n");
154 OutputWidth
= strtol(optarg
, NULL
, 0);
157 OutputHeight
= strtol(optarg
, NULL
, 0);
160 fprintf(stderr
, "Unknown option %c\n", ch
);
165 if (cmd
!= 'x' && ac
!= optind
) {
166 fprintf(stderr
, "Unknown argument %s\n", av
[optind
]);
174 if (cmd
== 'x' || cmd
== 'w')
175 start_gnuplot(ac
- optind
, av
+ optind
, datafile
);
179 * Snarf as much data as we can. If we are looping,
180 * snarf less (no point snarfing stuff we already have).
183 sysctlbyname("kern.collect_data", NULL
, &bytes
, NULL
, 0);
185 bytes
= sizeof(kcollect_t
) * 2;
190 loop_bytes
= sizeof(kcollect_t
) *
191 (4 + SLEEP_INTERVAL
/ KCOLLECT_INTERVAL
);
192 if (bytes
> loop_bytes
)
197 sysctlbyname("kern.collect_data", ary
, &bytes
, NULL
, 0);
198 count
= bytes
/ sizeof(kcollect_t
);
201 * If we got specified a file to load from: replace the data
205 load_dbm(dbmFile
, &dbmAry
, &count
);
211 adjust_fields(&ary
[1], fields
);
215 * Delete duplicate entries when looping
219 if ((int)(ary
[count
-1].ticks
- last_ticks
) > 0)
226 * Delete any entries beyond the time limit
229 maxtime
*= ary
[0].hz
;
231 if ((int)(ary
[0].ticks
- ary
[count
-1].ticks
) <
242 dump_text(ary
, count
, total_count
,
243 (fromFile
? DISPLAY_FULL_DATE
:
249 dump_dbm(ary
, count
, datafile
);
253 restore_headers(ary
, datafile
);
258 break; /* NOT REACHED */
261 dump_gnuplot(ary
, count
);
265 dump_gnuplot(ary
, count
);
269 dump_gnuplot(ary
, count
);
272 if (keepalive
&& !fromFile
) {
289 last_ticks
= ary
[2].ticks
;
291 total_count
+= count
- 2;
294 * Loop for incremental aquisition. When outputting to
295 * gunplot, we have to send the whole data-set again so
296 * do not increment loops in that case.
298 if (cmd
!= 'g' && cmd
!= 'x' && cmd
!= 'w')
310 format_output(uintmax_t value
,char fmt
,uintmax_t scale
, char* ret
)
319 sprintf(ret
, "%5ju.%02ju",
320 value
/ 100, value
% 100);
324 * Percentage fractional x100 (100% = 10000)
326 sprintf(ret
,"%4ju.%02ju%%",
327 value
/ 100, value
% 100);
333 humanize_number(buf
, sizeof(buf
), value
, "",
337 sprintf(ret
,"%8.8s", buf
);
341 * Raw count over period (this is not total)
343 humanize_number(buf
, sizeof(buf
), value
, "",
348 sprintf(ret
,"%8.8s", buf
);
352 * Total bytes (this is a total), output
355 if (scale
> 100000000) {
356 humanize_number(buf
, sizeof(buf
),
362 humanize_number(buf
, sizeof(buf
),
368 sprintf(ret
,"%8.8s", buf
);
371 sprintf(ret
,"%s"," ");
378 dump_text(kcollect_t
*ary
, size_t count
, size_t total_count
,
379 const char* display_fmt
)
390 for (i
= count
- 1; i
>= 2; --i
) {
391 if ((total_count
& 15) == 0) {
392 if (!strcmp(display_fmt
, DISPLAY_FULL_DATE
)) {
393 printf("%20s", "timestamp ");
395 printf("%8.8s", "time");
397 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
398 if (ary
[1].data
[j
]) {
400 (char *)&ary
[1].data
[j
]);
409 t
= ary
[i
].realtime
.tv_sec
;
414 strftime(sbuf
, sizeof(sbuf
), display_fmt
, tmv
);
417 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
418 if (ary
[1].data
[j
] == 0)
422 * NOTE: kernel does not have to provide the scale
423 * (that is, the highest likely value), nor
424 * does it make sense in all cases.
426 * Example scale - kernel provides total amount
427 * of memory available for memory related
428 * statistics in the scale field.
430 value
= ary
[i
].data
[j
];
431 scale
= KCOLLECT_GETSCALE(ary
[0].data
[j
]);
432 fmt
= KCOLLECT_GETFMT(ary
[0].data
[j
]);
436 format_output(value
, fmt
, scale
, sbuf
);
445 /* restores the DBM database header records to current machine */
448 restore_headers(kcollect_t
*ary
, const char *datafile
)
451 char hdr_fmt
[] = HDR_FMT
;
452 char hdr_title
[] = HDR_TITLE
;
455 db
= dbm_open(datafile
, (O_RDWR
),
456 (S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
));
462 "[ERR] database file \"%s\" is read-only, "
463 "check permissions. (%i)\n",
468 "[ERR] opening our database file \"%s\" "
469 "produced an error. (%i)\n",
475 key
.dsize
= sizeof(HDR_FMT
);
476 value
.dptr
= &ary
[0].data
;
477 value
.dsize
= sizeof(uint64_t) * KCOLLECT_ENTRIES
;
478 if (dbm_store(db
,key
,value
,DBM_REPLACE
) == -1) {
480 "[ERR] error storing the value in "
481 "the database file \"%s\" (%i)\n",
487 key
.dptr
= hdr_title
;
488 key
.dsize
= sizeof(HDR_FMT
);
489 value
.dptr
= &ary
[1].data
;
490 value
.dsize
= sizeof(uint64_t) * KCOLLECT_ENTRIES
;
491 if (dbm_store(db
,key
,value
,DBM_REPLACE
) == -1) {
493 "[ERR] error storing the value in "
494 "the database file \"%s\" (%i)\n",
505 * Store the array of kcollect_t records in a dbm db database,
506 * path passed in datafile
510 dump_dbm(kcollect_t
*ary
, size_t count
, const char *datafile
)
514 db
= dbm_open(datafile
, (O_RDWR
| O_CREAT
),
515 (S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
));
520 "[ERR] database file \"%s\" is read-only, "
521 "check permissions. (%i)\n",
526 "[ERR] opening our database file \"%s\" "
527 "produced an error. (%i)\n",
538 char hdr_fmt
[] = HDR_FMT
;
539 char hdr_title
[] = HDR_TITLE
;
541 for (i
= 0; i
< (count
); ++i
) {
542 /* first 2 INFO records are special and get 0|1 */
548 key
.dptr
= hdr_title
;
549 key
.dsize
= sizeof(HDR_FMT
);
551 t
= ary
[i
].realtime
.tv_sec
;
556 strftime(buf
, sizeof(buf
),
557 DISPLAY_FULL_DATE
, tmv
);
559 key
.dsize
= sizeof(buf
);
561 value
.dptr
= ary
[i
].data
;
562 value
.dsize
= sizeof(uint64_t) * KCOLLECT_ENTRIES
;
563 if (dbm_store(db
, key
, value
, DBM_INSERT
) == -1) {
565 "[ERR] error storing the value in "
566 "the database file \"%s\" (%i)\n",
578 * Transform a string (str) matching a format string (fmt) into a unix
579 * timestamp and return it used by load_dbm()
583 str2unix(const char* str
, const char* fmt
){
588 * Reset all the fields because strptime only sets what it
589 * finds, which may lead to undefined members
591 memset(&tm
, 0, sizeof(struct tm
));
592 strptime(str
, fmt
, &tm
);
599 * Sorts the ckollect_t records by time, to put youngest first,
600 * so desc by timestamp used by load_dbm()
604 rec_comparator(const void *c1
, const void *c2
)
606 const kcollect_t
*k1
= (const kcollect_t
*)c1
;
607 const kcollect_t
*k2
= (const kcollect_t
*)c2
;
609 if (k1
->realtime
.tv_sec
< k2
->realtime
.tv_sec
)
611 if (k1
->realtime
.tv_sec
> k2
->realtime
.tv_sec
)
617 * Loads the ckollect records from a dbm DB database specified in datafile.
618 * returns the resulting array in ret_ary and the array counter in counter
622 load_dbm(const char* datafile
, kcollect_t
**ret_ary
,
625 DBM
* db
= dbm_open(datafile
,(O_RDONLY
),(S_IRUSR
|S_IRGRP
));
628 size_t recCounter
= 0;
629 int headersFound
= 0;
633 "[ERR] opening our database \"%s\" produced "
639 for (key
= dbm_firstkey(db
); key
.dptr
; key
= dbm_nextkey(db
)) {
640 value
= dbm_fetch(db
, key
);
641 if (value
.dptr
!= NULL
)
645 /* with the count allocate enough memory */
648 *ret_ary
= malloc(sizeof(kcollect_t
) * recCounter
);
649 bzero(*ret_ary
, sizeof(kcollect_t
) * recCounter
);
650 if (*ret_ary
== NULL
) {
652 "[ERR] failed to allocate enough memory to "
653 "hold the database! Aborting.\n");
660 * Actual data retrieval but only of recCounter
664 key
= dbm_firstkey(db
);
665 while (key
.dptr
&& c
< recCounter
) {
666 value
= dbm_fetch(db
, key
);
667 if (value
.dptr
!= NULL
) {
668 if (!strcmp(key
.dptr
, HDR_FMT
)) {
672 else if (!strcmp(key
.dptr
, HDR_TITLE
)) {
681 memcpy((*ret_ary
)[sc
].data
,
683 sizeof(uint64_t) * KCOLLECT_ENTRIES
);
684 (*ret_ary
)[sc
].realtime
.tv_sec
=
688 key
= dbm_nextkey(db
);
695 * and sort the non-header records.
697 *counter
= recCounter
;
698 qsort(&(*ret_ary
)[2], recCounter
- 2, sizeof(kcollect_t
), rec_comparator
);
701 if (headersFound
!= 3) {
702 fprintf(stderr
, "We could not retrieve all necessary headers, might be the database file is corrupted? (%i)\n", headersFound
);
709 dump_fields(kcollect_t
*ary
)
713 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
714 if (ary
[1].data
[j
] == 0)
717 (char *)&ary
[1].data
[j
],
718 KCOLLECT_GETFMT(ary
[0].data
[j
]));
723 adjust_fields(kcollect_t
*ent
, const char *fields
)
725 char *copy
= strdup(fields
);
727 int selected
[KCOLLECT_ENTRIES
];
730 bzero(selected
, sizeof(selected
));
732 word
= strtok(copy
, ", \t");
734 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {
735 if (strncmp(word
, (char *)&ent
->data
[j
], 8) == 0) {
740 word
= strtok(NULL
, ", \t");
743 for (j
= 0; j
< KCOLLECT_ENTRIES
; ++j
) {