diff options
-rw-r--r-- | cmd/zfs/zfs_main.c | 4 | ||||
-rw-r--r-- | cmd/zpool/zpool_iter.c | 33 | ||||
-rw-r--r-- | cmd/zpool/zpool_main.c | 32 | ||||
-rw-r--r-- | cmd/zpool/zpool_util.h | 12 | ||||
-rw-r--r-- | module/zfs/vdev_raidz_math.c | 2 | ||||
-rw-r--r-- | module/zfs/vdev_raidz_math_avx512bw.c | 190 | ||||
-rw-r--r-- | module/zfs/zfs_vfsops.c | 19 | ||||
-rw-r--r-- | tests/runfiles/linux.run | 3 | ||||
-rwxr-xr-x | tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh | 23 |
9 files changed, 174 insertions, 144 deletions
diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 7379af280..6e818afcb 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -2529,7 +2529,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) namelen = strlen(name); } nameidx = us_field_index("name"); - if (namelen > cb->cb_width[nameidx]) + if (nameidx >= 0 && namelen > cb->cb_width[nameidx]) cb->cb_width[nameidx] = namelen; /* @@ -2574,7 +2574,7 @@ userspace_cb(void *arg, const char *domain, uid_t rid, uint64_t space) return (-1); } sizeidx = us_field_index(propname); - if (sizelen > cb->cb_width[sizeidx]) + if (sizeidx >= 0 && sizelen > cb->cb_width[sizeidx]) cb->cb_width[sizeidx] = sizelen; if (nvlist_add_uint64(props, propname, space) != 0) diff --git a/cmd/zpool/zpool_iter.c b/cmd/zpool/zpool_iter.c index 1d376d4fb..309247b6b 100644 --- a/cmd/zpool/zpool_iter.c +++ b/cmd/zpool/zpool_iter.c @@ -360,7 +360,8 @@ for_each_vdev_run_cb(zpool_handle_t *zhp, nvlist_t *nv, void *cb_vcdl) vdev_cmd_data_list_t *vcdl = cb_vcdl; vdev_cmd_data_t *data; char *path = NULL; - int i; + char *vname = NULL; + int i, match = 0; if (nvlist_lookup_string(nv, ZPOOL_CONFIG_PATH, &path) != 0) return (1); @@ -374,6 +375,21 @@ for_each_vdev_run_cb(zpool_handle_t *zhp, nvlist_t *nv, void *cb_vcdl) } } + /* Check for whitelisted vdevs here, if any */ + for (i = 0; i < vcdl->vdev_names_count; i++) { + vname = zpool_vdev_name(g_zfs, zhp, nv, vcdl->cb_name_flags); + if (strcmp(vcdl->vdev_names[i], vname) == 0) { + free(vname); + match = 1; + break; /* match */ + } + free(vname); + } + + /* If we whitelisted vdevs, and this isn't one of them, then bail out */ + if (!match && vcdl->vdev_names_count) + return (0); + /* * Resize our array and add in the new element. */ @@ -437,19 +453,28 @@ all_pools_for_each_vdev_run_vcdl(vdev_cmd_data_list_t *vcdl) } /* - * Run command 'cmd' on all vdevs in all pools. Saves the first line of output - * from the command in vcdk->data[].line for all vdevs. + * Run command 'cmd' on all vdevs in all pools in argv. Saves the first line of + * output from the command in vcdk->data[].line for all vdevs. If you want + * to run the command on only certain vdevs, fill in g_zfs, vdev_names, + * vdev_names_count, and cb_name_flags. Otherwise leave them as zero. * * Returns a vdev_cmd_data_list_t that must be freed with * free_vdev_cmd_data_list(); */ vdev_cmd_data_list_t * -all_pools_for_each_vdev_run(int argc, char **argv, char *cmd) +all_pools_for_each_vdev_run(int argc, char **argv, char *cmd, + libzfs_handle_t *g_zfs, char **vdev_names, int vdev_names_count, + int cb_name_flags) { vdev_cmd_data_list_t *vcdl; vcdl = safe_malloc(sizeof (vdev_cmd_data_list_t)); vcdl->cmd = cmd; + vcdl->vdev_names = vdev_names; + vcdl->vdev_names_count = vdev_names_count; + vcdl->cb_name_flags = cb_name_flags; + vcdl->g_zfs = g_zfs; + /* Gather our list of all vdevs in all pools */ for_each_pool(argc, argv, B_TRUE, NULL, all_pools_for_each_vdev_gather_cb, vcdl); diff --git a/cmd/zpool/zpool_main.c b/cmd/zpool/zpool_main.c index a8233e336..f5740c00e 100644 --- a/cmd/zpool/zpool_main.c +++ b/cmd/zpool/zpool_main.c @@ -312,7 +312,7 @@ get_usage(zpool_help_t idx) { "[-R root] [-F [-n]]\n" "\t <pool | id> [newpool]\n")); case HELP_IOSTAT: - return (gettext("\tiostat [-T d | u] [-ghHLpPvy] " + return (gettext("\tiostat [-c CMD] [-T d | u] [-ghHLpPvy] " "[[-lq]|[-r|-w]]\n" "\t [[pool ...]|[pool vdev ...]|[vdev ...]] " "[interval [count]]\n")); @@ -335,8 +335,8 @@ get_usage(zpool_help_t idx) { case HELP_SCRUB: return (gettext("\tscrub [-s] <pool> ...\n")); case HELP_STATUS: - return (gettext("\tstatus [-gLPvxD] [-T d|u] [pool] ... " - "[interval [count]]\n")); + return (gettext("\tstatus [-c CMD] [-gLPvxD] [-T d|u] [pool]" + " ... [interval [count]]\n")); case HELP_UPGRADE: return (gettext("\tupgrade\n" "\tupgrade -v\n" @@ -4055,8 +4055,13 @@ zpool_do_iostat(int argc, char **argv) usage(B_FALSE); break; case '?': - (void) fprintf(stderr, gettext("invalid option '%c'\n"), - optopt); + if (optopt == 'c') { + fprintf(stderr, + gettext("Missing CMD for -c\n")); + } else { + fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + } usage(B_FALSE); } } @@ -4256,9 +4261,10 @@ zpool_do_iostat(int argc, char **argv) continue; } - if (cmd != NULL) + if (cmd != NULL && cb.cb_verbose) cb.vcdl = all_pools_for_each_vdev_run(argc, - argv, cmd); + argv, cmd, g_zfs, cb.cb_vdev_names, + cb.cb_vdev_names_count, cb.cb_name_flags); pool_list_iter(list, B_FALSE, print_iostat, &cb); @@ -6113,8 +6119,13 @@ zpool_do_status(int argc, char **argv) get_timestamp_arg(*optarg); break; case '?': - (void) fprintf(stderr, gettext("invalid option '%c'\n"), - optopt); + if (optopt == 'c') { + fprintf(stderr, + gettext("Missing CMD for -c\n")); + } else { + fprintf(stderr, + gettext("invalid option '%c'\n"), optopt); + } usage(B_FALSE); } } @@ -6135,7 +6146,8 @@ zpool_do_status(int argc, char **argv) print_timestamp(timestamp_fmt); if (cmd != NULL) - cb.vcdl = all_pools_for_each_vdev_run(argc, argv, cmd); + cb.vcdl = all_pools_for_each_vdev_run(argc, argv, cmd, + NULL, NULL, 0, 0); ret = for_each_pool(argc, argv, B_TRUE, NULL, status_callback, &cb); diff --git a/cmd/zpool/zpool_util.h b/cmd/zpool/zpool_util.h index 0d83b54d5..acdfd5075 100644 --- a/cmd/zpool/zpool_util.h +++ b/cmd/zpool/zpool_util.h @@ -86,11 +86,21 @@ typedef struct vdev_cmd_data_list { char *cmd; /* Command to run */ unsigned int count; /* Number of vdev_cmd_data items (vdevs) */ + + /* vars to whitelist only certain vdevs, if requested */ + libzfs_handle_t *g_zfs; + char **vdev_names; + int vdev_names_count; + int cb_name_flags; + vdev_cmd_data_t *data; /* Array of vdevs */ + } vdev_cmd_data_list_t; vdev_cmd_data_list_t *all_pools_for_each_vdev_run(int argc, char **argv, - char *cmd); + char *cmd, libzfs_handle_t *g_zfs, char **vdev_names, int vdev_names_count, + int cb_name_flags); + void free_vdev_cmd_data_list(vdev_cmd_data_list_t *vcdl); #ifdef __cplusplus diff --git a/module/zfs/vdev_raidz_math.c b/module/zfs/vdev_raidz_math.c index 85dd15cc3..a175bcf77 100644 --- a/module/zfs/vdev_raidz_math.c +++ b/module/zfs/vdev_raidz_math.c @@ -61,7 +61,7 @@ const raidz_impl_ops_t *raidz_all_maths[] = { &vdev_raidz_avx512f_impl, #endif #if defined(__x86_64) && defined(HAVE_AVX512BW) /* only x86_64 for now */ - // &vdev_raidz_avx512bw_impl, + &vdev_raidz_avx512bw_impl, #endif #if defined(__aarch64__) &vdev_raidz_aarch64_neon_impl, diff --git a/module/zfs/vdev_raidz_math_avx512bw.c b/module/zfs/vdev_raidz_math_avx512bw.c index 33b2d388f..3d5326b9e 100644 --- a/module/zfs/vdev_raidz_math_avx512bw.c +++ b/module/zfs/vdev_raidz_math_avx512bw.c @@ -20,11 +20,12 @@ */ /* * Copyright (C) 2016 Romain Dolbeau. All rights reserved. + * Copyright (C) 2016 Gvozden Nešković. All rights reserved. */ #include <sys/isa_defs.h> -#if 0 // defined(__x86_64) && defined(HAVE_AVX512BW) +#if defined(__x86_64) && defined(HAVE_AVX512BW) #include <sys/types.h> #include <linux/simd_x86.h> @@ -66,20 +67,6 @@ typedef struct v { uint8_t b[ELEM_SIZE] __attribute__((aligned(ELEM_SIZE))); } v_t; -#define PREFETCHNTA(ptr, offset) \ -{ \ - __asm( \ - "prefetchnta " #offset "(%[MEM])\n" \ - : : [MEM] "r" (ptr)); \ -} - -#define PREFETCH(ptr, offset) \ -{ \ - __asm( \ - "prefetcht0 " #offset "(%[MEM])\n" \ - : : [MEM] "r" (ptr)); \ -} - #define XOR_ACC(src, r...) \ { \ switch (REG_CNT(r)) { \ @@ -122,25 +109,7 @@ typedef struct v { } \ } -#define ZERO(r...) \ -{ \ - switch (REG_CNT(r)) { \ - case 4: \ - __asm( \ - "vpxorq %" VR0(r) ", %" VR0(r)", %" VR0(r) "\n" \ - "vpxorq %" VR1(r) ", %" VR1(r)", %" VR1(r) "\n" \ - "vpxorq %" VR2(r) ", %" VR2(r)", %" VR2(r) "\n" \ - "vpxorq %" VR3(r) ", %" VR3(r)", %" VR3(r)); \ - break; \ - case 2: \ - __asm( \ - "vpxorq %" VR0(r) ", %" VR0(r)", %" VR0(r) "\n" \ - "vpxorq %" VR1(r) ", %" VR1(r)", %" VR1(r)); \ - break; \ - default: \ - ASM_BUG(); \ - } \ -} +#define ZERO(r...) XOR(r, r) #define COPY(r...) \ { \ @@ -206,20 +175,11 @@ typedef struct v { } \ } -#define FLUSH() \ -{ \ - __asm("vzeroupper"); \ -} - -#define MUL2_SETUP() \ -{ \ - __asm("vmovq %0, %%xmm14" :: "r"(0x1d1d1d1d1d1d1d1d)); \ - __asm("vpbroadcastq %xmm14, %zmm14"); \ - __asm("vmovq %0, %%xmm13" :: "r"(0x8080808080808080)); \ - __asm("vpbroadcastq %xmm13, %zmm13"); \ - __asm("vmovq %0, %%xmm12" :: "r"(0xfefefefefefefefe)); \ - __asm("vpbroadcastq %xmm12, %zmm12"); \ - __asm("vpxorq %zmm15, %zmm15 ,%zmm15"); \ +#define MUL2_SETUP() \ +{ \ + __asm("vmovq %0, %%xmm22" :: "r"(0x1d1d1d1d1d1d1d1d)); \ + __asm("vpbroadcastq %xmm22, %zmm22"); \ + __asm("vpxord %zmm23, %zmm23 ,%zmm23"); \ } #define _MUL2(r...) \ @@ -227,20 +187,14 @@ typedef struct v { switch (REG_CNT(r)) { \ case 2: \ __asm( \ - "vpandq %" VR0(r)", %zmm13, %zmm10\n" \ - "vpandq %" VR1(r)", %zmm13, %zmm11\n" \ - "vpsrlq $7, %zmm10, %zmm8\n" \ - "vpsrlq $7, %zmm11, %zmm9\n" \ - "vpsllq $1, %zmm10, %zmm10\n" \ - "vpsllq $1, %zmm11, %zmm11\n" \ - "vpsubq %zmm8, %zmm10, %zmm10\n" \ - "vpsubq %zmm9, %zmm11, %zmm11\n" \ - "vpsllq $1, %" VR0(r)", %" VR0(r) "\n" \ - "vpsllq $1, %" VR1(r)", %" VR1(r) "\n" \ - "vpandq %zmm10, %zmm14, %zmm10\n" \ - "vpandq %zmm11, %zmm14, %zmm11\n" \ - "vpternlogd $0x6c,%zmm12, %zmm10, %" VR0(r) "\n" \ - "vpternlogd $0x6c,%zmm12, %zmm11, %" VR1(r)); \ + "vpcmpb $1, %zmm23, %" VR0(r)", %k1\n" \ + "vpcmpb $1, %zmm23, %" VR1(r)", %k2\n" \ + "vpaddb %" VR0(r)", %" VR0(r)", %" VR0(r) "\n" \ + "vpaddb %" VR1(r)", %" VR1(r)", %" VR1(r) "\n" \ + "vpxord %zmm22, %" VR0(r)", %zmm12\n" \ + "vpxord %zmm22, %" VR1(r)", %zmm13\n" \ + "vmovdqu8 %zmm12, %" VR0(r) "{%k1}\n" \ + "vmovdqu8 %zmm13, %" VR1(r) "{%k2}"); \ break; \ default: \ ASM_BUG(); \ @@ -276,7 +230,7 @@ typedef struct v { #define _ta "zmm10" #define _tb "zmm15" -static const uint8_t __attribute__((aligned(32))) _mul_mask = 0x0F; +static const uint8_t __attribute__((aligned(64))) _mul_mask = 0x0F; #define _MULx2(c, r...) \ { \ @@ -339,11 +293,15 @@ static const uint8_t __attribute__((aligned(32))) _mul_mask = 0x0F; } #define raidz_math_begin() kfpu_begin() -#define raidz_math_end() \ -{ \ - FLUSH(); \ - kfpu_end(); \ -} +#define raidz_math_end() kfpu_end() + +/* + * ZERO, COPY, and MUL operations are already 2x unrolled, which means that + * the stride of these operations for avx512 must not exceed 4. Otherwise, a + * single step would exceed 512B block size. + */ + +#define SYN_STRIDE 4 #define ZERO_STRIDE 4 #define ZERO_DEFINE() {} @@ -361,59 +319,67 @@ static const uint8_t __attribute__((aligned(32))) _mul_mask = 0x0F; #define MUL_DEFINE() {} #define MUL_D 0, 1, 2, 3 -#define GEN_P_DEFINE() {} #define GEN_P_STRIDE 4 +#define GEN_P_DEFINE() {} #define GEN_P_P 0, 1, 2, 3 -#define GEN_PQ_DEFINE() {} #define GEN_PQ_STRIDE 4 +#define GEN_PQ_DEFINE() {} #define GEN_PQ_D 0, 1, 2, 3 -#define GEN_PQ_P 4, 5, 6, 7 -#define GEN_PQ_Q 20, 21, 22, 23 - -#define GEN_PQR_DEFINE() {} -#define GEN_PQR_STRIDE 2 -#define GEN_PQR_D 0, 1 -#define GEN_PQR_P 2, 3 -#define GEN_PQR_Q 4, 5 -#define GEN_PQR_R 6, 7 - -#define REC_P_DEFINE() {} -#define REC_P_STRIDE 4 -#define REC_P_X 0, 1, 2, 3 - -#define REC_Q_DEFINE() {} -#define REC_Q_STRIDE 4 -#define REC_Q_X 0, 1, 2, 3 - -#define REC_R_DEFINE() {} -#define REC_R_STRIDE 4 -#define REC_R_X 0, 1, 2, 3 - -#define REC_PQ_DEFINE() {} -#define REC_PQ_STRIDE 4 -#define REC_PQ_X 0, 1, 2, 3 -#define REC_PQ_Y 4, 5, 6, 7 -#define REC_PQ_D 20, 21, 22, 23 - -#define REC_PR_DEFINE() {} -#define REC_PR_STRIDE 4 -#define REC_PR_X 0, 1, 2, 3 -#define REC_PR_Y 4, 5, 6, 7 -#define REC_PR_D 20, 21, 22, 23 - -#define REC_QR_DEFINE() {} -#define REC_QR_STRIDE 4 -#define REC_QR_X 0, 1, 2, 3 -#define REC_QR_Y 4, 5, 6, 7 -#define REC_QR_D 20, 21, 22, 23 - -#define REC_PQR_DEFINE() {} +#define GEN_PQ_C 4, 5, 6, 7 + +#define GEN_PQR_STRIDE 4 +#define GEN_PQR_DEFINE() {} +#define GEN_PQR_D 0, 1, 2, 3 +#define GEN_PQR_C 4, 5, 6, 7 + +#define SYN_Q_DEFINE() {} +#define SYN_Q_D 0, 1, 2, 3 +#define SYN_Q_X 4, 5, 6, 7 + +#define SYN_R_DEFINE() {} +#define SYN_R_D 0, 1, 2, 3 +#define SYN_R_X 4, 5, 6, 7 + +#define SYN_PQ_DEFINE() {} +#define SYN_PQ_D 0, 1, 2, 3 +#define SYN_PQ_X 4, 5, 6, 7 + +#define REC_PQ_STRIDE 2 +#define REC_PQ_DEFINE() {} +#define REC_PQ_X 0, 1 +#define REC_PQ_Y 2, 3 +#define REC_PQ_T 4, 5 + +#define SYN_PR_DEFINE() {} +#define SYN_PR_D 0, 1, 2, 3 +#define SYN_PR_X 4, 5, 6, 7 + +#define REC_PR_STRIDE 2 +#define REC_PR_DEFINE() {} +#define REC_PR_X 0, 1 +#define REC_PR_Y 2, 3 +#define REC_PR_T 4, 5 + +#define SYN_QR_DEFINE() {} +#define SYN_QR_D 0, 1, 2, 3 +#define SYN_QR_X 4, 5, 6, 7 + +#define REC_QR_STRIDE 2 +#define REC_QR_DEFINE() {} +#define REC_QR_X 0, 1 +#define REC_QR_Y 2, 3 +#define REC_QR_T 4, 5 + +#define SYN_PQR_DEFINE() {} +#define SYN_PQR_D 0, 1, 2, 3 +#define SYN_PQR_X 4, 5, 6, 7 + #define REC_PQR_STRIDE 2 +#define REC_PQR_DEFINE() {} #define REC_PQR_X 0, 1 #define REC_PQR_Y 2, 3 #define REC_PQR_Z 4, 5 -#define REC_PQR_D 6, 7 #define REC_PQR_XS 6, 7 #define REC_PQR_YS 8, 9 diff --git a/module/zfs/zfs_vfsops.c b/module/zfs/zfs_vfsops.c index 1c3dccdca..3c76cfe6f 100644 --- a/module/zfs/zfs_vfsops.c +++ b/module/zfs/zfs_vfsops.c @@ -744,19 +744,17 @@ zfs_sb_create(const char *osname, zfs_mntopts_t *zmo, zfs_sb_t **zsbp) zsb = kmem_zalloc(sizeof (zfs_sb_t), KM_SLEEP); /* - * We claim to always be readonly so we can open snapshots; - * other ZPL code will prevent us from writing to snapshots. + * Optional temporary mount options, free'd in zfs_sb_free(). */ - error = dmu_objset_own(osname, DMU_OST_ZFS, B_TRUE, zsb, &os); - if (error) { - kmem_free(zsb, sizeof (zfs_sb_t)); - return (error); - } + zsb->z_mntopts = (zmo ? zmo : zfs_mntopts_alloc()); /* - * Optional temporary mount options, free'd in zfs_sb_free(). + * We claim to always be readonly so we can open snapshots; + * other ZPL code will prevent us from writing to snapshots. */ - zsb->z_mntopts = (zmo ? zmo : zfs_mntopts_alloc()); + error = dmu_objset_own(osname, DMU_OST_ZFS, B_TRUE, zsb, &os); + if (error) + goto out_zmo; /* * Initialize the zfs-specific filesystem structure. @@ -896,8 +894,9 @@ zfs_sb_create(const char *osname, zfs_mntopts_t *zmo, zfs_sb_t **zsbp) out: dmu_objset_disown(os, zsb); +out_zmo: *zsbp = NULL; - + zfs_mntopts_free(zsb->z_mntopts); kmem_free(zsb, sizeof (zfs_sb_t)); return (error); } diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run index 8a9785992..5e16395b9 100644 --- a/tests/runfiles/linux.run +++ b/tests/runfiles/linux.run @@ -365,7 +365,8 @@ user = [tests/functional/cli_user/zpool_iostat] tests = ['zpool_iostat_001_neg', 'zpool_iostat_002_pos', - 'zpool_iostat_003_neg', 'zpool_iostat_004_pos'] + 'zpool_iostat_003_neg', 'zpool_iostat_004_pos', + 'zpool_iostat_005_pos'] user = [tests/functional/cli_user/zpool_list] diff --git a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh index be38933c1..89a9a4975 100755 --- a/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh +++ b/tests/zfs-tests/tests/functional/cli_user/zpool_iostat/zpool_iostat_005_pos.ksh @@ -54,10 +54,27 @@ fi # a '/' when we specify the path (-P) flag. We check for "{}" to see if one # of the VDEV variables isn't set. # -C1=$($ZPOOL iostat -Pv | $GREP -E '^\s+/' | $WC -l) -C2=$($ZPOOL iostat -Pv -c 'echo vdev_test{$VDEV_PATH}{$VDEV_UPATH}' | $GREP -E '^\s+/' | $GREP -v '{}' | $WC -l) +C1=$($ZPOOL iostat -Pv $testpool | $GREP -E '^\s+/' | $WC -l) +C2=$($ZPOOL iostat -Pv -c 'echo vdev_test{$VDEV_PATH}{$VDEV_UPATH}' $testpool \ + | $GREP -E '^\s+/' | $GREP -v '{}' | $WC -l) if [ "$C1" != "$C2" ] ; then log_fail "zpool iostat -c failed, expected $C1 vdevs, got $C2" else - log_pass "zpool iostat -c passed, expected $C1 vdevs, got $C2" + log_note "zpool iostat -c passed, expected $C1 vdevs, got $C2" +fi + +# Call iostat on only a specific vdev, and verify that the command only gets +# run on the vdev. We write the command results to a temp file to verify that +# the command actually gets run, rather than just verifying that the results +# are *displayed* for the specific vdev. +TMP=$($MKTEMP) +FIRST_VDEV=$($ZPOOL iostat -Pv $testpool | $GREP -Eo '^\s+/[^ ]+' | $HEAD -n 1) +log_must $ZPOOL iostat -Pv -c "echo \$VDEV_PATH >> $TMP" $testpool \ + $FIRST_VDEV > /dev/null +C2=$($WC -w < $TMP) +$RM $TMP +if [ "$C2" != "1" ] ; then + log_fail "zpool iostat -c <VDEV> failed, expected 1 vdev, got $C2" +else + log_note "zpool iostat -c <VDEV> passed, expected 1 vdev, got $C2" fi |