On Fri, Apr 4, 2014 at 3:52 PM, Konstantinos Skarlatos <k.skarla...@gmail.com> wrote: > On 4/4/2014 6:20 μμ, Filipe David Borba Manana wrote: >> >> This new send flag makes send calculate first the amount of new file data >> (in bytes) >> the send root has relatively to the parent root, or for the case of a >> non-incremental >> send, the total amount of file data we will send through the send stream. >> In other words, >> it computes the sum of the lengths of all write and clone operations that >> will be sent >> through the send stream. >> >> This data size value is sent in a new command, named >> BTRFS_SEND_C_TOTAL_DATA_SIZE, that >> immediately follows a BTRFS_SEND_C_SUBVOL or BTRFS_SEND_C_SNAPSHOT >> command, and precedes >> any command that changes a file or the filesystem hierarchy. Upon >> receiving a write or >> clone command, the receiving end can increment a counter by the data >> length of that >> command and therefore report progress by comparing the counter's value >> with the data size >> value received in the BTRFS_SEND_C_TOTAL_DATA_SIZE command. >> >> The approach is simple, before the normal operation of send, do a scan in >> the file system >> tree for new inodes and file extent items, just like in send's normal >> operation, and keep >> incrementing a counter with new inodes' size and the size of file extents >> that are going >> to be written or cloned. This is actually a simpler and more lightweight >> tree scan/processing >> than the one we do when sending the changes, as it doesn't process inode >> references nor does >> any lookups in the extent tree for example. >> >> After modifying btrfs-progs to understand this new command and report >> progress, here's an >> example (the -o flag tells btrfs send to pass the new flag to the kernel's >> send ioctl): >> >> $ btrfs send -o /mnt/sdd/base | btrfs receive /mnt/sdc >> At subvol /mnt/sdd/base >> At subvol base >> About to receive 9211507211 bytes >> Subvolume/snapshot /mnt/sdc//base, progress 24.73%, 2278015008 bytes >> received (9211507211 total bytes) >> >> $ btrfs send -o -p /mnt/sdd/base /mnt/sdd/incr | btrfs receive >> /mnt/sdc >> At subvol /mnt/sdd/incr >> At snapshot incr >> About to receive 9211747739 bytes >> Subvolume/snapshot /mnt/sdc//incr, progress 63.42%, 5843024211 bytes >> received (9211747739 total bytes) > > Hi, as a user of send i can say that this feature is very useful. Is it > possible to add current speed indication (MB/sec)?
Yes, it is. > > >> >> Signed-off-by: Filipe David Borba Manana <fdman...@gmail.com> >> --- >> fs/btrfs/send.c | 194 >> +++++++++++++++++++++++++++++++++++++-------- >> fs/btrfs/send.h | 1 + >> include/uapi/linux/btrfs.h | 13 ++- >> 3 files changed, 175 insertions(+), 33 deletions(-) >> >> diff --git a/fs/btrfs/send.c b/fs/btrfs/send.c >> index c81e0d9..fa378c7 100644 >> --- a/fs/btrfs/send.c >> +++ b/fs/btrfs/send.c >> @@ -81,7 +81,13 @@ struct clone_root { >> #define SEND_CTX_MAX_NAME_CACHE_SIZE 128 >> #define SEND_CTX_NAME_CACHE_CLEAN_SIZE (SEND_CTX_MAX_NAME_CACHE_SIZE * >> 2) >> +enum btrfs_send_phase { >> + SEND_PHASE_STREAM_CHANGES, >> + SEND_PHASE_COMPUTE_DATA_SIZE, >> +}; >> + >> struct send_ctx { >> + enum btrfs_send_phase phase; >> struct file *send_filp; >> loff_t send_off; >> char *send_buf; >> @@ -116,6 +122,7 @@ struct send_ctx { >> u64 cur_inode_last_extent; >> u64 send_progress; >> + u64 total_data_size; >> struct list_head new_refs; >> struct list_head deleted_refs; >> @@ -687,6 +694,8 @@ static int send_rename(struct send_ctx *sctx, >> { >> int ret; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_rename %s -> %s\n", from->start, to->start); >> ret = begin_cmd(sctx, BTRFS_SEND_C_RENAME); >> @@ -711,6 +720,8 @@ static int send_link(struct send_ctx *sctx, >> { >> int ret; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_link %s -> %s\n", path->start, lnk->start); >> ret = begin_cmd(sctx, BTRFS_SEND_C_LINK); >> @@ -734,6 +745,8 @@ static int send_unlink(struct send_ctx *sctx, struct >> fs_path *path) >> { >> int ret; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_unlink %s\n", path->start); >> ret = begin_cmd(sctx, BTRFS_SEND_C_UNLINK); >> @@ -756,6 +769,8 @@ static int send_rmdir(struct send_ctx *sctx, struct >> fs_path *path) >> { >> int ret; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_rmdir %s\n", path->start); >> ret = begin_cmd(sctx, BTRFS_SEND_C_RMDIR); >> @@ -2286,6 +2301,9 @@ static int send_truncate(struct send_ctx *sctx, u64 >> ino, u64 gen, u64 size) >> int ret = 0; >> struct fs_path *p; >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + return 0; >> + >> verbose_printk("btrfs: send_truncate %llu size=%llu\n", ino, size); >> p = fs_path_alloc(); >> @@ -2315,6 +2333,8 @@ static int send_chmod(struct send_ctx *sctx, u64 >> ino, u64 gen, u64 mode) >> int ret = 0; >> struct fs_path *p; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_chmod %llu mode=%llu\n", ino, mode); >> p = fs_path_alloc(); >> @@ -2344,6 +2364,8 @@ static int send_chown(struct send_ctx *sctx, u64 >> ino, u64 gen, u64 uid, u64 gid) >> int ret = 0; >> struct fs_path *p; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_chown %llu uid=%llu, gid=%llu\n", ino, uid, >> gid); >> p = fs_path_alloc(); >> @@ -2379,6 +2401,8 @@ static int send_utimes(struct send_ctx *sctx, u64 >> ino, u64 gen) >> struct btrfs_key key; >> int slot; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_utimes %llu\n", ino); >> p = fs_path_alloc(); >> @@ -2441,6 +2465,8 @@ static int send_create_inode(struct send_ctx *sctx, >> u64 ino) >> u64 mode; >> u64 rdev; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_create_inode %llu\n", ino); >> p = fs_path_alloc(); >> @@ -2588,6 +2614,8 @@ static int send_create_inode_if_needed(struct >> send_ctx *sctx) >> { >> int ret; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> if (S_ISDIR(sctx->cur_inode_mode)) { >> ret = did_create_dir(sctx, sctx->cur_ino); >> if (ret < 0) >> @@ -2693,6 +2721,8 @@ static int orphanize_inode(struct send_ctx *sctx, >> u64 ino, u64 gen, >> int ret; >> struct fs_path *orphan; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> orphan = fs_path_alloc(); >> if (!orphan) >> return -ENOMEM; >> @@ -3061,6 +3091,8 @@ static int apply_dir_move(struct send_ctx *sctx, >> struct pending_dir_move *pm) >> int ret; >> u64 ancestor = 0; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> name = fs_path_alloc(); >> from_path = fs_path_alloc(); >> if (!name || !from_path) { >> @@ -3315,6 +3347,9 @@ static int process_recorded_refs(struct send_ctx >> *sctx, int *pending_move) >> int is_orphan = 0; >> u64 last_dir_ino_rm = 0; >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + return 0; >> + >> verbose_printk("btrfs: process_recorded_refs %llu\n", sctx->cur_ino); >> /* >> @@ -3823,6 +3858,8 @@ static int process_all_refs(struct send_ctx *sctx, >> iterate_inode_ref_t cb; >> int pending_move = 0; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> path = alloc_path_for_send(); >> if (!path) >> return -ENOMEM; >> @@ -4142,6 +4179,8 @@ static int process_all_new_xattrs(struct send_ctx >> *sctx) >> struct extent_buffer *eb; >> int slot; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> path = alloc_path_for_send(); >> if (!path) >> return -ENOMEM; >> @@ -4272,6 +4311,8 @@ static int send_write(struct send_ctx *sctx, u64 >> offset, u32 len) >> struct fs_path *p; >> ssize_t num_read = 0; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> p = fs_path_alloc(); >> if (!p) >> return -ENOMEM; >> @@ -4307,6 +4348,22 @@ out: >> return num_read; >> } >> +static int send_total_data_size(struct send_ctx *sctx, u64 data_size) >> +{ >> + int ret; >> + >> + ret = begin_cmd(sctx, BTRFS_SEND_C_TOTAL_DATA_SIZE); >> + if (ret < 0) >> + goto out; >> + >> + TLV_PUT_U64(sctx, BTRFS_SEND_A_SIZE, data_size); >> + ret = send_cmd(sctx); >> + >> +tlv_put_failure: >> +out: >> + return ret; >> +} >> + >> /* >> * Send a clone command to user space. >> */ >> @@ -4318,6 +4375,8 @@ static int send_clone(struct send_ctx *sctx, >> struct fs_path *p; >> u64 gen; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> verbose_printk("btrfs: send_clone offset=%llu, len=%d, clone_root=%llu, >> " >> "clone_inode=%llu, clone_offset=%llu\n", offset, len, >> clone_root->root->objectid, clone_root->ino, >> @@ -4376,6 +4435,8 @@ static int send_update_extent(struct send_ctx *sctx, >> int ret = 0; >> struct fs_path *p; >> + ASSERT(sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE); >> + >> p = fs_path_alloc(); >> if (!p) >> return -ENOMEM; >> @@ -4407,6 +4468,11 @@ static int send_hole(struct send_ctx *sctx, u64 >> end) >> u64 len; >> int ret = 0; >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) { >> + sctx->total_data_size += end - offset; >> + return 0; >> + } >> + >> p = fs_path_alloc(); >> if (!p) >> return -ENOMEM; >> @@ -4470,6 +4536,12 @@ static int send_write_or_clone(struct send_ctx >> *sctx, >> goto out; >> } >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) { >> + if (offset < sctx->cur_inode_size) >> + sctx->total_data_size += len; >> + goto out; >> + } >> + >> if (clone_root && IS_ALIGNED(offset + len, bs)) { >> ret = send_clone(sctx, offset, len, clone_root); >> } else if (sctx->flags & BTRFS_SEND_FLAG_NO_FILE_DATA) { >> @@ -4803,10 +4875,12 @@ static int process_extent(struct send_ctx *sctx, >> } >> } >> - ret = find_extent_clone(sctx, path, key->objectid, key->offset, >> - sctx->cur_inode_size, &found_clone); >> - if (ret != -ENOENT && ret < 0) >> - goto out; >> + if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) { >> + ret = find_extent_clone(sctx, path, key->objectid, >> key->offset, >> + sctx->cur_inode_size, >> &found_clone); >> + if (ret != -ENOENT && ret < 0) >> + goto out; >> + } >> ret = send_write_or_clone(sctx, path, key, found_clone); >> if (ret) >> @@ -4936,6 +5010,9 @@ static int finish_inode_if_needed(struct send_ctx >> *sctx, int at_end) >> if (!at_end && sctx->cmp_key->objectid == sctx->cur_ino) >> goto out; >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + goto truncate_inode; >> + >> ret = get_inode_info(sctx->send_root, sctx->cur_ino, NULL, NULL, >> &left_mode, &left_uid, &left_gid, NULL); >> if (ret < 0) >> @@ -4958,6 +5035,7 @@ static int finish_inode_if_needed(struct send_ctx >> *sctx, int at_end) >> need_chmod = 1; >> } >> +truncate_inode: >> if (S_ISREG(sctx->cur_inode_mode)) { >> if (need_send_hole(sctx)) { >> if (sctx->cur_inode_last_extent == (u64)-1 || >> @@ -4997,7 +5075,8 @@ static int finish_inode_if_needed(struct send_ctx >> *sctx, int at_end) >> * If other directory inodes depended on our current directory >> * inode's move/rename, now do their move/rename operations. >> */ >> - if (!is_waiting_for_move(sctx, sctx->cur_ino)) { >> + if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE && >> + !is_waiting_for_move(sctx, sctx->cur_ino)) { >> ret = apply_children_dir_moves(sctx); >> if (ret) >> goto out; >> @@ -5081,7 +5160,8 @@ static int changed_inode(struct send_ctx *sctx, >> sctx->left_path->nodes[0], left_ii); >> sctx->cur_inode_rdev = btrfs_inode_rdev( >> sctx->left_path->nodes[0], left_ii); >> - if (sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID) >> + if (sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID && >> + sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) >> ret = send_create_inode_if_needed(sctx); >> } else if (result == BTRFS_COMPARE_TREE_DELETED) { >> sctx->cur_inode_gen = right_gen; >> @@ -5103,17 +5183,19 @@ static int changed_inode(struct send_ctx *sctx, >> /* >> * First, process the inode as if it was deleted. >> */ >> - sctx->cur_inode_gen = right_gen; >> - sctx->cur_inode_new = 0; >> - sctx->cur_inode_deleted = 1; >> - sctx->cur_inode_size = btrfs_inode_size( >> + if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) { >> + sctx->cur_inode_gen = right_gen; >> + sctx->cur_inode_new = 0; >> + sctx->cur_inode_deleted = 1; >> + sctx->cur_inode_size = btrfs_inode_size( >> sctx->right_path->nodes[0], >> right_ii); >> - sctx->cur_inode_mode = btrfs_inode_mode( >> + sctx->cur_inode_mode = btrfs_inode_mode( >> sctx->right_path->nodes[0], >> right_ii); >> - ret = process_all_refs(sctx, >> - BTRFS_COMPARE_TREE_DELETED); >> - if (ret < 0) >> - goto out; >> + ret = process_all_refs(sctx, >> + >> BTRFS_COMPARE_TREE_DELETED); >> + if (ret < 0) >> + goto out; >> + } >> /* >> * Now process the inode as if it was new. >> @@ -5127,29 +5209,38 @@ static int changed_inode(struct send_ctx *sctx, >> sctx->left_path->nodes[0], >> left_ii); >> sctx->cur_inode_rdev = btrfs_inode_rdev( >> sctx->left_path->nodes[0], >> left_ii); >> - ret = send_create_inode_if_needed(sctx); >> - if (ret < 0) >> - goto out; >> - >> - ret = process_all_refs(sctx, >> BTRFS_COMPARE_TREE_NEW); >> - if (ret < 0) >> - goto out; >> + if (sctx->phase != SEND_PHASE_COMPUTE_DATA_SIZE) { >> + ret = send_create_inode_if_needed(sctx); >> + if (ret < 0) >> + goto out; >> + ret = process_all_refs(sctx, >> + >> BTRFS_COMPARE_TREE_NEW); >> + if (ret < 0) >> + goto out; >> + } >> /* >> * Advance send_progress now as we did not get >> into >> * process_recorded_refs_if_needed in the new_gen >> case. >> */ >> sctx->send_progress = sctx->cur_ino + 1; >> - /* >> - * Now process all extents and xattrs of the inode >> as if >> - * they were all new. >> - */ >> - ret = process_all_extents(sctx); >> - if (ret < 0) >> - goto out; >> - ret = process_all_new_xattrs(sctx); >> - if (ret < 0) >> - goto out; >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) { >> + if (S_ISREG(sctx->cur_inode_mode)) >> + sctx->total_data_size += >> + sctx->cur_inode_size; >> + /* TODO: maybe account for xattrs one day >> too */ >> + } else { >> + /* >> + * Now process all extents and xattrs of >> the >> + * inode as if they were all new. >> + */ >> + ret = process_all_extents(sctx); >> + if (ret < 0) >> + goto out; >> + ret = process_all_new_xattrs(sctx); >> + if (ret < 0) >> + goto out; >> + } >> } else { >> sctx->cur_inode_gen = left_gen; >> sctx->cur_inode_new = 0; >> @@ -5183,6 +5274,9 @@ static int changed_ref(struct send_ctx *sctx, >> BUG_ON(sctx->cur_ino != sctx->cmp_key->objectid); >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + return 0; >> + >> if (!sctx->cur_inode_new_gen && >> sctx->cur_ino != BTRFS_FIRST_FREE_OBJECTID) { >> if (result == BTRFS_COMPARE_TREE_NEW) >> @@ -5208,6 +5302,9 @@ static int changed_xattr(struct send_ctx *sctx, >> BUG_ON(sctx->cur_ino != sctx->cmp_key->objectid); >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + return 0; >> + >> if (!sctx->cur_inode_new_gen && !sctx->cur_inode_deleted) { >> if (result == BTRFS_COMPARE_TREE_NEW) >> ret = process_new_xattr(sctx); >> @@ -5317,6 +5414,8 @@ static int changed_cb(struct btrfs_root *left_root, >> if (result == BTRFS_COMPARE_TREE_SAME) { >> if (key->type == BTRFS_INODE_REF_KEY || >> key->type == BTRFS_INODE_EXTREF_KEY) { >> + if (sctx->phase == SEND_PHASE_COMPUTE_DATA_SIZE) >> + return 0; >> ret = compare_refs(sctx, left_path, key); >> if (!ret) >> return 0; >> @@ -5468,6 +5567,24 @@ out: >> return ret; >> } >> +static int compute_total_data_size(struct send_ctx *sctx) >> +{ >> + int ret; >> + >> + sctx->total_data_size = 0; >> + >> + if (sctx->parent_root) { >> + ret = btrfs_compare_trees(sctx->send_root, >> sctx->parent_root, >> + changed_cb, sctx); >> + if (!ret) >> + ret = finish_inode_if_needed(sctx, 1); >> + } else { >> + ret = full_send_tree(sctx); >> + } >> + >> + return ret; >> +} >> + >> static int send_subvol(struct send_ctx *sctx) >> { >> int ret; >> @@ -5482,6 +5599,19 @@ static int send_subvol(struct send_ctx *sctx) >> if (ret < 0) >> goto out; >> + if (sctx->flags & BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE) { >> + sctx->phase = SEND_PHASE_COMPUTE_DATA_SIZE; >> + ret = compute_total_data_size(sctx); >> + if (ret < 0) >> + goto out; >> + ret = send_total_data_size(sctx, sctx->total_data_size); >> + if (ret < 0) >> + goto out; >> + sctx->phase = SEND_PHASE_STREAM_CHANGES; >> + sctx->cur_ino = 0; >> + sctx->send_progress = 0; >> + } >> + >> if (sctx->parent_root) { >> ret = btrfs_compare_trees(sctx->send_root, >> sctx->parent_root, >> changed_cb, sctx); >> diff --git a/fs/btrfs/send.h b/fs/btrfs/send.h >> index 48d425a..febeb72 100644 >> --- a/fs/btrfs/send.h >> +++ b/fs/btrfs/send.h >> @@ -87,6 +87,7 @@ enum btrfs_send_cmd { >> BTRFS_SEND_C_END, >> BTRFS_SEND_C_UPDATE_EXTENT, >> + BTRFS_SEND_C_TOTAL_DATA_SIZE, >> __BTRFS_SEND_C_MAX, >> }; >> #define BTRFS_SEND_C_MAX (__BTRFS_SEND_C_MAX - 1) >> diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h >> index b4d6909..afc1529 100644 >> --- a/include/uapi/linux/btrfs.h >> +++ b/include/uapi/linux/btrfs.h >> @@ -464,10 +464,21 @@ struct btrfs_ioctl_received_subvol_args { >> */ >> #define BTRFS_SEND_FLAG_OMIT_END_CMD 0x4 >> +/* >> + * Calculate the amount (in bytes) of new file data between the send and >> + * parent snapshots, or in case of a full send, the total amount of file >> data >> + * we will send. >> + * This corresponds to the sum of the data lengths of each write and >> clone >> + * commands that are sent through the send stream. The receiving end can >> use >> + * this information to compute progress. >> + */ >> +#define BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE 0x8 >> + >> #define BTRFS_SEND_FLAG_MASK \ >> (BTRFS_SEND_FLAG_NO_FILE_DATA | \ >> BTRFS_SEND_FLAG_OMIT_STREAM_HEADER | \ >> - BTRFS_SEND_FLAG_OMIT_END_CMD) >> + BTRFS_SEND_FLAG_OMIT_END_CMD | \ >> + BTRFS_SEND_FLAG_CALCULATE_DATA_SIZE) >> struct btrfs_ioctl_send_args { >> __s64 send_fd; /* in */ > > -- Filipe David Manana, "Reasonable men adapt themselves to the world. Unreasonable men adapt the world to themselves. That's why all progress depends on unreasonable men." -- To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html