![Filipe David Borba Manana](/assets/img/avatar_default.png)
This change adds infrastructure to allow for generic properties for inodes. Properties are name/value pairs that can be associated with inodes for different purposes. They are stored as xattrs with the prefix "btrfs." Properties can be inherited - this means when a directory inode has inheritable properties set, these are added to new inodes created under that directory. Further, subvolumes can also have properties associated with them, and they can be inherited from their parent subvolume. Naturally, directory properties have priority over subvolume properties (in practice a subvolume property is just a regular property associated with the root inode, objectid 256, of the subvolume's fs tree). This change also adds one specific property implementation, named "compression", whose values can be "lzo" or "zlib" and it's an inheritable property. The corresponding changes to btrfs-progs were also implemented. A patch with xfstests for this feature will follow once there's agreement on this change/feature. Further, the script at the bottom of this commit message was used to do some benchmarks to measure any performance penalties of this feature. Basically the tests correspond to: Test 1 - create a filesystem and mount it with compress-force=lzo, then sequentially create N files of 64Kb each, measure how long it took to create the files, unmount the filesystem, mount the filesystem and perform an 'ls -lha' against the test directory holding the N files, and report the time the command took. Test 2 - create a filesystem and don't use any compression option when mounting it - instead set the compression property of the subvolume's root to 'lzo'. Then create N files of 64Kb, and report the time it took. The unmount the filesystem, mount it again and perform an 'ls -lha' like in the former test. This means every single file ends up with a property (xattr) associated to it. Test 3 - same as test 2, but uses 4 properties - 3 are duplicates of the compression property, have no real effect other than adding more work when inheriting properties and taking more btree leaf space. Test 4 - same as test 3 but with 10 properties per file. Results (in seconds, and averages of 5 runs each), for different N numbers of files follow. * Without properties (test 1) file creation time ls -lha time 10 000 files 3.49 0.76 100 000 files 47.19 8.37 1 000 000 files 518.51 107.06 * With 1 property (compression property set to lzo - test 2) file creation time ls -lha time 10 000 files 3.63 0.93 100 000 files 48.56 9.74 1 000 000 files 537.72 125.11 * With 4 properties (test 3) file creation time ls -lha time 10 000 files 3.94 1.20 100 000 files 52.14 11.48 1 000 000 files 572.70 142.13 * With 10 properties (test 4) file creation time ls -lha time 10 000 files 4.61 1.35 100 000 files 58.86 13.83 1 000 000 files 656.01 177.61 The increased latencies with properties are essencialy because of: *) When creating an inode, we now synchronously write 1 more item (an xattr item) for each property inherited from the parent dir (or subvolume). This could be done in an asynchronous way such as we do for dir intex items (delayed-inode.c), which could help reduce the file creation latency; *) With properties, we now have larger fs trees. For this particular test each xattr item uses 75 bytes of leaf space in the fs tree. This could be less by using a new item for xattr items, instead of the current btrfs_dir_item, since we could cut the 'location' and 'type' fields (saving 18 bytes) and maybe 'transid' too (saving a total of 26 bytes per xattr item) from the btrfs_dir_item type. Also tried batching the xattr insertions (ignoring proper hash collision handling, since it didn't exist) when creating files that inherit properties from their parent inode/subvolume, but the end results were (surprisingly) essentially the same. Test script: $ cat test.pl #!/usr/bin/perl -w use strict; use Time::HiRes qw(time); use constant NUM_FILES => 10_000; use constant FILE_SIZES => (64 * 1024); use constant DEV => '/dev/sdb4'; use constant MNT_POINT => '/home/fdmanana/btrfs-tests/dev'; use constant TEST_DIR => (MNT_POINT . '/testdir'); system("mkfs.btrfs", "-l", "16384", "-f", DEV) == 0 or die "mkfs.btrfs failed!"; # following line for testing without properties #system("mount", "-o", "compress-force=lzo", DEV, MNT_POINT) == 0 or die "mount failed!"; # following 2 lines for testing with properties system("mount", DEV, MNT_POINT) == 0 or die "mount failed!"; system("btrfs", "prop", "set", MNT_POINT, "compression", "lzo") == 0 or die "set prop failed!"; system("mkdir", TEST_DIR) == 0 or die "mkdir failed!"; my ($t1, $t2); $t1 = time(); for (my $i = 1; $i <= NUM_FILES; $i++) { my $p = TEST_DIR . '/file_' . $i; open(my $f, '>', $p) or die "Error opening file!"; $f->autoflush(1); for (my $j = 0; $j < FILE_SIZES; $j += 4096) { print $f ('A' x 4096) or die "Error writing to file!"; } close($f); } $t2 = time(); print "Time to create " . NUM_FILES . ": " . ($t2 - $t1) . " seconds.\n"; system("umount", DEV) == 0 or die "umount failed!"; system("mount", DEV, MNT_POINT) == 0 or die "mount failed!"; $t1 = time(); system("bash -c 'ls -lha " . TEST_DIR . " > /dev/null'") == 0 or die "ls failed!"; $t2 = time(); print "Time to ls -lha all files: " . ($t2 - $t1) . " seconds.\n"; system("umount", DEV) == 0 or die "umount failed!"; Signed-off-by: Filipe David Borba Manana <fdmanana@gmail.com> Signed-off-by: Josef Bacik <jbacik@fb.com> Signed-off-by: Chris Mason <clm@fb.com>
288 lines
7.4 KiB
C
288 lines
7.4 KiB
C
/*
|
|
* Copyright (C) 2007 Oracle. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License v2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*/
|
|
|
|
#ifndef __BTRFS_I__
|
|
#define __BTRFS_I__
|
|
|
|
#include <linux/hash.h>
|
|
#include "extent_map.h"
|
|
#include "extent_io.h"
|
|
#include "ordered-data.h"
|
|
#include "delayed-inode.h"
|
|
|
|
/*
|
|
* ordered_data_close is set by truncate when a file that used
|
|
* to have good data has been truncated to zero. When it is set
|
|
* the btrfs file release call will add this inode to the
|
|
* ordered operations list so that we make sure to flush out any
|
|
* new data the application may have written before commit.
|
|
*/
|
|
#define BTRFS_INODE_ORDERED_DATA_CLOSE 0
|
|
#define BTRFS_INODE_ORPHAN_META_RESERVED 1
|
|
#define BTRFS_INODE_DUMMY 2
|
|
#define BTRFS_INODE_IN_DEFRAG 3
|
|
#define BTRFS_INODE_DELALLOC_META_RESERVED 4
|
|
#define BTRFS_INODE_HAS_ORPHAN_ITEM 5
|
|
#define BTRFS_INODE_HAS_ASYNC_EXTENT 6
|
|
#define BTRFS_INODE_NEEDS_FULL_SYNC 7
|
|
#define BTRFS_INODE_COPY_EVERYTHING 8
|
|
#define BTRFS_INODE_IN_DELALLOC_LIST 9
|
|
#define BTRFS_INODE_READDIO_NEED_LOCK 10
|
|
#define BTRFS_INODE_HAS_PROPS 11
|
|
|
|
/* in memory btrfs inode */
|
|
struct btrfs_inode {
|
|
/* which subvolume this inode belongs to */
|
|
struct btrfs_root *root;
|
|
|
|
/* key used to find this inode on disk. This is used by the code
|
|
* to read in roots of subvolumes
|
|
*/
|
|
struct btrfs_key location;
|
|
|
|
/* Lock for counters */
|
|
spinlock_t lock;
|
|
|
|
/* the extent_tree has caches of all the extent mappings to disk */
|
|
struct extent_map_tree extent_tree;
|
|
|
|
/* the io_tree does range state (DIRTY, LOCKED etc) */
|
|
struct extent_io_tree io_tree;
|
|
|
|
/* special utility tree used to record which mirrors have already been
|
|
* tried when checksums fail for a given block
|
|
*/
|
|
struct extent_io_tree io_failure_tree;
|
|
|
|
/* held while logging the inode in tree-log.c */
|
|
struct mutex log_mutex;
|
|
|
|
/* held while doing delalloc reservations */
|
|
struct mutex delalloc_mutex;
|
|
|
|
/* used to order data wrt metadata */
|
|
struct btrfs_ordered_inode_tree ordered_tree;
|
|
|
|
/* list of all the delalloc inodes in the FS. There are times we need
|
|
* to write all the delalloc pages to disk, and this list is used
|
|
* to walk them all.
|
|
*/
|
|
struct list_head delalloc_inodes;
|
|
|
|
/*
|
|
* list for tracking inodes that must be sent to disk before a
|
|
* rename or truncate commit
|
|
*/
|
|
struct list_head ordered_operations;
|
|
|
|
/* node for the red-black tree that links inodes in subvolume root */
|
|
struct rb_node rb_node;
|
|
|
|
unsigned long runtime_flags;
|
|
|
|
/* Keep track of who's O_SYNC/fsyncing currently */
|
|
atomic_t sync_writers;
|
|
|
|
/* full 64 bit generation number, struct vfs_inode doesn't have a big
|
|
* enough field for this.
|
|
*/
|
|
u64 generation;
|
|
|
|
/*
|
|
* transid of the trans_handle that last modified this inode
|
|
*/
|
|
u64 last_trans;
|
|
|
|
/*
|
|
* log transid when this inode was last modified
|
|
*/
|
|
u64 last_sub_trans;
|
|
|
|
/*
|
|
* transid that last logged this inode
|
|
*/
|
|
u64 logged_trans;
|
|
|
|
/* total number of bytes pending delalloc, used by stat to calc the
|
|
* real block usage of the file
|
|
*/
|
|
u64 delalloc_bytes;
|
|
|
|
/*
|
|
* the size of the file stored in the metadata on disk. data=ordered
|
|
* means the in-memory i_size might be larger than the size on disk
|
|
* because not all the blocks are written yet.
|
|
*/
|
|
u64 disk_i_size;
|
|
|
|
/*
|
|
* if this is a directory then index_cnt is the counter for the index
|
|
* number for new files that are created
|
|
*/
|
|
u64 index_cnt;
|
|
|
|
/* Cache the directory index number to speed the dir/file remove */
|
|
u64 dir_index;
|
|
|
|
/* the fsync log has some corner cases that mean we have to check
|
|
* directories to see if any unlinks have been done before
|
|
* the directory was logged. See tree-log.c for all the
|
|
* details
|
|
*/
|
|
u64 last_unlink_trans;
|
|
|
|
/*
|
|
* Number of bytes outstanding that are going to need csums. This is
|
|
* used in ENOSPC accounting.
|
|
*/
|
|
u64 csum_bytes;
|
|
|
|
/* flags field from the on disk inode */
|
|
u32 flags;
|
|
|
|
/* a local copy of root's last_log_commit */
|
|
unsigned long last_log_commit;
|
|
|
|
/*
|
|
* Counters to keep track of the number of extent item's we may use due
|
|
* to delalloc and such. outstanding_extents is the number of extent
|
|
* items we think we'll end up using, and reserved_extents is the number
|
|
* of extent items we've reserved metadata for.
|
|
*/
|
|
unsigned outstanding_extents;
|
|
unsigned reserved_extents;
|
|
|
|
/*
|
|
* always compress this one file
|
|
*/
|
|
unsigned force_compress;
|
|
|
|
struct btrfs_delayed_node *delayed_node;
|
|
|
|
struct inode vfs_inode;
|
|
};
|
|
|
|
extern unsigned char btrfs_filetype_table[];
|
|
|
|
static inline struct btrfs_inode *BTRFS_I(struct inode *inode)
|
|
{
|
|
return container_of(inode, struct btrfs_inode, vfs_inode);
|
|
}
|
|
|
|
static inline unsigned long btrfs_inode_hash(u64 objectid,
|
|
const struct btrfs_root *root)
|
|
{
|
|
u64 h = objectid ^ (root->objectid * GOLDEN_RATIO_PRIME);
|
|
|
|
#if BITS_PER_LONG == 32
|
|
h = (h >> 32) ^ (h & 0xffffffff);
|
|
#endif
|
|
|
|
return (unsigned long)h;
|
|
}
|
|
|
|
static inline void btrfs_insert_inode_hash(struct inode *inode)
|
|
{
|
|
unsigned long h = btrfs_inode_hash(inode->i_ino, BTRFS_I(inode)->root);
|
|
|
|
__insert_inode_hash(inode, h);
|
|
}
|
|
|
|
static inline u64 btrfs_ino(struct inode *inode)
|
|
{
|
|
u64 ino = BTRFS_I(inode)->location.objectid;
|
|
|
|
/*
|
|
* !ino: btree_inode
|
|
* type == BTRFS_ROOT_ITEM_KEY: subvol dir
|
|
*/
|
|
if (!ino || BTRFS_I(inode)->location.type == BTRFS_ROOT_ITEM_KEY)
|
|
ino = inode->i_ino;
|
|
return ino;
|
|
}
|
|
|
|
static inline void btrfs_i_size_write(struct inode *inode, u64 size)
|
|
{
|
|
i_size_write(inode, size);
|
|
BTRFS_I(inode)->disk_i_size = size;
|
|
}
|
|
|
|
static inline bool btrfs_is_free_space_inode(struct inode *inode)
|
|
{
|
|
struct btrfs_root *root = BTRFS_I(inode)->root;
|
|
|
|
if (root == root->fs_info->tree_root &&
|
|
btrfs_ino(inode) != BTRFS_BTREE_INODE_OBJECTID)
|
|
return true;
|
|
if (BTRFS_I(inode)->location.objectid == BTRFS_FREE_INO_OBJECTID)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static inline int btrfs_inode_in_log(struct inode *inode, u64 generation)
|
|
{
|
|
if (BTRFS_I(inode)->logged_trans == generation &&
|
|
BTRFS_I(inode)->last_sub_trans <=
|
|
BTRFS_I(inode)->last_log_commit &&
|
|
BTRFS_I(inode)->last_sub_trans <=
|
|
BTRFS_I(inode)->root->last_log_commit)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
struct btrfs_dio_private {
|
|
struct inode *inode;
|
|
u64 logical_offset;
|
|
u64 disk_bytenr;
|
|
u64 bytes;
|
|
void *private;
|
|
|
|
/* number of bios pending for this dio */
|
|
atomic_t pending_bios;
|
|
|
|
/* IO errors */
|
|
int errors;
|
|
|
|
/* orig_bio is our btrfs_io_bio */
|
|
struct bio *orig_bio;
|
|
|
|
/* dio_bio came from fs/direct-io.c */
|
|
struct bio *dio_bio;
|
|
u8 csum[0];
|
|
};
|
|
|
|
/*
|
|
* Disable DIO read nolock optimization, so new dio readers will be forced
|
|
* to grab i_mutex. It is used to avoid the endless truncate due to
|
|
* nonlocked dio read.
|
|
*/
|
|
static inline void btrfs_inode_block_unlocked_dio(struct inode *inode)
|
|
{
|
|
set_bit(BTRFS_INODE_READDIO_NEED_LOCK, &BTRFS_I(inode)->runtime_flags);
|
|
smp_mb();
|
|
}
|
|
|
|
static inline void btrfs_inode_resume_unlocked_dio(struct inode *inode)
|
|
{
|
|
smp_mb__before_clear_bit();
|
|
clear_bit(BTRFS_INODE_READDIO_NEED_LOCK,
|
|
&BTRFS_I(inode)->runtime_flags);
|
|
}
|
|
|
|
#endif
|