#define DISK_TYPE_PARTITION 2
#define DISK_TYPE_CONTAINER 3
+#ifndef NETDATA_RELOAD_MOUNTINFO_EVERY
+#define NETDATA_RELOAD_MOUNTINFO_EVERY 10
+#endif
+
static struct disk {
char *disk; // the name of the disk (sda, sdb, etc)
unsigned long major;
unsigned long minor;
int sector_size;
int type;
+
char *mount_point;
+ uint32_t mount_point_hash;
// disk options caching
int configured;
struct disk *next;
} *disk_root = NULL;
-/* Return true if N is a known integer value. On many file systems,
- UINTMAX_MAX represents an unknown value; on AIX, UINTMAX_MAX - 1
- represents unknown. Use a rule that works on AIX file systems, and
- that almost-always works on other types. */
-static inline int known_value (uintmax_t n) { return n < UINTMAX_MAX - 1; }
+static struct mountinfo *disk_mountinfo_root = NULL;
+
+static inline void mountinfo_reload(int force) {
+ static time_t last_loaded = 0;
+ time_t now = time(NULL);
+
+ if(force || now - last_loaded >= NETDATA_RELOAD_MOUNTINFO_EVERY) {
+//#ifdef NETDATA_INTERNAL_CHECKS
+// info("Reloading mountinfo");
+//#endif
+
+ // mountinfo_free() can be called with NULL disk_mountinfo_root
+ mountinfo_free(disk_mountinfo_root);
+
+ // re-read mountinfo in case something changed
+ disk_mountinfo_root = mountinfo_read();
+
+ last_loaded = now;
+ }
+}
-static inline void disk_space_stats(struct disk *d, const char *disk, const char *family, int update_every, unsigned long long dt) {
+static inline void do_disk_space_stats(struct disk *d, const char *mount_point, const char *mount_source, const char *disk, const char *family, int update_every, unsigned long long dt) {
struct statvfs buff_statvfs;
- RRDSET *st;
+ if (statvfs(mount_point, &buff_statvfs) < 0) {
+ error("Failed statvfs() for '%s' (disk '%s')", mount_point, disk);
+ return;
+ }
- if (statvfs(d->mount_point, &buff_statvfs) < 0)
- error("Failed statvfs() for '%s' (disk '%s')", d->mount_point, d->disk);
- else {
- struct stat buff_stat;
+ int do_space, do_inodes;
+ if(d) {
// verify we collected the metrics for the right disk.
// if not the mountpoint has changed.
- if(stat(d->mount_point, &buff_stat) == -1)
- error("Failed to stat() for '%s' (disk '%s')", d->mount_point, d->disk);
- else {
- if(major(buff_stat.st_dev) == d->major && minor(buff_stat.st_dev) == d->minor) {
-
- // http://stackoverflow.com/a/4965511
- // taken from get_fs_usage() found in coreutils
- unsigned long bsize = (buff_statvfs.f_frsize) ? buff_statvfs.f_frsize : buff_statvfs.f_bsize;
-
- fsblkcnt_t bavail = buff_statvfs.f_bavail;
- fsblkcnt_t btotal = buff_statvfs.f_blocks;
- fsblkcnt_t bavail_root = buff_statvfs.f_bfree;
- fsblkcnt_t breserved_root = bavail_root - bavail;
- fsblkcnt_t bused;
- if(likely(btotal >= bavail_root))
- bused = btotal - bavail_root;
- else
- bused = bavail_root - btotal;
+ struct stat buff_stat;
+ if(stat(mount_point, &buff_stat) == -1) {
+ error("Failed to stat() for '%s' (disk '%s')", mount_point, disk);
+ return;
+ }
+ else if(major(buff_stat.st_dev) != d->major || minor(buff_stat.st_dev) != d->minor) {
+ error("Disk '%s' (disk '%s') switched major:minor", mount_point, disk);
+ freez(d->mount_point);
+ d->mount_point = NULL;
+ d->mount_point_hash = 0;
+ return;
+ }
+
+ do_space = d->do_space;
+ do_inodes = d->do_inodes;
+ }
+ else {
+ char var_name[4096 + 1];
+ snprintfz(var_name, 4096, "plugin:proc:/proc/diskstats:%s", mount_point);
+
+ int def_space = CONFIG_ONDEMAND_ONDEMAND;
+
+ // check the user configuration (this will also show our 'on demand' decision)
+ def_space = config_get_boolean_ondemand(var_name, "enable space metrics", def_space);
+
+ int ddo_space = def_space,
+ ddo_inodes = def_space;
+
+ do_space = config_get_boolean_ondemand(var_name, "space usage", ddo_space);
+ do_inodes = config_get_boolean_ondemand(var_name, "inodes usage", ddo_inodes);
+ }
+
+ // taken from get_fs_usage() found in coreutils
+ unsigned long bsize = (buff_statvfs.f_frsize) ? buff_statvfs.f_frsize : buff_statvfs.f_bsize;
+
+ fsblkcnt_t bavail = buff_statvfs.f_bavail;
+ fsblkcnt_t btotal = buff_statvfs.f_blocks;
+ fsblkcnt_t bavail_root = buff_statvfs.f_bfree;
+ fsblkcnt_t breserved_root = bavail_root - bavail;
+ fsblkcnt_t bused;
+ if(likely(btotal >= bavail_root))
+ bused = btotal - bavail_root;
+ else
+ bused = bavail_root - btotal;
#ifdef NETDATA_INTERNAL_CHECKS
- if(unlikely(btotal != bavail + breserved_root + bused))
- error("Disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", d->mount_point, d->disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
+ if(unlikely(btotal != bavail + breserved_root + bused))
+ error("Disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
#endif
- // --------------------------------------------------------------------------
+ // --------------------------------------------------------------------------
- fsfilcnt_t favail = buff_statvfs.f_favail;
- fsfilcnt_t ftotal = buff_statvfs.f_files;
- fsfilcnt_t favail_root = buff_statvfs.f_ffree;
- fsfilcnt_t freserved_root = favail_root - favail;
- fsfilcnt_t fused = ftotal - favail_root;
+ fsfilcnt_t favail = buff_statvfs.f_favail;
+ fsfilcnt_t ftotal = buff_statvfs.f_files;
+ fsfilcnt_t favail_root = buff_statvfs.f_ffree;
+ fsfilcnt_t freserved_root = favail_root - favail;
+ fsfilcnt_t fused = ftotal - favail_root;
#ifdef NETDATA_INTERNAL_CHECKS
- if(unlikely(btotal != bavail + breserved_root + bused))
- error("Disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", d->mount_point, d->disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
+ if(unlikely(btotal != bavail + breserved_root + bused))
+ error("Disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
#endif
- // --------------------------------------------------------------------------
-
- if(d->do_space == CONFIG_ONDEMAND_YES || (d->do_space == CONFIG_ONDEMAND_ONDEMAND && (bavail || breserved_root || bused))) {
- d->do_space = CONFIG_ONDEMAND_YES;
+ // --------------------------------------------------------------------------
- st = rrdset_find_bytype("disk_space", disk);
- if(!st) {
- st = rrdset_create("disk_space", disk, NULL, family, "disk.space", "Disk Space Usage", "GB", 2023, update_every, RRDSET_TYPE_STACKED);
- st->isdetail = 1;
+ RRDSET *st;
- rrddim_add(st, "avail", NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
- rrddim_add(st, "used" , NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
- rrddim_add(st, "reserved_for_root", "reserved for root", bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
- }
- else rrdset_next_usec(st, dt);
+ if(do_space == CONFIG_ONDEMAND_YES || (do_space == CONFIG_ONDEMAND_ONDEMAND && (bavail || breserved_root || bused))) {
+ st = rrdset_find_bytype("disk_space", disk);
+ if(!st) {
+ char title[4096 + 1];
+ snprintfz(title, 4096, "Disk Space Usage for %s [%s]", family, mount_source);
+ st = rrdset_create("disk_space", disk, NULL, family, "disk.space", title, "GB", 2023, update_every, RRDSET_TYPE_STACKED);
- rrddim_set(st, "avail", bavail);
- rrddim_set(st, "used", bused);
- rrddim_set(st, "reserved_for_root", breserved_root);
- rrdset_done(st);
- }
+ rrddim_add(st, "avail", NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "used" , NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "reserved_for_root", "reserved for root", bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+ }
+ else rrdset_next_usec(st, dt);
- // --------------------------------------------------------------------------
+ rrddim_set(st, "avail", bavail);
+ rrddim_set(st, "used", bused);
+ rrddim_set(st, "reserved_for_root", breserved_root);
+ rrdset_done(st);
+ }
- if(d->do_inodes == CONFIG_ONDEMAND_YES || (d->do_inodes == CONFIG_ONDEMAND_ONDEMAND && (favail || freserved_root || fused))) {
- st = rrdset_find_bytype("disk_inodes", disk);
- if(!st) {
- st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", "Disk Inodes Usage", "Inodes", 2024, update_every, RRDSET_TYPE_STACKED);
- st->isdetail = 1;
+ // --------------------------------------------------------------------------
- rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE);
- rrddim_add(st, "used" , NULL, 1, 1, RRDDIM_ABSOLUTE);
- rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE);
- }
- else rrdset_next_usec(st, dt);
+ if(do_inodes == CONFIG_ONDEMAND_YES || (do_inodes == CONFIG_ONDEMAND_ONDEMAND && (favail || freserved_root || fused))) {
+ st = rrdset_find_bytype("disk_inodes", disk);
+ if(!st) {
+ char title[4096 + 1];
+ snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", family, mount_source);
+ st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", title, "Inodes", 2024, update_every, RRDSET_TYPE_STACKED);
- rrddim_set(st, "avail", favail);
- rrddim_set(st, "used", fused);
- rrddim_set(st, "reserved_for_root", freserved_root);
- rrdset_done(st);
- }
- }
+ rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "used" , NULL, 1, 1, RRDDIM_ABSOLUTE);
+ rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE);
}
+ else rrdset_next_usec(st, dt);
+
+ rrddim_set(st, "avail", favail);
+ rrddim_set(st, "used", fused);
+ rrddim_set(st, "reserved_for_root", freserved_root);
+ rrdset_done(st);
}
}
-static struct mountinfo *disk_mountinfo_root = NULL;
-
static struct disk *get_disk(unsigned long major, unsigned long minor, char *disk) {
static char path_to_get_hw_sector_size[FILENAME_MAX + 1] = "";
static char path_to_get_hw_sector_size_partitions[FILENAME_MAX + 1] = "";
// mountinfo_find() can be called with NULL disk_mountinfo_root
struct mountinfo *mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
if(unlikely(!mi)) {
- // mountinfo_free() can be called with NULL disk_mountinfo_root
- mountinfo_free(disk_mountinfo_root);
-
- // re-read mountinfo in case something changed
- disk_mountinfo_root = mountinfo_read();
+ mountinfo_reload(1);
// search again for this disk
mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
}
- if(mi)
+ if(mi) {
d->mount_point = strdupz(mi->mount_point);
- // no need to check for NULL
- else
+ d->mount_point_hash = mi->mount_point_hash;
+ }
+ else {
d->mount_point = NULL;
+ d->mount_point_hash = 0;
+ }
// ------------------------------------------------------------------------
// find the disk sector size
uint32_t lines = procfile_lines(ff), l;
uint32_t words;
+ // this is smart enough not to reload it every time
+ mountinfo_reload(0);
+
for(l = 0; l < lines ;l++) {
// --------------------------------------------------------------------------
// Read parameters
}
}
+ /*
// --------------------------------------------------------------------------
// space metrics
if(d->mount_point && (d->do_space || d->do_inodes) ) {
- disk_space_stats(d, disk, family, update_every, dt);
+ do_disk_space_stats(d, d->mount_point, disk, disk, family, update_every, dt);
}
+ */
+ }
+
+ // --------------------------------------------------------------------------
+ // space metrics for non-block devices
+
+ struct mountinfo *mi;
+ for(mi = disk_mountinfo_root; mi ;mi = mi->next) {
+ if(unlikely(mi->flags & MOUNTINFO_IS_DUMMY || mi->flags & MOUNTINFO_IS_BIND || mi->flags & MOUNTINFO_IS_SAME_DEV || mi->flags & MOUNTINFO_NO_STAT || mi->flags & MOUNTINFO_NO_SIZE))
+ continue;
+
+ /*
+ // skip the ones with block devices
+ int skip = 0;
+ struct disk *d;
+ for(d = disk_root; d ;d = d->next) {
+ if(unlikely(d->mount_point && mi->mount_point_hash == d->mount_point_hash && strcmp(mi->mount_point, d->mount_point))) {
+ skip = 1;
+ break;
+ }
+ }
+
+ if(unlikely(skip))
+ continue;
+ */
+
+ // fprintf(stderr, "Will process mount point '%s', source '%s', filesystem '%s'\n", mi->mount_point, mi->mount_source, mi->filesystem);
+ do_disk_space_stats(NULL, mi->mount_point, mi->mount_source, mi->persistent_id, mi->mount_point , update_every, dt);
}
return 0;
#include "common.h"
+// ----------------------------------------------------------------------------
+// taken from gnulib/mountlist.c
+
+#ifndef ME_REMOTE
+/* A file system is "remote" if its Fs_name contains a ':'
+ or if (it is of type (smbfs or cifs) and its Fs_name starts with '//')
+ or Fs_name is equal to "-hosts" (used by autofs to mount remote fs). */
+# define ME_REMOTE(Fs_name, Fs_type) \
+ (strchr (Fs_name, ':') != NULL \
+ || ((Fs_name)[0] == '/' \
+ && (Fs_name)[1] == '/' \
+ && (strcmp (Fs_type, "smbfs") == 0 \
+ || strcmp (Fs_type, "cifs") == 0)) \
+ || (strcmp("-hosts", Fs_name) == 0))
+#endif
+
+#define ME_DUMMY_0(Fs_name, Fs_type) \
+ (strcmp (Fs_type, "autofs") == 0 \
+ || strcmp (Fs_type, "proc") == 0 \
+ || strcmp (Fs_type, "subfs") == 0 \
+ /* for Linux 2.6/3.x */ \
+ || strcmp (Fs_type, "debugfs") == 0 \
+ || strcmp (Fs_type, "devpts") == 0 \
+ || strcmp (Fs_type, "fusectl") == 0 \
+ || strcmp (Fs_type, "mqueue") == 0 \
+ || strcmp (Fs_type, "rpc_pipefs") == 0 \
+ || strcmp (Fs_type, "sysfs") == 0 \
+ /* FreeBSD, Linux 2.4 */ \
+ || strcmp (Fs_type, "devfs") == 0 \
+ /* for NetBSD 3.0 */ \
+ || strcmp (Fs_type, "kernfs") == 0 \
+ /* for Irix 6.5 */ \
+ || strcmp (Fs_type, "ignore") == 0)
+
+/* Historically, we have marked as "dummy" any file system of type "none",
+ but now that programs like du need to know about bind-mounted directories,
+ we grant an exception to any with "bind" in its list of mount options.
+ I.e., those are *not* dummy entries. */
+# define ME_DUMMY(Fs_name, Fs_type) \
+ (ME_DUMMY_0 (Fs_name, Fs_type) || strcmp (Fs_type, "none") == 0)
+
+// ----------------------------------------------------------------------------
+
// find the mount info with the given major:minor
// in the supplied linked list of mountinfo structures
struct mountinfo *mountinfo_find(struct mountinfo *root, unsigned long major, unsigned long minor) {
freez(mi->root);
freez(mi->mount_point);
freez(mi->mount_options);
+ freez(mi->persistent_id);
/*
if(mi->optional_fields_count) {
mi = mallocz(sizeof(struct mountinfo));
- if(unlikely(!root))
- root = last = mi;
- else
- last->next = mi;
-
- last = mi;
- mi->next = NULL;
-
unsigned long w = 0;
mi->id = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
mi->parentid = strtoul(procfile_lineword(ff, l, w), NULL, 10); w++;
if(!*minor) {
error("Cannot parse major:minor on '%s' at line %lu of '%s'", major, l + 1, filename);
+ freez(mi);
continue;
}
*minor = '\0';
minor++;
+ mi->flags = 0;
+
mi->major = strtoul(major, NULL, 10);
mi->minor = strtoul(minor, NULL, 10);
mi->mount_point = strdupz_decoding_octal(procfile_lineword(ff, l, w)); w++;
mi->mount_point_hash = simple_hash(mi->mount_point);
+ mi->persistent_id = strdupz(mi->mount_point);
+ netdata_fix_chart_id(mi->persistent_id);
+ mi->persistent_id_hash = simple_hash(mi->persistent_id);
+
mi->mount_options = strdupz(procfile_lineword(ff, l, w)); w++;
// count the optional fields
mi->mount_source_hash = simple_hash(mi->mount_source);
mi->super_options = strdupz(procfile_lineword(ff, l, w)); w++;
+
+ if(ME_DUMMY(mi->mount_source, mi->filesystem))
+ mi->flags |= MOUNTINFO_IS_DUMMY;
+
+ if(ME_REMOTE(mi->mount_source, mi->filesystem))
+ mi->flags |= MOUNTINFO_IS_REMOTE;
+
+ // mark as BIND the duplicates (i.e. same filesystem + same source)
+ {
+ struct stat buf;
+ if(unlikely(stat(mi->mount_point, &buf) == -1)) {
+ mi->st_dev = 0;
+ mi->flags |= MOUNTINFO_NO_STAT;
+ }
+ else {
+ mi->st_dev = buf.st_dev;
+
+ struct mountinfo *mt;
+ for(mt = root; mt; mt = mt->next) {
+ if(unlikely(mt->st_dev == mi->st_dev && !(mi->flags & MOUNTINFO_NO_STAT))) {
+ if(strlen(mi->mount_point) < strlen(mt->mount_point))
+ mt->flags |= MOUNTINFO_IS_SAME_DEV;
+ else
+ mi->flags |= MOUNTINFO_IS_SAME_DEV;
+ }
+ }
+ }
+ }
}
else {
mi->filesystem = NULL;
+ mi->filesystem_hash = 0;
+
mi->mount_source = NULL;
+ mi->mount_source_hash = 0;
+
mi->super_options = NULL;
+
+ mi->st_dev = 0;
+ }
+
+ // check if it has size
+ {
+ struct statvfs buff_statvfs;
+ if(statvfs(mi->mount_point, &buff_statvfs) < 0) {
+ mi->flags |= MOUNTINFO_NO_STAT;
+ }
+ else if(!buff_statvfs.f_blocks /* || !buff_statvfs.f_files */) {
+ mi->flags |= MOUNTINFO_NO_SIZE;
+ }
}
+ // link it
+ if(unlikely(!root))
+ root = mi;
+ else
+ last->next = mi;
+
+ last = mi;
+ mi->next = NULL;
+
/*
- info("MOUNTINFO: %u %u %u:%u root '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'",
+#ifdef NETDATA_INTERNAL_CHECKS
+ fprintf(stderr, "MOUNTINFO: %ld %ld %lu:%lu root '%s', persistent id '%s', mount point '%s', mount options '%s', filesystem '%s', mount source '%s', super options '%s'%s%s%s%s%s%s\n",
mi->id,
mi->parentid,
mi->major,
mi->minor,
mi->root,
- mi->mount_point,
- mi->mount_options,
- mi->filesystem,
- mi->mount_source,
- mi->super_options
+ mi->persistent_id,
+ (mi->mount_point)?mi->mount_point:"",
+ (mi->mount_options)?mi->mount_options:"",
+ (mi->filesystem)?mi->filesystem:"",
+ (mi->mount_source)?mi->mount_source:"",
+ (mi->super_options)?mi->super_options:"",
+ (mi->flags & MOUNTINFO_IS_DUMMY)?" DUMMY":"",
+ (mi->flags & MOUNTINFO_IS_BIND)?" BIND":"",
+ (mi->flags & MOUNTINFO_IS_REMOTE)?" REMOTE":"",
+ (mi->flags & MOUNTINFO_NO_STAT)?" NOSTAT":"",
+ (mi->flags & MOUNTINFO_NO_SIZE)?" NOSIZE":"",
+ (mi->flags & MOUNTINFO_IS_SAME_DEV)?" SAMEDEV":""
);
+#endif
*/
}
+/*
+ {
+ FILE *fp = setmntent(MOUNTED, "r");
+ if (fp != NULL) {
+ struct mntent mntbuf;
+ struct mntent *mnt;
+ char buf[4096 + 1];
+
+ while ((mnt = getmntent_r(fp, &mntbuf, buf, 4096))) {
+ char *bind = hasmntopt(mnt, "bind");
+ if(bind) {
+ struct mountinfo *mi;
+ for(mi = root; mi ; mi = mi->next) {
+ if(strcmp(mnt->mnt_dir, mi->mount_point) == 0) {
+ fprintf(stderr, "Mount point '%s' is BIND\n", mi->mount_point);
+ mi->flags |= MOUNTINFO_IS_BIND;
+ break;
+ }
+ }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ if(!mi) {
+ error("Mount point '%s' not found in /proc/self/mountinfo", mnt->mnt_dir);
+ }
+#endif
+ }
+ }
+ endmntent(fp);
+ }
+ }
+*/
+
procfile_close(ff);
return root;
}