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