@cryptotaxi247 / netdata-1 / commits / d4e069f72

Fix ZFS bugs (diskspace.plugin) (#22188)

thiagoftsm committed Apr 13, 2026 at 02:08 UTC d4e069f7298014600c1131ef0fc137a935cb5e29
1 file changed +40 -66
src/collectors/diskspace.plugin/plugin_diskspace.c
+40 -66
@@ -162,7 +162,7 @@ static struct basic_mountinfo *basic_mountinfo_create_and_copy(struct mountinfo*
162 bmi->mount_point_stat_path = strdupz(mi->mount_point_stat_path);
163 bmi->mount_point = strdupz(mi->mount_point);
164 bmi->mount_source = mi->mount_source ? strdupz(mi->mount_source) : NULL;
165 - bmi->filesystem = strdupz(mi->filesystem);
165 + bmi->filesystem = mi->filesystem ? strdupz(mi->filesystem) : NULL;
166 }
167
168 return bmi;
@@ -351,76 +351,44 @@ static const char *extract_zfs_pool_name(const char *mount_source, char *buf, si
351 return buf;
352 }
353
354 -static inline bool is_zfs_filesystem(struct mountinfo *mi) {
355 - return mi && mi->filesystem && strcmp(mi->filesystem, "zfs") == 0;
356 -}
357 -
358 -// Check if a ZFS filesystem entry is a dataset (not a pool)
359 -// Dataset = has '/' in mount_source (e.g., "tank/home")
360 -// Pool = no '/' in mount_source (e.g., "tank")
361 -static inline bool is_zfs_dataset(struct mountinfo *mi) {
362 - return is_zfs_filesystem(mi) &&
363 - mi->mount_source &&
364 - mi->mount_source[0] &&
365 - strchr(mi->mount_source, '/') != NULL;
366 -}
367 -
368 -// Cached LXC detection result (checked once at first call, can't change at runtime)
354 +// LXC detection result – set once at startup in diskspace_main, never changes at runtime
355 static bool zfs_inside_lxc_container = false;
356
371 -// Collect ZFS pool capacities for the heuristic
372 -// Called on every collection cycle but only updates pools (quick operation)
373 -static void zfs_collect_pool_capacities(void) {
374 - // LXC detection - only once (can't change at runtime)
375 - static bool lxc_checked = false;
376 - if (!lxc_checked) {
377 - zfs_inside_lxc_container = is_lxcfs_proc_mounted();
378 - lxc_checked = true;
379 - }
380 -
357 +// Cache the capacity of a ZFS pool mount into zfs_cache.
358 +// Called from do_disk_space_stats / do_slow_disk_space_stats after their existing statvfs()
359 +// succeeds, so no extra blocking call is introduced.
360 +static void zfs_cache_pool_capacity(const char *filesystem, const char *mount_source,
361 + const struct statvfs *buff)
362 +{
363 if (!zfs_datasets_heuristic || !zfs_cache)
364 return;
365 + if (!filesystem || strcmp(filesystem, "zfs") != 0)
366 + return;
367 + // Only pool mounts – datasets have '/' in mount_source
368 + if (!mount_source || !mount_source[0] || strchr(mount_source, '/'))
369 + return;
370
371 + // Skip if the cached entry is still fresh
372 time_t now = now_realtime_sec();
373 + const DICTIONARY_ITEM *existing = dictionary_get_and_acquire_item(zfs_cache, mount_source);
374 + if (existing) {
375 + struct zfs_cache_entry *entry = dictionary_acquired_item_value(existing);
376 + bool fresh = entry->is_pool && (now - entry->last_checked < ZFS_DATASET_RECHECK_SECONDS);
377 + dictionary_acquired_item_release(zfs_cache, existing);
378 + if (fresh)
379 + return;
380 + }
381
386 - for (struct mountinfo *mi = disk_mountinfo_root; mi; mi = mi->next) {
387 - if (!is_zfs_filesystem(mi))
388 - continue;
389 -
390 - if (!mi->mount_source || !mi->mount_source[0])
391 - continue;
392 -
393 - // Only process pool mounts (no '/' in mount_source), not datasets
394 - if (strchr(mi->mount_source, '/'))
395 - continue;
396 -
397 - // Check if this pool entry needs refresh
398 - const DICTIONARY_ITEM *existing = dictionary_get_and_acquire_item(zfs_cache, mi->mount_source);
399 - if (existing) {
400 - struct zfs_cache_entry *entry = dictionary_acquired_item_value(existing);
401 - if (entry->is_pool && now - entry->last_checked < ZFS_DATASET_RECHECK_SECONDS) {
402 - dictionary_acquired_item_release(zfs_cache, existing);
403 - continue; // still fresh
404 - }
405 - dictionary_acquired_item_release(zfs_cache, existing);
406 - }
407 -
408 - // Get capacity from the pool mount
409 - struct statvfs buff;
410 - if (statvfs(mi->mount_point_stat_path, &buff) != 0)
411 - continue;
412 -
413 - unsigned long bsize = buff.f_frsize ? buff.f_frsize : buff.f_bsize;
414 - uint64_t capacity = (uint64_t)buff.f_blocks * bsize;
382 + unsigned long bsize = buff->f_frsize ? buff->f_frsize : buff->f_bsize;
383 + uint64_t capacity = (uint64_t)buff->f_blocks * bsize;
384
416 - struct zfs_cache_entry new_entry = {
417 - .last_checked = now,
418 - .is_pool = true,
419 - .pool_capacity = capacity,
420 - .excluded = false
421 - };
422 - dictionary_set(zfs_cache, mi->mount_source, &new_entry, sizeof(new_entry));
423 - }
385 + struct zfs_cache_entry new_entry = {
386 + .last_checked = now,
387 + .is_pool = true,
388 + .pool_capacity = capacity,
389 + .excluded = false
390 + };
391 + dictionary_set(zfs_cache, mount_source, &new_entry, sizeof(new_entry));
392 }
393
394 // Check if a ZFS dataset has a cached exclusion decision that's still valid
@@ -720,6 +688,9 @@ static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
688 if ((now_monotonic_high_precision_usec() - start_time) > slow_timeout)
689 m->slow = true;
690
691 + // Cache ZFS pool capacity so dataset exclusion heuristic can work next cycle
692 + zfs_cache_pool_capacity(mi->filesystem, mi->mount_source, &buff_statvfs);
693 +
694 // Check if this ZFS mount should be excluded (capacity-based heuristic)
695 if (should_exclude_zfs(mi->filesystem, mi->mount_point, mi->mount_source, &buff_statvfs))
696 goto cleanup;
@@ -760,6 +731,9 @@ static inline void do_slow_disk_space_stats(struct basic_mountinfo *mi, int upda
731 }
732 m->shown_error = false;
733
734 + // Cache ZFS pool capacity so dataset exclusion heuristic can work next cycle
735 + zfs_cache_pool_capacity(mi->filesystem, mi->mount_source, &buff_statvfs);
736 +
737 // Check if this ZFS mount should be excluded (same logic as fast path)
738 if (should_exclude_zfs(mi->filesystem, mi->mount_point, mi->mount_source, &buff_statvfs))
739 goto cleanup;
@@ -1151,6 +1125,9 @@ void diskspace_main(void *ptr) {
1125 diskspace_slow_worker,
1126 &slow_worker_data);
1127
1128 + // LXC detection – done once; virtualised mounts inside LXC bypass the ZFS exclusion heuristic
1129 + zfs_inside_lxc_container = is_lxcfs_proc_mounted();
1130 +
1131 heartbeat_t hb;
1132 heartbeat_init(&hb, update_every * USEC_PER_SEC);
1133 while(service_running(SERVICE_COLLECTORS)) {
@@ -1172,9 +1149,6 @@ void diskspace_main(void *ptr) {
1149 free_basic_mountinfo_list(slow_mountinfo_tmp_root);
1150 slow_mountinfo_tmp_root = NULL;
1151
1175 - // Collect ZFS pool capacities for the heuristic (Pass 1)
1176 - zfs_collect_pool_capacities();
1177 -
1152 struct mountinfo *mi;
1153 for(mi = disk_mountinfo_root; mi; mi = mi->next) {
1154 if(unlikely(mi->flags & (MOUNTINFO_IS_DUMMY | MOUNTINFO_IS_BIND)))