From edc9e626d1991fe186b98e75f8114745f630ceee Mon Sep 17 00:00:00 2001 From: Self-Perfection Date: Wed, 17 Dec 2025 18:49:10 +0000 Subject: [PATCH 1/4] munin: refactor munin_values to use usage_info dictionary Refactor the munin_values() function to store all calculated values in a usage_info dictionary before printing them. This change separates the calculation logic from the output logic, making the code easier to understand and preparing it for extraction into a separate function in a future commit. No functional changes are made to the output. --- examples/munin/plugins/btrfs_usage | 49 ++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/examples/munin/plugins/btrfs_usage b/examples/munin/plugins/btrfs_usage index 9c0d734..910afd5 100755 --- a/examples/munin/plugins/btrfs_usage +++ b/examples/munin/plugins/btrfs_usage @@ -107,32 +107,55 @@ def munin_values(fs): data_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_DATA].allocated metadata_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].used metadata_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].allocated - print("data_used.value {}".format(data_used)) - print("data_unused.value {}".format(data_allocated - data_used)) - print("metadata_used.value {}".format(metadata_used)) - print("metadata_unused.value {}".format(metadata_allocated - metadata_used)) left = left - data_allocated - metadata_allocated + usage_info = { + 'data_used': data_used, + 'data_allocated': data_allocated, + 'data_unused': data_allocated - data_used, + 'metadata_used': metadata_used, + 'metadata_allocated': metadata_allocated, + 'metadata_unused': metadata_allocated - metadata_used, + } else: mixed_type = btrfs.BLOCK_GROUP_DATA | btrfs.BLOCK_GROUP_METADATA used = usage.block_group_type_usage[mixed_type].used allocated = usage.block_group_type_usage[mixed_type].allocated - print("data_metadata_used.value {}".format(used)) - print("data_metadata_unused.value {}".format(allocated - used)) left -= allocated + usage_info = { + 'data_metadata_used': used, + 'data_metadata_unused': allocated-used, + } system_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].used + usage_info['system_used'] = system_used system_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].allocated - print("system_used.value {}".format(system_used)) - print("system_unused.value {}".format(system_allocated - system_used)) + usage_info['system_unused'] = system_allocated - system_used left -= system_allocated - print("parity.value {}".format(usage.parity)) + usage_info['parity'] = usage.parity left -= usage.parity - print("non_alloc_reclaimable.value {}".format(usage.unallocatable_reclaimable)) + usage_info['non_alloc_reclaimable'] = usage.unallocatable_reclaimable left -= usage.unallocatable_reclaimable - print("non_alloc.value {}".format(usage.unallocatable_hard)) + usage_info['non_alloc'] = usage.unallocatable_hard left = max(left - usage.unallocatable_hard, 0) - print("unallocated.value {}".format(left)) - print("total.value {}".format(usage.total)) + usage_info['unallocated'] = left + usage_info['total'] = usage.total + + if not fs.mixed_groups(): + print("data_used.value {}".format(usage_info['data_used'])) + print("data_unused.value {}".format(usage_info['data_unused'])) + print("metadata_used.value {}".format(usage_info['metadata_used'])) + print("metadata_unused.value {}".format(usage_info['metadata_unused'])) + else: + print("data_metadata_used.value {}".format(usage_info['data_metadata_used'])) + print("data_metadata_unused.value {}".format(usage_info['data_metadata_unused'])) + print("system_used.value {}".format(usage_info['system_used'])) + print("system_unused.value {}".format(usage_info['system_unused'])) + + print("parity.value {}".format(usage_info['parity'])) + print("non_alloc_reclaimable.value {}".format(usage_info['non_alloc_reclaimable'])) + print("non_alloc.value {}".format(usage_info['non_alloc'])) + print("unallocated.value {}".format(usage_info['unallocated'])) + print("total.value {}".format(usage_info['total'])) print("") From c52d026db754d9c2845dc7a11b0b583f1ff2c907 Mon Sep 17 00:00:00 2001 From: Self-Perfection Date: Wed, 17 Dec 2025 18:49:47 +0000 Subject: [PATCH 2/4] munin: extract calculation logic into calculate_munin_values function Extract the usage calculation logic from munin_values() into a new calculate_munin_values() function. This function encapsulates all the logic for calculating usage statistics and returns a dictionary with the computed values. The munin_values() function now simply calls calculate_munin_values() and uses the returned dictionary to print the values. This makes the code more modular and prepares it for use in other contexts, such as calculating alert thresholds. --- examples/munin/plugins/btrfs_usage | 96 ++++++++++++++++-------------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/examples/munin/plugins/btrfs_usage b/examples/munin/plugins/btrfs_usage index 910afd5..4d0801c 100755 --- a/examples/munin/plugins/btrfs_usage +++ b/examples/munin/plugins/btrfs_usage @@ -23,6 +23,56 @@ import os import sys +def calculate_munin_values(fs): + # Get detailed usage statistics. + usage = fs.usage() + + # Whatever happens, we should not stack the graph above this. IOW, the + # unallocated bytes we end up with is just whatever is left over after + # doing all other things. + left = usage.total + + if not fs.mixed_groups(): + data_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_DATA].used + data_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_DATA].allocated + metadata_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].used + metadata_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].allocated + left = left - data_allocated - metadata_allocated + usage_info = { + 'data_used': data_used, + 'data_allocated': data_allocated, + 'data_unused': data_allocated - data_used, + 'metadata_used': metadata_used, + 'metadata_allocated': metadata_allocated, + 'metadata_unused': metadata_allocated - metadata_used, + } + else: + mixed_type = btrfs.BLOCK_GROUP_DATA | btrfs.BLOCK_GROUP_METADATA + used = usage.block_group_type_usage[mixed_type].used + allocated = usage.block_group_type_usage[mixed_type].allocated + left -= allocated + usage_info = { + 'data_metadata_used': used, + 'data_metadata_unused': allocated-used, + } + system_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].used + usage_info['system_used'] = system_used + system_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].allocated + usage_info['system_unused'] = system_allocated - system_used + left -= system_allocated + + usage_info['parity'] = usage.parity + left -= usage.parity + usage_info['non_alloc_reclaimable'] = usage.unallocatable_reclaimable + left -= usage.unallocatable_reclaimable + usage_info['non_alloc'] = usage.unallocatable_hard + left = max(left - usage.unallocatable_hard, 0) + usage_info['unallocated'] = left + usage_info['total'] = usage.total + + return usage_info + + def munin_config(fs): print("multigraph btrfs_usage_{0}".format(str(fs.fsid).replace('-', '_'))) print("graph_args --base 1024 -l 0") @@ -94,51 +144,7 @@ def munin_config(fs): def munin_values(fs): print("multigraph btrfs_usage_{0}".format(str(fs.fsid).replace('-', '_'))) - # Get detailed usage statistics. - usage = fs.usage() - - # Whatever happens, we should not stack the graph above this. IOW, the - # unallocated bytes we end up with is just whatever is left over after - # doing all other things. - left = usage.total - - if not fs.mixed_groups(): - data_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_DATA].used - data_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_DATA].allocated - metadata_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].used - metadata_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_METADATA].allocated - left = left - data_allocated - metadata_allocated - usage_info = { - 'data_used': data_used, - 'data_allocated': data_allocated, - 'data_unused': data_allocated - data_used, - 'metadata_used': metadata_used, - 'metadata_allocated': metadata_allocated, - 'metadata_unused': metadata_allocated - metadata_used, - } - else: - mixed_type = btrfs.BLOCK_GROUP_DATA | btrfs.BLOCK_GROUP_METADATA - used = usage.block_group_type_usage[mixed_type].used - allocated = usage.block_group_type_usage[mixed_type].allocated - left -= allocated - usage_info = { - 'data_metadata_used': used, - 'data_metadata_unused': allocated-used, - } - system_used = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].used - usage_info['system_used'] = system_used - system_allocated = usage.block_group_type_usage[btrfs.BLOCK_GROUP_SYSTEM].allocated - usage_info['system_unused'] = system_allocated - system_used - left -= system_allocated - - usage_info['parity'] = usage.parity - left -= usage.parity - usage_info['non_alloc_reclaimable'] = usage.unallocatable_reclaimable - left -= usage.unallocatable_reclaimable - usage_info['non_alloc'] = usage.unallocatable_hard - left = max(left - usage.unallocatable_hard, 0) - usage_info['unallocated'] = left - usage_info['total'] = usage.total + usage_info = calculate_munin_values(fs) if not fs.mixed_groups(): print("data_used.value {}".format(usage_info['data_used'])) From 4147ac98f837144d8d85caba5d5010dd9f81db32 Mon Sep 17 00:00:00 2001 From: Self-Perfection Date: Wed, 17 Dec 2025 18:50:17 +0000 Subject: [PATCH 3/4] munin: use string templates instead of multiple prints Replace multiple individual print() calls in munin_values() with a single template string that is formatted with all values at once. This makes the code more concise and easier to maintain. No functional changes are made to the output. --- examples/munin/plugins/btrfs_usage | 33 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/examples/munin/plugins/btrfs_usage b/examples/munin/plugins/btrfs_usage index 4d0801c..c7c2cba 100755 --- a/examples/munin/plugins/btrfs_usage +++ b/examples/munin/plugins/btrfs_usage @@ -147,22 +147,25 @@ def munin_values(fs): usage_info = calculate_munin_values(fs) if not fs.mixed_groups(): - print("data_used.value {}".format(usage_info['data_used'])) - print("data_unused.value {}".format(usage_info['data_unused'])) - print("metadata_used.value {}".format(usage_info['metadata_used'])) - print("metadata_unused.value {}".format(usage_info['metadata_unused'])) + template = ( + "data_used.value {data_used}\n" + "data_unused.value {data_unused}\n" + "metadata_used.value {metadata_used}\n" + "metadata_unused.value {metadata_unused}\n") else: - print("data_metadata_used.value {}".format(usage_info['data_metadata_used'])) - print("data_metadata_unused.value {}".format(usage_info['data_metadata_unused'])) - print("system_used.value {}".format(usage_info['system_used'])) - print("system_unused.value {}".format(usage_info['system_unused'])) - - print("parity.value {}".format(usage_info['parity'])) - print("non_alloc_reclaimable.value {}".format(usage_info['non_alloc_reclaimable'])) - print("non_alloc.value {}".format(usage_info['non_alloc'])) - print("unallocated.value {}".format(usage_info['unallocated'])) - print("total.value {}".format(usage_info['total'])) - print("") + template = ( + "data_metadata_used.value {data_metadata_used}\n" + "data_metadata_unused.value {data_metadata_unused}\n") + + template += ( + "system_used.value {system_used}\n" + "system_unused.value {system_unused}\n" + "parity.value {parity}\n" + "non_alloc_reclaimable.value {non_alloc_reclaimable}\n" + "non_alloc.value {non_alloc}\n" + "unallocated.value {unallocated}\n" + "total.value {total}\n") + print(template.format(**usage_info)) def filter_env_mounts(mounts): From 3db0f402a4da1bb430c4ece4689a0c8174aaf5e9 Mon Sep 17 00:00:00 2001 From: Self-Perfection Date: Wed, 17 Dec 2025 18:51:09 +0000 Subject: [PATCH 4/4] munin: add metadata usage warning and critical levels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add warning and critical alert thresholds for metadata usage to help prevent out-of-space situations caused by metadata exhaustion. The thresholds are calculated based on: - Currently allocated metadata space - Available unallocated space that can be used for metadata (rounded down to 256MB chunks, the typical metadata allocation size) Warning is triggered when free metadata drops below 15% of allocated metadata minus available unallocated space. Critical is triggered at 10%. This addresses the issue where btrfs can run out of metadata space even when data space is available, which can cause filesystem problems. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- examples/munin/plugins/btrfs_usage | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/munin/plugins/btrfs_usage b/examples/munin/plugins/btrfs_usage index c7c2cba..cbd5b9f 100755 --- a/examples/munin/plugins/btrfs_usage +++ b/examples/munin/plugins/btrfs_usage @@ -21,6 +21,7 @@ import btrfs import os import sys +from math import floor def calculate_munin_values(fs): @@ -98,6 +99,14 @@ def munin_config(fs): print("metadata_unused.draw STACK") print("metadata_unused.info Unused Metadata") print("metadata_unused.colour 0000CC") + + usage_info = calculate_munin_values(fs) + #Assuming metadata is allocated by 256MB chunks + available_unallocated_for_metadata = floor(usage_info['unallocated'] / (2**28)) * (2**28) + metadata_warning = int((usage_info['metadata_allocated'] * 0.15) - available_unallocated_for_metadata) + metadata_critical = int((usage_info['metadata_allocated'] * 0.10) - available_unallocated_for_metadata) + print("metadata_unused.warning {}:".format(metadata_warning)) + print("metadata_unused.critical {}:".format(metadata_critical)) else: print("data_metadata_used.label Used Data+Metadata") print("data_metadata_used.draw AREA")