Whamcloud - gitweb
EX-7644 mmap: add mmap support for compression
authorPatrick Farrell <pfarrell@whamcloud.com>
Wed, 25 Oct 2023 16:15:59 +0000 (12:15 -0400)
committerAndreas Dilger <adilger@whamcloud.com>
Fri, 29 Dec 2023 10:58:12 +0000 (10:58 +0000)
This removes the EOPNOTSUPP for compression with mmap and
adds an mmap sanity test for compression.  This patch
removes all the restrictions for mmap, but we actually only
have unaligned read support right now, so the test is
deliberately simplified to only test reads.

A more complicated version which also tests mmap writes
comes later in the series, once read-modify-write is
supported.

The test tests mmap by copying data at several different
block sizes with several different compression chunk sizes.

Test-Parameters: testlist=sanity-compr env=ONLY="1003",ONLY_REPEAT=10
Signed-off-by: Patrick Farrell <pfarrell@whamcloud.com>
Change-Id: I4a37b106831a903d90e8a8871e9a93baac4e201e
Reviewed-on: https://review.whamcloud.com/c/ex/lustre-release/+/52280
Tested-by: jenkins <devops@whamcloud.com>
Tested-by: Maloo <maloo@whamcloud.com>
Reviewed-by: Andreas Dilger <adilger@whamcloud.com>
Reviewed-by: Artem Blagodarenko <ablagodarenko@ddn.com>
lustre/llite/llite_mmap.c
lustre/tests/Makefile.am
lustre/tests/mmap_copy_file.c [new file with mode: 0644]
lustre/tests/sanity-compr.sh
lustre/tests/sanity.sh

index 78657ed..74d40ee 100644 (file)
@@ -617,54 +617,6 @@ static const struct vm_operations_struct ll_file_vm_ops = {
        .close                  = ll_vm_close,
 };
 
