diff mbox series

[3/3] btrfs-progs: subvolume-list: output qgroup sizes for subvolumes

Message ID 1ef88bdfed7ea8e0e05a3518d74921782213fd16.1701160698.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs-progs: subvolume-list: add qgroup sizes output | expand

Commit Message

Qu Wenruo Nov. 28, 2023, 8:44 a.m. UTC
Inspired by ZFS `zpool list`, which would output the sizes of each
subvolume, we can do better for out "btrfs subvolume list" UI/UX.

This patch would introduce the auto-detection and auto-output using
qgroup sizes for subvolumes.

The output would look like this:

 # ./btrfs subv list -t /mnt/btrfs/
 ID	gen	top level	rfer	excl	path
 --	---	---------	----	----	----
 256	11	5		1064960	1064960	subvol1
 257	11	5		4210688	4210688	subvol2

Unfortunately there would be some pitfalls:

- No output for subvolume 5 (fs tree)
  As we do not output subvolume 5 (fs tree) at all, thus if the end user
  wants the size of fs tree, they would still be upset.

- If qgroup is not enabled, the sizes would be omitted
  This may lead to different outputs for different use cases and can
  lead to some confusion.

- Over simplified column name
  As a developer get too used to qgroup, I don't have any better names for
  the sizes right now.

- Table output can easily be screwed up by the larger sizes
  This requires some update to the table output to know the possible
  longest values to adjust.
  Which is definitely worthy some other patchsets to address.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 Documentation/btrfs-subvolume.rst |  12 ++-
 cmds/subvolume-list.c             | 140 +++++++++++++++++++++++++++++-
 2 files changed, 150 insertions(+), 2 deletions(-)
diff mbox series

Patch

diff --git a/Documentation/btrfs-subvolume.rst b/Documentation/btrfs-subvolume.rst
index eb116c4bdc95..8eaab5872d70 100644
--- a/Documentation/btrfs-subvolume.rst
+++ b/Documentation/btrfs-subvolume.rst
@@ -130,7 +130,7 @@  list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
 
         For every subvolume the following information is shown by default:
 
-        ID *ID* gen *generation* top level *parent_ID* path *path*
+        ID *ID* gen *generation* top level *parent_ID* [size (referenced) *rfer* size (exclusive) *excl*] path *path*
 
         where *ID* is subvolume's (root)id, *generation* is an internal counter which is
         updated every transaction, *parent_ID* is the same as the parent subvolume's id,
@@ -138,6 +138,11 @@  list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
         The subvolume's ID may be used by the subvolume set-default command,
         or at mount time via the *subvolid=* option.
 
+	The sizes of a subvolume will only be outputted if qgroup is enabled,
+	as that's the only way to get accurate size of a subvolume.
+	The referenced size is the total bytes of every extent referred by that subvolume.
+	While the exclusive size is total bytes that is exclusive to that subvolume.
+
         ``Options``
 
         Path filtering:
@@ -163,6 +168,11 @@  list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
         -q
                 print the parent UUID of the subvolume
                 (*parent* here means subvolume of which this subvolume is a snapshot).
+
+	-Q
+		always print the qgroup sizes of the subvolume
+		If qgroup is not enabled, a warning would be outputted and all
+		qgroup sizes would be zero.
         -R
                 print the UUID of the sent subvolume, where the subvolume is the result of a receive operation.
 
