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 **/
24 #include "collectd.h"
26 #include "common.h"
27 #include "plugin.h"
28 #include "utils_ignorelist.h"
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
62 #if HAVE_LIMITS_H
63 #include <limits.h>
64 #endif
65 #ifndef UINT_MAX
66 #define UINT_MAX 4294967295U
67 #endif
69 #if HAVE_STATGRAB_H
70 #include <statgrab.h>
71 #endif
73 #if HAVE_PERFSTAT
74 #ifndef _AIXVERSION_610
75 #include <sys/systemcfg.h>
76 #endif
77 #include <libperfstat.h>
78 #include <sys/protosw.h>
79 #endif
81 #if HAVE_IOKIT_IOKITLIB_H
82 static mach_port_t io_master_port = MACH_PORT_NULL;
83 /* This defaults to false for backwards compatibility. Please fix in the next
84 * major version. */
85 static _Bool use_bsd_name = 0;
86 /* #endif HAVE_IOKIT_IOKITLIB_H */
88 #elif KERNEL_LINUX
89 typedef struct diskstats {
90 char *name;
92 /* This overflows in roughly 1361 years */
93 unsigned int poll_count;
95 derive_t read_sectors;
96 derive_t write_sectors;
98 derive_t read_bytes;
99 derive_t write_bytes;
101 derive_t read_ops;
102 derive_t write_ops;
103 derive_t read_time;
104 derive_t write_time;
106 derive_t avg_read_time;
107 derive_t avg_write_time;
109 _Bool has_merged;
110 _Bool has_in_progress;
111 _Bool has_io_time;
113 struct diskstats *next;
114 } diskstats_t;
116 static diskstats_t *disklist;
117 /* #endif KERNEL_LINUX */
118 #elif KERNEL_FREEBSD
119 static struct gmesh geom_tree;
120 /* #endif KERNEL_FREEBSD */
122 #elif HAVE_LIBKSTAT
123 #define MAX_NUMDISK 1024
124 extern kstat_ctl_t *kc;
125 static kstat_t *ksp[MAX_NUMDISK];
126 static int numdisk = 0;
127 /* #endif HAVE_LIBKSTAT */
129 #elif defined(HAVE_LIBSTATGRAB)
130 /* #endif HAVE_LIBKSTATGRAB */
132 #elif HAVE_PERFSTAT
133 static perfstat_disk_t *stat_disk;
134 static int numdisk;
135 static int pnumdisk;
136 /* #endif HAVE_PERFSTAT */
138 #else
139 #error "No applicable input method."
140 #endif
142 #if HAVE_LIBUDEV
143 #include <libudev.h>
145 static char *conf_udev_name_attr = NULL;
146 static struct udev *handle_udev;
147 #endif
149 static const char *config_keys[] = {"Disk", "UseBSDName", "IgnoreSelected",
150 "UdevNameAttr"};
151 static int config_keys_num = STATIC_ARRAY_SIZE(config_keys);
153 static ignorelist_t *ignorelist = NULL;
155 static int disk_config(const char *key, const char *value) {
156 if (ignorelist == NULL)
157 ignorelist = ignorelist_create(/* invert = */ 1);
158 if (ignorelist == NULL)
159 return (1);
161 if (strcasecmp("Disk", key) == 0) {
162 ignorelist_add(ignorelist, value);
163 } else if (strcasecmp("IgnoreSelected", key) == 0) {
164 int invert = 1;
165 if (IS_TRUE(value))
166 invert = 0;
167 ignorelist_set_invert(ignorelist, invert);
168 } else if (strcasecmp("UseBSDName", key) == 0) {
169 #if HAVE_IOKIT_IOKITLIB_H
170 use_bsd_name = IS_TRUE(value) ? 1 : 0;
171 #else
172 WARNING("disk plugin: The \"UseBSDName\" option is only supported "
173 "on Mach / Mac OS X and will be ignored.");
174 #endif
175 } else if (strcasecmp("UdevNameAttr", key) == 0) {
176 #if HAVE_LIBUDEV
177 if (conf_udev_name_attr != NULL) {
178 free(conf_udev_name_attr);
179 conf_udev_name_attr = NULL;
180 }
181 if ((conf_udev_name_attr = strdup(value)) == NULL)
182 return (1);
183 #else
184 WARNING("disk plugin: The \"UdevNameAttr\" option is only supported "
185 "if collectd is built with libudev support");
186 #endif
187 } else {
188 return (-1);
189 }
191 return (0);
192 } /* int disk_config */
194 static int disk_init(void) {
195 #if HAVE_IOKIT_IOKITLIB_H
196 kern_return_t status;
198 if (io_master_port != MACH_PORT_NULL) {
199 mach_port_deallocate(mach_task_self(), io_master_port);
200 io_master_port = MACH_PORT_NULL;
201 }
203 status = IOMasterPort(MACH_PORT_NULL, &io_master_port);
204 if (status != kIOReturnSuccess) {
205 ERROR("IOMasterPort failed: %s", mach_error_string(status));
206 io_master_port = MACH_PORT_NULL;
207 return (-1);
208 }
209 /* #endif HAVE_IOKIT_IOKITLIB_H */
211 #elif KERNEL_LINUX
212 #if HAVE_LIBUDEV
213 if (conf_udev_name_attr != NULL) {
214 handle_udev = udev_new();
215 if (handle_udev == NULL) {
216 ERROR("disk plugin: udev_new() failed!");
217 return (-1);
218 }
219 }
220 #endif /* HAVE_LIBUDEV */
221 /* #endif KERNEL_LINUX */
223 #elif KERNEL_FREEBSD
224 int rv;
226 rv = geom_gettree(&geom_tree);
227 if (rv != 0) {
228 ERROR("geom_gettree() failed, returned %d", rv);
229 return (-1);
230 }
231 rv = geom_stats_open();
232 if (rv != 0) {
233 ERROR("geom_stats_open() failed, returned %d", rv);
234 return (-1);
235 }
236 /* #endif KERNEL_FREEBSD */
238 #elif HAVE_LIBKSTAT
239 kstat_t *ksp_chain;
241 numdisk = 0;
243 if (kc == NULL)
244 return (-1);
246 for (numdisk = 0, ksp_chain = kc->kc_chain;
247 (numdisk < MAX_NUMDISK) && (ksp_chain != NULL);
248 ksp_chain = ksp_chain->ks_next) {
249 if (strncmp(ksp_chain->ks_class, "disk", 4) &&
250 strncmp(ksp_chain->ks_class, "partition", 9))
251 continue;
252 if (ksp_chain->ks_type != KSTAT_TYPE_IO)
253 continue;
254 ksp[numdisk++] = ksp_chain;
255 }
256 #endif /* HAVE_LIBKSTAT */
258 return (0);
259 } /* int disk_init */
261 static int disk_shutdown(void) {
262 #if KERNEL_LINUX
263 #if HAVE_LIBUDEV
264 if (handle_udev != NULL)
265 udev_unref(handle_udev);
266 #endif /* HAVE_LIBUDEV */
267 #endif /* KERNEL_LINUX */
268 return (0);
269 } /* int disk_shutdown */
271 static void disk_submit(const char *plugin_instance, const char *type,
272 derive_t read, derive_t write) {
273 value_t values[2];
274 value_list_t vl = VALUE_LIST_INIT;
276 values[0].derive = read;
277 values[1].derive = write;
279 vl.values = values;
280 vl.values_len = 2;
281 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
282 sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
283 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
284 sstrncpy(vl.type, type, sizeof(vl.type));
286 plugin_dispatch_values(&vl);
287 } /* void disk_submit */
289 #if KERNEL_FREEBSD || KERNEL_LINUX
290 static void submit_io_time(char const *plugin_instance, derive_t io_time,
291 derive_t weighted_time) {
292 value_t values[2];
293 value_list_t vl = VALUE_LIST_INIT;
295 values[0].derive = io_time;
296 values[1].derive = weighted_time;
298 vl.values = values;
299 vl.values_len = 2;
300 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
301 sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
302 sstrncpy(vl.plugin_instance, plugin_instance, sizeof(vl.plugin_instance));
303 sstrncpy(vl.type, "disk_io_time", sizeof(vl.type));
305 plugin_dispatch_values(&vl);
306 } /* void submit_io_time */
307 #endif /* KERNEL_FREEBSD || KERNEL_LINUX */
309 #if KERNEL_LINUX
310 static void submit_in_progress(char const *disk_name, gauge_t in_progress) {
311 value_t v;
312 value_list_t vl = VALUE_LIST_INIT;
314 v.gauge = in_progress;
316 vl.values = &v;
317 vl.values_len = 1;
318 sstrncpy(vl.host, hostname_g, sizeof(vl.host));
319 sstrncpy(vl.plugin, "disk", sizeof(vl.plugin));
320 sstrncpy(vl.plugin_instance, disk_name, sizeof(vl.plugin_instance));
321 sstrncpy(vl.type, "pending_operations", sizeof(vl.type));
323 plugin_dispatch_values(&vl);
324 }
326 static counter_t disk_calc_time_incr(counter_t delta_time,
327 counter_t delta_ops) {
328 double interval = CDTIME_T_TO_DOUBLE(plugin_get_interval());
329 double avg_time = ((double)delta_time) / ((double)delta_ops);
330 double avg_time_incr = interval * avg_time;
332 return ((counter_t)(avg_time_incr + .5));
333 }
334 #endif
336 #if HAVE_LIBUDEV
337 /**
338 * Attempt to provide an rename disk instance from an assigned udev attribute.
339 *
340 * On success, it returns a strduped char* to the desired attribute value.
341 * Otherwise it returns NULL.
342 */
344 static char *disk_udev_attr_name(struct udev *udev, char *disk_name,
345 const char *attr) {
346 struct udev_device *dev;
347 const char *prop;
348 char *output = NULL;
350 dev = udev_device_new_from_subsystem_sysname(udev, "block", disk_name);
351 if (dev != NULL) {
352 prop = udev_device_get_property_value(dev, attr);
353 if (prop) {
354 output = strdup(prop);
355 DEBUG("disk plugin: renaming %s => %s", disk_name, output);
356 }
357 udev_device_unref(dev);
358 }
359 return output;
360 }
361 #endif
363 #if HAVE_IOKIT_IOKITLIB_H
364 static signed long long dict_get_value(CFDictionaryRef dict, const char *key) {
365 signed long long val_int;
366 CFNumberRef val_obj;
367 CFStringRef key_obj;
369 /* `key_obj' needs to be released. */
370 key_obj = CFStringCreateWithCString(kCFAllocatorDefault, key,
371 kCFStringEncodingASCII);
372 if (key_obj == NULL) {
373 DEBUG("CFStringCreateWithCString (%s) failed.", key);
374 return (-1LL);
375 }
377 /* get => we don't need to release (== free) the object */
378 val_obj = (CFNumberRef)CFDictionaryGetValue(dict, key_obj);
380 CFRelease(key_obj);
382 if (val_obj == NULL) {
383 DEBUG("CFDictionaryGetValue (%s) failed.", key);
384 return (-1LL);
385 }
387 if (!CFNumberGetValue(val_obj, kCFNumberSInt64Type, &val_int)) {
388 DEBUG("CFNumberGetValue (%s) failed.", key);
389 return (-1LL);
390 }
392 return (val_int);
393 }
394 #endif /* HAVE_IOKIT_IOKITLIB_H */
396 static int disk_read(void) {
397 #if HAVE_IOKIT_IOKITLIB_H
398 io_registry_entry_t disk;
399 io_registry_entry_t disk_child;
400 io_iterator_t disk_list;
401 CFMutableDictionaryRef props_dict, child_dict;
402 CFDictionaryRef stats_dict;
403 CFStringRef tmp_cf_string_ref;
404 kern_return_t status;
406 signed long long read_ops, read_byt, read_tme;
407 signed long long write_ops, write_byt, write_tme;
409 int disk_major, disk_minor;
410 char disk_name[DATA_MAX_NAME_LEN];
411 char child_disk_name_bsd[DATA_MAX_NAME_LEN],
412 props_disk_name_bsd[DATA_MAX_NAME_LEN];
414 /* Get the list of all disk objects. */
415 if (IOServiceGetMatchingServices(
416 io_master_port, IOServiceMatching(kIOBlockStorageDriverClass),
417 &disk_list) != kIOReturnSuccess) {
418 ERROR("disk plugin: IOServiceGetMatchingServices failed.");
419 return (-1);
420 }
422 while ((disk = IOIteratorNext(disk_list)) != 0) {
423 props_dict = NULL;
424 stats_dict = NULL;
425 child_dict = NULL;
427 /* get child of disk entry and corresponding property dictionary */
428 if ((status = IORegistryEntryGetChildEntry(
429 disk, kIOServicePlane, &disk_child)) != kIOReturnSuccess) {
430 /* This fails for example for DVD/CD drives, which we want to ignore
431 * anyway */
432 DEBUG("IORegistryEntryGetChildEntry (disk) failed: 0x%08x", status);
433 IOObjectRelease(disk);
434 continue;
435 }
436 if (IORegistryEntryCreateCFProperties(
437 disk_child, (CFMutableDictionaryRef *)&child_dict,
438 kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess ||
439 child_dict == NULL) {
440 ERROR("disk plugin: IORegistryEntryCreateCFProperties (disk_child) "
441 "failed.");
442 IOObjectRelease(disk_child);
443 IOObjectRelease(disk);
444 continue;
445 }
447 /* extract name and major/minor numbers */
448 memset(child_disk_name_bsd, 0, sizeof(child_disk_name_bsd));
449 tmp_cf_string_ref =
450 (CFStringRef)CFDictionaryGetValue(child_dict, CFSTR(kIOBSDNameKey));
451 if (tmp_cf_string_ref) {
452 assert(CFGetTypeID(tmp_cf_string_ref) == CFStringGetTypeID());
453 CFStringGetCString(tmp_cf_string_ref, child_disk_name_bsd,
454 sizeof(child_disk_name_bsd), kCFStringEncodingUTF8);
455 }
456 disk_major = (int)dict_get_value(child_dict, kIOBSDMajorKey);
457 disk_minor = (int)dict_get_value(child_dict, kIOBSDMinorKey);
458 DEBUG("disk plugin: child_disk_name_bsd=\"%s\" major=%d minor=%d",
459 child_disk_name_bsd, disk_major, disk_minor);
460 CFRelease(child_dict);
461 IOObjectRelease(disk_child);
463 /* get property dictionary of the disk entry itself */
464 if (IORegistryEntryCreateCFProperties(
465 disk, (CFMutableDictionaryRef *)&props_dict, kCFAllocatorDefault,
466 kNilOptions) != kIOReturnSuccess ||
467 props_dict == NULL) {
468 ERROR("disk-plugin: IORegistryEntryCreateCFProperties failed.");
469 IOObjectRelease(disk);
470 continue;
471 }
473 /* extract name and stats dictionary */
474 memset(props_disk_name_bsd, 0, sizeof(props_disk_name_bsd));
475 tmp_cf_string_ref =
476 (CFStringRef)CFDictionaryGetValue(props_dict, CFSTR(kIOBSDNameKey));
477 if (tmp_cf_string_ref) {
478 assert(CFGetTypeID(tmp_cf_string_ref) == CFStringGetTypeID());
479 CFStringGetCString(tmp_cf_string_ref, props_disk_name_bsd,
480 sizeof(props_disk_name_bsd), kCFStringEncodingUTF8);
481 }
482 stats_dict = (CFDictionaryRef)CFDictionaryGetValue(
483 props_dict, CFSTR(kIOBlockStorageDriverStatisticsKey));
484 if (stats_dict == NULL) {
485 ERROR("disk plugin: CFDictionaryGetValue (%s) failed.",
486 kIOBlockStorageDriverStatisticsKey);
487 CFRelease(props_dict);
488 IOObjectRelease(disk);
489 continue;
490 }
491 DEBUG("disk plugin: props_disk_name_bsd=\"%s\"", props_disk_name_bsd);
493 /* choose name */
494 if (use_bsd_name) {
495 if (child_disk_name_bsd[0] != 0)
496 sstrncpy(disk_name, child_disk_name_bsd, sizeof(disk_name));
497 else if (props_disk_name_bsd[0] != 0)
498 sstrncpy(disk_name, props_disk_name_bsd, sizeof(disk_name));
499 else {
500 ERROR("disk plugin: can't find bsd disk name.");
501 ssnprintf(disk_name, sizeof(disk_name), "%i-%i", disk_major,
502 disk_minor);
503 }
504 } else
505 ssnprintf(disk_name, sizeof(disk_name), "%i-%i", disk_major, disk_minor);
507 DEBUG("disk plugin: disk_name = \"%s\"", disk_name);
509 /* check the name against ignore list */
510 if (ignorelist_match(ignorelist, disk_name) != 0) {
511 CFRelease(props_dict);
512 IOObjectRelease(disk);
513 continue;
514 }
516 /* extract the stats */
517 read_ops =
518 dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsReadsKey);
519 read_byt =
520 dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsBytesReadKey);
521 read_tme = dict_get_value(stats_dict,
522 kIOBlockStorageDriverStatisticsTotalReadTimeKey);
523 write_ops =
524 dict_get_value(stats_dict, kIOBlockStorageDriverStatisticsWritesKey);
525 write_byt = dict_get_value(stats_dict,
526 kIOBlockStorageDriverStatisticsBytesWrittenKey);
527 write_tme = dict_get_value(
528 stats_dict, kIOBlockStorageDriverStatisticsTotalWriteTimeKey);
529 CFRelease(props_dict);
530 IOObjectRelease(disk);
532 /* and submit */
533 if ((read_byt != -1LL) || (write_byt != -1LL))
534 disk_submit(disk_name, "disk_octets", read_byt, write_byt);
535 if ((read_ops != -1LL) || (write_ops != -1LL))
536 disk_submit(disk_name, "disk_ops", read_ops, write_ops);
537 if ((read_tme != -1LL) || (write_tme != -1LL))
538 disk_submit(disk_name, "disk_time", read_tme / 1000, write_tme / 1000);
539 }
540 IOObjectRelease(disk_list);
541 /* #endif HAVE_IOKIT_IOKITLIB_H */
543 #elif KERNEL_FREEBSD
544 int retry, dirty;
546 void *snap = NULL;
547 struct devstat *snap_iter;
549 struct gident *geom_id;
551 const char *disk_name;
552 long double read_time, write_time, busy_time, total_duration;
554 for (retry = 0, dirty = 1; retry < 5 && dirty == 1; retry++) {
555 if (snap != NULL)
556 geom_stats_snapshot_free(snap);
558 /* Get a fresh copy of stats snapshot */
559 snap = geom_stats_snapshot_get();
560 if (snap == NULL) {
561 ERROR("disk plugin: geom_stats_snapshot_get() failed.");
562 return (-1);
563 }
565 /* Check if we have dirty read from this snapshot */
566 dirty = 0;
567 geom_stats_snapshot_reset(snap);
568 while ((snap_iter = geom_stats_snapshot_next(snap)) != NULL) {
569 if (snap_iter->id == NULL)
570 continue;
571 geom_id = geom_lookupid(&geom_tree, snap_iter->id);
573 /* New device? refresh GEOM tree */
574 if (geom_id == NULL) {
575 geom_deletetree(&geom_tree);
576 if (geom_gettree(&geom_tree) != 0) {
577 ERROR("disk plugin: geom_gettree() failed");
578 geom_stats_snapshot_free(snap);
579 return (-1);
580 }
581 geom_id = geom_lookupid(&geom_tree, snap_iter->id);
582 }
583 /*
584 * This should be rare: the device come right before we take the
585 * snapshot and went away right after it. We will handle this
586 * case later, so don't mark dirty but silently ignore it.
587 */
588 if (geom_id == NULL)
589 continue;
591 /* Only collect PROVIDER data */
592 if (geom_id->lg_what != ISPROVIDER)
593 continue;
595 /* Only collect data when rank is 1 (physical devices) */
596 if (((struct gprovider *)(geom_id->lg_ptr))->lg_geom->lg_rank != 1)
597 continue;
599 /* Check if this is a dirty read quit for another try */
600 if (snap_iter->sequence0 != snap_iter->sequence1) {
601 dirty = 1;
602 break;
603 }
604 }
605 }
607 /* Reset iterator */
608 geom_stats_snapshot_reset(snap);
609 for (;;) {
610 snap_iter = geom_stats_snapshot_next(snap);
611 if (snap_iter == NULL)
612 break;
614 if (snap_iter->id == NULL)
615 continue;
616 geom_id = geom_lookupid(&geom_tree, snap_iter->id);
617 if (geom_id == NULL)
618 continue;
619 if (geom_id->lg_what != ISPROVIDER)
620 continue;
621 if (((struct gprovider *)(geom_id->lg_ptr))->lg_geom->lg_rank != 1)
622 continue;
623 /* Skip dirty reads, if present */
624 if (dirty && (snap_iter->sequence0 != snap_iter->sequence1))
625 continue;
627 disk_name = ((struct gprovider *)geom_id->lg_ptr)->lg_name;
629 if (ignorelist_match(ignorelist, disk_name) != 0)
630 continue;
632 if ((snap_iter->bytes[DEVSTAT_READ] != 0) ||
633 (snap_iter->bytes[DEVSTAT_WRITE] != 0)) {
634 disk_submit(disk_name, "disk_octets",
635 (derive_t)snap_iter->bytes[DEVSTAT_READ],
636 (derive_t)snap_iter->bytes[DEVSTAT_WRITE]);
637 }
639 if ((snap_iter->operations[DEVSTAT_READ] != 0) ||
640 (snap_iter->operations[DEVSTAT_WRITE] != 0)) {
641 disk_submit(disk_name, "disk_ops",
642 (derive_t)snap_iter->operations[DEVSTAT_READ],
643 (derive_t)snap_iter->operations[DEVSTAT_WRITE]);
644 }
646 read_time = devstat_compute_etime(&snap_iter->duration[DEVSTAT_READ], NULL);
647 write_time =
648 devstat_compute_etime(&snap_iter->duration[DEVSTAT_WRITE], NULL);
649 if ((read_time != 0) || (write_time != 0)) {
650 disk_submit(disk_name, "disk_time", (derive_t)(read_time * 1000),
651 (derive_t)(write_time * 1000));
652 }
653 if (devstat_compute_statistics(snap_iter, NULL, 1.0, DSM_TOTAL_BUSY_TIME,
654 &busy_time, DSM_TOTAL_DURATION,
655 &total_duration, DSM_NONE) != 0) {
656 WARNING("%s", devstat_errbuf);
657 } else {
658 submit_io_time(disk_name, busy_time, total_duration);
659 }
660 }
661 geom_stats_snapshot_free(snap);
663 #elif KERNEL_LINUX
664 FILE *fh;
665 char buffer[1024];
667 char *fields[32];
668 int numfields;
669 int fieldshift = 0;
671 int minor = 0;
673 derive_t read_sectors = 0;
674 derive_t write_sectors = 0;
676 derive_t read_ops = 0;
677 derive_t read_merged = 0;
678 derive_t read_time = 0;
679 derive_t write_ops = 0;
680 derive_t write_merged = 0;
681 derive_t write_time = 0;
682 gauge_t in_progress = NAN;
683 derive_t io_time = 0;
684 derive_t weighted_time = 0;
685 int is_disk = 0;
687 diskstats_t *ds, *pre_ds;
689 if ((fh = fopen("/proc/diskstats", "r")) == NULL) {
690 fh = fopen("/proc/partitions", "r");
691 if (fh == NULL) {
692 ERROR("disk plugin: fopen (/proc/{diskstats,partitions}) failed.");
693 return (-1);
694 }
696 /* Kernel is 2.4.* */
697 fieldshift = 1;
698 }
700 while (fgets(buffer, sizeof(buffer), fh) != NULL) {
701 char *disk_name;
702 char *output_name;
704 numfields = strsplit(buffer, fields, 32);
706 if ((numfields != (14 + fieldshift)) && (numfields != 7))
707 continue;
709 minor = atoll(fields[1]);
711 disk_name = fields[2 + fieldshift];
713 for (ds = disklist, pre_ds = disklist; ds != NULL;
714 pre_ds = ds, ds = ds->next)
715 if (strcmp(disk_name, ds->name) == 0)
716 break;
718 if (ds == NULL) {
719 if ((ds = (diskstats_t *)calloc(1, sizeof(diskstats_t))) == NULL)
720 continue;
722 if ((ds->name = strdup(disk_name)) == NULL) {
723 free(ds);
724 continue;
725 }
727 if (pre_ds == NULL)
728 disklist = ds;
729 else
730 pre_ds->next = ds;
731 }
733 is_disk = 0;
734 if (numfields == 7) {
735 /* Kernel 2.6, Partition */
736 read_ops = atoll(fields[3]);
737 read_sectors = atoll(fields[4]);
738 write_ops = atoll(fields[5]);
739 write_sectors = atoll(fields[6]);
740 } else if (numfields == (14 + fieldshift)) {
741 read_ops = atoll(fields[3 + fieldshift]);
742 write_ops = atoll(fields[7 + fieldshift]);
744 read_sectors = atoll(fields[5 + fieldshift]);
745 write_sectors = atoll(fields[9 + fieldshift]);
747 if ((fieldshift == 0) || (minor == 0)) {
748 is_disk = 1;
749 read_merged = atoll(fields[4 + fieldshift]);
750 read_time = atoll(fields[6 + fieldshift]);
751 write_merged = atoll(fields[8 + fieldshift]);
752 write_time = atoll(fields[10 + fieldshift]);
754 in_progress = atof(fields[11 + fieldshift]);
756 io_time = atof(fields[12 + fieldshift]);
757 weighted_time = atof(fields[13 + fieldshift]);
758 }
759 } else {
760 DEBUG("numfields = %i; => unknown file format.", numfields);
761 continue;
762 }
764 {
765 derive_t diff_read_sectors;
766 derive_t diff_write_sectors;
768 /* If the counter wraps around, it's only 32 bits.. */
769 if (read_sectors < ds->read_sectors)
770 diff_read_sectors = 1 + read_sectors + (UINT_MAX - ds->read_sectors);
771 else
772 diff_read_sectors = read_sectors - ds->read_sectors;
773 if (write_sectors < ds->write_sectors)
774 diff_write_sectors = 1 + write_sectors + (UINT_MAX - ds->write_sectors);
775 else
776 diff_write_sectors = write_sectors - ds->write_sectors;
778 ds->read_bytes += 512 * diff_read_sectors;
779 ds->write_bytes += 512 * diff_write_sectors;
780 ds->read_sectors = read_sectors;
781 ds->write_sectors = write_sectors;
782 }
784 /* Calculate the average time an io-op needs to complete */
785 if (is_disk) {
786 derive_t diff_read_ops;
787 derive_t diff_write_ops;
788 derive_t diff_read_time;
789 derive_t diff_write_time;
791 if (read_ops < ds->read_ops)
792 diff_read_ops = 1 + read_ops + (UINT_MAX - ds->read_ops);
793 else
794 diff_read_ops = read_ops - ds->read_ops;
795 DEBUG("disk plugin: disk_name = %s; read_ops = %" PRIi64 "; "
796 "ds->read_ops = %" PRIi64 "; diff_read_ops = %" PRIi64 ";",
797 disk_name, read_ops, ds->read_ops, diff_read_ops);
799 if (write_ops < ds->write_ops)
800 diff_write_ops = 1 + write_ops + (UINT_MAX - ds->write_ops);
801 else
802 diff_write_ops = write_ops - ds->write_ops;
804 if (read_time < ds->read_time)
805 diff_read_time = 1 + read_time + (UINT_MAX - ds->read_time);
806 else
807 diff_read_time = read_time - ds->read_time;
809 if (write_time < ds->write_time)
810 diff_write_time = 1 + write_time + (UINT_MAX - ds->write_time);
811 else
812 diff_write_time = write_time - ds->write_time;
814 if (diff_read_ops != 0)
815 ds->avg_read_time += disk_calc_time_incr(diff_read_time, diff_read_ops);
816 if (diff_write_ops != 0)
817 ds->avg_write_time +=
818 disk_calc_time_incr(diff_write_time, diff_write_ops);
820 ds->read_ops = read_ops;
821 ds->read_time = read_time;
822 ds->write_ops = write_ops;
823 ds->write_time = write_time;
825 if (read_merged || write_merged)
826 ds->has_merged = 1;
828 if (in_progress)
829 ds->has_in_progress = 1;
831 if (io_time)
832 ds->has_io_time = 1;
834 } /* if (is_disk) */
836 /* Don't write to the RRDs if we've just started.. */
837 ds->poll_count++;
838 if (ds->poll_count <= 2) {
839 DEBUG("disk plugin: (ds->poll_count = %i) <= "
840 "(min_poll_count = 2); => Not writing.",
841 ds->poll_count);
842 continue;
843 }
845 if ((read_ops == 0) && (write_ops == 0)) {
846 DEBUG("disk plugin: ((read_ops == 0) && "
847 "(write_ops == 0)); => Not writing.");
848 continue;
849 }
851 output_name = disk_name;
853 #if HAVE_LIBUDEV
854 char *alt_name = NULL;
855 if (conf_udev_name_attr != NULL) {
856 alt_name =
857 disk_udev_attr_name(handle_udev, disk_name, conf_udev_name_attr);
858 if (alt_name != NULL)
859 output_name = alt_name;
860 }
861 #endif
863 if (ignorelist_match(ignorelist, output_name) != 0) {
864 #if HAVE_LIBUDEV
865 /* release udev-based alternate name, if allocated */
866 sfree(alt_name);
867 #endif
868 continue;
869 }
871 if ((ds->read_bytes != 0) || (ds->write_bytes != 0))
872 disk_submit(output_name, "disk_octets", ds->read_bytes, ds->write_bytes);
874 if ((ds->read_ops != 0) || (ds->write_ops != 0))
875 disk_submit(output_name, "disk_ops", read_ops, write_ops);
877 if ((ds->avg_read_time != 0) || (ds->avg_write_time != 0))
878 disk_submit(output_name, "disk_time", ds->avg_read_time,
879 ds->avg_write_time);
881 if (is_disk) {
882 if (ds->has_merged)
883 disk_submit(output_name, "disk_merged", read_merged, write_merged);
884 if (ds->has_in_progress)
885 submit_in_progress(output_name, in_progress);
886 if (ds->has_io_time)
887 submit_io_time(output_name, io_time, weighted_time);
888 } /* if (is_disk) */
890 #if HAVE_LIBUDEV
891 /* release udev-based alternate name, if allocated */
892 sfree(alt_name);
893 #endif
894 } /* while (fgets (buffer, sizeof (buffer), fh) != NULL) */
896 fclose(fh);
897 /* #endif defined(KERNEL_LINUX) */
899 #elif HAVE_LIBKSTAT
900 #if HAVE_KSTAT_IO_T_WRITES && HAVE_KSTAT_IO_T_NWRITES && HAVE_KSTAT_IO_T_WTIME
901 #define KIO_ROCTETS reads
902 #define KIO_WOCTETS writes
903 #define KIO_ROPS nreads
904 #define KIO_WOPS nwrites
905 #define KIO_RTIME rtime
906 #define KIO_WTIME wtime
907 #elif HAVE_KSTAT_IO_T_NWRITTEN && HAVE_KSTAT_IO_T_WRITES && \
908 HAVE_KSTAT_IO_T_WTIME
909 #define KIO_ROCTETS nread
910 #define KIO_WOCTETS nwritten
911 #define KIO_ROPS reads
912 #define KIO_WOPS writes
913 #define KIO_RTIME rtime
914 #define KIO_WTIME wtime
915 #else
916 #error "kstat_io_t does not have the required members"
917 #endif
918 static kstat_io_t kio;
920 if (kc == NULL)
921 return (-1);
923 for (int i = 0; i < numdisk; i++) {
924 if (kstat_read(kc, ksp[i], &kio) == -1)
925 continue;
927 if (strncmp(ksp[i]->ks_class, "disk", 4) == 0) {
928 if (ignorelist_match(ignorelist, ksp[i]->ks_name) != 0)
929 continue;
931 disk_submit(ksp[i]->ks_name, "disk_octets", kio.KIO_ROCTETS,
932 kio.KIO_WOCTETS);
933 disk_submit(ksp[i]->ks_name, "disk_ops", kio.KIO_ROPS, kio.KIO_WOPS);
934 /* FIXME: Convert this to microseconds if necessary */
935 disk_submit(ksp[i]->ks_name, "disk_time", kio.KIO_RTIME, kio.KIO_WTIME);
936 } else if (strncmp(ksp[i]->ks_class, "partition", 9) == 0) {
937 if (ignorelist_match(ignorelist, ksp[i]->ks_name) != 0)
938 continue;
940 disk_submit(ksp[i]->ks_name, "disk_octets", kio.KIO_ROCTETS,
941 kio.KIO_WOCTETS);
942 disk_submit(ksp[i]->ks_name, "disk_ops", kio.KIO_ROPS, kio.KIO_WOPS);
943 }
944 }
945 /* #endif defined(HAVE_LIBKSTAT) */
947 #elif defined(HAVE_LIBSTATGRAB)
948 sg_disk_io_stats *ds;
949 #if HAVE_LIBSTATGRAB_0_90
950 size_t disks;
951 #else
952 int disks;
953 #endif
954 char name[DATA_MAX_NAME_LEN];
956 if ((ds = sg_get_disk_io_stats(&disks)) == NULL)
957 return (0);
959 for (int counter = 0; counter < disks; counter++) {
960 strncpy(name, ds->disk_name, sizeof(name));
961 name[sizeof(name) - 1] =
962 '\0'; /* strncpy doesn't terminate longer strings */
964 if (ignorelist_match(ignorelist, name) != 0) {
965 ds++;
966 continue;
967 }
969 disk_submit(name, "disk_octets", ds->read_bytes, ds->write_bytes);
970 ds++;
971 }
972 /* #endif defined(HAVE_LIBSTATGRAB) */
974 #elif defined(HAVE_PERFSTAT)
975 derive_t read_sectors;
976 derive_t write_sectors;
977 derive_t read_time;
978 derive_t write_time;
979 derive_t read_ops;
980 derive_t write_ops;
981 perfstat_id_t firstpath;
982 int rnumdisk;
984 if ((numdisk = perfstat_disk(NULL, NULL, sizeof(perfstat_disk_t), 0)) < 0) {
985 char errbuf[1024];
986 WARNING("disk plugin: perfstat_disk: %s",
987 sstrerror(errno, errbuf, sizeof(errbuf)));
988 return (-1);
989 }
991 if (numdisk != pnumdisk || stat_disk == NULL) {
992 if (stat_disk != NULL)
993 free(stat_disk);
994 stat_disk = (perfstat_disk_t *)calloc(numdisk, sizeof(perfstat_disk_t));
995 }
996 pnumdisk = numdisk;
998 firstpath.name[0] = '\0';
999 if ((rnumdisk = perfstat_disk(&firstpath, stat_disk, sizeof(perfstat_disk_t),
1000 numdisk)) < 0) {
1001 char errbuf[1024];
1002 WARNING("disk plugin: perfstat_disk : %s",
1003 sstrerror(errno, errbuf, sizeof(errbuf)));
1004 return (-1);
1005 }
1007 for (int i = 0; i < rnumdisk; i++) {
1008 if (ignorelist_match(ignorelist, stat_disk[i].name) != 0)
1009 continue;
1011 read_sectors = stat_disk[i].rblks * stat_disk[i].bsize;
1012 write_sectors = stat_disk[i].wblks * stat_disk[i].bsize;
1013 disk_submit(stat_disk[i].name, "disk_octets", read_sectors, write_sectors);
1015 read_ops = stat_disk[i].xrate;
1016 write_ops = stat_disk[i].xfers - stat_disk[i].xrate;
1017 disk_submit(stat_disk[i].name, "disk_ops", read_ops, write_ops);
1019 read_time = stat_disk[i].rserv;
1020 read_time *= ((double)(_system_configuration.Xint) /
1021 (double)(_system_configuration.Xfrac)) /
1022 1000000.0;
1023 write_time = stat_disk[i].wserv;
1024 write_time *= ((double)(_system_configuration.Xint) /
1025 (double)(_system_configuration.Xfrac)) /
1026 1000000.0;
1027 disk_submit(stat_disk[i].name, "disk_time", read_time, write_time);
1028 }
1029 #endif /* defined(HAVE_PERFSTAT) */
1031 return (0);
1032 } /* int disk_read */
1034 void module_register(void) {
1035 plugin_register_config("disk", disk_config, config_keys, config_keys_num);
1036 plugin_register_init("disk", disk_init);
1037 plugin_register_shutdown("disk", disk_shutdown);
1038 plugin_register_read("disk", disk_read);
1039 } /* void module_register */