1 /**
2  * collectd - src/disk.c
3  * Copyright (C) 2005-2012  Florian octo Forster
4  * Copyright (C) 2009       Manuel Sanmartin
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Authors:
20  *   Florian octo Forster <octo at collectd.org>
21  *   Manuel Sanmartin
22  **/
23 
24 #include "collectd.h"
25 
26 #include "plugin.h"
27 #include "utils/common/common.h"
28 #include "utils/ignorelist/ignorelist.h"
29 
30 #if HAVE_MACH_MACH_TYPES_H
31 #include <mach/mach_types.h>
32 #endif
33 #if HAVE_MACH_MACH_INIT_H
34 #include <mach/mach_init.h>
35 #endif
36 #if HAVE_MACH_MACH_ERROR_H
37 #include <mach/mach_error.h>
38 #endif
39 #if HAVE_MACH_MACH_PORT_H
40 #include <mach/mach_port.h>
41 #endif
42 #if HAVE_COREFOUNDATION_COREFOUNDATION_H
43 #include <CoreFoundation/CoreFoundation.h>
44 #endif
45 #if HAVE_IOKIT_IOKITLIB_H
46 #include <IOKit/IOKitLib.h>
47 #endif
48 #if HAVE_IOKIT_IOTYPES_H
49 #include <IOKit/IOTypes.h>
50 #endif
51 #if HAVE_IOKIT_STORAGE_IOBLOCKSTORAGEDRIVER_H
52 #include <IOKit/storage/IOBlockStorageDriver.h>
53 #endif
54 #if HAVE_IOKIT_IOBSD_H
55 #include <IOKit/IOBSD.h>
56 #endif
57 #if KERNEL_FREEBSD
58 #include <devstat.h>
59 #include <libgeom.h>
60 #endif
61 
62 #ifndef UINT_MAX
63 #define UINT_MAX 4294967295U
64 #endif
65 
66 #if HAVE_STATGRAB_H
67 #include <statgrab.h>
68 #endif
69 
70 #if HAVE_PERFSTAT
71 #ifndef _AIXVERSION_610
72 #include <sys/systemcfg.h>
73 #endif
74 #include <libperfstat.h>
75 #include <sys/protosw.h>
76 #endif
77 
78 #if HAVE_IOKIT_IOKITLIB_H
79 static mach_port_t io_master_port = MACH_PORT_NULL;
80 /* This defaults to false for backwards compatibility. Please fix in the next
81  * major version. */
82 static bool use_bsd_name;
83 /* #endif HAVE_IOKIT_IOKITLIB_H */
84 
85 #elif KERNEL_LINUX
86 typedef struct diskstats {
87   char *name;
88 
89   /* This overflows in roughly 1361 years */
90   unsigned int poll_count;
91 
92   derive_t read_sectors;
93   derive_t write_sectors;
94 
95   derive_t read_bytes;
96   derive_t write_bytes;
97 
98   derive_t read_ops;
99   derive_t write_ops;
100   derive_t read_time;
101   derive_t write_time;
102 
103   derive_t avg_read_time;
104   derive_t avg_write_time;
105 
106   bool has_merged;
107   bool has_in_progress;
108   bool has_io_time;
109 
110   struct diskstats *next;
111 } diskstats_t;
112 
113 static diskstats_t *disklist;
114 /* #endif KERNEL_LINUX */
115 #elif KERNEL_FREEBSD
116 static struct gmesh geom_tree;
117 /* #endif KERNEL_FREEBSD */
118 
119 #elif HAVE_LIBKSTAT
120 #if HAVE_KSTAT_H
121 #include <kstat.h>
122 #endif
123 #define MAX_NUMDISK 1024
124 extern kstat_ctl_t *kc;
125 static kstat_t *ksp[MAX_NUMDISK];
126 static int numdisk;
127 /* #endif HAVE_LIBKSTAT */
128 
129 #elif defined(HAVE_LIBSTATGRAB)
130 /* #endif HAVE_LIBSTATGRAB */
131 
132 #elif HAVE_PERFSTAT
133 static perfstat_disk_t *stat_disk;
134 static int numdisk;
135 static int pnumdisk;
136 /* #endif HAVE_PERFSTAT */
137 
138 #elif HAVE_SYSCTL && KERNEL_NETBSD
139 
140 #include <sys/iostat.h>
141 #include <sys/sysctl.h>
142 
143 static struct io_sysctl *drives = NULL;
144 static size_t ndrive = 0;
145 
146 /* #endif HAVE_SYSCTL && KERNEL_NETBSD */
147 
148 #else
149 #error "No applicable input method."
150 #endif
151 
152 #if HAVE_LIBUDEV_H
153 #include <libudev.h>
154 
155 static char *conf_udev_name_attr;
156 static struct udev *handle_udev;
157 #endif
158 
159 static const char *config_keys[] = {"Disk", "UseBSDName", "IgnoreSelected",
160                                     "UdevNameAttr"};
161 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
162 
163 static ignorelist_t *ignorelist;
164 
disk_config(const char * key,const char * value)165 static int disk_config(const char *key, const char *value) {
166   if (ignorelist == NULL)
167     ignorelist = ignorelist_create(/* invert = */ 1);
168   if (ignorelist == NULL)
169     return 1;
170 
171   if (strcasecmp("Disk", key) == 0) {
172     ignorelist_add(ignorelist, value);
173   } else if (strcasecmp("IgnoreSelected", key) == 0) {
174     int invert = 1;
175     if (IS_TRUE(value))
176       invert = 0;
177     ignorelist_set_invert(ignorelist, invert);
178   } else if (strcasecmp("UseBSDName", key) == 0) {
179 #if HAVE_IOKIT_IOKITLIB_H
180     use_bsd_name = IS_TRUE(value);
181 #else
182     WARNING("disk plugin: The \"UseBSDName\" option is only supported "
183             "on Mach / Mac OS X and will be ignored.");
184 #endif
185   } else if (strcasecmp("UdevNameAttr", key) == 0) {
186 #if HAVE_LIBUDEV_H
187     if (conf_udev_name_attr != NULL) {
188       free(conf_udev_name_attr);
189       conf_udev_name_attr = NULL;
190     }
191     if ((conf_udev_name_attr = strdup(value)) == NULL)
192       return 1;
193 #else
194     WARNING("disk plugin: The \"UdevNameAttr\" option is only supported "
195             "if collectd is built with libudev support");
196 #endif
197   } else {
198     return -1;
199   }
200 
201   return 0;
202 } /* int disk_config */
203 
disk_init(void)204 static int disk_init(void) {
205 #if HAVE_IOKIT_IOKITLIB_H
206   kern_return_t status;
207 
208   if (io_master_port != MACH_PORT_NULL) {
209     mach_port_deallocate(mach_task_self(), io_master_port);
210     io_master_port = MACH_PORT_NULL;
211   }
212 
213   status = IOMasterPort(MACH_PORT_NULL, &io_master_port);
214   if (status != kIOReturnSuccess) {
215     ERROR("IOMasterPort failed: %s", mach_error_string(status));
216     io_master_port = MACH_PORT_NULL;
217     return -1;
218   }
219     /* #endif HAVE_IOKIT_IOKITLIB_H */
220 
221 #elif KERNEL_LINUX
222 #if HAVE_LIBUDEV_H
223   if (conf_udev_name_attr != NULL) {
224     handle_udev = udev_new();
225     if (handle_udev == NULL) {
226       ERROR("disk plugin: udev_new() failed!");
227       return -1;
228     }
229   }
230 #endif /* HAVE_LIBUDEV_H */
231     /* #endif KERNEL_LINUX */
232 
233 #elif KERNEL_FREEBSD
234   int rv;
235 
236   rv = geom_gettree(&geom_tree);
237   if (rv != 0) {
238     ERROR("geom_gettree() failed, returned %d", rv);
239     return -1;
240   }
241   rv = geom_stats_open();
242   if (rv != 0) {
243     ERROR("geom_stats_open() failed, returned %d", rv);
244     return -1;
245   }
246     /* #endif KERNEL_FREEBSD */
247 
248 #elif HAVE_LIBKSTAT
249   kstat_t *ksp_chain;
250 
251   numdisk = 0;
252 
253   if (kc == NULL)
254     return -1;
255 
256   for (numdisk = 0, ksp_chain = kc->kc_chain;
257        (numdisk < MAX_NUMDISK) && (ksp_chain != NULL);
258        ksp_chain = ksp_chain->ks_next) {
259     if (strncmp(ksp_chain->ks_class, "disk", 4) &&
260         strncmp(ksp_chain->ks_class, "partition", 9))
261       continue;
262     if (ksp_chain->ks_type != KSTAT_TYPE_IO)
263       continue;
264     ksp[numdisk++] = ksp_chain;
265   }
266 /* #endif HAVE_LIBKSTAT */
267 #elif HAVE_SYSCTL && KERNEL_NETBSD
268   int mib[3];
269   size_t size;
270 
271   /* figure out number of drives */
272   mib[0] = CTL_HW;
273   mib[1] = HW_IOSTATS;
274   mib[2] = sizeof(struct io_sysctl);
275   if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) {
276     ERROR("disk plugin: sysctl for ndrives failed");
277     return -1;
278   }
279   ndrive = size / sizeof(struct io_sysctl);
280 
281   if (size == 0) {
282     ERROR("disk plugin: no drives found");
283     return -1;
284   }
285   drives = (struct io_sysctl *)malloc(size);
286   if (drives == NULL) {
287     ERROR("disk plugin: memory allocation failure");
288     return -1;
289   }
290 #endif /* HAVE_SYSCTL && KERNEL_NETBSD */
291 
292   return 0;
293 } /* int disk_init */
294 
disk_shutdown(void)295 static int disk_shutdown(void) {
296 #if KERNEL_LINUX
297 #if HAVE_LIBUDEV_H
298   if (handle_udev != NULL)
299     udev_unref(handle_udev);
300 #endif /* HAVE_LIBUDEV_H */
301 #endif /* KERNEL_LINUX */
302   return 0;
303 } /* int disk_shutdown */
304 
disk_submit(const char * plugin_instance,const char * type,derive_t read,derive_t write)305 static void disk_submit(const char *plugin_instance, const char *type,
306                         derive_t read, derive_t write) {
307   value_list_t vl = VALUE_LIST_INIT;
308   value_t values[] = {
309       {.derive = read},
310       {.derive = write},
311   };
312 
313   vl.values = values;
314   vl.values_len = STATIC_ARRAY_SIZE(values);
315   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
316   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
317   sstrncpy(vl.type, type, sizeof(vl.type));
318 
319   plugin_dispatch_values(&vl);
320 } /* void disk_submit */
321 
322 #if KERNEL_FREEBSD || (HAVE_SYSCTL && KERNEL_NETBSD) || KERNEL_LINUX
submit_io_time(char const * plugin_instance,derive_t io_time,derive_t weighted_time)323 static void submit_io_time(char const *plugin_instance, derive_t io_time,
324                            derive_t weighted_time) {
325   value_list_t vl = VALUE_LIST_INIT;
326   value_t values[] = {
327       {.derive = io_time},
328       {.derive = weighted_time},
329   };
330 
331   vl.values = values;
332   vl.values_len = STATIC_ARRAY_SIZE(values);
333   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
334   sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
335   sstrncpy(vl.type, "disk_io_time", sizeof(vl.type));
336 
337   plugin_dispatch_values(&vl);
338 } /* void submit_io_time */
339 #endif /* KERNEL_FREEBSD || (HAVE_SYSCTL && KERNEL_NETBSD) || KERNEL_LINUX */
340 
341 #if KERNEL_FREEBSD || KERNEL_LINUX
submit_in_progress(char const * disk_name,gauge_t in_progress)342 static void submit_in_progress(char const *disk_name, gauge_t in_progress) {
343   value_list_t vl = VALUE_LIST_INIT;
344 
345   vl.values = &(value_t){.gauge = in_progress};
346   vl.values_len = 1;
347   sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
348   sstrncpy(vl.plugin_instance, disk_name, sizeof(vl.plugin_instance));
349   sstrncpy(vl.type, "pending_operations", sizeof(vl.type));
350 
351   plugin_dispatch_values(&vl);
352 }
353 #endif /* KERNEL_FREEBSD || KERNEL_LINUX */
354 
355 #if KERNEL_LINUX
disk_calc_time_incr(counter_t delta_time,counter_t delta_ops)356 static counter_t disk_calc_time_incr(counter_t delta_time,
357                                      counter_t delta_ops) {
358   double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval());
359   double avg_time = ((double)delta_time) / ((double)delta_ops);
360   double avg_time_incr = interval * avg_time;
361 
362   return (counter_t)(avg_time_incr + .5);
363 }
364 #endif
365 
366 #if HAVE_LIBUDEV_H
367 /**
368  * Attempt to provide an rename disk instance from an assigned udev attribute.
369  *
370  * On success, it returns a strduped char* to the desired attribute value.
371  * Otherwise it returns NULL.
372  */
373 
disk_udev_attr_name(struct udev * udev,char * disk_name,const char * attr)374 static char *disk_udev_attr_name(struct udev *udev, char *disk_name,
375                                  const char *attr) {
376   struct udev_device *dev;
377   const char *prop;
378   char *output = NULL;
379 
380   dev = udev_device_new_from_subsystem_sysname(udev, "block", disk_name);
381   if (dev != NULL) {
382     prop = udev_device_get_property_value(dev, attr);
383     if (prop) {
384       output = strdup(prop);
385       DEBUG("disk plugin: renaming %s => %s", disk_name, output);
386     }
387     udev_device_unref(dev);
388   }
389   return output;
390 }
391 #endif
392 
393 #if HAVE_IOKIT_IOKITLIB_H
dict_get_value(CFDictionaryRef dict,const char * key)394 static signed long long dict_get_value(CFDictionaryRef dict, const char *key) {
395   signed long long val_int;
396   CFNumberRef val_obj;
397   CFStringRef key_obj;
398 
399   /* `key_obj' needs to be released. */
400   key_obj = CFStringCreateWithCString(kCFAllocatorDefault, key,
401                                       kCFStringEncodingASCII);
402   if (key_obj == NULL) {
403     DEBUG("CFStringCreateWithCString (%s) failed.", key);
404     return -1LL;
405   }
406 
407   /* get => we don't need to release (== free) the object */
408   val_obj = (CFNumberRef)CFDictionaryGetValue(dict, key_obj);
409 
410   CFRelease(key_obj);
411 
412   if (val_obj == NULL) {
413     DEBUG("CFDictionaryGetValue (%s) failed.", key);
414     return -1LL;
415   }
416 
417   if (!CFNumberGetValue(val_obj, kCFNumberSInt64Type, &val_int)) {
418     DEBUG("CFNumberGetValue (%s) failed.", key);
419     return -1LL;
420   }
421 
422   return val_int;
423 }
424 #endif /* HAVE_IOKIT_IOKITLIB_H */
425 
disk_read(void)426 static int disk_read(void) {
427 #if HAVE_IOKIT_IOKITLIB_H
428   io_registry_entry_t disk;
429   io_registry_entry_t disk_child;
430   io_iterator_t disk_list;
431   CFMutableDictionaryRef props_dict, child_dict;
432   CFDictionaryRef stats_dict;
433   CFStringRef tmp_cf_string_ref;
434   kern_return_t status;
435 
436   signed long long read_ops, read_byt, read_tme;
437   signed long long write_ops, write_byt, write_tme;
438 
439   int disk_major, disk_minor;
440   char disk_name[DATA_MAX_NAME_LEN];
441   char child_disk_name_bsd[DATA_MAX_NAME_LEN],
442       props_disk_name_bsd[DATA_MAX_NAME_LEN];
443 
444   /* Get the list of all disk objects. */
445   if (IOServiceGetMatchingServices(
446           io_master_port, IOServiceMatching(kIOBlockStorageDriverClass),
447           &disk_list) != kIOReturnSuccess) {
448     ERROR("disk plugin: IOServiceGetMatchingServices failed.");
449     return -1;
450   }
451 
452   while ((disk = IOIteratorNext(disk_list)) != 0) {
453     props_dict = NULL;
454     stats_dict = NULL;
455     child_dict = NULL;
456 
457     /* get child of disk entry and corresponding property dictionary */
458     if ((status = IORegistryEntryGetChildEntry(
459              disk, kIOServicePlane, &disk_child)) != kIOReturnSuccess) {
460       /* This fails for example for DVD/CD drives, which we want to ignore
461        * anyway */
462       DEBUG("IORegistryEntryGetChildEntry (disk) failed: 0x%08x", status);
463       IOObjectRelease(disk);
464       continue;
465     }
466     if (IORegistryEntryCreateCFProperties(
467             disk_child, (CFMutableDictionaryRef *)&child_dict,
468             kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess ||
469         child_dict == NULL) {
470       ERROR("disk plugin: IORegistryEntryCreateCFProperties (disk_child) "
471             "failed.");
472       IOObjectRelease(disk_child);
473       IOObjectRelease(disk);
474       continue;
475     }
476 
477     /* extract name and major/minor numbers */
478     memset(child_disk_name_bsd, 0, sizeof(child_disk_name_bsd));
479     tmp_cf_string_ref =
480         (CFStringRef)CFDictionaryGetValue(child_dict, CFSTR(kIOBSDNameKey));
481     if (tmp_cf_string_ref) {
482       assert(CFGetTypeID(tmp_cf_string_ref) == CFStringGetTypeID());
483       CFStringGetCString(tmp_cf_string_ref, child_disk_name_bsd,
484                          sizeof(child_disk_name_bsd), kCFStringEncodingUTF8);
485     }
486     disk_major = (int)dict_get_value(child_dict, kIOBSDMajorKey);
487     disk_minor = (int)dict_get_value(child_dict, kIOBSDMinorKey);
488     DEBUG("disk plugin: child_disk_name_bsd=\"%s\" major=%d minor=%d",
489           child_disk_name_bsd, disk_major, disk_minor);
490     CFRelease(child_dict);
491     IOObjectRelease(disk_child);
492 
493     /* get property dictionary of the disk entry itself */
494     if (IORegistryEntryCreateCFProperties(
495             disk, (CFMutableDictionaryRef *)&props_dict, kCFAllocatorDefault,
496             kNilOptions) != kIOReturnSuccess ||
497         props_dict == NULL) {
498       ERROR("disk-plugin: IORegistryEntryCreateCFProperties failed.");
499       IOObjectRelease(disk);
500       continue;
501     }
502 
503     /* extract name and stats dictionary */
504     memset(props_disk_name_bsd, 0, sizeof(props_disk_name_bsd));
505     tmp_cf_string_ref =
506         (CFStringRef)CFDictionaryGetValue(props_dict, CFSTR(kIOBSDNameKey));
507     if (tmp_cf_string_ref) {
508       assert(CFGetTypeID(tmp_cf_string_ref) == CFStringGetTypeID());
509       CFStringGetCString(tmp_cf_string_ref, props_disk_name_bsd,
510                          sizeof(props_disk_name_bsd), kCFStringEncodingUTF8);
511     }
512     stats_dict = (CFDictionaryRef)CFDictionaryGetValue(
513         props_dict, CFSTR(kIOBlockStorageDriverStatisticsKey));
514     if (stats_dict == NULL) {
515       ERROR("disk plugin: CFDictionaryGetValue (%s) failed.",
516             kIOBlockStorageDriverStatisticsKey);
517       CFRelease(props_dict);
518       IOObjectRelease(disk);
519       continue;
520     }
521     DEBUG("disk plugin: props_disk_name_bsd=\"%s\"", props_disk_name_bsd);
522 
523     /* choose name */
524     if (use_bsd_name) {
525       if (child_disk_name_bsd[0] != 0)
526         sstrncpy(disk_name, child_disk_name_bsd, sizeof(disk_name));
527       else if (props_disk_name_bsd[0] != 0)
528         sstrncpy(disk_name, props_disk_name_bsd, sizeof(disk_name));
529       else {
530         ERROR("disk plugin: can't find bsd disk name.");
531         ssnprintf(disk_name, sizeof(disk_name), "%i-%i", disk_major,
532                   disk_minor);
533       }
534     } else
535       ssnprintf(disk_name, sizeof(disk_name), "%i-%i", disk_major, disk_minor);
536 
537     DEBUG("disk plugin: disk_name = \"%s\"", disk_name);
538 
539     /* check the name against ignore list */
540     if (ignorelist_match(ignorelist, disk_name) != 0) {
541       CFRelease(props_dict);
542       IOObjectRelease(disk);
543       continue;
544     }
545 
546     /* extract the stats */
547     read_ops =
548         dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsReadsKey);
549     read_byt =
550         dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsBytesReadKey);
551     read_tme = dict_get_value(stats_dict,
552                               kIOBlockStorageDriverStatisticsTotalReadTimeKey);
553     write_ops =
554         dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsWritesKey);
555     write_byt = dict_get_value(stats_dict,
556                                kIOBlockStorageDriverStatisticsBytesWrittenKey);
557     write_tme = dict_get_value(
558         stats_dict, kIOBlockStorageDriverStatisticsTotalWriteTimeKey);
559     CFRelease(props_dict);
560     IOObjectRelease(disk);
561 
562     /* and submit */
563     if ((read_byt != -1LL) || (write_byt != -1LL))
564       disk_submit(disk_name, "disk_octets", read_byt, write_byt);
565     if ((read_ops != -1LL) || (write_ops != -1LL))
566       disk_submit(disk_name, "disk_ops", read_ops, write_ops);
567     if ((read_tme != -1LL) || (write_tme != -1LL))
568       disk_submit(disk_name, "disk_time", read_tme / 1000, write_tme / 1000);
569   }
570   IOObjectRelease(disk_list);
571   /* #endif HAVE_IOKIT_IOKITLIB_H */
572 
573 #elif KERNEL_FREEBSD
574   int retry, dirty;
575 
576   void *snap = NULL;
577   struct devstat *snap_iter;
578 
579   struct gident *geom_id;
580 
581   const char *disk_name;
582   long double read_time, write_time, busy_time, total_duration;
583   uint64_t queue_length;
584 
585   for (retry = 0, dirty = 1; retry < 5 && dirty == 1; retry++) {
586     if (snap != NULL)
587       geom_stats_snapshot_free(snap);
588 
589     /* Get a fresh copy of stats snapshot */
590     snap = geom_stats_snapshot_get();
591     if (snap == NULL) {
592       ERROR("disk plugin: geom_stats_snapshot_get() failed.");
593       return -1;
594     }
595 
596     /* Check if we have dirty read from this snapshot */
597     dirty = 0;
598     geom_stats_snapshot_reset(snap);
599     while ((snap_iter = geom_stats_snapshot_next(snap)) != NULL) {
600       if (snap_iter->id == NULL)
601         continue;
602       geom_id = geom_lookupid(&geom_tree, snap_iter->id);
603 
604       /* New device? refresh GEOM tree */
605       if (geom_id == NULL) {
606         geom_deletetree(&geom_tree);
607         if (geom_gettree(&geom_tree) != 0) {
608           ERROR("disk plugin: geom_gettree() failed");
609           geom_stats_snapshot_free(snap);
610           return -1;
611         }
612         geom_id = geom_lookupid(&geom_tree, snap_iter->id);
613       }
614       /*
615        * This should be rare: the device come right before we take the
616        * snapshot and went away right after it.  We will handle this
617        * case later, so don't mark dirty but silently ignore it.
618        */
619       if (geom_id == NULL)
620         continue;
621 
622       /* Only collect PROVIDER data */
623       if (geom_id->lg_what != ISPROVIDER)
624         continue;
625 
626       /* Only collect data when rank is 1 (physical devices) */
627       if (((struct gprovider *)(geom_id->lg_ptr))->lg_geom->lg_rank != 1)
628         continue;
629 
630       /* Check if this is a dirty read quit for another try */
631       if (snap_iter->sequence0 != snap_iter->sequence1) {
632         dirty = 1;
633         break;
634       }
635     }
636   }
637 
638   /* Reset iterator */
639   geom_stats_snapshot_reset(snap);
640   for (;;) {
641     snap_iter = geom_stats_snapshot_next(snap);
642     if (snap_iter == NULL)
643       break;
644 
645     if (snap_iter->id == NULL)
646       continue;
647     geom_id = geom_lookupid(&geom_tree, snap_iter->id);
648     if (geom_id == NULL)
649       continue;
650     if (geom_id->lg_what != ISPROVIDER)
651       continue;
652     if (((struct gprovider *)(geom_id->lg_ptr))->lg_geom->lg_rank != 1)
653       continue;
654     /* Skip dirty reads, if present */
655     if (dirty && (snap_iter->sequence0 != snap_iter->sequence1))
656       continue;
657 
658     disk_name = ((struct gprovider *)geom_id->lg_ptr)->lg_name;
659 
660     if (ignorelist_match(ignorelist, disk_name) != 0)
661       continue;
662 
663     if ((snap_iter->bytes[DEVSTAT_READ] != 0) ||
664         (snap_iter->bytes[DEVSTAT_WRITE] != 0)) {
665       disk_submit(disk_name, "disk_octets",
666                   (derive_t)snap_iter->bytes[DEVSTAT_READ],
667                   (derive_t)snap_iter->bytes[DEVSTAT_WRITE]);
668     }
669 
670     if ((snap_iter->operations[DEVSTAT_READ] != 0) ||
671         (snap_iter->operations[DEVSTAT_WRITE] != 0)) {
672       disk_submit(disk_name, "disk_ops",
673                   (derive_t)snap_iter->operations[DEVSTAT_READ],
674                   (derive_t)snap_iter->operations[DEVSTAT_WRITE]);
675     }
676 
677     read_time = devstat_compute_etime(&snap_iter->duration[DEVSTAT_READ], NULL);
678     write_time =
679         devstat_compute_etime(&snap_iter->duration[DEVSTAT_WRITE], NULL);
680     if ((read_time != 0) || (write_time != 0)) {
681       disk_submit(disk_name, "disk_time", (derive_t)(read_time * 1000),
682                   (derive_t)(write_time * 1000));
683     }
684     if (devstat_compute_statistics(snap_iter, NULL, 1.0, DSM_TOTAL_BUSY_TIME,
685                                    &busy_time, DSM_TOTAL_DURATION,
686                                    &total_duration, DSM_QUEUE_LENGTH,
687                                    &queue_length, DSM_NONE) != 0) {
688       WARNING("%s", devstat_errbuf);
689     } else {
690       submit_io_time(disk_name, busy_time, total_duration);
691       submit_in_progress(disk_name, (gauge_t)queue_length);
692     }
693   }
694   geom_stats_snapshot_free(snap);
695 
696 #elif KERNEL_LINUX
697   FILE *fh;
698   char buffer[1024];
699 
700   char *fields[32];
701   static unsigned int poll_count = 0;
702 
703   derive_t read_sectors = 0;
704   derive_t write_sectors = 0;
705 
706   derive_t read_ops = 0;
707   derive_t read_merged = 0;
708   derive_t read_time = 0;
709   derive_t write_ops = 0;
710   derive_t write_merged = 0;
711   derive_t write_time = 0;
712   gauge_t in_progress = NAN;
713   derive_t io_time = 0;
714   derive_t weighted_time = 0;
715   int is_disk = 0;
716 
717   diskstats_t *ds, *pre_ds;
718 
719   if ((fh = fopen("/proc/diskstats", "r")) == NULL) {
720     ERROR("disk plugin: fopen(\"/proc/diskstats\"): %s", STRERRNO);
721     return -1;
722   }
723 
724   poll_count++;
725   while (fgets(buffer, sizeof(buffer), fh) != NULL) {
726     int numfields = strsplit(buffer, fields, 32);
727 
728     /* need either 7 fields (partition) or at least 14 fields */
729     if ((numfields != 7) && (numfields < 14))
730       continue;
731 
732     char *disk_name = fields[2];
733 
734     for (ds = disklist, pre_ds = disklist; ds != NULL;
735          pre_ds = ds, ds = ds->next)
736       if (strcmp(disk_name, ds->name) == 0)
737         break;
738 
739     if (ds == NULL) {
740       if ((ds = calloc(1, sizeof(*ds))) == NULL)
741         continue;
742 
743       if ((ds->name = strdup(disk_name)) == NULL) {
744         free(ds);
745         continue;
746       }
747 
748       if (pre_ds == NULL)
749         disklist = ds;
750       else
751         pre_ds->next = ds;
752     }
753 
754     is_disk = 0;
755     if (numfields == 7) {
756       /* Kernel 2.6, Partition */
757       read_ops = atoll(fields[3]);
758       read_sectors = atoll(fields[4]);
759       write_ops = atoll(fields[5]);
760       write_sectors = atoll(fields[6]);
761     } else {
762       assert(numfields >= 14);
763       read_ops = atoll(fields[3]);
764       write_ops = atoll(fields[7]);
765 
766       read_sectors = atoll(fields[5]);
767       write_sectors = atoll(fields[9]);
768 
769       is_disk = 1;
770       read_merged = atoll(fields[4]);
771       read_time = atoll(fields[6]);
772       write_merged = atoll(fields[8]);
773       write_time = atoll(fields[10]);
774 
775       in_progress = atof(fields[11]);
776 
777       io_time = atof(fields[12]);
778       weighted_time = atof(fields[13]);
779     }
780 
781     {
782       derive_t diff_read_sectors;
783       derive_t diff_write_sectors;
784 
785       /* If the counter wraps around, it's only 32 bits.. */
786       if (read_sectors < ds->read_sectors)
787         diff_read_sectors = 1 + read_sectors + (UINT_MAX - ds->read_sectors);
788       else
789         diff_read_sectors = read_sectors - ds->read_sectors;
790       if (write_sectors < ds->write_sectors)
791         diff_write_sectors = 1 + write_sectors + (UINT_MAX - ds->write_sectors);
792       else
793         diff_write_sectors = write_sectors - ds->write_sectors;
794 
795       ds->read_bytes += 512 * diff_read_sectors;
796       ds->write_bytes += 512 * diff_write_sectors;
797       ds->read_sectors = read_sectors;
798       ds->write_sectors = write_sectors;
799     }
800 
801     /* Calculate the average time an io-op needs to complete */
802     if (is_disk) {
803       derive_t diff_read_ops;
804       derive_t diff_write_ops;
805       derive_t diff_read_time;
806       derive_t diff_write_time;
807 
808       if (read_ops < ds->read_ops)
809         diff_read_ops = 1 + read_ops + (UINT_MAX - ds->read_ops);
810       else
811         diff_read_ops = read_ops - ds->read_ops;
812       DEBUG("disk plugin: disk_name = %s; read_ops = %" PRIi64 "; "
813             "ds->read_ops = %" PRIi64 "; diff_read_ops = %" PRIi64 ";",
814             disk_name, read_ops, ds->read_ops, diff_read_ops);
815 
816       if (write_ops < ds->write_ops)
817         diff_write_ops = 1 + write_ops + (UINT_MAX - ds->write_ops);
818       else
819         diff_write_ops = write_ops - ds->write_ops;
820 
821       if (read_time < ds->read_time)
822         diff_read_time = 1 + read_time + (UINT_MAX - ds->read_time);
823       else
824         diff_read_time = read_time - ds->read_time;
825 
826       if (write_time < ds->write_time)
827         diff_write_time = 1 + write_time + (UINT_MAX - ds->write_time);
828       else
829         diff_write_time = write_time - ds->write_time;
830 
831       if (diff_read_ops != 0)
832         ds->avg_read_time += disk_calc_time_incr(diff_read_time, diff_read_ops);
833       if (diff_write_ops != 0)
834         ds->avg_write_time +=
835             disk_calc_time_incr(diff_write_time, diff_write_ops);
836 
837       ds->read_ops = read_ops;
838       ds->read_time = read_time;
839       ds->write_ops = write_ops;
840       ds->write_time = write_time;
841 
842       if (read_merged || write_merged)
843         ds->has_merged = true;
844 
845       if (in_progress)
846         ds->has_in_progress = true;
847 
848       if (io_time)
849         ds->has_io_time = true;
850 
851     } /* if (is_disk) */
852 
853     /* Skip first cycle for newly-added disk */
854     if (ds->poll_count == 0) {
855       DEBUG("disk plugin: (ds->poll_count = 0) => Skipping.");
856       ds->poll_count = poll_count;
857       continue;
858     }
859     ds->poll_count = poll_count;
860 
861     if ((read_ops == 0) && (write_ops == 0)) {
862       DEBUG("disk plugin: ((read_ops == 0) && "
863             "(write_ops == 0)); => Not writing.");
864       continue;
865     }
866 
867     char *output_name = disk_name;
868 
869 #if HAVE_LIBUDEV_H
870     char *alt_name = NULL;
871     if (conf_udev_name_attr != NULL) {
872       alt_name =
873           disk_udev_attr_name(handle_udev, disk_name, conf_udev_name_attr);
874       if (alt_name != NULL)
875         output_name = alt_name;
876     }
877 #endif
878 
879     if (ignorelist_match(ignorelist, output_name) != 0) {
880 #if HAVE_LIBUDEV_H
881       /* release udev-based alternate name, if allocated */
882       sfree(alt_name);
883 #endif
884       continue;
885     }
886 
887     if ((ds->read_bytes != 0) || (ds->write_bytes != 0))
888       disk_submit(output_name, "disk_octets", ds->read_bytes, ds->write_bytes);
889 
890     if ((ds->read_ops != 0) || (ds->write_ops != 0))
891       disk_submit(output_name, "disk_ops", read_ops, write_ops);
892 
893     if ((ds->avg_read_time != 0) || (ds->avg_write_time != 0))
894       disk_submit(output_name, "disk_time", ds->avg_read_time,
895                   ds->avg_write_time);
896 
897     if (is_disk) {
898       if (ds->has_merged)
899         disk_submit(output_name, "disk_merged", read_merged, write_merged);
900       if (ds->has_in_progress)
901         submit_in_progress(output_name, in_progress);
902       if (ds->has_io_time)
903         submit_io_time(output_name, io_time, weighted_time);
904     } /* if (is_disk) */
905 
906 #if HAVE_LIBUDEV_H
907     /* release udev-based alternate name, if allocated */
908     sfree(alt_name);
909 #endif
910   } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
911 
912   /* Remove disks that have disappeared from diskstats */
913   for (ds = disklist, pre_ds = disklist; ds != NULL;) {
914     /* Disk exists */
915     if (ds->poll_count == poll_count) {
916       pre_ds = ds;
917       ds = ds->next;
918       continue;
919     }
920 
921     /* Disk is missing, remove it */
922     diskstats_t *missing_ds = ds;
923     if (ds == disklist) {
924       pre_ds = disklist = ds->next;
925     } else {
926       pre_ds->next = ds->next;
927     }
928     ds = ds->next;
929 
930     DEBUG("disk plugin: Disk %s disappeared.", missing_ds->name);
931     free(missing_ds->name);
932     free(missing_ds);
933   }
934   fclose(fh);
935   /* #endif defined(KERNEL_LINUX) */
936 
937 #elif HAVE_LIBKSTAT
938 #if HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_NWRITES && HAVE_KSTAT_IO_T_WTIME
939 #define KIO_ROCTETS reads
940 #define KIO_WOCTETS writes
941 #define KIO_ROPS nreads
942 #define KIO_WOPS nwrites
943 #define KIO_RTIME rtime
944 #define KIO_WTIME wtime
945 #elif HAVE_KSTAT_IO_T_NWRITTEN && HAVE_KSTAT_IO_T_WRITES &&                    \
946     HAVE_KSTAT_IO_T_WTIME
947 #define KIO_ROCTETS nread
948 #define KIO_WOCTETS nwritten
949 #define KIO_ROPS reads
950 #define KIO_WOPS writes
951 #define KIO_RTIME rtime
952 #define KIO_WTIME wtime
953 #else
954 #error "kstat_io_t does not have the required members"
955 #endif
956   static kstat_io_t kio;
957 
958   if (kc == NULL)
959     return -1;
960 
961   for (int i = 0; i < numdisk; i++) {
962     if (kstat_read(kc, ksp[i], &kio) == -1)
963       continue;
964 
965     if (strncmp(ksp[i]->ks_class, "disk", 4) == 0) {
966       if (ignorelist_match(ignorelist, ksp[i]->ks_name) != 0)
967         continue;
968 
969       disk_submit(ksp[i]->ks_name, "disk_octets", kio.KIO_ROCTETS,
970                   kio.KIO_WOCTETS);
971       disk_submit(ksp[i]->ks_name, "disk_ops", kio.KIO_ROPS, kio.KIO_WOPS);
972       /* FIXME: Convert this to microseconds if necessary */
973       disk_submit(ksp[i]->ks_name, "disk_time", kio.KIO_RTIME, kio.KIO_WTIME);
974     } else if (strncmp(ksp[i]->ks_class, "partition", 9) == 0) {
975       if (ignorelist_match(ignorelist, ksp[i]->ks_name) != 0)
976         continue;
977 
978       disk_submit(ksp[i]->ks_name, "disk_octets", kio.KIO_ROCTETS,
979                   kio.KIO_WOCTETS);
980       disk_submit(ksp[i]->ks_name, "disk_ops", kio.KIO_ROPS, kio.KIO_WOPS);
981     }
982   }
983     /* #endif defined(HAVE_LIBKSTAT) */
984 
985 #elif defined(HAVE_LIBSTATGRAB)
986   sg_disk_io_stats *ds;
987 #if HAVE_LIBSTATGRAB_0_90
988   size_t disks;
989 #else
990   int disks;
991 #endif
992   char name[DATA_MAX_NAME_LEN];
993 
994   if ((ds = sg_get_disk_io_stats(&disks)) == NULL)
995     return 0;
996 
997   for (int counter = 0; counter < disks; counter++) {
998     strncpy(name, ds->disk_name, sizeof(name));
999     name[sizeof(name) - 1] =
1000         '\0'; /* strncpy doesn't terminate longer strings */
1001 
1002     if (ignorelist_match(ignorelist, name) != 0) {
1003       ds++;
1004       continue;
1005     }
1006 
1007     disk_submit(name, "disk_octets", ds->read_bytes, ds->write_bytes);
1008     ds++;
1009   }
1010     /* #endif defined(HAVE_LIBSTATGRAB) */
1011 
1012 #elif defined(HAVE_PERFSTAT)
1013   derive_t read_sectors;
1014   derive_t write_sectors;
1015   derive_t read_time;
1016   derive_t write_time;
1017   derive_t read_ops;
1018   derive_t write_ops;
1019   perfstat_id_t firstpath;
1020   int rnumdisk;
1021 
1022   if ((numdisk = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0)) < 0) {
1023     WARNING("disk plugin: perfstat_disk: %s", STRERRNO);
1024     return -1;
1025   }
1026 
1027   if (numdisk != pnumdisk || stat_disk == NULL) {
1028     free(stat_disk);
1029     stat_disk = calloc(numdisk, sizeof(*stat_disk));
1030   }
1031   pnumdisk = numdisk;
1032 
1033   firstpath.name[0] = '\0';
1034   if ((rnumdisk = perfstat_disk(&firstpath, stat_disk, sizeof(perfstat_disk_t),
1035                                 numdisk)) < 0) {
1036     WARNING("disk plugin: perfstat_disk : %s", STRERRNO);
1037     return -1;
1038   }
1039 
1040   for (int i = 0; i < rnumdisk; i++) {
1041     if (ignorelist_match(ignorelist, stat_disk[i].name) != 0)
1042       continue;
1043 
1044     read_sectors = stat_disk[i].rblks * stat_disk[i].bsize;
1045     write_sectors = stat_disk[i].wblks * stat_disk[i].bsize;
1046     disk_submit(stat_disk[i].name, "disk_octets", read_sectors, write_sectors);
1047 
1048     read_ops = stat_disk[i].xrate;
1049     write_ops = stat_disk[i].xfers - stat_disk[i].xrate;
1050     disk_submit(stat_disk[i].name, "disk_ops", read_ops, write_ops);
1051 
1052     read_time = stat_disk[i].rserv;
1053     read_time *= ((double)(_system_configuration.Xint) /
1054                   (double)(_system_configuration.Xfrac)) /
1055                  1000000.0;
1056     write_time = stat_disk[i].wserv;
1057     write_time *= ((double)(_system_configuration.Xint) /
1058                    (double)(_system_configuration.Xfrac)) /
1059                   1000000.0;
1060     disk_submit(stat_disk[i].name, "disk_time", read_time, write_time);
1061   }
1062 /* #endif defined(HAVE_PERFSTAT) */
1063 #elif HAVE_SYSCTL && KERNEL_NETBSD
1064   int mib[3];
1065   size_t size, i, nndrive;
1066 
1067   /* figure out number of drives */
1068   mib[0] = CTL_HW;
1069   mib[1] = HW_IOSTATS;
1070   mib[2] = sizeof(struct io_sysctl);
1071   if (sysctl(mib, 3, NULL, &size, NULL, 0) == -1) {
1072     ERROR("disk plugin: sysctl for ndrives failed");
1073     return -1;
1074   }
1075   nndrive = size / sizeof(struct io_sysctl);
1076 
1077   if (size == 0) {
1078     ERROR("disk plugin: no drives found");
1079     return -1;
1080   }
1081   /* number of drives changed, reallocate buffer */
1082   if (nndrive != ndrive) {
1083     drives = (struct io_sysctl *)realloc(drives, size);
1084     if (drives == NULL) {
1085       ERROR("disk plugin: memory allocation failure");
1086       return -1;
1087     }
1088     ndrive = nndrive;
1089   }
1090 
1091   /* get stats for all drives */
1092   mib[0] = CTL_HW;
1093   mib[1] = HW_IOSTATS;
1094   mib[2] = sizeof(struct io_sysctl);
1095   if (sysctl(mib, 3, drives, &size, NULL, 0) == -1) {
1096     ERROR("disk plugin: sysctl for drive stats failed");
1097     return -1;
1098   }
1099 
1100   for (i = 0; i < ndrive; i++) {
1101     if (drives[i].type != IOSTAT_DISK)
1102       continue;
1103     if (ignorelist_match(ignorelist, drives[i].name))
1104       continue;
1105 
1106     disk_submit(drives[i].name, "disk_octets", drives[i].rbytes,
1107                 drives[i].wbytes);
1108     disk_submit(drives[i].name, "disk_ops", drives[i].rxfer, drives[i].wxfer);
1109     submit_io_time(drives[i].name,
1110                    drives[i].time_sec * 1000 + drives[i].time_usec / 1000, 0);
1111   }
1112 #endif /* HAVE_SYSCTL && KERNEL_NETBSD */
1113 
1114   return 0;
1115 } /* int disk_read */
1116 
module_register(void)1117 void module_register(void) {
1118   plugin_register_config("disk", disk_config, config_keys, config_keys_num);
1119   plugin_register_init("disk", disk_init);
1120   plugin_register_shutdown("disk", disk_shutdown);
1121   plugin_register_read("disk", disk_read);
1122 } /* void module_register */
1123