-int ll_mmap_check_compression(struct file *file, loff_t off)
-{
-       struct inode *inode = file_inode(file);
-       struct ll_inode_info *lli = ll_i2info(inode);
-       struct lu_env *env;
-       struct cl_io *io;
-       __u16 refcheck;
-       int rc = 0;
-       __u32 gen;
-
-       /* mmap reads of compressed files are supported */
-       if ((file->f_flags & O_ACCMODE) == O_RDONLY)
-               RETURN(0);
-
-       rc = ll_layout_refresh(inode, &gen);
-       if (rc) {
-               CERROR("%s: "DFID" cannot refresh layout: rc = %d\n",
-                      ll_i2sbi(inode)->ll_fsname, PFID(&lli->lli_fid), rc);
-               RETURN(rc);
-       }
-       if (ll_layout_version_get(lli) == CL_LAYOUT_GEN_EMPTY)
-               RETURN(0);
-       /* XXX: ignore PCC for a while */
-       if (lli->lli_pcc_inode || lli->lli_pcc_generation)
-               RETURN(0);
-
-       rc = cl_io_get(inode, &env, &io, &refcheck);
-       if (rc <= 0)
-               RETURN(rc);
-
-       rc = cl_io_init(env, io, CIT_MISC, lli->lli_clob);
-       if (rc == 0) {
-               if (io->ci_compressed_file)
-                       rc = -EOPNOTSUPP;
-       } else if (rc == 1 || rc == -ENODATA) {
-               rc = 0;
-       } else {
-               CERROR("%s: "DFID" cl_io_init() failed: rc = %d\n",
-                      ll_i2sbi(inode)->ll_fsname, PFID(&lli->lli_fid), rc);
-       }
-
-       cl_io_fini(env, io);
-
-       cl_env_put(env, &refcheck);
-
-       RETURN(rc);
-}
-
 int ll_file_mmap(struct file *file, struct vm_area_struct * vma)
 {
        struct inode *inode = file_inode(file);
@@ -681,10 +633,6 @@ int ll_file_mmap(struct file *file, struct vm_area_struct * vma)
        if (ll_file_nolock(file))
                RETURN(-EOPNOTSUPP);
 
-       rc = ll_mmap_check_compression(file, (loff_t)vma->vm_pgoff<<PAGE_SHIFT);
-       if (rc)
-               RETURN(rc);
-
        rc = pcc_file_mmap(file, vma, &cached);
        if (cached && rc != 0)
                RETURN(rc);
index 9746b9f..e385ef6 100644 (file)
@@ -74,7 +74,7 @@ THETESTS += create_foreign_file parse_foreign_file
 THETESTS += create_foreign_dir parse_foreign_dir
 THETESTS += check_fallocate splice-test lseek_test expand_truncate_test
 THETESTS += lov_getstripe_old io_uring_probe fadvise_dontneed_helper
-THETESTS += llapi_root_test
+THETESTS += llapi_root_test mmap_copy_file
 
 if LIBAIO
 THETESTS += aiocp
diff --git a/lustre/tests/mmap_copy_file.c b/lustre/tests/mmap_copy_file.c
new file mode 100644 (file)
index 0000000..8f2fe79
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * GPL HEADER START
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 only,
+ * 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 version 2 for more details (a copy is included
+ * in the LICENSE file that accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; If not, see
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * GPL HEADER END
+ */
+
+/*
+ * Copyright 2023 DDN Inc. All rights reserved.
+ * Authors: Patrick Farrell <pfarrell@whamcloud.com>
+ *
+ * Simple program to copy a file using mmap.  Created to test client side data
+ * compression feature.
+ *
+ * Syncs data after each write in order to force compression to act on partial
+ * chunks of data.
+ */
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* < minimum chunk size of 64K and not-chunk or page aligned */
+#define DEFAULT_BLOCK_SIZE (35 * 1024) /* 35K */
+
+int main(int argc, char *argv[]) {
+       struct stat src_stat;
+       unsigned int random_seed = (unsigned int)time(NULL);
+       size_t block_size = DEFAULT_BLOCK_SIZE;
+       const char *src_filename;
+       const char *dst_filename;
+       size_t bytes_copied = 0;
+       float percentage = 1.0;
+       size_t remaining_size;
+       size_t chunks_to_copy;
+       int partial_copy = 0;
+       size_t copy_size;
+       void *dst_map;
+       size_t offset;
+       void *src_map;
+       int src_fd;
+       int dst_fd;
+       int opt;
+
+       while ((opt = getopt(argc, argv, "p:s:b:")) != -1) {
+               switch (opt) {
+                       case 'p':
+                               partial_copy = 1;
+                               percentage = atof(optarg) / 100.0;
+                               if (percentage <= 0.01 || percentage > 1.0) {
+                                       fprintf(stderr,
+                                               "Percentage must be between 1 and 100\n");
+                                       return 1;
+                               }
+                               break;
+                       case 's':
+                               random_seed = (unsigned int)atoi(optarg);
+                               break;
+                       case 'b':
+                               block_size = atoi(optarg);
+                               if (block_size <= 0) {
+                                       fprintf(stderr, "Block size must be greater than 0\n");
+                                       return 1;
+                               }
+                               break;
+                       default:
+                               fprintf(stderr, "Usage: %s [-p PERCENT_TO_COPY(1-100)] [-s SEED] [-b BLOCK_SIZE] <source_file> <destination_file>\n",
+                                       argv[0]);
+                       return 1;
+               }
+       }
+
+       if (optind + 2 > argc) {
+               fprintf(stderr,
+                       "Usage: %s [-p PERCENT_TO_COPY(1-100)] [-s SEED] <source_file> <destination_file>\n",
+                       argv[0]);
+               return 1;
+       }
+
+       src_filename = argv[optind];
+       dst_filename = argv[optind + 1];
+
+       src_fd = open(src_filename, O_RDONLY);
+       if (src_fd == -1) {
+               perror("Failed to open source file");
+               return 1;
+       }
+
+       if (fstat(src_fd, &src_stat) == -1) {
+               perror("Failed to get source file size");
+               close(src_fd);
+               return 1;
+       }
+
+       dst_fd = open(dst_filename, O_RDWR | O_CREAT, 0666);
+       if (dst_fd == -1) {
+               perror("Failed to create destination file");
+               close(src_fd);
+               return 1;
+       }
+
+       if (ftruncate(dst_fd, src_stat.st_size) == -1) {
+               perror("Failed to set destination file size");
+               close(src_fd);
+               close(dst_fd);
+               return 1;
+       }
+
+       src_map = mmap(NULL, src_stat.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
+       if (src_map == MAP_FAILED) {
+               perror("Failed to mmap source file");
+               close(src_fd);
+               close(dst_fd);
+               return 1;
+       }
+
+       dst_map = mmap(NULL, src_stat.st_size, PROT_WRITE, MAP_SHARED, dst_fd, 0);
+       if (dst_map == MAP_FAILED) {
+               perror("Failed to mmap destination file");
+               munmap(src_map, src_stat.st_size);
+               close(src_fd);
+               close(dst_fd);
+               return 1;
+       }
+
+       remaining_size = src_stat.st_size;
+       offset = 0;
+
+       if (partial_copy) {
+               size_t total_chunks =
+                       src_stat.st_size / block_size + (src_stat.st_size % block_size != 0);
+               size_t i;
+
+               chunks_to_copy = total_chunks * percentage;
+
+               srand(random_seed);
+               for (i = 0; i < chunks_to_copy; i++) {
+                       offset = (rand() % (total_chunks - 1)) * block_size;
+                       copy_size = (src_stat.st_size - offset < block_size) ?
+                               src_stat.st_size - offset : block_size;
+                       memcpy(dst_map + offset, src_map + offset, copy_size);
+                       bytes_copied += copy_size;
+                       fsync(dst_fd);
+               }
+       } else {
+               /* Existing copy logic */
+               while (remaining_size > 0) {
+                       copy_size = (remaining_size < block_size) ?
+                               remaining_size : block_size;
+                       memcpy(dst_map + offset, src_map + offset, copy_size);
+                       offset += copy_size;
+                       remaining_size -= copy_size;
+                       bytes_copied += copy_size;
+                       fsync(dst_fd);
+               }
+       }
+       munmap(src_map, src_stat.st_size);
+       munmap(dst_map, src_stat.st_size);
+       close(src_fd);
+       close(dst_fd);
+
+       if (!partial_copy)
+               printf("File '%s' (%lu bytes) mmap-copied to '%s' successfully (block size %lu)!\n",
+                      src_filename, bytes_copied, dst_filename, block_size);
+       else
+               printf("Copied %lu bytes (%d%%) of file '%s' (%lu total bytes) mmap-copied to '%s' successfully (block size %lu)!\n",
+                      bytes_copied, (int)(percentage * 100), src_filename,
+                      src_stat.st_size, dst_filename, block_size);
+
+       return 0;
+}
+
index 7a83577..f80359d 100644 (file)
@@ -394,6 +394,87 @@ test_1002() {
 }
 run_test 1002 "test reads with incomplete chunks"
 
+test_1003() {
+       (( MDS1_VERSION >= $(version_code 2.14.0.91) )) ||
+               skip "Need MDS version at least 2.14.0.91"
+
+       local tf=$DIR/$tfile
+       local hdf=$LUSTRE/tests/AMSR_E_L3_DailyOcean_V05_20111003.hdf
+       local tmp_hdf=$TMP/$tfile.hdf
+       local source=$tmp_hdf
+       # Larger than arm page size
+       local chunksize=128
+
+       if [[ -f $hdf.bz2 ]] && type -p bzcat >/dev/null; then
+               bzcat $hdf.bz2 > $tmp_hdf
+       elif [[ -f $hdf.bz2 ]] && type -p bunzip2 >/dev/null; then
+               cp $hdf.bz2 $tmp_hdf.bz2 || error "cp $tmp_hdf.bz2"
+               bunzip2 $tmp_hdf.bz2 || error "bunzip2 $tmp_hdf.bz2"
+       else
+               echo "bunzip2 is not installed, skip it"
+               return 0
+       fi
+
+       # Fail test if source size changes so we catch this
+       # Source should be a few MiB in size
+       $CHECKSTAT -s 14625450 $source || error "checkstat wrong size"
+
+       stack_trap "rm -f $tf; disable_compression"
+       enable_compression
+
+       local chunksizes=(
+               64
+               128
+               1024
+       )
+       local blocksizes=(
+               # Use a mix of chunk aligned and chunk unaligned sizes
+               $((35 * 1024)) # 35K (etc)
+               $((64 * 1024))
+               $((100 * 1024))
+               $((128 * 1024))
+       )
+
+       local seed=$(date +%s)
+       echo "Random seed $seed."
+       # Just test mmap reads, read-modify-write is not supported yet
+       for chunksize in "${chunksizes[@]}"; do
+       for blocksize in "${blocksizes[@]}"; do
+               rm -f $tf*
+               echo "Compression, using ${chunksize}K chunksize"
+               $LFS setstripe -E -1 -Z lz4:0 --compress-chunk="$chunksize" "$tf" ||
+                       error "setstripe on $tf failed, chunsksize $chunksize"
+
+               dd if=$source bs=1M of=$tf oflag=direct ||
+                       error "copy to create $tf failed"
+               flush_and_compare $source $tf "(0)"
+
+               # Copy from compressed file to non-compressed file
+               # (test reading)
+               mmap_copy_file -b $blocksize $tf $tf.2 ||
+                       error "mmap file copy failed (1)"
+
+               flush_and_compare $source $tf.2 "(2)"
+
+               rm -f $tf.2
+               rm -f $tf.3
+
+               # Test reading again by copying random chunks totalling 50% of
+               # file size (but copies are at random offsets)
+               # We seed the random copy and copy from the original source,
+               # then repeat the random copy from the compressed file with the
+               # same seed and compare those two
+               mmap_copy_file -s $seed -p 50 -b $blocksize $source $tf.2 ||
+                       error "mmap file copy failed (3)"
+               mmap_copy_file -s $seed -p 50 -b $blocksize $tf $tf.3 ||
+                       error "mmap file copy failed (4)"
+
+               flush_and_compare $tf.2 $tf.3 "(5)"
+       done
+       done
+}
+run_test 1003 "mmap test for compression"
+
 complete_test $SECONDS
 check_and_cleanup_lustre
 declare -a logs=($ONLY)
index 6a42a30..c25a54d 100755 (executable)
@@ -28451,8 +28451,8 @@ test_460e() {
        $LFS getstripe $tf | grep -q lcme_flags.*compress ||
                error "no compressed component"
 
-       $MULTIOP $tf OSMc &&
-               error "(0) writeable mmap of compressed files should not work"
+       $MULTIOP $tf OSMc ||
+               error "(0) writeable mmap of compressed files should work"
        $MULTIOP $tf oSMc ||
                error "(1) readonly mmap of compressed files should work"
 
@@ -28463,7 +28463,7 @@ test_460e() {
        $MULTIOP $tf oSMc ||
                error "(3) readonly mmap of uncompressed files should work"
 }
-run_test 460e "verify mmap is disabled with compressed files"
+run_test 460e "verify mmap is enabled with compressed files"
 
 test_460f() {
        (( MDS1_VERSION >= $(version_code 2.14.0.91) )) ||