kcollect - Implement gnuplot output feature
[dragonfly.git] / usr.bin / kcollect / kcollect.c
1 /*
2  * Copyright (c) 2017 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 #define SLEEP_INTERVAL  60      /* minimum is KCOLLECT_INTERVAL */
35
36 static void dump_text(kcollect_t *ary, size_t count, size_t total_count);
37 static void dump_dbm(kcollect_t *ary, size_t count, const char *datafile);
38 static void dump_fields(kcollect_t *ary);
39 static void adjust_fields(kcollect_t *ent, const char *fields);
40
41 FILE *OutFP;
42 int UseGMT;
43 int OutputWidth = 1024;
44 int OutputHeight = 1024;
45
46 int
47 main(int ac, char **av)
48 {
49         kcollect_t *ary;
50         size_t bytes = 0;
51         size_t count;
52         size_t total_count;
53         const char *datafile = NULL;
54         const char *fields = NULL;
55         int cmd = 't';
56         int ch;
57         int keepalive = 0;
58         int last_ticks;
59         int loops = 0;
60
61         OutFP = stdout;
62
63         sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
64         if (bytes == 0) {
65                 fprintf(stderr, "kern.collect_data not available\n");
66                 exit(1);
67         }
68
69         while ((ch = getopt(ac, av, "o:b:flgxw:GW:H:")) != -1) {
70                 switch(ch) {
71                 case 'o':
72                         fields = optarg;
73                         break;
74                 case 'b':
75                         datafile = optarg;
76                         cmd = 'b';
77                         break;
78                 case 'f':
79                         keepalive = 1;
80                         break;
81                 case 'l':
82                         cmd = 'l';
83                         break;
84                 case 'w':
85                         datafile = optarg;
86                         cmd = 'w';
87                         break;
88                 case 'g':
89                         cmd = 'g';
90                         break;
91                 case 'x':
92                         cmd = 'x';
93                         break;
94                 case 'G':
95                         UseGMT = 1;
96                         break;
97                 case 'W':
98                         OutputWidth = strtol(optarg, NULL, 0);
99                         break;
100                 case 'H':
101                         OutputHeight = strtol(optarg, NULL, 0);
102                         break;
103                 default:
104                         fprintf(stderr, "Unknown option %c\n", ch);
105                         exit(1);
106                         /* NOT REACHED */
107                 }
108         }
109         if (cmd != 'x' && ac != optind) {
110                 fprintf(stderr, "Unknown argument %s\n", av[optind]);
111                 exit(1);
112                 /* NOT REACHED */
113         }
114
115         total_count = 0;
116         last_ticks = 0;
117
118         if (cmd == 'x' || cmd == 'w')
119                 start_gnuplot(ac - optind, av + optind);
120
121         do {
122                 /*
123                  * Snarf as much data as we can.  If we are looping,
124                  * snarf less (no point snarfing stuff we already have).
125                  */
126                 bytes = 0;
127                 sysctlbyname("kern.collect_data", NULL, &bytes, NULL, 0);
128                 if (cmd == 'l')
129                         bytes = sizeof(kcollect_t) * 2;
130
131                 if (loops) {
132                         size_t loop_bytes;
133
134                         loop_bytes = sizeof(kcollect_t) *
135                                      (4 + SLEEP_INTERVAL / KCOLLECT_INTERVAL);
136                         if (bytes > loop_bytes)
137                                 bytes = loop_bytes;
138                 }
139
140                 ary = malloc(bytes);
141                 sysctlbyname("kern.collect_data", ary, &bytes, NULL, 0);
142
143                 if (fields)
144                         adjust_fields(&ary[1], fields);
145
146                 count = bytes / sizeof(kcollect_t);
147                 if (loops) {
148                         while (count > 2) {
149                                 if ((int)(ary[count-1].ticks - last_ticks) > 0)
150                                         break;
151                                 --count;
152                         }
153                 }
154
155                 switch(cmd) {
156                 case 't':
157                         if (count > 2)
158                                 dump_text(ary, count, total_count);
159                         break;
160                 case 'b':
161                         if (count > 2)
162                                 dump_dbm(ary, count, datafile);
163                         break;
164                 case 'l':
165                         dump_fields(ary);
166                         exit(0);
167                         break;          /* NOT REACHED */
168                 case 'g':
169                         if (count > 2)
170                                 dump_gnuplot(ary, count, NULL);
171                         break;
172                 case 'w':
173                         if (count >= 2)
174                                 dump_gnuplot(ary, count, datafile);
175                         break;
176                 case 'x':
177                         if (count > 2)
178                                 dump_gnuplot(ary, count, NULL);
179                         break;
180                 }
181                 if (keepalive) {
182                         fflush(OutFP);
183                         fflush(stdout);
184                         switch(cmd) {
185                         case 't':
186                                 sleep(1);
187                                 break;
188                         case 'x':
189                         case 'g':
190                         case 'w':
191                                 sleep(60);
192                                 break;
193                         default:
194                                 sleep(10);
195                                 break;
196                         }
197                 }
198                 last_ticks = ary[2].ticks;
199                 if (count >= 2)
200                         total_count += count - 2;
201
202                 /*
203                  * Loop for incremental aquisition.  When outputting to
204                  * gunplot, we have to send the whole data-set again so
205                  * do not increment loops in that case.
206                  */
207                 if (cmd != 'g' && cmd != 'x' && cmd != 'w')
208                         ++loops;
209
210                 free(ary);
211         } while (keepalive);
212
213         if (cmd == 'x')
214                 pclose(OutFP);
215 }
216
217 static
218 void
219 dump_text(kcollect_t *ary, size_t count, size_t total_count)
220 {
221         int j;
222         int i;
223         uintmax_t scale;
224         uintmax_t value;
225         char fmt;
226         char buf[9];
227         struct tm *tmv;
228         time_t t;
229
230         for (i = count - 1; i >= 2; --i) {
231                 if ((total_count & 15) == 0) {
232                         printf("%8.8s", "time");
233                         for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
234                                 if (ary[1].data[j]) {
235                                         printf(" %8.8s",
236                                                 (char *)&ary[1].data[j]);
237                                 }
238                         }
239                         printf("\n");
240                 }
241
242                 /*
243                  * Timestamp
244                  */
245                 t = ary[i].realtime.tv_sec;
246                 if (UseGMT)
247                         tmv = gmtime(&t);
248                 else
249                         tmv = localtime(&t);
250                 strftime(buf, sizeof(buf), "%H:%M:%S", tmv);
251                 printf("%8.8s", buf);
252
253                 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
254                         if (ary[1].data[j] == 0)
255                                 continue;
256
257                         /*
258                          * NOTE: kernel does not have to provide the scale
259                          *       (that is, the highest likely value), nor
260                          *       does it make sense in all cases.
261                          *
262                          *       Example scale - kernel provides total amount
263                          *       of memory available for memory related
264                          *       statistics in the scale field.
265                          */
266                         value = ary[i].data[j];
267                         scale = KCOLLECT_GETSCALE(ary[0].data[j]);
268                         fmt = KCOLLECT_GETFMT(ary[0].data[j]);
269
270                         printf(" ");
271
272                         switch(fmt) {
273                         case '2':
274                                 /*
275                                  * fractional x100
276                                  */
277                                 printf("%5ju.%02ju", value / 100, value % 100);
278                                 break;
279                         case 'p':
280                                 /*
281                                  * Percentage fractional x100 (100% = 10000)
282                                  */
283                                 printf("%4ju.%02ju%%",
284                                         value / 100, value % 100);
285                                 break;
286                         case 'm':
287                                 /*
288                                  * Megabytes
289                                  */
290                                 humanize_number(buf, sizeof(buf), value, "",
291                                                 2,
292                                                 HN_FRACTIONAL |
293                                                 HN_NOSPACE);
294                                 printf("%8.8s", buf);
295                                 break;
296                         case 'c':
297                                 /*
298                                  * Raw count over period (this is not total)
299                                  */
300                                 humanize_number(buf, sizeof(buf), value, "",
301                                                 HN_AUTOSCALE,
302                                                 HN_FRACTIONAL |
303                                                 HN_NOSPACE |
304                                                 HN_DIVISOR_1000);
305                                 printf("%8.8s", buf);
306                                 break;
307                         case 'b':
308                                 /*
309                                  * Total bytes (this is a total), output
310                                  * in megabytes.
311                                  */
312                                 if (scale > 100000000) {
313                                         humanize_number(buf, sizeof(buf),
314                                                         value, "",
315                                                         3,
316                                                         HN_FRACTIONAL |
317                                                         HN_NOSPACE);
318                                 } else {
319                                         humanize_number(buf, sizeof(buf),
320                                                         value, "",
321                                                         2,
322                                                         HN_FRACTIONAL |
323                                                         HN_NOSPACE);
324                                 }
325                                 printf("%8.8s", buf);
326                                 break;
327                         default:
328                                 printf("        ");
329                                 break;
330                         }
331                 }
332                 printf("\n");
333                 ++total_count;
334         }
335 }
336
337 static void
338 dump_dbm(kcollect_t *ary __unused, size_t count __unused, const char *datafile __unused)
339 {
340 }
341
342 static void
343 dump_fields(kcollect_t *ary)
344 {
345         int j;
346
347         for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
348                 if (ary[1].data[j] == 0)
349                         continue;
350                 printf("%8.8s %c\n",
351                        (char *)&ary[1].data[j],
352                        KCOLLECT_GETFMT(ary[0].data[j]));
353         }
354 }
355
356 static void
357 adjust_fields(kcollect_t *ent, const char *fields)
358 {
359         char *copy = strdup(fields);
360         char *word;
361         int selected[KCOLLECT_ENTRIES];
362         int j;
363
364         bzero(selected, sizeof(selected));
365
366         word = strtok(copy, ", \t");
367         while (word) {
368                 for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
369                         if (strncmp(word, (char *)&ent->data[j], 8) == 0) {
370                                 selected[j] = 1;
371                                 break;
372                         }
373                 }
374                 word = strtok(NULL, ", \t");
375         }
376         free(copy);
377         for (j = 0; j < KCOLLECT_ENTRIES; ++j) {
378                 if (!selected[j])
379                         ent->data[j] = 0;
380         }
381 }