]> git.ipfire.org Git - thirdparty/xfsprogs-dev.git/commitdiff
xfs: allocate xattr buffer on demand
authorDave Chinner <dchinner@redhat.com>
Fri, 13 Dec 2019 00:54:33 +0000 (19:54 -0500)
committerEric Sandeen <sandeen@redhat.com>
Fri, 13 Dec 2019 00:54:33 +0000 (19:54 -0500)
Source kernel commit: ddbca70cc45c0ac97ff6d9529e45f10b8ae73ad4

When doing file lookups and checking for permissions, we end up in
xfs_get_acl() to see if there are any ACLs on the inode. This
requires and xattr lookup, and to do that we have to supply a buffer
large enough to hold an maximum sized xattr.

On workloads were we are accessing a wide range of cache cold files
under memory pressure (e.g. NFS fileservers) we end up spending a
lot of time allocating the buffer. The buffer is 64k in length, so
is a contiguous multi-page allocation, and if that then fails we
fall back to vmalloc(). Hence the allocation here is /expensive/
when we are looking up hundreds of thousands of files a second.

Initial numbers from a bpf trace show average time in xfs_get_acl()
is ~32us, with ~19us of that in the memory allocation. Note these
are average times, so there are going to be affected by the worst
case allocations more than the common fast case...

To avoid this, we could just do a "null"  lookup to see if the ACL
xattr exists and then only do the allocation if it exists. This,
however, optimises the path for the "no ACL present" case at the
expense of the "acl present" case. i.e. we can halve the time in
xfs_get_acl() for the no acl case (i.e down to ~10-15us), but that
then increases the ACL case by 30% (i.e. up to 40-45us).

To solve this and speed up both cases, drive the xattr buffer
allocation into the attribute code once we know what the actual
xattr length is. For the no-xattr case, we avoid the allocation
completely, speeding up that case. For the common ACL case, we'll
end up with a fast heap allocation (because it'll be smaller than a
page), and only for the rarer "we have a remote xattr" will we have
a multi-page allocation occur. Hence the common ACL case will be
much faster, too.

Signed-off-by: Dave Chinner <dchinner@redhat.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
Signed-off-by: Eric Sandeen <sandeen@sandeen.net>
include/kmem.h
libxfs/kmem.c
libxfs/libxfs_priv.h
libxfs/xfs_attr.c
libxfs/xfs_attr.h
libxfs/xfs_attr_leaf.c
libxfs/xfs_da_btree.h

index 89475ffcb4dc7a4b70413fb8eb76855ba2487150..0f48dc686d07fc3011f3e14f50fa4584c12be045 100644 (file)
@@ -29,6 +29,7 @@ kmem_zone_free(kmem_zone_t *zone, void *ptr)
 }
 
 extern void    *kmem_alloc(size_t, int);
+extern void    *kmem_alloc_large(size_t, int);
 extern void    *kmem_zalloc(size_t, int);
 
 static inline void
index 5f65d97c9907525ee5067e6809de03822a00cacb..8827b858cb6f9823a861e556b49609ed299d7d28 100644 (file)
@@ -75,6 +75,12 @@ kmem_alloc(size_t size, int flags)
        return ptr;
 }
 
+void *
+kmem_alloc_large(size_t size, int flags)
+{
+       return kmem_alloc(size, flags);
+}
+
 void *
 kmem_zalloc(size_t size, int flags)
 {
index ff35c51e4ff230088a71a6bee19f3d4f6dbccde7..d944efc0c4536a1504b5aa262c9e8dd3f4fffdf8 100644 (file)
@@ -610,7 +610,7 @@ static inline int test_and_set_bit(int nr, volatile unsigned long *addr)
 int xfs_inode_hasattr(struct xfs_inode *ip);
 int xfs_attr_get_ilocked(struct xfs_inode *ip, struct xfs_da_args *args);
 int xfs_attr_get(struct xfs_inode *ip, const unsigned char *name,
-                unsigned char *value, int *valuelenp, int flags);
+                unsigned char **value, int *valuelenp, int flags);
 int xfs_attr_set(struct xfs_inode *dp, const unsigned char *name,
                  unsigned char *value, int valuelen, int flags);
 int xfs_attr_remove(struct xfs_inode *dp, const unsigned char *name, int flags);
index c9c04eb1f92c2e23e327641ef6adb11f655f5731..ada1b5f40ba3523e8490ecf0008080daa90cd0b6 100644 (file)
@@ -117,12 +117,28 @@ xfs_attr_get_ilocked(
                return xfs_attr_node_get(args);
 }
 
