| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "plugin_freebsd.h" |
| 4 | |
| 5 | #include <sys/mount.h> |
| 6 | |
| 7 | struct mount_point { |
| 8 | char *name; |
| 9 | uint32_t hash; |
| 10 | size_t len; |
| 11 | |
| 12 | // flags |
| 13 | int configured; |
| 14 | int enabled; |
| 15 | int updated; |
| 16 | |
| 17 | int do_space; |
| 18 | int do_inodes; |
| 19 | |
| 20 | size_t collected; // the number of times this has been collected |
| 21 | |
| 22 | // charts and dimensions |
| 23 | |
| 24 | RRDSET *st_space; |
| 25 | RRDDIM *rd_space_used; |
| 26 | RRDDIM *rd_space_avail; |
| 27 | RRDDIM *rd_space_reserved; |
| 28 | |
| 29 | RRDSET *st_inodes; |
| 30 | RRDDIM *rd_inodes_used; |
| 31 | RRDDIM *rd_inodes_avail; |
| 32 | |
| 33 | struct mount_point *next; |
| 34 | }; |
| 35 | |
| 36 | static struct mount_point *mount_points_root = NULL, *mount_points_last_used = NULL; |
| 37 | |
| 38 | static size_t mount_points_added = 0, mount_points_found = 0; |
| 39 | |
| 40 | static void mount_point_free(struct mount_point *m) { |
| 41 | if (likely(m->st_space)) |
| 42 | rrdset_is_obsolete___safe_from_collector_thread(m->st_space); |
| 43 | if (likely(m->st_inodes)) |
| 44 | rrdset_is_obsolete___safe_from_collector_thread(m->st_inodes); |
| 45 | |
| 46 | mount_points_added--; |
| 47 | freez(m->name); |
| 48 | freez(m); |
| 49 | } |
| 50 | |
| 51 | static void mount_points_cleanup() { |
| 52 | if (likely(mount_points_found == mount_points_added)) return; |
| 53 | |
| 54 | struct mount_point *m = mount_points_root, *last = NULL; |
| 55 | while(m) { |
| 56 | if (unlikely(!m->updated)) { |
| 57 | // collector_info("Removing mount point '%s', linked after '%s'", m->name, last?last->name:"ROOT"); |
| 58 | |
| 59 | if (mount_points_last_used == m) |
| 60 | mount_points_last_used = last; |
| 61 | |
| 62 | struct mount_point *t = m; |
| 63 | |
| 64 | if (m == mount_points_root || !last) |
| 65 | mount_points_root = m = m->next; |
| 66 | |
| 67 | else |
| 68 | last->next = m = m->next; |
| 69 | |
| 70 | t->next = NULL; |
| 71 | mount_point_free(t); |
| 72 | } |
| 73 | else { |
| 74 | last = m; |
| 75 | m->updated = 0; |
| 76 | m = m->next; |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | static struct mount_point *get_mount_point(const char *name) { |
| 82 | struct mount_point *m; |
| 83 | |
| 84 | uint32_t hash = simple_hash(name); |
| 85 | |
| 86 | // search it, from the last position to the end |
| 87 | for(m = mount_points_last_used ; m ; m = m->next) { |
| 88 | if (unlikely(hash == m->hash && !strcmp(name, m->name))) { |
| 89 | mount_points_last_used = m->next; |
| 90 | return m; |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // search it from the beginning to the last position we used |
| 95 | for(m = mount_points_root ; m != mount_points_last_used ; m = m->next) { |
| 96 | if (unlikely(hash == m->hash && !strcmp(name, m->name))) { |
| 97 | mount_points_last_used = m->next; |
| 98 | return m; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // create a new one |
| 103 | m = callocz(1, sizeof(struct mount_point)); |
| 104 | m->name = strdupz(name); |
| 105 | m->hash = simple_hash(m->name); |
| 106 | m->len = strlen(m->name); |
| 107 | mount_points_added++; |
| 108 | |
| 109 | // link it to the end |
| 110 | if (mount_points_root) { |
| 111 | struct mount_point *e; |
| 112 | for(e = mount_points_root; e->next ; e = e->next) ; |
| 113 | e->next = m; |
| 114 | } |
| 115 | else |
| 116 | mount_points_root = m; |
| 117 | |
| 118 | return m; |
| 119 | } |
| 120 | |
| 121 | // -------------------------------------------------------------------------------------------------------------------- |
| 122 | // getmntinfo |
| 123 | |
| 124 | int do_getmntinfo(int update_every, usec_t dt) { |
| 125 | (void)dt; |
| 126 | |
| 127 | #define DEFAULT_EXCLUDED_PATHS "/proc/*" |
| 128 | // taken from gnulib/mountlist.c and shortened to FreeBSD related fstypes |
| 129 | #define DEFAULT_EXCLUDED_FILESYSTEMS "autofs procfs subfs devfs none" |
| 130 | #define CONFIG_SECTION_GETMNTINFO "plugin:freebsd:getmntinfo" |
| 131 | |
| 132 | static int enable_new_mount_points = -1; |
| 133 | static int do_space = -1, do_inodes = -1; |
| 134 | static SIMPLE_PATTERN *excluded_mountpoints = NULL; |
| 135 | static SIMPLE_PATTERN *excluded_filesystems = NULL; |
| 136 | |
| 137 | if (unlikely(enable_new_mount_points == -1)) { |
| 138 | enable_new_mount_points = inicfg_get_boolean_ondemand(&netdata_config, CONFIG_SECTION_GETMNTINFO, |
| 139 | "enable new mount points detected at runtime", |
| 140 | CONFIG_BOOLEAN_AUTO); |
| 141 | |
| 142 | do_space = inicfg_get_boolean_ondemand(&netdata_config, CONFIG_SECTION_GETMNTINFO, "space usage for all disks", CONFIG_BOOLEAN_AUTO); |
| 143 | do_inodes = inicfg_get_boolean_ondemand(&netdata_config, CONFIG_SECTION_GETMNTINFO, "inodes usage for all disks", CONFIG_BOOLEAN_AUTO); |
| 144 | |
| 145 | excluded_mountpoints = simple_pattern_create( |
| 146 | inicfg_get(&netdata_config, CONFIG_SECTION_GETMNTINFO, "exclude space metrics on paths", DEFAULT_EXCLUDED_PATHS), |
| 147 | NULL, |
| 148 | SIMPLE_PATTERN_EXACT, |
| 149 | true); |
| 150 | |
| 151 | excluded_filesystems = simple_pattern_create( |
| 152 | inicfg_get(&netdata_config, CONFIG_SECTION_GETMNTINFO, "exclude space metrics on filesystems", DEFAULT_EXCLUDED_FILESYSTEMS), |
| 153 | NULL, |
| 154 | SIMPLE_PATTERN_EXACT, |
| 155 | true); |
| 156 | } |
| 157 | |
| 158 | if (likely(do_space || do_inodes)) { |
| 159 | struct statfs *mntbuf; |
| 160 | int mntsize; |
| 161 | |
| 162 | // there is no mount info in sysctl MIBs |
| 163 | if (unlikely(!(mntsize = getmntinfo(&mntbuf, MNT_NOWAIT)))) { |
| 164 | collector_error("FREEBSD: getmntinfo() failed"); |
| 165 | do_space = 0; |
| 166 | collector_error("DISABLED: disk_space.* charts"); |
| 167 | do_inodes = 0; |
| 168 | collector_error("DISABLED: disk_inodes.* charts"); |
| 169 | collector_error("DISABLED: getmntinfo module"); |
| 170 | return 1; |
| 171 | } else { |
| 172 | int i; |
| 173 | |
| 174 | mount_points_found = 0; |
| 175 | |
| 176 | for (i = 0; i < mntsize; i++) { |
| 177 | struct mount_point *m = get_mount_point(mntbuf[i].f_mntonname); |
| 178 | m->updated = 1; |
| 179 | mount_points_found++; |
| 180 | |
| 181 | if (unlikely(!m->configured)) { |
| 182 | char var_name[4096 + 1]; |
| 183 | |
| 184 | // this is the first time we see this filesystem |
| 185 | |
| 186 | // remember we configured it |
| 187 | m->configured = 1; |
| 188 | |
| 189 | m->enabled = enable_new_mount_points; |
| 190 | |
| 191 | if (likely(m->enabled)) |
| 192 | m->enabled = !(simple_pattern_matches(excluded_mountpoints, mntbuf[i].f_mntonname) |
| 193 | || simple_pattern_matches(excluded_filesystems, mntbuf[i].f_fstypename)); |
| 194 | |
| 195 | snprintfz(var_name, 4096, "%s:%s", CONFIG_SECTION_GETMNTINFO, mntbuf[i].f_mntonname); |
| 196 | m->enabled = inicfg_get_boolean_ondemand(&netdata_config, var_name, "enabled", m->enabled); |
| 197 | |
| 198 | if (unlikely(m->enabled == CONFIG_BOOLEAN_NO)) |
| 199 | continue; |
| 200 | |
| 201 | m->do_space = inicfg_get_boolean_ondemand(&netdata_config, var_name, "space usage", do_space); |
| 202 | m->do_inodes = inicfg_get_boolean_ondemand(&netdata_config, var_name, "inodes usage", do_inodes); |
| 203 | } |
| 204 | |
| 205 | if (unlikely(!m->enabled)) |
| 206 | continue; |
| 207 | |
| 208 | if (unlikely(mntbuf[i].f_flags & MNT_RDONLY && !m->collected)) |
| 209 | continue; |
| 210 | |
| 211 | int rendered = 0; |
| 212 | |
| 213 | if (m->do_space == CONFIG_BOOLEAN_YES || m->do_space == CONFIG_BOOLEAN_AUTO) { |
| 214 | if (unlikely(!m->st_space)) { |
| 215 | m->st_space = rrdset_create_localhost("disk_space", |
| 216 | mntbuf[i].f_mntonname, |
| 217 | NULL, |
| 218 | "used space", |
| 219 | "disk.space", |
| 220 | "Disk Space Usage", |
| 221 | "GiB", |
| 222 | "freebsd.plugin", |
| 223 | "getmntinfo", |
| 224 | NETDATA_CHART_PRIO_DISKSPACE_SPACE, |
| 225 | update_every, |
| 226 | RRDSET_TYPE_STACKED |
| 227 | ); |
| 228 | |
| 229 | m->rd_space_avail = rrddim_add(m->st_space, "avail", NULL, |
| 230 | mntbuf[i].f_bsize, GIGA_FACTOR, RRD_ALGORITHM_ABSOLUTE); |
| 231 | m->rd_space_used = rrddim_add(m->st_space, "used", NULL, |
| 232 | mntbuf[i].f_bsize, GIGA_FACTOR, RRD_ALGORITHM_ABSOLUTE); |
| 233 | m->rd_space_reserved = rrddim_add(m->st_space, "reserved_for_root", "reserved for root", |
| 234 | mntbuf[i].f_bsize, GIGA_FACTOR, RRD_ALGORITHM_ABSOLUTE); |
| 235 | rrdlabels_add(m->st_space->rrdlabels, "mount_point", mntbuf[i].f_mntonname, RRDLABEL_SRC_AUTO); |
| 236 | rrdlabels_add(m->st_space->rrdlabels, "filesystem", mntbuf[i].f_fstypename, RRDLABEL_SRC_AUTO); |
| 237 | } |
| 238 | |
| 239 | rrddim_set_by_pointer(m->st_space, m->rd_space_avail, (collected_number) mntbuf[i].f_bavail); |
| 240 | rrddim_set_by_pointer(m->st_space, m->rd_space_used, (collected_number) (mntbuf[i].f_blocks - |
| 241 | mntbuf[i].f_bfree)); |
| 242 | rrddim_set_by_pointer(m->st_space, m->rd_space_reserved, (collected_number) (mntbuf[i].f_bfree - |
| 243 | mntbuf[i].f_bavail)); |
| 244 | rrdset_done(m->st_space); |
| 245 | |
| 246 | rendered++; |
| 247 | } |
| 248 | |
| 249 | if (m->do_inodes == CONFIG_BOOLEAN_YES || m->do_inodes == CONFIG_BOOLEAN_AUTO) { |
| 250 | if (unlikely(!m->st_inodes)) { |
| 251 | m->st_inodes = rrdset_create_localhost("disk_inodes", |
| 252 | mntbuf[i].f_mntonname, |
| 253 | NULL, |
| 254 | "used inodes", |
| 255 | "disk.inodes", |
| 256 | "Disk Files (inodes) Usage", |
| 257 | "inodes", |
| 258 | "freebsd.plugin", |
| 259 | "getmntinfo", |
| 260 | NETDATA_CHART_PRIO_DISKSPACE_INODES, |
| 261 | update_every, |
| 262 | RRDSET_TYPE_STACKED |
| 263 | ); |
| 264 | |
| 265 | m->rd_inodes_avail = rrddim_add(m->st_inodes, "avail", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 266 | m->rd_inodes_used = rrddim_add(m->st_inodes, "used", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 267 | rrdlabels_add(m->st_inodes->rrdlabels, "mount_point", mntbuf[i].f_mntonname, RRDLABEL_SRC_AUTO); |
| 268 | rrdlabels_add(m->st_inodes->rrdlabels, "filesystem", mntbuf[i].f_fstypename, RRDLABEL_SRC_AUTO); |
| 269 | } |
| 270 | |
| 271 | rrddim_set_by_pointer(m->st_inodes, m->rd_inodes_avail, (collected_number) mntbuf[i].f_ffree); |
| 272 | rrddim_set_by_pointer(m->st_inodes, m->rd_inodes_used, (collected_number) (mntbuf[i].f_files - |
| 273 | mntbuf[i].f_ffree)); |
| 274 | rrdset_done(m->st_inodes); |
| 275 | |
| 276 | rendered++; |
| 277 | } |
| 278 | |
| 279 | if (likely(rendered)) |
| 280 | m->collected++; |
| 281 | } |
| 282 | } |
| 283 | } else { |
| 284 | collector_error("DISABLED: getmntinfo module"); |
| 285 | return 1; |
| 286 | } |
| 287 | |
| 288 | mount_points_cleanup(); |
| 289 | |
| 290 | return 0; |
| 291 | } |