hammer2 - Fix kmalloc pool blowout on low-memory machines
[dragonfly.git] / usr.bin / kcollect / kcollect.c
blobbfbb6a5ae48a0353f11db2759195ad58a2acf785
1 /*
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
9 * are met:
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.
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.
32 #include "kcollect.h"
34 #include <ndbm.h>
35 #include <fcntl.h>
36 #include <errno.h>
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);
57 FILE *OutFP;
58 int UseGMT;
59 int OutputWidth = 1024;
60 int OutputHeight = 1024;
61 int SmoothOpt;
62 int LoadedFromDB = 0;
64 int
65 main(int ac, char **av)
67 kcollect_t *ary;
68 size_t bytes = 0;
69 size_t count;
70 size_t total_count;
71 const char *datafile = NULL;
72 const char *fields = NULL;
73 int cmd = 't';
74 int ch;
75 int keepalive = 0;
76 int last_ticks;
77 int loops = 0;
78 int maxtime = 0;
80 kcollect_t *dbmAry = NULL;
81 const char *dbmFile = NULL;
82 int fromFile = 0;
84 OutFP = stdout;
86 sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
87 if (bytes == 0) {
88 fprintf(stderr, "kern.collect_data not available\n");
89 exit(1);
92 while ((ch = getopt(ac, av, "o:b:d:r:flsgt:xw:GW:H:")) != -1) {
93 char *suffix;
95 switch(ch) {
96 case 'o':
97 fields = optarg;
98 break;
99 case 'b':
100 datafile = optarg;
101 cmd = 'b';
102 break;
103 case 'd':
104 dbmFile = optarg;
105 fromFile = 1;
106 break;
107 case 'r':
108 datafile = optarg;
109 cmd = 'r';
110 break;
111 case 'f':
112 keepalive = 1;
113 break;
114 case 'l':
115 cmd = 'l';
116 break;
117 case 's':
118 SmoothOpt = 1;
119 break;
120 case 'w':
121 datafile = optarg;
122 cmd = 'w';
123 break;
124 case 'g':
125 cmd = 'g';
126 break;
127 case 'x':
128 cmd = 'x';
129 break;
130 case 't':
131 maxtime = strtol(optarg, &suffix, 0);
132 switch(*suffix) {
133 case 'd':
134 maxtime *= 24;
135 /* fall through */
136 case 'h':
137 maxtime *= 60;
138 /* fall through */
139 case 'm':
140 maxtime *= 60;
141 break;
142 case 0:
143 break;
144 default:
145 fprintf(stderr,
146 "Illegal suffix in -t option\n");
147 exit(1);
149 break;
150 case 'G':
151 UseGMT = 1;
152 break;
153 case 'W':
154 OutputWidth = strtol(optarg, NULL, 0);
155 break;
156 case 'H':
157 OutputHeight = strtol(optarg, NULL, 0);
158 break;
159 default:
160 fprintf(stderr, "Unknown option %c\n", ch);
161 exit(1);
162 /* NOT REACHED */
165 if (cmd != 'x' && ac != optind) {
166 fprintf(stderr, "Unknown argument %s\n", av[optind]);
167 exit(1);
168 /* NOT REACHED */
171 total_count = 0;
172 last_ticks = 0;
174 if (cmd == 'x' || cmd == 'w')
175 start_gnuplot(ac - optind, av + optind, datafile);
177 do {
179 * Snarf as much data as we can. If we are looping,
180 * snarf less (no point snarfing stuff we already have).
182 bytes = 0;
183 sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
184 if (cmd == 'l')
185 bytes = sizeof(kcollect_t) * 2;
187 if (loops) {
188 size_t loop_bytes;
190 loop_bytes = sizeof(kcollect_t) *
191 (4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
192 if (bytes > loop_bytes)
193 bytes = loop_bytes;
196 ary = malloc(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
202 * array and counter
204 if (fromFile) {
205 load_dbm(dbmFile, &dbmAry, &count);
206 free(ary);
207 ary = dbmAry;
210 if (fields)
211 adjust_fields(&ary[1], fields);
215 * Delete duplicate entries when looping
217 if (loops) {
218 while (count > 2) {
219 if ((int)(ary[count-1].ticks - last_ticks) > 0)
220 break;
221 --count;
226 * Delete any entries beyond the time limit
228 if (maxtime) {
229 maxtime *= ary[0].hz;
230 while (count > 2) {
231 if ((int)(ary[0].ticks - ary[count-1].ticks) <
232 maxtime) {
233 break;
235 --count;
239 switch(cmd) {
240 case 't':
241 if (count > 2) {
242 dump_text(ary, count, total_count,
243 (fromFile ? DISPLAY_FULL_DATE :
244 DISPLAY_TIME_ONLY));
246 break;
247 case 'b':
248 if (count > 2)
249 dump_dbm(ary, count, datafile);
250 break;
251 case 'r':
252 if (count >= 2)
253 restore_headers(ary, datafile);
254 break;
255 case 'l':
256 dump_fields(ary);
257 exit(0);
258 break; /* NOT REACHED */
259 case 'g':
260 if (count > 2)
261 dump_gnuplot(ary, count);
262 break;
263 case 'w':
264 if (count >= 2)
265 dump_gnuplot(ary, count);
266 break;
267 case 'x':
268 if (count > 2)
269 dump_gnuplot(ary, count);
270 break;
272 if (keepalive && !fromFile) {
273 fflush(OutFP);
274 fflush(stdout);
275 switch(cmd) {
276 case 't':
277 sleep(1);
278 break;
279 case 'x':
280 case 'g':
281 case 'w':
282 sleep(60);
283 break;
284 default:
285 sleep(10);
286 break;
289 last_ticks = ary[2].ticks;
290 if (count >= 2)
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')
299 ++loops;
301 free(ary);
302 } while (keepalive);
304 if (cmd == 'x')
305 pclose(OutFP);
308 static
309 void
310 format_output(uintmax_t value,char fmt,uintmax_t scale, char* ret)
312 char buf[9];
314 switch(fmt) {
315 case '2':
317 * fractional x100
319 sprintf(ret, "%5ju.%02ju",
320 value / 100, value % 100);
321 break;
322 case 'p':
324 * Percentage fractional x100 (100% = 10000)
326 sprintf(ret,"%4ju.%02ju%%",
327 value / 100, value % 100);
328 break;
329 case 'm':
331 * Megabytes
333 humanize_number(buf, sizeof(buf), value, "",
335 HN_FRACTIONAL |
336 HN_NOSPACE);
337 sprintf(ret,"%8.8s", buf);
338 break;
339 case 'c':
341 * Raw count over period (this is not total)
343 humanize_number(buf, sizeof(buf), value, "",
344 HN_AUTOSCALE,
345 HN_FRACTIONAL |
346 HN_NOSPACE |
347 HN_DIVISOR_1000);
348 sprintf(ret,"%8.8s", buf);
349 break;
350 case 'b':
352 * Total bytes (this is a total), output
353 * in megabytes.
355 if (scale > 100000000) {
356 humanize_number(buf, sizeof(buf),
357 value, "",
359 HN_FRACTIONAL |
360 HN_NOSPACE);
361 } else {
362 humanize_number(buf, sizeof(buf),
363 value, "",
365 HN_FRACTIONAL |
366 HN_NOSPACE);
368 sprintf(ret,"%8.8s", buf);
369 break;
370 default:
371 sprintf(ret,"%s"," ");
372 break;
376 static
377 void
378 dump_text(kcollect_t *ary, size_t count, size_t total_count,
379 const char* display_fmt)
381 int j;
382 int i;
383 uintmax_t scale;
384 uintmax_t value;
385 char fmt;
386 char sbuf[20];
387 struct tm *tmv;
388 time_t t;
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 ");
394 } else {
395 printf("%8.8s", "time");
397 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
398 if (ary[1].data[j]) {
399 printf(" %8.8s",
400 (char *)&ary[1].data[j]);
403 printf("\n");
407 * Timestamp
409 t = ary[i].realtime.tv_sec;
410 if (UseGMT)
411 tmv = gmtime(&t);
412 else
413 tmv = localtime(&t);
414 strftime(sbuf, sizeof(sbuf), display_fmt, tmv);
415 printf("%8s", sbuf);
417 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
418 if (ary[1].data[j] == 0)
419 continue;
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]);
434 printf(" ");
436 format_output(value, fmt, scale, sbuf);
437 printf("%s",sbuf);
440 printf("\n");
441 ++total_count;
445 /* restores the DBM database header records to current machine */
446 static
447 void
448 restore_headers(kcollect_t *ary, const char *datafile)
450 DBM *db;
451 char hdr_fmt[] = HDR_FMT;
452 char hdr_title[] = HDR_TITLE;
453 datum key, value;
455 db = dbm_open(datafile, (O_RDWR),
456 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
458 if (db == NULL) {
459 switch (errno) {
460 case EACCES:
461 fprintf(stderr,
462 "[ERR] database file \"%s\" is read-only, "
463 "check permissions. (%i)\n",
464 datafile, errno);
465 break;
466 default:
467 fprintf(stderr,
468 "[ERR] opening our database file \"%s\" "
469 "produced an error. (%i)\n",
470 datafile, errno);
472 exit(EXIT_FAILURE);
473 } else {
474 key.dptr = hdr_fmt;
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) {
479 fprintf(stderr,
480 "[ERR] error storing the value in "
481 "the database file \"%s\" (%i)\n",
482 datafile, errno);
483 dbm_close(db);
484 exit(EXIT_FAILURE);
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) {
492 fprintf(stderr,
493 "[ERR] error storing the value in "
494 "the database file \"%s\" (%i)\n",
495 datafile, errno);
496 dbm_close(db);
497 exit(EXIT_FAILURE);
500 dbm_close(db);
505 * Store the array of kcollect_t records in a dbm db database,
506 * path passed in datafile
508 static
509 void
510 dump_dbm(kcollect_t *ary, size_t count, const char *datafile)
512 DBM * db;
514 db = dbm_open(datafile, (O_RDWR | O_CREAT),
515 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP));
516 if (db == NULL) {
517 switch (errno) {
518 case EACCES:
519 fprintf(stderr,
520 "[ERR] database file \"%s\" is read-only, "
521 "check permissions. (%i)\n",
522 datafile, errno);
523 break;
524 default:
525 fprintf(stderr,
526 "[ERR] opening our database file \"%s\" "
527 "produced an error. (%i)\n",
528 datafile, errno);
530 exit(EXIT_FAILURE);
531 } else {
532 struct tm *tmv;
533 char buf[20];
534 datum key;
535 datum value;
536 time_t t;
537 uint i;
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 */
544 if (i < 2) {
545 if (i == 0)
546 key.dptr = hdr_fmt;
547 else
548 key.dptr = hdr_title;
549 key.dsize = sizeof(HDR_FMT);
550 } else {
551 t = ary[i].realtime.tv_sec;
552 if (LoadedFromDB)
553 tmv = localtime(&t);
554 else
555 tmv = gmtime(&t);
556 strftime(buf, sizeof(buf),
557 DISPLAY_FULL_DATE, tmv);
558 key.dptr = buf;
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) {
564 fprintf(stderr,
565 "[ERR] error storing the value in "
566 "the database file \"%s\" (%i)\n",
567 datafile, errno);
568 dbm_close(db);
569 exit(EXIT_FAILURE);
573 dbm_close(db);
578 * Transform a string (str) matching a format string (fmt) into a unix
579 * timestamp and return it used by load_dbm()
581 static
583 str2unix(const char* str, const char* fmt){
584 struct tm tm;
585 time_t ts;
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);
593 ts = timegm(&tm);
595 return (int)ts;
599 * Sorts the ckollect_t records by time, to put youngest first,
600 * so desc by timestamp used by load_dbm()
602 static
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)
610 return -1;
611 if (k1->realtime.tv_sec > k2->realtime.tv_sec)
612 return 1;
613 return 0;
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
620 static
621 void
622 load_dbm(const char* datafile, kcollect_t **ret_ary,
623 size_t *counter)
625 DBM * db = dbm_open(datafile,(O_RDONLY),(S_IRUSR|S_IRGRP));
626 datum key;
627 datum value;
628 size_t recCounter = 0;
629 int headersFound = 0;
631 if (db == NULL) {
632 fprintf(stderr,
633 "[ERR] opening our database \"%s\" produced "
634 "an error! (%i)\n",
635 datafile, errno);
636 exit(EXIT_FAILURE);
637 } else {
638 /* counting loop */
639 for (key = dbm_firstkey(db); key.dptr; key = dbm_nextkey(db)) {
640 value = dbm_fetch(db, key);
641 if (value.dptr != NULL)
642 recCounter++;
645 /* with the count allocate enough memory */
646 if (*ret_ary)
647 free(*ret_ary);
648 *ret_ary = malloc(sizeof(kcollect_t) * recCounter);
649 bzero(*ret_ary, sizeof(kcollect_t) * recCounter);
650 if (*ret_ary == NULL) {
651 fprintf(stderr,
652 "[ERR] failed to allocate enough memory to "
653 "hold the database! Aborting.\n");
654 dbm_close(db);
655 exit(EXIT_FAILURE);
656 } else {
657 uint c;
658 uint sc;
660 * Actual data retrieval but only of recCounter
661 * records
663 c = 2;
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)) {
669 sc = 0;
670 headersFound |= 1;
672 else if (!strcmp(key.dptr, HDR_TITLE)) {
673 sc = 1;
674 headersFound |= 2;
676 else {
677 sc = c;
678 c++;
681 memcpy((*ret_ary)[sc].data,
682 value.dptr,
683 sizeof(uint64_t) * KCOLLECT_ENTRIES);
684 (*ret_ary)[sc].realtime.tv_sec =
685 str2unix(key.dptr,
686 DISPLAY_FULL_DATE);
688 key = dbm_nextkey(db);
694 * Set the counter,
695 * and sort the non-header records.
697 *counter = recCounter;
698 qsort(&(*ret_ary)[2], recCounter - 2, sizeof(kcollect_t), rec_comparator);
699 dbm_close(db);
701 if (headersFound != 3) {
702 fprintf(stderr, "We could not retrieve all necessary headers, might be the database file is corrupted? (%i)\n", headersFound);
703 exit(EXIT_FAILURE);
705 LoadedFromDB = 1;
708 static void
709 dump_fields(kcollect_t *ary)
711 int j;
713 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
714 if (ary[1].data[j] == 0)
715 continue;
716 printf("%8.8s %c\n",
717 (char *)&ary[1].data[j],
718 KCOLLECT_GETFMT(ary[0].data[j]));
722 static void
723 adjust_fields(kcollect_t *ent, const char *fields)
725 char *copy = strdup(fields);
726 char *word;
727 int selected[KCOLLECT_ENTRIES];
728 int j;
730 bzero(selected, sizeof(selected));
732 word = strtok(copy, ", \t");
733 while (word) {
734 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
735 if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
736 selected[j] = 1;
737 break;
740 word = strtok(NULL, ", \t");
742 free(copy);
743 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
744 if (!selected[j])
745 ent->data[j] = 0;