diff --git a/cmds/subvolume-list.c b/cmds/subvolume-list.c
index e05f7228b889..28826571ab97 100644
--- a/cmds/subvolume-list.c
+++ b/cmds/subvolume-list.c
@@ -66,6 +66,7 @@  static const char * const cmd_subvolume_list_usage[] = {
 	OPTLINE("-g", "print the generation of the subvolume"),
 	OPTLINE("-u", "print the uuid of subvolumes (and snapshots)"),
 	OPTLINE("-q", "print the parent uuid of the snapshots"),
+	OPTLINE("-Q", "always print the qgroup size of each subvolume (instead of auto detect)"),
 	OPTLINE("-R", "print the uuid of the received snapshots"),
 	"",
 	"Type filtering:",
@@ -131,6 +132,10 @@  struct root_attr {
 	/* creation time of this root in sec*/
 	time_t otime;
 
+	/* Qgroup accounting. */
+	u64 rfer;
+	u64 excl;
+
 	u8 uuid[BTRFS_UUID_SIZE];
 	u8 puuid[BTRFS_UUID_SIZE];
 	u8 ruuid[BTRFS_UUID_SIZE];
@@ -195,6 +200,8 @@  enum btrfs_list_column_enum {
 	BTRFS_LIST_PUUID,
 	BTRFS_LIST_RUUID,
 	BTRFS_LIST_UUID,
+	BTRFS_LIST_QGROUP_RFER,
+	BTRFS_LIST_QGROUP_EXCL,
 	BTRFS_LIST_PATH,
 	BTRFS_LIST_ALL,
 };
@@ -286,6 +293,16 @@  static struct {
 		.column_name	= "UUID",
 		.need_print	= 0,
 	},
+	{
+		.name		= "rfer",
+		.column_name	= "RFER",
+		.need_print	= 0,
+	},
+	{
+		.name		= "excl",
+		.column_name	= "EXCL",
+		.need_print	= 0,
+	},
 	{
 		.name		= "path",
 		.column_name	= "Path",
@@ -567,6 +584,10 @@  static void update_root_attr(struct root_attr *found,
 		found->ogen = new->root_offset;
 	if (new->otime)
 		found->otime = new->otime;
+	if (new->rfer)
+		found->rfer = new->rfer;
+	if (new->excl)
+		found->excl = new->excl;
 	if (new->uuid[0])
 		memcpy(&found->uuid, new->uuid, BTRFS_UUID_SIZE);
 	if (new->puuid[0])
@@ -574,6 +595,7 @@  static void update_root_attr(struct root_attr *found,
 	if (new->ruuid[0])
 		memcpy(&found->ruuid, new->ruuid, BTRFS_UUID_SIZE);
 
+
 }
 
 static int update_root(struct rb_root *root_lookup,
@@ -789,6 +811,88 @@  static int lookup_ino_path(int fd, struct root_info *ri)
 	return 0;
 }
 
+static int fill_qgroup_attrs(int fd, struct rb_root *root_lookup)
+{
+	struct btrfs_ioctl_search_args args = {
+		.key = {
+			.tree_id = BTRFS_QUOTA_TREE_OBJECTID,
+			.max_type = BTRFS_QGROUP_INFO_KEY,
+			.min_type = BTRFS_QGROUP_INFO_KEY,
+			.max_objectid = (u64)-1,
+			.max_offset = (u64)-1,
+			.max_transid = (u64)-1,
+			.nr_items = 4096,
+		},
+	};
+	struct btrfs_ioctl_search_key *sk = &args.key;
+	int ret;
+
+	while (1) {
+		unsigned long off = 0;
+
+		ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+		if (ret < 0) {
+			if (errno == ENOENT)
+				ret = -ENOTTY;
+			else
+				ret = -errno;
+			goto out;
+		}
+		/* the ioctl returns the number of item it found in nr_items */
+		if (sk->nr_items == 0)
+			goto out;
+
+		for (int i = 0; i < sk->nr_items; i++) {
+			struct root_attr attr = { 0 };
+			struct btrfs_ioctl_search_header *sh;
+			struct btrfs_qgroup_info_item *info;
+			struct btrfs_key key;
+
+			sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+			off += sizeof(*sh);
+
+			key.objectid = btrfs_search_header_objectid(sh);
+			key.type = btrfs_search_header_type(sh);
+			key.offset = btrfs_search_header_offset(sh);
+
+			/* Skip unrealted items and higher level qgroups. */
+			if (key.type != BTRFS_QGROUP_INFO_KEY ||
+			    btrfs_qgroup_level(key.offset))
+				goto next;
+
+			info = (struct btrfs_qgroup_info_item *)(args.buf + off);
+
+			attr.root_id = key.offset;
+			attr.rfer = btrfs_stack_qgroup_info_rfer(info);
+			attr.excl = btrfs_stack_qgroup_info_excl(info);
+			ret = add_root(root_lookup, &attr);
+			if (ret < 0)
+				break;
+next:
+			off += btrfs_search_header_len(sh);
+
+			/*
+			 * record the mins in sk so we can make sure the
+			 * next search doesn't repeat this root
+			 */
+			sk->min_type = key.type;
+			sk->min_offset = key.offset;
+			sk->min_objectid = key.objectid;
+		}
+		sk->nr_items = 4096;
+		/*
+		 * this iteration is done, step forward one qgroup for the next
+		 * ioctl
+		 */
+		if (sk->min_offset < (u64)-1)
+			sk->min_offset++;
+		else
+			break;
+	}
+out:
+	return ret;
+}
+
 static int list_subvol_search(int fd, struct rb_root *root_lookup)
 {
 	int ret;
@@ -1157,6 +1261,12 @@  static void print_subvolume_column(struct root_info *subv,
 			uuid_unparse(attrs->ruuid, uuidparse);
 		pr_verbose(LOG_DEFAULT, "%-36s", uuidparse);
 		break;
+	case BTRFS_LIST_QGROUP_RFER:
+		pr_verbose(LOG_DEFAULT, "%llu", attrs->rfer);
+		break;
+	case BTRFS_LIST_QGROUP_EXCL:
+		pr_verbose(LOG_DEFAULT, "%llu", attrs->excl);
+		break;
 	case BTRFS_LIST_PATH:
 		BUG_ON(!attrs->full_path);
 		pr_verbose(LOG_DEFAULT, "%s", attrs->full_path);
@@ -1280,6 +1390,12 @@  static void print_subvol_json_key(struct format_ctx *fctx,
 	case BTRFS_LIST_UUID:
 		fmt_print(fctx, column_name, attrs->uuid);
 		break;
+	case BTRFS_LIST_QGROUP_RFER:
+		fmt_print(fctx, column_name, attrs->rfer);
+		break;
+	case BTRFS_LIST_QGROUP_EXCL:
+		fmt_print(fctx, column_name, attrs->excl);
+		break;
 	case BTRFS_LIST_PUUID:
 		fmt_print(fctx, column_name, attrs->puuid);
 		break;
@@ -1363,6 +1479,7 @@  static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 {
 	int ret;
 	struct rb_node *n;
+	bool qgroup_enabled = false;
 
 	ret = list_subvol_search(fd, root_lookup);
 	if (ret) {
@@ -1370,6 +1487,17 @@  static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 		return ret;
 	}
 
+	ret = fill_qgroup_attrs(fd, root_lookup);
+	/*
+	 * Qgroup is not enabled but the user is trying to output qgroup info
+	 * explicitly, give a warning at least.
+	 */
+	if (ret < 0 && (btrfs_list_columns[BTRFS_LIST_QGROUP_RFER].need_print ||
+			btrfs_list_columns[BTRFS_LIST_QGROUP_EXCL].need_print)) {
+		errno = -ret;
+		warning("qgroup output is not available: %m");
+	}
+
 	/*
 	 * now we have an rbtree full of root_info objects, but we need to fill
 	 * in their path names within the subvol that is referencing each one.
@@ -1379,11 +1507,17 @@  static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 		struct root_info *entry;
 
 		entry = to_root_info(n);
+		if (entry->attrs.rfer && entry->attrs.excl)
+			qgroup_enabled = true;
 		ret = lookup_ino_path(fd, entry);
 		if (ret && ret != -ENOENT)
 			return ret;
 		n = rb_next(n);
 	}
+	if (qgroup_enabled) {
+		btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+		btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+	}
 
 	return 0;
 }
@@ -1568,7 +1702,7 @@  static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
 		};
 
 		c = getopt_long(argc, argv,
-				    "acdgopqsurRG:C:t", long_options, NULL);
+				    "acdgopqQsurRG:C:t", long_options, NULL);
 		if (c < 0)
 			break;
 
@@ -1609,6 +1743,10 @@  static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
 		case 'q':
 			btrfs_list_setup_print_column(BTRFS_LIST_PUUID);
 			break;
+		case 'Q':
+			btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+			btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+			break;
 		case 'R':
 			btrfs_list_setup_print_column(BTRFS_LIST_RUUID);
 			break;