-/* Retrieve an extended attribute by name, and its value. */
+/*
+ * Retrieve an extended attribute by name, and its value if requested.
+ *
+ * If ATTR_KERNOVAL is set in @flags, then the caller does not want the value,
+ * just an indication whether the attribute exists and the size of the value if
+ * it exists. The size is returned in @valuelenp,
+ *
+ * If the attribute is found, but exceeds the size limit set by the caller in
+ * @valuelenp, return -ERANGE with the size of the attribute that was found in
+ * @valuelenp.
+ *
+ * If ATTR_ALLOC is set in @flags, allocate the buffer for the value after
+ * existence of the attribute has been determined. On success, return that
+ * buffer to the caller and leave them to free it. On failure, free any
+ * allocated buffer and ensure the buffer pointer returned to the caller is
+ * null.
+ */
 int
 xfs_attr_get(
        struct xfs_inode        *ip,
        const unsigned char     *name,
-       unsigned char           *value,
+       unsigned char           **value,
        int                     *valuelenp,
        int                     flags)
 {
@@ -130,6 +146,8 @@ xfs_attr_get(
        uint                    lock_mode;
        int                     error;
 
+       ASSERT((flags & (ATTR_ALLOC | ATTR_KERNOVAL)) || *value);
+
        XFS_STATS_INC(ip->i_mount, xs_attr_get);
 
        if (XFS_FORCED_SHUTDOWN(ip->i_mount))
@@ -139,17 +157,29 @@ xfs_attr_get(
        if (error)
                return error;
 
-       args.value = value;
-       args.valuelen = *valuelenp;
        /* Entirely possible to look up a name which doesn't exist */
        args.op_flags = XFS_DA_OP_OKNOENT;
+       if (flags & ATTR_ALLOC)
+               args.op_flags |= XFS_DA_OP_ALLOCVAL;
+       else
+               args.value = *value;
+       args.valuelen = *valuelenp;
 
        lock_mode = xfs_ilock_attr_map_shared(ip);
        error = xfs_attr_get_ilocked(ip, &args);
        xfs_iunlock(ip, lock_mode);
-
        *valuelenp = args.valuelen;
-       return error;
+
+       /* on error, we have to clean up allocated value buffers */
+       if (error) {
+               if (flags & ATTR_ALLOC) {
+                       kmem_free(args.value);
+                       *value = NULL;
+               }
+               return error;
+       }
+       *value = args.value;
+       return 0;
 }
 
 /*
index ff28ebf3b635d1940f1306006623a072602ecd5f..94badfa1743e372ef9b34125e83a0ddb6ae181ca 100644 (file)
@@ -37,6 +37,7 @@ struct xfs_attr_list_context;
 #define ATTR_KERNOVAL  0x2000  /* [kernel] get attr size only, not value */
 
 #define ATTR_INCOMPLETE        0x4000  /* [kernel] return INCOMPLETE attr keys */
+#define ATTR_ALLOC     0x8000  /* allocate xattr buffer on demand */
 
 #define XFS_ATTR_FLAGS \
        { ATTR_DONTFOLLOW,      "DONTFOLLOW" }, \
@@ -47,7 +48,8 @@ struct xfs_attr_list_context;
        { ATTR_REPLACE,         "REPLACE" }, \
        { ATTR_KERNOTIME,       "KERNOTIME" }, \
        { ATTR_KERNOVAL,        "KERNOVAL" }, \
-       { ATTR_INCOMPLETE,      "INCOMPLETE" }
+       { ATTR_INCOMPLETE,      "INCOMPLETE" }, \
+       { ATTR_ALLOC,           "ALLOC" }
 
 /*
  * The maximum size (into the kernel or returned from the kernel) of an
@@ -143,7 +145,7 @@ int xfs_attr_list_int(struct xfs_attr_list_context *);
 int xfs_inode_hasattr(struct xfs_inode *ip);
 int xfs_attr_get_ilocked(struct xfs_inode *ip, struct xfs_da_args *args);
 int xfs_attr_get(struct xfs_inode *ip, const unsigned char *name,
-                unsigned char *value, int *valuelenp, int flags);
+                unsigned char **value, int *valuelenp, int flags);
 int xfs_attr_set(struct xfs_inode *dp, const unsigned char *name,
                 unsigned char *value, int valuelen, int flags);
 int xfs_attr_set_args(struct xfs_da_args *args);
index 0ed2478a31bc17cd8d11c8c1221c0cbf5d981820..a91e5e1ed85f3ae5581f6713e431177125ed021b 100644 (file)
@@ -410,6 +410,12 @@ xfs_attr_copy_value(
                args->valuelen = valuelen;
                return -ERANGE;
        }
+
+       if (args->op_flags & XFS_DA_OP_ALLOCVAL) {
+               args->value = kmem_alloc_large(valuelen, 0);
+               if (!args->value)
+                       return -ENOMEM;
+       }
        args->valuelen = valuelen;
 
        /* remote block xattr requires IO for copy-in */
index 84dd865b6c3d8cb30d25c38aaecebad9d5a82e9a..ae0bbd20d9caf141a7ae7c57803c57006f9e3d58 100644 (file)
@@ -81,13 +81,15 @@ typedef struct xfs_da_args {
 #define XFS_DA_OP_ADDNAME      0x0004  /* this is an add operation */
 #define XFS_DA_OP_OKNOENT      0x0008  /* lookup/add op, ENOENT ok, else die */
 #define XFS_DA_OP_CILOOKUP     0x0010  /* lookup to return CI name if found */
+#define XFS_DA_OP_ALLOCVAL     0x0020  /* lookup to alloc buffer if found  */
 
 #define XFS_DA_OP_FLAGS \
        { XFS_DA_OP_JUSTCHECK,  "JUSTCHECK" }, \
        { XFS_DA_OP_RENAME,     "RENAME" }, \
        { XFS_DA_OP_ADDNAME,    "ADDNAME" }, \
        { XFS_DA_OP_OKNOENT,    "OKNOENT" }, \
-       { XFS_DA_OP_CILOOKUP,   "CILOOKUP" }
+       { XFS_DA_OP_CILOOKUP,   "CILOOKUP" }, \
+       { XFS_DA_OP_ALLOCVAL,   "ALLOCVAL" }
 
 /*
  * Storage for holding state during Btree searches and split/join ops.