]> arthur.barton.de Git - netdata.git/blob - src/macos_fw.c
Add disk space and inodes utilization charts to macOS plugin
[netdata.git] / src / macos_fw.c
1 #include "common.h"
2 #include <CoreFoundation/CoreFoundation.h>
3 #include <IOKit/IOKitLib.h>
4 #include <IOKit/storage/IOBlockStorageDriver.h>
5 #include <IOKit/IOBSD.h>
6 // NEEDED BY do_space, do_inodes
7 #include <sys/mount.h>
8
9 #define MAXDRIVENAME 31
10
11 #define KILO_FACTOR 1024
12 #define MEGA_FACTOR 1048576     // 1024 * 1024
13 #define GIGA_FACTOR 1073741824  // 1024 * 1024 * 1024
14
15 int do_macos_iokit(int update_every, usec_t dt) {
16     (void)dt;
17
18     static int do_io = -1, do_space = -1, do_inodes = -1;
19
20     if (unlikely(do_io == -1)) {
21         do_io                  = config_get_boolean("plugin:macos:iokit", "disk i/o", 1);
22         do_space                = config_get_boolean("plugin:macos:sysctl", "space usage for all disks", 1);
23         do_inodes               = config_get_boolean("plugin:macos:sysctl", "inodes usage for all disks", 1);
24     }
25
26     RRDSET *st;
27
28     mach_port_t         master_port;
29     io_registry_entry_t drive, drive_media;
30     io_iterator_t       drive_list;
31     CFDictionaryRef     properties, statistics;
32     CFStringRef         name;
33     CFNumberRef         number;
34     kern_return_t       status;
35     collected_number    total_disk_reads = 0;
36     collected_number    total_disk_writes = 0;
37     struct diskstat {
38         char name[MAXDRIVENAME];
39         collected_number bytes_read;
40         collected_number bytes_write;
41         collected_number reads;
42         collected_number writes;
43         collected_number time_read;
44         collected_number time_write;
45         collected_number latency_read;
46         collected_number latency_write;
47     } diskstat;
48     struct cur_diskstat {
49         collected_number duration_read_ns;
50         collected_number duration_write_ns;
51         collected_number busy_time_ns;
52     } cur_diskstat;
53     struct prev_diskstat {
54         collected_number bytes_read;
55         collected_number bytes_write;
56         collected_number operations_read;
57         collected_number operations_write;
58         collected_number duration_read_ns;
59         collected_number duration_write_ns;
60         collected_number busy_time_ns;
61     } prev_diskstat;
62
63     // NEEDED BY: do_space, do_inodes
64     struct statfs *mntbuf;
65     int mntsize, i;
66     char mntonname[MNAMELEN + 1];
67     char title[4096 + 1];
68
69     /* Get ports and services for drive statistics. */
70     if (unlikely(IOMasterPort(bootstrap_port, &master_port))) {
71         error("MACOS: IOMasterPort() failed");
72         do_io = 0;
73         error("DISABLED: system.io");
74     /* Get the list of all drive objects. */
75     } else if (unlikely(IOServiceGetMatchingServices(master_port, IOServiceMatching("IOBlockStorageDriver"), &drive_list))) {
76         error("MACOS: IOServiceGetMatchingServices() failed");
77         do_io = 0;
78         error("DISABLED: system.io");
79     } else {
80         while ((drive = IOIteratorNext(drive_list)) != 0) {
81             properties = 0;
82             statistics = 0;
83             number = 0;
84             bzero(&diskstat, sizeof(diskstat));
85
86             /* Get drive media object. */
87             status = IORegistryEntryGetChildEntry(drive, kIOServicePlane, &drive_media);
88             if (unlikely(status != KERN_SUCCESS)) {
89                 IOObjectRelease(drive);
90                 continue;
91             }
92
93             /* Get drive media properties. */
94             if (likely(!IORegistryEntryCreateCFProperties(drive_media, (CFMutableDictionaryRef *)&properties, kCFAllocatorDefault, 0))) {
95                 /* Get disk name. */
96                 if (likely(name = (CFStringRef)CFDictionaryGetValue(properties, CFSTR(kIOBSDNameKey)))) {
97                     CFStringGetCString(name, diskstat.name, MAXDRIVENAME, kCFStringEncodingUTF8);
98                 }
99             }
100
101             /* Release. */
102             CFRelease(properties);
103             IOObjectRelease(drive_media);
104
105             /* Obtain the properties for this drive object. */
106             if (unlikely(IORegistryEntryCreateCFProperties(drive, (CFMutableDictionaryRef *)&properties, kCFAllocatorDefault, 0))) {
107                 error("MACOS: IORegistryEntryCreateCFProperties() failed");
108                 do_io = 0;
109                 error("DISABLED: system.io");
110                 break;
111             } else if (likely(properties)) {
112                 /* Obtain the statistics from the drive properties. */
113                 if (likely(statistics = (CFDictionaryRef)CFDictionaryGetValue(properties, CFSTR(kIOBlockStorageDriverStatisticsKey)))) {
114
115                     // --------------------------------------------------------------------
116
117                     /* Get bytes read. */
118                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) {
119                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.bytes_read);
120                         total_disk_reads += diskstat.bytes_read;
121                     }
122
123                     /* Get bytes written. */
124                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) {
125                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.bytes_write);
126                         total_disk_writes += diskstat.bytes_write;
127                     }
128
129                     st = rrdset_find_bytype("disk", diskstat.name);
130                     if (unlikely(!st)) {
131                         st = rrdset_create("disk", diskstat.name, NULL, diskstat.name, "disk.io", "Disk I/O Bandwidth", "kilobytes/s", 2000, update_every, RRDSET_TYPE_AREA);
132
133                         rrddim_add(st, "reads", NULL, 1, 1024, RRDDIM_INCREMENTAL);
134                         rrddim_add(st, "writes", NULL, -1, 1024, RRDDIM_INCREMENTAL);
135                     }
136                     else rrdset_next(st);
137
138                     prev_diskstat.bytes_read = rrddim_set(st, "reads", diskstat.bytes_read);
139                     prev_diskstat.bytes_write = rrddim_set(st, "writes", diskstat.bytes_write);
140                     rrdset_done(st);
141
142                     // --------------------------------------------------------------------
143
144                     /* Get number of reads. */
145                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) {
146                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.reads);
147                     }
148
149                     /* Get number of writes. */
150                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) {
151                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.writes);
152                     }
153
154                     st = rrdset_find_bytype("disk_ops", diskstat.name);
155                     if (unlikely(!st)) {
156                         st = rrdset_create("disk_ops", diskstat.name, NULL, diskstat.name, "disk.ops", "Disk Completed I/O Operations", "operations/s", 2001, update_every, RRDSET_TYPE_LINE);
157                         st->isdetail = 1;
158
159                         rrddim_add(st, "reads", NULL, 1, 1, RRDDIM_INCREMENTAL);
160                         rrddim_add(st, "writes", NULL, -1, 1, RRDDIM_INCREMENTAL);
161                     }
162                     else rrdset_next(st);
163
164                     prev_diskstat.operations_read = rrddim_set(st, "reads", diskstat.reads);
165                     prev_diskstat.operations_write = rrddim_set(st, "writes", diskstat.writes);
166                     rrdset_done(st);
167
168                     // --------------------------------------------------------------------
169
170                     /* Get reads time. */
171                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) {
172                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.time_read);
173                     }
174
175                     /* Get writes time. */
176                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) {
177                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.time_write);
178                     }
179
180                     st = rrdset_find_bytype("disk_util", diskstat.name);
181                     if (unlikely(!st)) {
182                         st = rrdset_create("disk_util", diskstat.name, NULL, diskstat.name, "disk.util", "Disk Utilization Time", "% of time working", 2004, update_every, RRDSET_TYPE_AREA);
183                         st->isdetail = 1;
184
185                         rrddim_add(st, "utilization", NULL, 1, 10000000, RRDDIM_INCREMENTAL);
186                     }
187                     else rrdset_next(st);
188
189                     cur_diskstat.busy_time_ns = (diskstat.time_read + diskstat.time_write);
190                     prev_diskstat.busy_time_ns = rrddim_set(st, "utilization", cur_diskstat.busy_time_ns);
191                     rrdset_done(st);
192
193                     // --------------------------------------------------------------------
194
195                     /* Get reads latency. */
196                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsLatentReadTimeKey)))) {
197                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.latency_read);
198                     }
199
200                     /* Get writes latency. */
201                     if (likely(number = (CFNumberRef)CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsLatentWriteTimeKey)))) {
202                         CFNumberGetValue(number, kCFNumberSInt64Type, &diskstat.latency_write);
203                     }
204
205                     st = rrdset_find_bytype("disk_iotime", diskstat.name);
206                     if (unlikely(!st)) {
207                         st = rrdset_create("disk_iotime", diskstat.name, NULL, diskstat.name, "disk.iotime", "Disk Total I/O Time", "milliseconds/s", 2022, update_every, RRDSET_TYPE_LINE);
208                         st->isdetail = 1;
209
210                         rrddim_add(st, "reads", NULL, 1, 1000000, RRDDIM_INCREMENTAL);
211                         rrddim_add(st, "writes", NULL, -1, 1000000, RRDDIM_INCREMENTAL);
212                     }
213                     else rrdset_next(st);
214
215                     cur_diskstat.duration_read_ns = diskstat.time_read + diskstat.latency_read;
216                     cur_diskstat.duration_write_ns = diskstat.time_write + diskstat.latency_write;
217                     prev_diskstat.duration_read_ns = rrddim_set(st, "reads", cur_diskstat.duration_read_ns);
218                     prev_diskstat.duration_write_ns = rrddim_set(st, "writes", cur_diskstat.duration_write_ns);
219                     rrdset_done(st);
220
221                     // --------------------------------------------------------------------
222                     // calculate differential charts
223                     // only if this is not the first time we run
224
225                     if (likely(dt)) {
226
227                         // --------------------------------------------------------------------
228
229                         st = rrdset_find_bytype("disk_await", diskstat.name);
230                         if (unlikely(!st)) {
231                             st = rrdset_create("disk_await", diskstat.name, NULL, diskstat.name, "disk.await", "Average Completed I/O Operation Time", "ms per operation", 2005, update_every, RRDSET_TYPE_LINE);
232                             st->isdetail = 1;
233
234                             rrddim_add(st, "reads", NULL, 1, 1000000, RRDDIM_ABSOLUTE);
235                             rrddim_add(st, "writes", NULL, -1, 1000000, RRDDIM_ABSOLUTE);
236                         }
237                         else rrdset_next(st);
238
239                         rrddim_set(st, "reads", (diskstat.reads - prev_diskstat.operations_read) ?
240                             (cur_diskstat.duration_read_ns - prev_diskstat.duration_read_ns) / (diskstat.reads - prev_diskstat.operations_read) : 0);
241                         rrddim_set(st, "writes", (diskstat.writes - prev_diskstat.operations_write) ?
242                             (cur_diskstat.duration_write_ns - prev_diskstat.duration_write_ns) / (diskstat.writes - prev_diskstat.operations_write) : 0);
243                         rrdset_done(st);
244
245                         // --------------------------------------------------------------------
246
247                         st = rrdset_find_bytype("disk_avgsz", diskstat.name);
248                         if (unlikely(!st)) {
249                             st = rrdset_create("disk_avgsz", diskstat.name, NULL, diskstat.name, "disk.avgsz", "Average Completed I/O Operation Bandwidth", "kilobytes per operation", 2006, update_every, RRDSET_TYPE_AREA);
250                             st->isdetail = 1;
251
252                             rrddim_add(st, "reads", NULL, 1, 1024, RRDDIM_ABSOLUTE);
253                             rrddim_add(st, "writes", NULL, -1, 1024, RRDDIM_ABSOLUTE);
254                         }
255                         else rrdset_next(st);
256
257                         rrddim_set(st, "reads", (diskstat.reads - prev_diskstat.operations_read) ?
258                             (diskstat.bytes_read - prev_diskstat.bytes_read) / (diskstat.reads - prev_diskstat.operations_read) : 0);
259                         rrddim_set(st, "writes", (diskstat.writes - prev_diskstat.operations_write) ?
260                             (diskstat.bytes_write - prev_diskstat.bytes_write) / (diskstat.writes - prev_diskstat.operations_write) : 0);
261                         rrdset_done(st);
262
263                         // --------------------------------------------------------------------
264
265                         st = rrdset_find_bytype("disk_svctm", diskstat.name);
266                         if (unlikely(!st)) {
267                             st = rrdset_create("disk_svctm", diskstat.name, NULL, diskstat.name, "disk.svctm", "Average Service Time", "ms per operation", 2007, update_every, RRDSET_TYPE_LINE);
268                             st->isdetail = 1;
269
270                             rrddim_add(st, "svctm", NULL, 1, 1000000, RRDDIM_ABSOLUTE);
271                         }
272                         else rrdset_next(st);
273
274                         rrddim_set(st, "svctm", ((diskstat.reads - prev_diskstat.operations_read) + (diskstat.writes - prev_diskstat.operations_write)) ?
275                             (cur_diskstat.busy_time_ns - prev_diskstat.busy_time_ns) / ((diskstat.reads - prev_diskstat.operations_read) + (diskstat.writes - prev_diskstat.operations_write)) : 0);
276                         rrdset_done(st);
277                     }
278                 }
279
280                 /* Release. */
281                 CFRelease(properties);
282             }
283
284             /* Release. */
285             IOObjectRelease(drive);
286         }
287         IOIteratorReset(drive_list);
288
289         /* Release. */
290         IOObjectRelease(drive_list);
291     }
292
293     if (likely(do_io)) {
294         st = rrdset_find_bytype("system", "io");
295         if (unlikely(!st)) {
296             st = rrdset_create("system", "io", NULL, "disk", NULL, "Disk I/O", "kilobytes/s", 150, update_every, RRDSET_TYPE_AREA);
297             rrddim_add(st, "in",  NULL,  1, 1024, RRDDIM_INCREMENTAL);
298             rrddim_add(st, "out", NULL, -1, 1024, RRDDIM_INCREMENTAL);
299         }
300         else rrdset_next(st);
301
302         rrddim_set(st, "in", total_disk_reads);
303         rrddim_set(st, "out", total_disk_writes);
304         rrdset_done(st);
305     }
306
307     // --------------------------------------------------------------------------
308
309     if (likely(do_space || do_inodes)) {
310         // there is no mount info in sysctl MIBs
311         if (unlikely(!(mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)))) {
312             error("FREEBSD: getmntinfo() failed");
313             do_space = 0;
314             error("DISABLED: disk_space.X");
315             do_inodes = 0;
316             error("DISABLED: disk_inodes.X");
317         } else {
318             for (i = 0; i < mntsize; i++) {
319                 if (mntbuf[i].f_flags == MNT_RDONLY ||
320                         mntbuf[i].f_blocks == 0 ||
321                         // taken from gnulib/mountlist.c and shortened to FreeBSD related fstypes
322                         strcmp(mntbuf[i].f_fstypename, "autofs") == 0 ||
323                         strcmp(mntbuf[i].f_fstypename, "procfs") == 0 ||
324                         strcmp(mntbuf[i].f_fstypename, "subfs") == 0 ||
325                         strcmp(mntbuf[i].f_fstypename, "devfs") == 0 ||
326                         strcmp(mntbuf[i].f_fstypename, "none") == 0)
327                     continue;
328
329                 // --------------------------------------------------------------------------
330
331                 if (likely(do_space)) {
332                     st = rrdset_find_bytype("disk_space", mntbuf[i].f_mntonname);
333                     if (unlikely(!st)) {
334                         snprintfz(title, 4096, "Disk Space Usage for %s [%s]", mntbuf[i].f_mntonname, mntbuf[i].f_mntfromname);
335                         st = rrdset_create("disk_space", mntbuf[i].f_mntonname, NULL, mntbuf[i].f_mntonname, "disk.space", title, "GB", 2023,
336                                            update_every,
337                                            RRDSET_TYPE_STACKED);
338
339                         rrddim_add(st, "avail", NULL, mntbuf[i].f_bsize, GIGA_FACTOR, RRDDIM_ABSOLUTE);
340                         rrddim_add(st, "used", NULL, mntbuf[i].f_bsize, GIGA_FACTOR, RRDDIM_ABSOLUTE);
341                         rrddim_add(st, "reserved_for_root", "reserved for root", mntbuf[i].f_bsize, GIGA_FACTOR,
342                                    RRDDIM_ABSOLUTE);
343                     } else
344                         rrdset_next(st);
345
346                     rrddim_set(st, "avail", (collected_number) mntbuf[i].f_bavail);
347                     rrddim_set(st, "used", (collected_number) (mntbuf[i].f_blocks - mntbuf[i].f_bfree));
348                     rrddim_set(st, "reserved_for_root", (collected_number) (mntbuf[i].f_bfree - mntbuf[i].f_bavail));
349                     rrdset_done(st);
350                 }
351
352                 // --------------------------------------------------------------------------
353
354                 if (likely(do_inodes)) {
355                     st = rrdset_find_bytype("disk_inodes", mntbuf[i].f_mntonname);
356                     if (unlikely(!st)) {
357                         snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", mntbuf[i].f_mntonname, mntbuf[i].f_mntfromname);
358                         st = rrdset_create("disk_inodes", mntbuf[i].f_mntonname, NULL, mntbuf[i].f_mntonname, "disk.inodes", title, "Inodes", 2024,
359                                            update_every, RRDSET_TYPE_STACKED);
360
361                         rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE);
362                         rrddim_add(st, "used", NULL, 1, 1, RRDDIM_ABSOLUTE);
363                         rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE);
364                     } else
365                         rrdset_next(st);
366
367                     rrddim_set(st, "avail", (collected_number) mntbuf[i].f_ffree);
368                     rrddim_set(st, "used", (collected_number) (mntbuf[i].f_files - mntbuf[i].f_ffree));
369                     rrdset_done(st);
370                 }
371             }
372         }
373     }
374
375     return 0;
376 }