commit
d1cafc0892
@ -0,0 +1,11 @@ |
|||||||
|
Facebook Inc. |
||||||
|
Facebook Engineering Team |
||||||
|
|
||||||
|
Google Inc. |
||||||
|
# Initial version authors: |
||||||
|
Jeffrey Dean <jeff@google.com> |
||||||
|
Sanjay Ghemawat <sanjay@google.com> |
||||||
|
|
||||||
|
# Partial list of contributors: |
||||||
|
Kevin Regan <kevin.d.regan@gmail.com> |
||||||
|
Johan Bilien <jobi@litl.com> |
@ -0,0 +1,16 @@ |
|||||||
|
Vagrant.configure("2") do |config| |
||||||
|
|
||||||
|
config.vm.provider "virtualbox" do |v| |
||||||
|
v.memory = 4096 |
||||||
|
v.cpus = 2 |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.define "ubuntu14" do |box| |
||||||
|
box.vm.box = "ubuntu/trusty64" |
||||||
|
end |
||||||
|
|
||||||
|
config.vm.define "centos65" do |box| |
||||||
|
box.vm.box = "chef/centos-6.5" |
||||||
|
end |
||||||
|
|
||||||
|
end |
@ -1,74 +0,0 @@ |
|||||||
#!/bin/sh |
|
||||||
# |
|
||||||
# Set environment variables so that we can compile leveldb using |
|
||||||
# fbcode settings. It uses the latest g++ compiler and also |
|
||||||
# uses jemalloc |
|
||||||
|
|
||||||
TOOLCHAIN_REV=fbe3b095a4cc4a3713730050d182b7b4a80c342f |
|
||||||
TOOLCHAIN_EXECUTABLES="/mnt/gvfs/third-party/$TOOLCHAIN_REV/centos5.2-native" |
|
||||||
TOOLCHAIN_LIB_BASE="/mnt/gvfs/third-party/$TOOLCHAIN_REV/gcc-4.7.1-glibc-2.14.1" |
|
||||||
TOOL_JEMALLOC=jemalloc-3.3.1/9202ce3 |
|
||||||
GLIBC_RUNTIME_PATH=/usr/local/fbcode/gcc-4.7.1-glibc-2.14.1 |
|
||||||
|
|
||||||
# location of libgcc |
|
||||||
LIBGCC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include" |
|
||||||
LIBGCC_LIBS=" -L $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/libs" |
|
||||||
|
|
||||||
# location of glibc |
|
||||||
GLIBC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/include" |
|
||||||
GLIBC_LIBS=" -L $TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/lib" |
|
||||||
|
|
||||||
# location of snappy headers and libraries |
|
||||||
SNAPPY_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/7518bbe/include" |
|
||||||
SNAPPY_LIBS=" $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/7518bbe/lib/libsnappy.a" |
|
||||||
|
|
||||||
# location of zlib headers and libraries |
|
||||||
ZLIB_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/91ddd43/include" |
|
||||||
ZLIB_LIBS=" $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/91ddd43/lib/libz.a" |
|
||||||
|
|
||||||
# location of gflags headers and libraries |
|
||||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/include" |
|
||||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/lib/libgflags.a" |
|
||||||
|
|
||||||
# location of bzip headers and libraries |
|
||||||
BZIP_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/91ddd43/include" |
|
||||||
BZIP_LIBS=" $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/91ddd43/lib/libbz2.a" |
|
||||||
|
|
||||||
# location of gflags headers and libraries |
|
||||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/include" |
|
||||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/lib/libgflags.a" |
|
||||||
|
|
||||||
# use Intel SSE support for checksum calculations |
|
||||||
export USE_SSE=" -msse -msse4.2 " |
|
||||||
|
|
||||||
CC="$TOOLCHAIN_EXECUTABLES/clang/clang-3.2/0b7c69d/bin/clang $CLANG_INCLUDES" |
|
||||||
CXX="$TOOLCHAIN_EXECUTABLES/clang/clang-3.2/0b7c69d/bin/clang++ $CLANG_INCLUDES $JINCLUDE $SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $GFLAGS_INCLUDE" |
|
||||||
AR=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ar |
|
||||||
RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib |
|
||||||
|
|
||||||
CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin -nostdlib " |
|
||||||
CFLAGS+=" -nostdinc -isystem $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1 " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/x86_64-facebook-linux " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/backward " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/include " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/clang/clang-3.2/0b7c69d/lib/clang/3.2/include " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/kernel-headers/kernel-headers-3.2.18_70_fbk11_00129_gc8882d0/da39a3e/include/linux " |
|
||||||
CFLAGS+=" -isystem $TOOLCHAIN_LIB_BASE/kernel-headers/kernel-headers-3.2.18_70_fbk11_00129_gc8882d0/da39a3e/include " |
|
||||||
CFLAGS+=" -Wall -Wno-sign-compare -Wno-unused-variable -Winvalid-pch -Wno-deprecated -Woverloaded-virtual" |
|
||||||
CFLAGS+=" $LIBGCC_INCLUDE $GLIBC_INCLUDE" |
|
||||||
CXXFLAGS="$CFLAGS -nostdinc++" |
|
||||||
|
|
||||||
CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC" |
|
||||||
|
|
||||||
EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a" |
|
||||||
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $TOOLCHAIN_LIB_BASE/libunwind/libunwind-1.0.1/350336c/lib/libunwind.a" |
|
||||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $GFLAGS_LIBS" |
|
||||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,$GLIBC_RUNTIME_PATH/lib/ld-linux-x86-64.so.2" |
|
||||||
EXEC_LDFLAGS+=" -B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin" |
|
||||||
|
|
||||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS " |
|
||||||
|
|
||||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $GFLAGS_LIBS" |
|
||||||
|
|
||||||
export CC CXX AR RANLIB CFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED |
|
@ -1,70 +0,0 @@ |
|||||||
#!/bin/sh |
|
||||||
# |
|
||||||
# Set environment variables so that we can compile leveldb using |
|
||||||
# fbcode settings. It uses the latest g++ compiler and also |
|
||||||
# uses jemalloc |
|
||||||
|
|
||||||
TOOLCHAIN_REV=fbe3b095a4cc4a3713730050d182b7b4a80c342f |
|
||||||
TOOLCHAIN_EXECUTABLES="/mnt/gvfs/third-party/$TOOLCHAIN_REV/centos5.2-native" |
|
||||||
TOOLCHAIN_LIB_BASE="/mnt/gvfs/third-party/$TOOLCHAIN_REV/gcc-4.7.1-glibc-2.14.1" |
|
||||||
TOOL_JEMALLOC=jemalloc-3.3.1/9202ce3 |
|
||||||
|
|
||||||
# location of libhdfs libraries |
|
||||||
if test "$USE_HDFS"; then |
|
||||||
JAVA_HOME="/usr/local/jdk-6u22-64" |
|
||||||
JINCLUDE="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux" |
|
||||||
GLIBC_RUNTIME_PATH="/usr/local/fbcode/gcc-4.7.1-glibc-2.14.1" |
|
||||||
HDFSLIB=" -Wl,--no-whole-archive hdfs/libhdfs.a -L$JAVA_HOME/jre/lib/amd64 " |
|
||||||
HDFSLIB+=" -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib " |
|
||||||
HDFSLIB+=" -ldl -lverify -ljava -ljvm " |
|
||||||
fi |
|
||||||
|
|
||||||
# location of libgcc |
|
||||||
LIBGCC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/include" |
|
||||||
LIBGCC_LIBS=" -L $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.7.1/afc21dc/libs" |
|
||||||
|
|
||||||
# location of glibc |
|
||||||
GLIBC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/include" |
|
||||||
GLIBC_LIBS=" -L $TOOLCHAIN_LIB_BASE/glibc/glibc-2.14.1/99df8fc/lib" |
|
||||||
|
|
||||||
# location of snappy headers and libraries |
|
||||||
SNAPPY_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/7518bbe/include" |
|
||||||
SNAPPY_LIBS=" $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/7518bbe/lib/libsnappy.a" |
|
||||||
|
|
||||||
# location of zlib headers and libraries |
|
||||||
ZLIB_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/91ddd43/include" |
|
||||||
ZLIB_LIBS=" $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/91ddd43/lib/libz.a" |
|
||||||
|
|
||||||
# location of bzip headers and libraries |
|
||||||
BZIP_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/91ddd43/include" |
|
||||||
BZIP_LIBS=" $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/91ddd43/lib/libbz2.a" |
|
||||||
|
|
||||||
# location of gflags headers and libraries |
|
||||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/include" |
|
||||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/91ddd43/lib/libgflags.a" |
|
||||||
|
|
||||||
# use Intel SSE support for checksum calculations |
|
||||||
export USE_SSE=" -msse -msse4.2 " |
|
||||||
|
|
||||||
CC="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1-glibc-2.14.1/bin/gcc" |
|
||||||
CXX="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.7.1-glibc-2.14.1/bin/g++ $JINCLUDE $SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $GFLAGS_INCLUDE" |
|
||||||
AR=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ar |
|
||||||
RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib |
|
||||||
|
|
||||||
CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic" |
|
||||||
CFLAGS+=" -I $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/include -DHAVE_JEMALLOC" |
|
||||||
CFLAGS+=" $LIBGCC_INCLUDE $GLIBC_INCLUDE" |
|
||||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT -DROCKSDB_FALLOCATE_PRESENT" |
|
||||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2" |
|
||||||
|
|
||||||
EXEC_LDFLAGS=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/$TOOL_JEMALLOC/lib/libjemalloc.a" |
|
||||||
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $TOOLCHAIN_LIB_BASE/libunwind/libunwind-1.0.1/350336c/lib/libunwind.a" |
|
||||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $GFLAGS_LIBS" |
|
||||||
|
|
||||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS " |
|
||||||
|
|
||||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $GFLAGS_LIBS" |
|
||||||
|
|
||||||
VALGRIND_VER="$TOOLCHAIN_LIB_BASE/valgrind/valgrind-3.8.1/91ddd43/bin/" |
|
||||||
|
|
||||||
export CC CXX AR RANLIB CFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER |
|
@ -1,86 +0,0 @@ |
|||||||
#!/bin/sh |
|
||||||
# |
|
||||||
# Set environment variables so that we can compile rocksdb using |
|
||||||
# fbcode settings. It uses the latest g++ compiler and also |
|
||||||
# uses jemalloc |
|
||||||
|
|
||||||
TOOLCHAIN_REV=53dc1fe83f84e9145b9ffb81b81aa7f6a49c87cc |
|
||||||
CENTOS_VERSION=`rpm -q --qf "%{VERSION}" $(rpm -q --whatprovides redhat-release)` |
|
||||||
if [ "$CENTOS_VERSION" = "6" ]; then |
|
||||||
TOOLCHAIN_EXECUTABLES="/mnt/gvfs/third-party/$TOOLCHAIN_REV/centos6-native" |
|
||||||
else |
|
||||||
TOOLCHAIN_EXECUTABLES="/mnt/gvfs/third-party/$TOOLCHAIN_REV/centos5.2-native" |
|
||||||
fi |
|
||||||
TOOLCHAIN_LIB_BASE="/mnt/gvfs/third-party/$TOOLCHAIN_REV/gcc-4.8.1-glibc-2.17" |
|
||||||
|
|
||||||
# location of libhdfs libraries |
|
||||||
if test "$USE_HDFS"; then |
|
||||||
JAVA_HOME="/usr/local/jdk-6u22-64" |
|
||||||
JINCLUDE="-I$JAVA_HOME/include -I$JAVA_HOME/include/linux" |
|
||||||
GLIBC_RUNTIME_PATH="/usr/local/fbcode/gcc-4.8.1-glibc-2.17" |
|
||||||
HDFSLIB=" -Wl,--no-whole-archive hdfs/libhdfs.a -L$JAVA_HOME/jre/lib/amd64 " |
|
||||||
HDFSLIB+=" -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib " |
|
||||||
HDFSLIB+=" -ldl -lverify -ljava -ljvm " |
|
||||||
fi |
|
||||||
|
|
||||||
# location of libgcc |
|
||||||
LIBGCC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.8.1/8aac7fc/include" |
|
||||||
LIBGCC_LIBS=" -L $TOOLCHAIN_LIB_BASE/libgcc/libgcc-4.8.1/8aac7fc/libs" |
|
||||||
|
|
||||||
# location of glibc |
|
||||||
GLIBC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/glibc/glibc-2.17/99df8fc/include" |
|
||||||
GLIBC_LIBS=" -L $TOOLCHAIN_LIB_BASE/glibc/glibc-2.17/99df8fc/lib" |
|
||||||
|
|
||||||
# location of snappy headers and libraries |
|
||||||
SNAPPY_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/43d84e2/include" |
|
||||||
SNAPPY_LIBS=" $TOOLCHAIN_LIB_BASE/snappy/snappy-1.0.3/43d84e2/lib/libsnappy.a" |
|
||||||
|
|
||||||
# location of zlib headers and libraries |
|
||||||
ZLIB_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/c3f970a/include" |
|
||||||
ZLIB_LIBS=" $TOOLCHAIN_LIB_BASE/zlib/zlib-1.2.5/c3f970a/lib/libz.a" |
|
||||||
|
|
||||||
# location of bzip headers and libraries |
|
||||||
BZIP_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/c3f970a/include" |
|
||||||
BZIP_LIBS=" $TOOLCHAIN_LIB_BASE/bzip2/bzip2-1.0.6/c3f970a/lib/libbz2.a" |
|
||||||
|
|
||||||
LZ4_REV=065ec7e38fe83329031f6668c43bef83eff5808b |
|
||||||
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/include" |
|
||||||
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/lib/liblz4.a" |
|
||||||
|
|
||||||
# location of gflags headers and libraries |
|
||||||
GFLAGS_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/c3f970a/include" |
|
||||||
GFLAGS_LIBS=" $TOOLCHAIN_LIB_BASE/gflags/gflags-1.6/c3f970a/lib/libgflags.a" |
|
||||||
|
|
||||||
# location of jemalloc |
|
||||||
JEMALLOC_INCLUDE=" -I $TOOLCHAIN_LIB_BASE/jemalloc/jemalloc-3.4.1/4d53c6f/include/" |
|
||||||
JEMALLOC_LIB=" -Wl,--whole-archive $TOOLCHAIN_LIB_BASE/jemalloc/jemalloc-3.4.1/4d53c6f/lib/libjemalloc.a" |
|
||||||
|
|
||||||
# location of numa |
|
||||||
NUMA_REV=829d10dac0230f99cd7e1778869d2adf3da24b65 |
|
||||||
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/include/" |
|
||||||
NUMA_LIB=" /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/lib/libnuma.a" |
|
||||||
|
|
||||||
# use Intel SSE support for checksum calculations |
|
||||||
export USE_SSE=" -msse -msse4.2 " |
|
||||||
|
|
||||||
CC="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.8.1/cc6c9dc/bin/gcc" |
|
||||||
CXX="$TOOLCHAIN_EXECUTABLES/gcc/gcc-4.8.1/cc6c9dc/bin/g++ $JINCLUDE $SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE" |
|
||||||
AR=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ar |
|
||||||
RANLIB=$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/ranlib |
|
||||||
|
|
||||||
CFLAGS="-B$TOOLCHAIN_EXECUTABLES/binutils/binutils-2.21.1/da39a3e/bin/gold -m64 -mtune=generic" |
|
||||||
CFLAGS+=" $LIBGCC_INCLUDE $GLIBC_INCLUDE" |
|
||||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_ATOMIC_PRESENT -DROCKSDB_FALLOCATE_PRESENT" |
|
||||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DNUMA" |
|
||||||
|
|
||||||
EXEC_LDFLAGS="-Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so" |
|
||||||
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $TOOLCHAIN_LIB_BASE/libunwind/libunwind-1.0.1/675d945/lib/libunwind.a" |
|
||||||
EXEC_LDFLAGS+=" $HDFSLIB $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB" |
|
||||||
|
|
||||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS " |
|
||||||
|
|
||||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS" |
|
||||||
|
|
||||||
VALGRIND_VER="$TOOLCHAIN_LIB_BASE/valgrind/valgrind-3.8.1/c3f970a/bin/" |
|
||||||
|
|
||||||
export CC CXX AR RANLIB CFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE |
|
@ -0,0 +1,125 @@ |
|||||||
|
#!/bin/sh |
||||||
|
# |
||||||
|
# Set environment variables so that we can compile rocksdb using |
||||||
|
# fbcode settings. It uses the latest g++ and clang compilers and also |
||||||
|
# uses jemalloc |
||||||
|
# Environment variables that change the behavior of this script: |
||||||
|
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included |
||||||
|
|
||||||
|
CFLAGS="" |
||||||
|
|
||||||
|
# location of libgcc |
||||||
|
LIBGCC_BASE="/mnt/gvfs/third-party2/libgcc/0473c80518a10d6efcbe24c5eeca3fb4ec9b519c/4.9.x/gcc-4.9-glibc-2.20/e1a7e4e" |
||||||
|
LIBGCC_INCLUDE="$LIBGCC_BASE/include" |
||||||
|
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs" |
||||||
|
|
||||||
|
# location of glibc |
||||||
|
GLIBC_REV=7397bed99280af5d9543439cdb7d018af7542720 |
||||||
|
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/include" |
||||||
|
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.20/gcc-4.9-glibc-2.20/99df8fc/lib" |
||||||
|
|
||||||
|
# location of snappy headers and libraries |
||||||
|
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/include/" |
||||||
|
if test -z $PIC_BUILD; then |
||||||
|
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy.a" |
||||||
|
else |
||||||
|
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/b0f269b3ca47770121aa159b99e1d8d2ab260e1f/1.0.3/gcc-4.9-glibc-2.20/c32916f/lib/libsnappy_pic.a" |
||||||
|
fi |
||||||
|
CFLAGS+=" -DSNAPPY" |
||||||
|
|
||||||
|
if test -z $PIC_BUILD; then |
||||||
|
# location of zlib headers and libraries |
||||||
|
ZLIB_INCLUDE=" -I /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/include/" |
||||||
|
ZLIB_LIBS=" /mnt/gvfs/third-party2/zlib/feb983d9667f4cf5e9da07ce75abc824764b67a1/1.2.8/gcc-4.9-glibc-2.20/4230243/lib/libz.a" |
||||||
|
CFLAGS+=" -DZLIB" |
||||||
|
|
||||||
|
# location of bzip headers and libraries |
||||||
|
BZIP_INCLUDE=" -I /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/include/" |
||||||
|
BZIP_LIBS=" /mnt/gvfs/third-party2/bzip2/af004cceebb2dfd173ca29933ea5915e727aad2f/1.0.6/gcc-4.9-glibc-2.20/4230243/lib/libbz2.a" |
||||||
|
CFLAGS+=" -DBZIP2" |
||||||
|
|
||||||
|
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/include/" |
||||||
|
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/79d2943e2dd7208a3e0b06cf95e9f85f05fe9e1b/r124/gcc-4.9-glibc-2.20/4230243/lib/liblz4.a" |
||||||
|
CFLAGS+=" -DLZ4" |
||||||
|
fi |
||||||
|
|
||||||
|
# location of gflags headers and libraries |
||||||
|
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/include/" |
||||||
|
if test -z $PIC_BUILD; then |
||||||
|
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags.a" |
||||||
|
else |
||||||
|
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/0fa60e2b88de3e469db6c482d6e6dac72f5d65f9/1.6/gcc-4.9-glibc-2.20/4230243/lib/libgflags_pic.a" |
||||||
|
fi |
||||||
|
CFLAGS+=" -DGFLAGS=google" |
||||||
|
|
||||||
|
# location of jemalloc |
||||||
|
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/include/" |
||||||
|
JEMALLOC_LIB=" -Wl,--whole-archive /mnt/gvfs/third-party2/jemalloc/bcd68e5e419efa4e61b9486d6854564d6d75a0b5/3.6.0/gcc-4.9-glibc-2.20/2aafc78/lib/libjemalloc.a" |
||||||
|
|
||||||
|
if test -z $PIC_BUILD; then |
||||||
|
# location of numa |
||||||
|
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/include/" |
||||||
|
NUMA_LIB=" /mnt/gvfs/third-party2/numa/bbefc39ecbf31d0ca184168eb613ef8d397790ee/2.0.8/gcc-4.9-glibc-2.20/4230243/lib/libnuma.a" |
||||||
|
CFLAGS+=" -DNUMA" |
||||||
|
|
||||||
|
# location of libunwind |
||||||
|
LIBUNWIND="/mnt/gvfs/third-party2/libunwind/1de3b75e0afedfe5585b231bbb340ec7a1542335/1.1/gcc-4.9-glibc-2.20/34235e8/lib/libunwind.a" |
||||||
|
fi |
||||||
|
|
||||||
|
# use Intel SSE support for checksum calculations |
||||||
|
export USE_SSE=1 |
||||||
|
|
||||||
|
BINUTILS="/mnt/gvfs/third-party2/binutils/0b6ad0c88ddd903333a48ae8bff134efac468e4a/2.25/centos6-native/da39a3e/bin" |
||||||
|
AR="$BINUTILS/ar" |
||||||
|
|
||||||
|
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE" |
||||||
|
|
||||||
|
GCC_BASE="/mnt/gvfs/third-party2/gcc/1c67a0b88f64d4d9ced0382d141c76aaa7d62fba/4.9.x/centos6-native/1317bc4" |
||||||
|
STDLIBS="-L $GCC_BASE/lib64" |
||||||
|
|
||||||
|
CLANG_BASE="/mnt/gvfs/third-party2/clang/290704c112bf894bf4a30d7bbd1be81e34998473/dev" |
||||||
|
CLANG_ANALYZER="$CLANG_BASE/centos6-native/af4b1a0/bin/clang++" |
||||||
|
CLANG_SCAN_BUILD="$CLANG_BASE/src/clang/tools/scan-build/scan-build" |
||||||
|
|
||||||
|
if [ -z "$USE_CLANG" ]; then |
||||||
|
# gcc |
||||||
|
CC="$GCC_BASE/bin/gcc" |
||||||
|
CXX="$GCC_BASE/bin/g++" |
||||||
|
|
||||||
|
CFLAGS+=" -B$BINUTILS/gold" |
||||||
|
CFLAGS+=" -isystem $GLIBC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_INCLUDE" |
||||||
|
else |
||||||
|
# clang |
||||||
|
CLANG_INCLUDE="$CLANG_BASE/gcc-4.9-glibc-2.20/74c386f/lib/clang/dev/include/" |
||||||
|
CC="$CLANG_BASE/centos6-native/af4b1a0/bin/clang" |
||||||
|
CXX="$CLANG_BASE/centos6-native/af4b1a0/bin/clang++" |
||||||
|
|
||||||
|
KERNEL_HEADERS_INCLUDE="/mnt/gvfs/third-party2/kernel-headers/ffd14f660a43c4b92717986b1bba66722ef089d0/3.2.18_70_fbk11_00129_gc8882d0/gcc-4.9-glibc-2.20/da39a3e/include" |
||||||
|
|
||||||
|
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x " |
||||||
|
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.9.x/x86_64-facebook-linux " |
||||||
|
CFLAGS+=" -isystem $GLIBC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $CLANG_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux " |
||||||
|
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE " |
||||||
|
CXXFLAGS="-nostdinc++" |
||||||
|
fi |
||||||
|
|
||||||
|
CFLAGS+=" $DEPS_INCLUDE" |
||||||
|
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT" |
||||||
|
CXXFLAGS+=" $CFLAGS" |
||||||
|
|
||||||
|
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB" |
||||||
|
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.9-glibc-2.20/lib/ld.so" |
||||||
|
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $LIBUNWIND" |
||||||
|
|
||||||
|
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++" |
||||||
|
|
||||||
|
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS" |
||||||
|
|
||||||
|
VALGRIND_VER="/mnt/gvfs/third-party2/valgrind/6c45ef049cbf11c2df593addb712cd891049e737/3.10.0/gcc-4.9-glibc-2.20/4230243/bin/" |
||||||
|
|
||||||
|
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD |
@ -0,0 +1,105 @@ |
|||||||
|
#!/bin/sh |
||||||
|
# |
||||||
|
# Set environment variables so that we can compile rocksdb using |
||||||
|
# fbcode settings. It uses the latest g++ compiler and also |
||||||
|
# uses jemalloc |
||||||
|
|
||||||
|
# location of libgcc |
||||||
|
LIBGCC_BASE="/mnt/gvfs/third-party2/libgcc/7712e757d7355cb51292454ee0b7b46a467fdfed/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc" |
||||||
|
LIBGCC_INCLUDE="$LIBGCC_BASE/include" |
||||||
|
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs" |
||||||
|
|
||||||
|
# location of glibc |
||||||
|
GLIBC_REV=6e40560b4e0b6d690fd1cf8c7a43ad7452b04cfa |
||||||
|
GLIBC_INCLUDE="/mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.17/gcc-4.8.1-glibc-2.17/99df8fc/include" |
||||||
|
GLIBC_LIBS=" -L /mnt/gvfs/third-party2/glibc/$GLIBC_REV/2.17/gcc-4.8.1-glibc-2.17/99df8fc/lib" |
||||||
|
|
||||||
|
# location of snappy headers and libraries |
||||||
|
SNAPPY_INCLUDE=" -I /mnt/gvfs/third-party2/snappy/aef17f6c0b44b4fe408bd06f67c93701ab0a6ceb/1.0.3/gcc-4.8.1-glibc-2.17/43d84e2/include" |
||||||
|
SNAPPY_LIBS=" /mnt/gvfs/third-party2/snappy/aef17f6c0b44b4fe408bd06f67c93701ab0a6ceb/1.0.3/gcc-4.8.1-glibc-2.17/43d84e2/lib/libsnappy.a" |
||||||
|
|
||||||
|
# location of zlib headers and libraries |
||||||
|
ZLIB_INCLUDE=" -I /mnt/gvfs/third-party2/zlib/25c6216928b4d77b59ddeca0990ff6fe9ac16b81/1.2.5/gcc-4.8.1-glibc-2.17/c3f970a/include" |
||||||
|
ZLIB_LIBS=" /mnt/gvfs/third-party2/zlib/25c6216928b4d77b59ddeca0990ff6fe9ac16b81/1.2.5/gcc-4.8.1-glibc-2.17/c3f970a/lib/libz.a" |
||||||
|
|
||||||
|
# location of bzip headers and libraries |
||||||
|
BZIP_INCLUDE=" -I /mnt/gvfs/third-party2/bzip2/c9ef7629c2aa0024f7a416e87602f06eb88f5eac/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a/include/" |
||||||
|
BZIP_LIBS=" /mnt/gvfs/third-party2/bzip2/c9ef7629c2aa0024f7a416e87602f06eb88f5eac/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a/lib/libbz2.a" |
||||||
|
|
||||||
|
LZ4_REV=065ec7e38fe83329031f6668c43bef83eff5808b |
||||||
|
LZ4_INCLUDE=" -I /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/include" |
||||||
|
LZ4_LIBS=" /mnt/gvfs/third-party2/lz4/$LZ4_REV/r108/gcc-4.8.1-glibc-2.17/c3f970a/lib/liblz4.a" |
||||||
|
|
||||||
|
# location of gflags headers and libraries |
||||||
|
GFLAGS_INCLUDE=" -I /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc670868205a243a/1.6/gcc-4.8.1-glibc-2.17/c3f970a/include/" |
||||||
|
GFLAGS_LIBS=" /mnt/gvfs/third-party2/gflags/1ad047a6e6f6673991918ecadc670868205a243a/1.6/gcc-4.8.1-glibc-2.17/c3f970a/lib/libgflags.a" |
||||||
|
|
||||||
|
# location of jemalloc |
||||||
|
JEMALLOC_INCLUDE=" -I /mnt/gvfs/third-party2/jemalloc/c60d854f7824f334195fe7fd34b2bc9057e3c1f9/3.6.0/gcc-4.8.1-glibc-2.17/4d53c6f/include" |
||||||
|
JEMALLOC_LIB=" -Wl,--whole-archive /mnt/gvfs/third-party2/jemalloc/c60d854f7824f334195fe7fd34b2bc9057e3c1f9/3.6.0/gcc-4.8.1-glibc-2.17/4d53c6f/lib/libjemalloc.a" |
||||||
|
|
||||||
|
# location of numa |
||||||
|
NUMA_REV=829d10dac0230f99cd7e1778869d2adf3da24b65 |
||||||
|
NUMA_INCLUDE=" -I /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/include/" |
||||||
|
NUMA_LIB=" /mnt/gvfs/third-party2/numa/$NUMA_REV/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a/lib/libnuma.a" |
||||||
|
|
||||||
|
# location of libunwind |
||||||
|
LIBUNWIND_REV=2c060e64064559905d46fd194000d61592087bdc |
||||||
|
LIBUNWIND="/mnt/gvfs/third-party2/libunwind/$LIBUNWIND_REV/1.1/gcc-4.8.1-glibc-2.17/675d945/lib/libunwind.a" |
||||||
|
|
||||||
|
# use Intel SSE support for checksum calculations |
||||||
|
export USE_SSE=1 |
||||||
|
|
||||||
|
BINUTILS="/mnt/gvfs/third-party2/binutils/2aff2e7b474cd3e6ab23495ad1224b7d214b9f8e/2.21.1/centos6-native/da39a3e/bin" |
||||||
|
AR="$BINUTILS/ar" |
||||||
|
|
||||||
|
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE" |
||||||
|
|
||||||
|
GCC_BASE="/mnt/gvfs/third-party2/gcc/1ec615e23800f0815d474478ba476a0adc3fe788/4.8.1/centos6-native/cc6c9dc" |
||||||
|
STDLIBS="-L $GCC_BASE/lib64" |
||||||
|
|
||||||
|
if [ -z "$USE_CLANG" ]; then |
||||||
|
# gcc |
||||||
|
CC="$GCC_BASE/bin/gcc" |
||||||
|
CXX="$GCC_BASE/bin/g++" |
||||||
|
|
||||||
|
CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic" |
||||||
|
CFLAGS+=" -isystem $GLIBC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_INCLUDE" |
||||||
|
else |
||||||
|
# clang |
||||||
|
CLANG_BASE="/mnt/gvfs/third-party2/clang/9ab68376f938992c4eb5946ca68f90c3185cffc8/3.4" |
||||||
|
CLANG_INCLUDE="$CLANG_BASE/gcc-4.8.1-glibc-2.17/fb0f730/lib/clang/3.4/include" |
||||||
|
CC="$CLANG_BASE/centos6-native/9cefd8a/bin/clang" |
||||||
|
CXX="$CLANG_BASE/centos6-native/9cefd8a/bin/clang++" |
||||||
|
|
||||||
|
KERNEL_HEADERS_INCLUDE="/mnt/gvfs/third-party2/kernel-headers/a683ed7135276731065a9d76d3016c9731f4e2f9/3.2.18_70_fbk11_00129_gc8882d0/gcc-4.8.1-glibc-2.17/da39a3e/include/" |
||||||
|
|
||||||
|
CFLAGS="-B$BINUTILS/gold -nostdinc -nostdlib" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1 " |
||||||
|
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1/x86_64-facebook-linux " |
||||||
|
CFLAGS+=" -isystem $GLIBC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $LIBGCC_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $CLANG_INCLUDE" |
||||||
|
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux " |
||||||
|
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE " |
||||||
|
CXXFLAGS="-nostdinc++" |
||||||
|
fi |
||||||
|
|
||||||
|
CFLAGS+=" $DEPS_INCLUDE" |
||||||
|
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_FALLOCATE_PRESENT" |
||||||
|
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DNUMA" |
||||||
|
CXXFLAGS+=" $CFLAGS" |
||||||
|
|
||||||
|
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS $NUMA_LIB" |
||||||
|
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so" |
||||||
|
EXEC_LDFLAGS+=" -Wl,--no-whole-archive $LIBUNWIND" |
||||||
|
|
||||||
|
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++" |
||||||
|
|
||||||
|
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $GFLAGS_LIBS" |
||||||
|
|
||||||
|
VALGRIND_REV=b2a9f85e4b70cd03abc85a7f3027fbc4cef35bd0 |
||||||
|
VALGRIND_VER="/mnt/gvfs/third-party2/valgrind/$VALGRIND_REV/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a/bin/" |
||||||
|
|
||||||
|
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE |
@ -1,25 +0,0 @@ |
|||||||
#!/bin/sh |
|
||||||
# Install gflags for mac developers. |
|
||||||
|
|
||||||
set -e |
|
||||||
|
|
||||||
DIR=`mktemp -d /tmp/rocksdb_gflags_XXXX` |
|
||||||
|
|
||||||
cd $DIR |
|
||||||
wget https://gflags.googlecode.com/files/gflags-2.0.tar.gz |
|
||||||
tar xvfz gflags-2.0.tar.gz |
|
||||||
cd gflags-2.0 |
|
||||||
|
|
||||||
./configure |
|
||||||
make |
|
||||||
make install |
|
||||||
|
|
||||||
# Add include/lib path for g++ |
|
||||||
echo 'export LIBRARY_PATH+=":/usr/local/lib"' >> ~/.bash_profile |
|
||||||
echo 'export CPATH+=":/usr/local/include"' >> ~/.bash_profile |
|
||||||
|
|
||||||
echo "" |
|
||||||
echo "-----------------------------------------------------------------------------" |
|
||||||
echo "| Installation Completed |" |
|
||||||
echo "-----------------------------------------------------------------------------" |
|
||||||
echo "Please run \`. ~/.bash_profile\` to be able to compile with gflags" |
|
@ -0,0 +1,116 @@ |
|||||||
|
#/usr/bin/env bash |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
function log() { |
||||||
|
echo "[+] $1" |
||||||
|
} |
||||||
|
|
||||||
|
function fatal() { |
||||||
|
echo "[!] $1" |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
function platform() { |
||||||
|
local __resultvar=$1 |
||||||
|
if [[ -f "/etc/yum.conf" ]]; then |
||||||
|
eval $__resultvar="centos" |
||||||
|
elif [[ -f "/etc/dpkg/dpkg.cfg" ]]; then |
||||||
|
eval $__resultvar="ubuntu" |
||||||
|
else |
||||||
|
fatal "Unknwon operating system" |
||||||
|
fi |
||||||
|
} |
||||||
|
platform OS |
||||||
|
|
||||||
|
function package() { |
||||||
|
if [[ $OS = "ubuntu" ]]; then |
||||||
|
if dpkg --get-selections | grep --quiet $1; then |
||||||
|
log "$1 is already installed. skipping." |
||||||
|
else |
||||||
|
apt-get install $@ -y |
||||||
|
fi |
||||||
|
elif [[ $OS = "centos" ]]; then |
||||||
|
if rpm -qa | grep --quiet $1; then |
||||||
|
log "$1 is already installed. skipping." |
||||||
|
else |
||||||
|
yum install $@ -y |
||||||
|
fi |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
function detect_fpm_output() { |
||||||
|
if [[ $OS = "ubuntu" ]]; then |
||||||
|
export FPM_OUTPUT=deb |
||||||
|
elif [[ $OS = "centos" ]]; then |
||||||
|
export FPM_OUTPUT=rpm |
||||||
|
fi |
||||||
|
} |
||||||
|
detect_fpm_output |
||||||
|
|
||||||
|
function gem_install() { |
||||||
|
if gem list | grep --quiet $1; then |
||||||
|
log "$1 is already installed. skipping." |
||||||
|
else |
||||||
|
gem install $@ |
||||||
|
fi |
||||||
|
} |
||||||
|
|
||||||
|
function main() { |
||||||
|
if [[ $# -ne 1 ]]; then |
||||||
|
fatal "Usage: $0 <rocksdb_version>" |
||||||
|
else |
||||||
|
log "using rocksdb version: $1" |
||||||
|
fi |
||||||
|
|
||||||
|
if [[ -d /vagrant ]]; then |
||||||
|
if [[ $OS = "ubuntu" ]]; then |
||||||
|
package g++-4.7 |
||||||
|
export CXX=g++-4.7 |
||||||
|
|
||||||
|
# the deb would depend on libgflags2, but the static lib is the only thing |
||||||
|
# installed by make install |
||||||
|
package libgflags-dev |
||||||
|
|
||||||
|
package ruby-all-dev |
||||||
|
elif [[ $OS = "centos" ]]; then |
||||||
|
pushd /etc/yum.repos.d |
||||||
|
if [[ ! -f /etc/yum.repos.d/devtools-1.1.repo ]]; then |
||||||
|
wget http://people.centos.org/tru/devtools-1.1/devtools-1.1.repo |
||||||
|
fi |
||||||
|
package devtoolset-1.1-gcc --enablerepo=testing-1.1-devtools-6 |
||||||
|
package devtoolset-1.1-gcc-c++ --enablerepo=testing-1.1-devtools-6 |
||||||
|
export CC=/opt/centos/devtoolset-1.1/root/usr/bin/gcc |
||||||
|
export CPP=/opt/centos/devtoolset-1.1/root/usr/bin/cpp |
||||||
|
export CXX=/opt/centos/devtoolset-1.1/root/usr/bin/c++ |
||||||
|
export PATH=$PATH:/opt/centos/devtoolset-1.1/root/usr/bin |
||||||
|
popd |
||||||
|
if ! rpm -qa | grep --quiet gflags; then |
||||||
|
rpm -i https://github.com/schuhschuh/gflags/releases/download/v2.1.0/gflags-devel-2.1.0-1.amd64.rpm |
||||||
|
fi |
||||||
|
|
||||||
|
package ruby |
||||||
|
package ruby-devel |
||||||
|
package rubygems |
||||||
|
package rpm-build |
||||||
|
fi |
||||||
|
fi |
||||||
|
gem_install fpm |
||||||
|
|
||||||
|
make static_lib |
||||||
|
make install INSTALL_PATH=package |
||||||
|
fpm \ |
||||||
|
-s dir \ |
||||||
|
-t $FPM_OUTPUT \ |
||||||
|
-n rocksdb \ |
||||||
|
-v $1 \ |
||||||
|
--prefix /usr \ |
||||||
|
--url http://rocksdb.org/ \ |
||||||
|
-m rocksdb@fb.com \ |
||||||
|
--license BSD \ |
||||||
|
--vendor Facebook \ |
||||||
|
--description "RocksDB is an embeddable persistent key-value store for fast storage." \ |
||||||
|
package |
||||||
|
} |
||||||
|
|
||||||
|
main $@ |
@ -0,0 +1,14 @@ |
|||||||
|
#!/bin/sh |
||||||
|
if [ "$#" = "0" ]; then |
||||||
|
echo "Usage: $0 major|minor|patch" |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
if [ "$1" = "major" ]; then |
||||||
|
cat include/rocksdb/version.h | grep MAJOR | head -n1 | awk '{print $3}' |
||||||
|
fi |
||||||
|
if [ "$1" = "minor" ]; then |
||||||
|
cat include/rocksdb/version.h | grep MINOR | head -n1 | awk '{print $3}' |
||||||
|
fi |
||||||
|
if [ "$1" = "patch" ]; then |
||||||
|
cat include/rocksdb/version.h | grep PATCH | head -n1 | awk '{print $3}' |
||||||
|
fi |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,127 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
#include <deque> |
||||||
|
#include <limits> |
||||||
|
#include <set> |
||||||
|
#include <utility> |
||||||
|
#include <vector> |
||||||
|
#include <string> |
||||||
|
#include <functional> |
||||||
|
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/log_writer.h" |
||||||
|
#include "db/snapshot.h" |
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/version_edit.h" |
||||||
|
#include "db/memtable_list.h" |
||||||
|
#include "port/port.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/memtablerep.h" |
||||||
|
#include "rocksdb/compaction_filter.h" |
||||||
|
#include "rocksdb/transaction_log.h" |
||||||
|
#include "util/autovector.h" |
||||||
|
#include "util/stop_watch.h" |
||||||
|
#include "util/thread_local.h" |
||||||
|
#include "util/scoped_arena_iterator.h" |
||||||
|
#include "db/internal_stats.h" |
||||||
|
#include "db/write_controller.h" |
||||||
|
#include "db/flush_scheduler.h" |
||||||
|
#include "db/write_thread.h" |
||||||
|
#include "db/job_context.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class MemTable; |
||||||
|
class TableCache; |
||||||
|
class Version; |
||||||
|
class VersionEdit; |
||||||
|
class VersionSet; |
||||||
|
class Arena; |
||||||
|
|
||||||
|
class CompactionJob { |
||||||
|
public: |
||||||
|
// TODO(icanadi) make effort to reduce number of parameters here
|
||||||
|
// IMPORTANT: mutable_cf_options needs to be alive while CompactionJob is
|
||||||
|
// alive
|
||||||
|
CompactionJob(Compaction* compaction, const DBOptions& db_options, |
||||||
|
const MutableCFOptions& mutable_cf_options, |
||||||
|
const EnvOptions& env_options, VersionSet* versions, |
||||||
|
std::atomic<bool>* shutting_down, LogBuffer* log_buffer, |
||||||
|
Directory* db_directory, Directory* output_directory, |
||||||
|
Statistics* stats, SnapshotList* snapshot_list, |
||||||
|
bool is_snapshot_supported, std::shared_ptr<Cache> table_cache, |
||||||
|
std::function<uint64_t()> yield_callback); |
||||||
|
|
||||||
|
~CompactionJob() { assert(compact_ == nullptr); } |
||||||
|
|
||||||
|
// no copy/move
|
||||||
|
CompactionJob(CompactionJob&& job) = delete; |
||||||
|
CompactionJob(const CompactionJob& job) = delete; |
||||||
|
CompactionJob& operator=(const CompactionJob& job) = delete; |
||||||
|
|
||||||
|
// REQUIRED: mutex held
|
||||||
|
void Prepare(); |
||||||
|
// REQUIRED mutex not held
|
||||||
|
Status Run(); |
||||||
|
// REQUIRED: mutex held
|
||||||
|
// status is the return of Run()
|
||||||
|
void Install(Status* status, InstrumentedMutex* db_mutex); |
||||||
|
|
||||||
|
private: |
||||||
|
void AllocateCompactionOutputFileNumbers(); |
||||||
|
// Call compaction filter if is_compaction_v2 is not true. Then iterate
|
||||||
|
// through input and compact the kv-pairs
|
||||||
|
Status ProcessKeyValueCompaction(int64_t* imm_micros, Iterator* input, |
||||||
|
bool is_compaction_v2); |
||||||
|
// Call compaction_filter_v2->Filter() on kv-pairs in compact
|
||||||
|
void CallCompactionFilterV2(CompactionFilterV2* compaction_filter_v2); |
||||||
|
Status FinishCompactionOutputFile(Iterator* input); |
||||||
|
Status InstallCompactionResults(InstrumentedMutex* db_mutex); |
||||||
|
SequenceNumber findEarliestVisibleSnapshot( |
||||||
|
SequenceNumber in, const std::vector<SequenceNumber>& snapshots, |
||||||
|
SequenceNumber* prev_snapshot); |
||||||
|
void RecordCompactionIOStats(); |
||||||
|
Status OpenCompactionOutputFile(); |
||||||
|
void CleanupCompaction(const Status& status); |
||||||
|
|
||||||
|
// CompactionJob state
|
||||||
|
struct CompactionState; |
||||||
|
CompactionState* compact_; |
||||||
|
|
||||||
|
bool bottommost_level_; |
||||||
|
SequenceNumber earliest_snapshot_; |
||||||
|
SequenceNumber visible_at_tip_; |
||||||
|
SequenceNumber latest_snapshot_; |
||||||
|
|
||||||
|
InternalStats::CompactionStats compaction_stats_; |
||||||
|
|
||||||
|
// DBImpl state
|
||||||
|
const DBOptions& db_options_; |
||||||
|
const MutableCFOptions& mutable_cf_options_; |
||||||
|
const EnvOptions& env_options_; |
||||||
|
Env* env_; |
||||||
|
VersionSet* versions_; |
||||||
|
std::atomic<bool>* shutting_down_; |
||||||
|
LogBuffer* log_buffer_; |
||||||
|
Directory* db_directory_; |
||||||
|
Directory* output_directory_; |
||||||
|
Statistics* stats_; |
||||||
|
SnapshotList* snapshots_; |
||||||
|
bool is_snapshot_supported_; |
||||||
|
std::shared_ptr<Cache> table_cache_; |
||||||
|
|
||||||
|
// yield callback
|
||||||
|
std::function<uint64_t()> yield_callback_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,183 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "db/compaction_job.h" |
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "db/writebuffer.h" |
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
#include "table/mock_table.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||||
|
class CompactionJobTest { |
||||||
|
public: |
||||||
|
CompactionJobTest() |
||||||
|
: env_(Env::Default()), |
||||||
|
dbname_(test::TmpDir() + "/compaction_job_test"), |
||||||
|
mutable_cf_options_(Options(), ImmutableCFOptions(Options())), |
||||||
|
table_cache_(NewLRUCache(50000, 16, 8)), |
||||||
|
write_buffer_(db_options_.db_write_buffer_size), |
||||||
|
versions_(new VersionSet(dbname_, &db_options_, env_options_, |
||||||
|
table_cache_.get(), &write_buffer_, |
||||||
|
&write_controller_)), |
||||||
|
shutting_down_(false), |
||||||
|
mock_table_factory_(new mock::MockTableFactory()) { |
||||||
|
ASSERT_OK(env_->CreateDirIfMissing(dbname_)); |
||||||
|
db_options_.db_paths.emplace_back(dbname_, |
||||||
|
std::numeric_limits<uint64_t>::max()); |
||||||
|
NewDB(); |
||||||
|
std::vector<ColumnFamilyDescriptor> column_families; |
||||||
|
cf_options_.table_factory = mock_table_factory_; |
||||||
|
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_); |
||||||
|
|
||||||
|
|
||||||
|
ASSERT_OK(versions_->Recover(column_families, false)); |
||||||
|
} |
||||||
|
|
||||||
|
std::string GenerateFileName(uint64_t file_number) { |
||||||
|
FileMetaData meta; |
||||||
|
std::vector<DbPath> db_paths; |
||||||
|
db_paths.emplace_back(dbname_, std::numeric_limits<uint64_t>::max()); |
||||||
|
meta.fd = FileDescriptor(file_number, 0, 0); |
||||||
|
return TableFileName(db_paths, meta.fd.GetNumber(), meta.fd.GetPathId()); |
||||||
|
} |
||||||
|
|
||||||
|
// returns expected result after compaction
|
||||||
|
mock::MockFileContents CreateTwoFiles() { |
||||||
|
mock::MockFileContents expected_results; |
||||||
|
const int kKeysPerFile = 10000; |
||||||
|
SequenceNumber sequence_number = 0; |
||||||
|
for (int i = 0; i < 2; ++i) { |
||||||
|
mock::MockFileContents contents; |
||||||
|
SequenceNumber smallest_seqno = 0, largest_seqno = 0; |
||||||
|
InternalKey smallest, largest; |
||||||
|
for (int k = 0; k < kKeysPerFile; ++k) { |
||||||
|
auto key = ToString(i * (kKeysPerFile / 2) + k); |
||||||
|
auto value = ToString(i * kKeysPerFile + k); |
||||||
|
InternalKey internal_key(key, ++sequence_number, kTypeValue); |
||||||
|
if (k == 0) { |
||||||
|
smallest = internal_key; |
||||||
|
smallest_seqno = sequence_number; |
||||||
|
} else if (k == kKeysPerFile - 1) { |
||||||
|
largest = internal_key; |
||||||
|
largest_seqno = sequence_number; |
||||||
|
} |
||||||
|
std::pair<std::string, std::string> key_value( |
||||||
|
{internal_key.Encode().ToString(), value}); |
||||||
|
contents.insert(key_value); |
||||||
|
if (i == 1 || k < kKeysPerFile / 2) { |
||||||
|
expected_results.insert(key_value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t file_number = versions_->NewFileNumber(); |
||||||
|
ASSERT_OK(mock_table_factory_->CreateMockTable( |
||||||
|
env_, GenerateFileName(file_number), std::move(contents))); |
||||||
|
|
||||||
|
VersionEdit edit; |
||||||
|
edit.AddFile(0, file_number, 0, 10, smallest, largest, smallest_seqno, |
||||||
|
largest_seqno); |
||||||
|
|
||||||
|
mutex_.Lock(); |
||||||
|
versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(), |
||||||
|
mutable_cf_options_, &edit, &mutex_); |
||||||
|
mutex_.Unlock(); |
||||||
|
} |
||||||
|
versions_->SetLastSequence(sequence_number); |
||||||
|
return expected_results; |
||||||
|
} |
||||||
|
|
||||||
|
void NewDB() { |
||||||
|
VersionEdit new_db; |
||||||
|
new_db.SetLogNumber(0); |
||||||
|
new_db.SetNextFile(2); |
||||||
|
new_db.SetLastSequence(0); |
||||||
|
|
||||||
|
const std::string manifest = DescriptorFileName(dbname_, 1); |
||||||
|
unique_ptr<WritableFile> file; |
||||||
|
Status s = env_->NewWritableFile( |
||||||
|
manifest, &file, env_->OptimizeForManifestWrite(env_options_)); |
||||||
|
ASSERT_OK(s); |
||||||
|
{ |
||||||
|
log::Writer log(std::move(file)); |
||||||
|
std::string record; |
||||||
|
new_db.EncodeTo(&record); |
||||||
|
s = log.AddRecord(record); |
||||||
|
} |
||||||
|
ASSERT_OK(s); |
||||||
|
// Make "CURRENT" file that points to the new manifest file.
|
||||||
|
s = SetCurrentFile(env_, dbname_, 1, nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
Env* env_; |
||||||
|
std::string dbname_; |
||||||
|
EnvOptions env_options_; |
||||||
|
MutableCFOptions mutable_cf_options_; |
||||||
|
std::shared_ptr<Cache> table_cache_; |
||||||
|
WriteController write_controller_; |
||||||
|
DBOptions db_options_; |
||||||
|
ColumnFamilyOptions cf_options_; |
||||||
|
WriteBuffer write_buffer_; |
||||||
|
std::unique_ptr<VersionSet> versions_; |
||||||
|
InstrumentedMutex mutex_; |
||||||
|
std::atomic<bool> shutting_down_; |
||||||
|
std::shared_ptr<mock::MockTableFactory> mock_table_factory_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(CompactionJobTest, Simple) { |
||||||
|
auto cfd = versions_->GetColumnFamilySet()->GetDefault(); |
||||||
|
|
||||||
|
auto expected_results = CreateTwoFiles(); |
||||||
|
|
||||||
|
auto files = cfd->current()->storage_info()->LevelFiles(0); |
||||||
|
ASSERT_EQ(2U, files.size()); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(Compaction::TEST_NewCompaction( |
||||||
|
7, 0, 1, 1024 * 1024, 10, 0, kNoCompression)); |
||||||
|
compaction->SetInputVersion(cfd->current()); |
||||||
|
|
||||||
|
auto compaction_input_files = compaction->TEST_GetInputFiles(0); |
||||||
|
compaction_input_files->level = 0; |
||||||
|
compaction_input_files->files.push_back(files[0]); |
||||||
|
compaction_input_files->files.push_back(files[1]); |
||||||
|
|
||||||
|
SnapshotList snapshots; |
||||||
|
int yield_callback_called = 0; |
||||||
|
std::function<uint64_t()> yield_callback = [&]() { |
||||||
|
yield_callback_called++; |
||||||
|
return 0; |
||||||
|
}; |
||||||
|
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get()); |
||||||
|
mutex_.Lock(); |
||||||
|
CompactionJob compaction_job(compaction.get(), db_options_, |
||||||
|
*cfd->GetLatestMutableCFOptions(), env_options_, |
||||||
|
versions_.get(), &shutting_down_, &log_buffer, |
||||||
|
nullptr, nullptr, nullptr, &snapshots, true, |
||||||
|
table_cache_, std::move(yield_callback)); |
||||||
|
compaction_job.Prepare(); |
||||||
|
mutex_.Unlock(); |
||||||
|
ASSERT_OK(compaction_job.Run()); |
||||||
|
mutex_.Lock(); |
||||||
|
Status s; |
||||||
|
compaction_job.Install(&s, &mutex_); |
||||||
|
ASSERT_OK(s); |
||||||
|
mutex_.Unlock(); |
||||||
|
|
||||||
|
mock_table_factory_->AssertLatestFile(expected_results); |
||||||
|
ASSERT_EQ(yield_callback_called, 20000); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,259 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include "db/compaction_picker.h" |
||||||
|
#include <limits> |
||||||
|
#include <string> |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class CountingLogger : public Logger { |
||||||
|
public: |
||||||
|
using Logger::Logv; |
||||||
|
virtual void Logv(const char* format, va_list ap) override { log_count++; } |
||||||
|
size_t log_count; |
||||||
|
}; |
||||||
|
|
||||||
|
class CompactionPickerTest { |
||||||
|
public: |
||||||
|
const Comparator* ucmp_; |
||||||
|
InternalKeyComparator icmp_; |
||||||
|
Options options_; |
||||||
|
ImmutableCFOptions ioptions_; |
||||||
|
MutableCFOptions mutable_cf_options_; |
||||||
|
LevelCompactionPicker level_compaction_picker; |
||||||
|
std::string cf_name_; |
||||||
|
CountingLogger logger_; |
||||||
|
LogBuffer log_buffer_; |
||||||
|
uint32_t file_num_; |
||||||
|
CompactionOptionsFIFO fifo_options_; |
||||||
|
std::unique_ptr<VersionStorageInfo> vstorage_; |
||||||
|
std::vector<std::unique_ptr<FileMetaData>> files_; |
||||||
|
|
||||||
|
CompactionPickerTest() |
||||||
|
: ucmp_(BytewiseComparator()), |
||||||
|
icmp_(ucmp_), |
||||||
|
ioptions_(options_), |
||||||
|
mutable_cf_options_(options_, ioptions_), |
||||||
|
level_compaction_picker(ioptions_, &icmp_), |
||||||
|
cf_name_("dummy"), |
||||||
|
log_buffer_(InfoLogLevel::INFO_LEVEL, &logger_), |
||||||
|
file_num_(1), |
||||||
|
vstorage_(nullptr) { |
||||||
|
fifo_options_.max_table_files_size = 1; |
||||||
|
mutable_cf_options_.RefreshDerivedOptions(ioptions_); |
||||||
|
ioptions_.db_paths.emplace_back("dummy", |
||||||
|
std::numeric_limits<uint64_t>::max()); |
||||||
|
} |
||||||
|
|
||||||
|
~CompactionPickerTest() { |
||||||
|
} |
||||||
|
|
||||||
|
void NewVersionStorage(int num_levels, CompactionStyle style) { |
||||||
|
DeleteVersionStorage(); |
||||||
|
options_.num_levels = num_levels; |
||||||
|
vstorage_.reset(new VersionStorageInfo( |
||||||
|
&icmp_, ucmp_, options_.num_levels, style, nullptr)); |
||||||
|
} |
||||||
|
|
||||||
|
void DeleteVersionStorage() { |
||||||
|
vstorage_.reset(); |
||||||
|
files_.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
void Add(int level, uint32_t file_number, const char* smallest, |
||||||
|
const char* largest, uint64_t file_size = 0, uint32_t path_id = 0, |
||||||
|
SequenceNumber smallest_seq = 100, |
||||||
|
SequenceNumber largest_seq = 100) { |
||||||
|
assert(level < vstorage_->num_levels()); |
||||||
|
FileMetaData* f = new FileMetaData; |
||||||
|
f->fd = FileDescriptor(file_number, path_id, file_size); |
||||||
|
f->smallest = InternalKey(smallest, smallest_seq, kTypeValue); |
||||||
|
f->largest = InternalKey(largest, largest_seq, kTypeValue); |
||||||
|
f->compensated_file_size = file_size; |
||||||
|
f->refs = 0; |
||||||
|
vstorage_->AddFile(level, f); |
||||||
|
files_.emplace_back(f); |
||||||
|
} |
||||||
|
|
||||||
|
void UpdateVersionStorageInfo() { |
||||||
|
vstorage_->UpdateFilesBySize(); |
||||||
|
vstorage_->UpdateNumNonEmptyLevels(); |
||||||
|
vstorage_->GenerateFileIndexer(); |
||||||
|
vstorage_->GenerateLevelFilesBrief(); |
||||||
|
vstorage_->ComputeCompactionScore(mutable_cf_options_, fifo_options_); |
||||||
|
vstorage_->SetFinalized(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, Empty) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() == nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, Single) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
mutable_cf_options_.level0_file_num_compaction_trigger = 2; |
||||||
|
Add(0, 1U, "p", "q"); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() == nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, Level0Trigger) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
mutable_cf_options_.level0_file_num_compaction_trigger = 2; |
||||||
|
Add(0, 1U, "150", "200"); |
||||||
|
Add(0, 2U, "200", "250"); |
||||||
|
|
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() != nullptr); |
||||||
|
ASSERT_EQ(2U, compaction->num_input_files(0)); |
||||||
|
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber()); |
||||||
|
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, Level1Trigger) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
Add(1, 66U, "150", "200", 1000000000U); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() != nullptr); |
||||||
|
ASSERT_EQ(1U, compaction->num_input_files(0)); |
||||||
|
ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, Level1Trigger2) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
Add(1, 66U, "150", "200", 1000000001U); |
||||||
|
Add(1, 88U, "201", "300", 1000000000U); |
||||||
|
Add(2, 6U, "150", "179", 1000000000U); |
||||||
|
Add(2, 7U, "180", "220", 1000000000U); |
||||||
|
Add(2, 8U, "221", "300", 1000000000U); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() != nullptr); |
||||||
|
ASSERT_EQ(1U, compaction->num_input_files(0)); |
||||||
|
ASSERT_EQ(2U, compaction->num_input_files(1)); |
||||||
|
ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber()); |
||||||
|
ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber()); |
||||||
|
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, LevelMaxScore) { |
||||||
|
NewVersionStorage(6, kCompactionStyleLevel); |
||||||
|
mutable_cf_options_.target_file_size_base = 10000000; |
||||||
|
mutable_cf_options_.target_file_size_multiplier = 10; |
||||||
|
Add(0, 1U, "150", "200", 1000000000U); |
||||||
|
// Level 1 score 1.2
|
||||||
|
Add(1, 66U, "150", "200", 6000000U); |
||||||
|
Add(1, 88U, "201", "300", 6000000U); |
||||||
|
// Level 2 score 1.8. File 7 is the largest. Should be picked
|
||||||
|
Add(2, 6U, "150", "179", 60000000U); |
||||||
|
Add(2, 7U, "180", "220", 60000001U); |
||||||
|
Add(2, 8U, "221", "300", 60000000U); |
||||||
|
// Level 3 score slightly larger than 1
|
||||||
|
Add(3, 26U, "150", "170", 260000000U); |
||||||
|
Add(3, 27U, "171", "179", 260000000U); |
||||||
|
Add(3, 28U, "191", "220", 260000000U); |
||||||
|
Add(3, 29U, "221", "300", 260000000U); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction( |
||||||
|
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_)); |
||||||
|
ASSERT_TRUE(compaction.get() != nullptr); |
||||||
|
ASSERT_EQ(1U, compaction->num_input_files(0)); |
||||||
|
ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber()); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, NeedsCompactionLevel) { |
||||||
|
const int kLevels = 6; |
||||||
|
const int kFileCount = 20; |
||||||
|
for (int level = 0; level < kLevels - 1; ++level) { |
||||||
|
uint64_t file_size = |
||||||
|
mutable_cf_options_.MaxBytesForLevel(level) * 2 / kFileCount; |
||||||
|
for (int file_count = 1; file_count <= kFileCount; ++file_count) { |
||||||
|
// start a brand new version in each test.
|
||||||
|
NewVersionStorage(kLevels, kCompactionStyleLevel); |
||||||
|
for (int i = 0; i < file_count; ++i) { |
||||||
|
Add(level, i, ToString((i + 100) * 1000).c_str(), |
||||||
|
ToString((i + 100) * 1000 + 999).c_str(), |
||||||
|
file_size, 0, i * 100, i * 100 + 99); |
||||||
|
} |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
ASSERT_EQ(vstorage_->CompactionScoreLevel(0), level); |
||||||
|
ASSERT_EQ(level_compaction_picker.NeedsCompaction(vstorage_.get()), |
||||||
|
vstorage_->CompactionScore(0) >= 1); |
||||||
|
// release the version storage
|
||||||
|
DeleteVersionStorage(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, NeedsCompactionUniversal) { |
||||||
|
NewVersionStorage(1, kCompactionStyleUniversal); |
||||||
|
UniversalCompactionPicker universal_compaction_picker( |
||||||
|
ioptions_, &icmp_); |
||||||
|
// must return false when there's no files.
|
||||||
|
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()), |
||||||
|
false); |
||||||
|
|
||||||
|
// verify the trigger given different number of L0 files.
|
||||||
|
for (int i = 1; |
||||||
|
i <= mutable_cf_options_.level0_file_num_compaction_trigger * 2; ++i) { |
||||||
|
Add(0, i, ToString((i + 100) * 1000).c_str(), |
||||||
|
ToString((i + 100) * 1000 + 999).c_str(), 1000000, 0, i * 100, |
||||||
|
i * 100 + 99); |
||||||
|
ASSERT_EQ(level_compaction_picker.NeedsCompaction(vstorage_.get()), |
||||||
|
vstorage_->CompactionScore(0) >= 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(CompactionPickerTest, NeedsCompactionFIFO) { |
||||||
|
NewVersionStorage(1, kCompactionStyleFIFO); |
||||||
|
const int kFileCount = |
||||||
|
mutable_cf_options_.level0_file_num_compaction_trigger * 3; |
||||||
|
const uint64_t kFileSize = 100000; |
||||||
|
const uint64_t kMaxSize = kFileSize * kFileCount / 2; |
||||||
|
|
||||||
|
fifo_options_.max_table_files_size = kMaxSize; |
||||||
|
ioptions_.compaction_options_fifo = fifo_options_; |
||||||
|
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_); |
||||||
|
|
||||||
|
// must return false when there's no files.
|
||||||
|
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), false); |
||||||
|
|
||||||
|
// verify whether compaction is needed based on the current
|
||||||
|
// size of L0 files.
|
||||||
|
uint64_t current_size = 0; |
||||||
|
for (int i = 1; i <= kFileCount; ++i) { |
||||||
|
Add(0, i, ToString((i + 100) * 1000).c_str(), |
||||||
|
ToString((i + 100) * 1000 + 999).c_str(), |
||||||
|
kFileSize, 0, i * 100, i * 100 + 99); |
||||||
|
current_size += kFileSize; |
||||||
|
ASSERT_EQ(level_compaction_picker.NeedsCompaction(vstorage_.get()), |
||||||
|
vstorage_->CompactionScore(0) >= 1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
@ -0,0 +1,434 @@ |
|||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "util/hash.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
#include "utilities/merge_operators.h" |
||||||
|
|
||||||
|
using std::unique_ptr; |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
namespace { |
||||||
|
|
||||||
|
static const Comparator* comparator; |
||||||
|
|
||||||
|
// A comparator for std::map, using comparator
|
||||||
|
struct MapComparator { |
||||||
|
bool operator()(const std::string& a, const std::string& b) const { |
||||||
|
return comparator->Compare(a, b) < 0; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
typedef std::map<std::string, std::string, MapComparator> KVMap; |
||||||
|
|
||||||
|
class KVIter : public Iterator { |
||||||
|
public: |
||||||
|
explicit KVIter(const KVMap* map) : map_(map), iter_(map_->end()) {} |
||||||
|
virtual bool Valid() const { return iter_ != map_->end(); } |
||||||
|
virtual void SeekToFirst() { iter_ = map_->begin(); } |
||||||
|
virtual void SeekToLast() { |
||||||
|
if (map_->empty()) { |
||||||
|
iter_ = map_->end(); |
||||||
|
} else { |
||||||
|
iter_ = map_->find(map_->rbegin()->first); |
||||||
|
} |
||||||
|
} |
||||||
|
virtual void Seek(const Slice& k) { iter_ = map_->lower_bound(k.ToString()); } |
||||||
|
virtual void Next() { ++iter_; } |
||||||
|
virtual void Prev() { |
||||||
|
if (iter_ == map_->begin()) { |
||||||
|
iter_ = map_->end(); |
||||||
|
return; |
||||||
|
} |
||||||
|
--iter_; |
||||||
|
} |
||||||
|
|
||||||
|
virtual Slice key() const { return iter_->first; } |
||||||
|
virtual Slice value() const { return iter_->second; } |
||||||
|
virtual Status status() const { return Status::OK(); } |
||||||
|
|
||||||
|
private: |
||||||
|
const KVMap* const map_; |
||||||
|
KVMap::const_iterator iter_; |
||||||
|
}; |
||||||
|
|
||||||
|
void AssertItersEqual(Iterator* iter1, Iterator* iter2) { |
||||||
|
ASSERT_EQ(iter1->Valid(), iter2->Valid()); |
||||||
|
if (iter1->Valid()) { |
||||||
|
ASSERT_EQ(iter1->key().ToString(), iter2->key().ToString()); |
||||||
|
ASSERT_EQ(iter1->value().ToString(), iter2->value().ToString()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Measuring operations on DB (expect to be empty).
|
||||||
|
// source_strings are candidate keys
|
||||||
|
void DoRandomIteraratorTest(DB* db, std::vector<std::string> source_strings, |
||||||
|
Random* rnd, int num_writes, int num_iter_ops, |
||||||
|
int num_trigger_flush) { |
||||||
|
KVMap map; |
||||||
|
|
||||||
|
for (int i = 0; i < num_writes; i++) { |
||||||
|
if (num_trigger_flush > 0 && i != 0 && i % num_trigger_flush == 0) { |
||||||
|
db->Flush(FlushOptions()); |
||||||
|
} |
||||||
|
|
||||||
|
int type = rnd->Uniform(2); |
||||||
|
int index = rnd->Uniform(static_cast<int>(source_strings.size())); |
||||||
|
auto& key = source_strings[index]; |
||||||
|
switch (type) { |
||||||
|
case 0: |
||||||
|
// put
|
||||||
|
map[key] = key; |
||||||
|
ASSERT_OK(db->Put(WriteOptions(), key, key)); |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
// delete
|
||||||
|
if (map.find(key) != map.end()) { |
||||||
|
map.erase(key); |
||||||
|
} |
||||||
|
ASSERT_OK(db->Delete(WriteOptions(), key)); |
||||||
|
break; |
||||||
|
default: |
||||||
|
assert(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions())); |
||||||
|
std::unique_ptr<Iterator> result_iter(new KVIter(&map)); |
||||||
|
|
||||||
|
bool is_valid = false; |
||||||
|
for (int i = 0; i < num_iter_ops; i++) { |
||||||
|
// Random walk and make sure iter and result_iter returns the
|
||||||
|
// same key and value
|
||||||
|
int type = rnd->Uniform(6); |
||||||
|
ASSERT_OK(iter->status()); |
||||||
|
switch (type) { |
||||||
|
case 0: |
||||||
|
// Seek to First
|
||||||
|
iter->SeekToFirst(); |
||||||
|
result_iter->SeekToFirst(); |
||||||
|
break; |
||||||
|
case 1: |
||||||
|
// Seek to last
|
||||||
|
iter->SeekToLast(); |
||||||
|
result_iter->SeekToLast(); |
||||||
|
break; |
||||||
|
case 2: { |
||||||
|
// Seek to random key
|
||||||
|
auto key_idx = rnd->Uniform(static_cast<int>(source_strings.size())); |
||||||
|
auto key = source_strings[key_idx]; |
||||||
|
iter->Seek(key); |
||||||
|
result_iter->Seek(key); |
||||||
|
break; |
||||||
|
} |
||||||
|
case 3: |
||||||
|
// Next
|
||||||
|
if (is_valid) { |
||||||
|
iter->Next(); |
||||||
|
result_iter->Next(); |
||||||
|
} else { |
||||||
|
continue; |
||||||
|
} |
||||||
|
break; |
||||||
|
case 4: |
||||||
|
// Prev
|
||||||
|
if (is_valid) { |
||||||
|
iter->Prev(); |
||||||
|
result_iter->Prev(); |
||||||
|
} else { |
||||||
|
continue; |
||||||
|
} |
||||||
|
break; |
||||||
|
default: { |
||||||
|
assert(type == 5); |
||||||
|
auto key_idx = rnd->Uniform(static_cast<int>(source_strings.size())); |
||||||
|
auto key = source_strings[key_idx]; |
||||||
|
std::string result; |
||||||
|
auto status = db->Get(ReadOptions(), key, &result); |
||||||
|
if (map.find(key) == map.end()) { |
||||||
|
ASSERT_TRUE(status.IsNotFound()); |
||||||
|
} else { |
||||||
|
ASSERT_EQ(map[key], result); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
AssertItersEqual(iter.get(), result_iter.get()); |
||||||
|
is_valid = iter->Valid(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class DoubleComparator : public Comparator { |
||||||
|
public: |
||||||
|
DoubleComparator() {} |
||||||
|
|
||||||
|
virtual const char* Name() const { return "DoubleComparator"; } |
||||||
|
|
||||||
|
virtual int Compare(const Slice& a, const Slice& b) const { |
||||||
|
double da = std::stod(a.ToString()); |
||||||
|
double db = std::stod(b.ToString()); |
||||||
|
if (da == db) { |
||||||
|
return a.compare(b); |
||||||
|
} else if (da > db) { |
||||||
|
return 1; |
||||||
|
} else { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
} |
||||||
|
virtual void FindShortestSeparator(std::string* start, |
||||||
|
const Slice& limit) const {} |
||||||
|
|
||||||
|
virtual void FindShortSuccessor(std::string* key) const {} |
||||||
|
}; |
||||||
|
|
||||||
|
class HashComparator : public Comparator { |
||||||
|
public: |
||||||
|
HashComparator() {} |
||||||
|
|
||||||
|
virtual const char* Name() const { return "HashComparator"; } |
||||||
|
|
||||||
|
virtual int Compare(const Slice& a, const Slice& b) const { |
||||||
|
uint32_t ha = Hash(a.data(), a.size(), 66); |
||||||
|
uint32_t hb = Hash(b.data(), b.size(), 66); |
||||||
|
if (ha == hb) { |
||||||
|
return a.compare(b); |
||||||
|
} else if (ha > hb) { |
||||||
|
return 1; |
||||||
|
} else { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
} |
||||||
|
virtual void FindShortestSeparator(std::string* start, |
||||||
|
const Slice& limit) const {} |
||||||
|
|
||||||
|
virtual void FindShortSuccessor(std::string* key) const {} |
||||||
|
}; |
||||||
|
|
||||||
|
class TwoStrComparator : public Comparator { |
||||||
|
public: |
||||||
|
TwoStrComparator() {} |
||||||
|
|
||||||
|
virtual const char* Name() const { return "TwoStrComparator"; } |
||||||
|
|
||||||
|
virtual int Compare(const Slice& a, const Slice& b) const { |
||||||
|
assert(a.size() >= 2); |
||||||
|
assert(b.size() >= 2); |
||||||
|
size_t size_a1 = static_cast<size_t>(a[0]); |
||||||
|
size_t size_b1 = static_cast<size_t>(b[0]); |
||||||
|
size_t size_a2 = static_cast<size_t>(a[1]); |
||||||
|
size_t size_b2 = static_cast<size_t>(b[1]); |
||||||
|
assert(size_a1 + size_a2 + 2 == a.size()); |
||||||
|
assert(size_b1 + size_b2 + 2 == b.size()); |
||||||
|
|
||||||
|
Slice a1 = Slice(a.data() + 2, size_a1); |
||||||
|
Slice b1 = Slice(b.data() + 2, size_b1); |
||||||
|
Slice a2 = Slice(a.data() + 2 + size_a1, size_a2); |
||||||
|
Slice b2 = Slice(b.data() + 2 + size_b1, size_b2); |
||||||
|
|
||||||
|
if (a1 != b1) { |
||||||
|
return a1.compare(b1); |
||||||
|
} |
||||||
|
return a2.compare(b2); |
||||||
|
} |
||||||
|
virtual void FindShortestSeparator(std::string* start, |
||||||
|
const Slice& limit) const {} |
||||||
|
|
||||||
|
virtual void FindShortSuccessor(std::string* key) const {} |
||||||
|
}; |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class ComparatorDBTest { |
||||||
|
private: |
||||||
|
std::string dbname_; |
||||||
|
Env* env_; |
||||||
|
DB* db_; |
||||||
|
Options last_options_; |
||||||
|
std::unique_ptr<const Comparator> comparator_guard; |
||||||
|
|
||||||
|
public: |
||||||
|
ComparatorDBTest() : env_(Env::Default()), db_(nullptr) { |
||||||
|
comparator = BytewiseComparator(); |
||||||
|
dbname_ = test::TmpDir() + "/comparator_db_test"; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, last_options_)); |
||||||
|
} |
||||||
|
|
||||||
|
~ComparatorDBTest() { |
||||||
|
delete db_; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, last_options_)); |
||||||
|
comparator = BytewiseComparator(); |
||||||
|
} |
||||||
|
|
||||||
|
DB* GetDB() { return db_; } |
||||||
|
|
||||||
|
void SetOwnedComparator(const Comparator* cmp) { |
||||||
|
comparator_guard.reset(cmp); |
||||||
|
comparator = cmp; |
||||||
|
last_options_.comparator = cmp; |
||||||
|
} |
||||||
|
|
||||||
|
// Return the current option configuration.
|
||||||
|
Options* GetOptions() { return &last_options_; } |
||||||
|
|
||||||
|
void DestroyAndReopen() { |
||||||
|
// Destroy using last options
|
||||||
|
Destroy(); |
||||||
|
ASSERT_OK(TryReopen()); |
||||||
|
} |
||||||
|
|
||||||
|
void Destroy() { |
||||||
|
delete db_; |
||||||
|
db_ = nullptr; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, last_options_)); |
||||||
|
} |
||||||
|
|
||||||
|
Status TryReopen() { |
||||||
|
delete db_; |
||||||
|
db_ = nullptr; |
||||||
|
last_options_.create_if_missing = true; |
||||||
|
|
||||||
|
return DB::Open(last_options_, dbname_, &db_); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, Bytewise) { |
||||||
|
for (int rand_seed = 301; rand_seed < 306; rand_seed++) { |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rand_seed); |
||||||
|
DoRandomIteraratorTest(GetDB(), |
||||||
|
{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, &rnd, |
||||||
|
8, 100, 3); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, SimpleSuffixReverseComparator) { |
||||||
|
SetOwnedComparator(new test::SimpleSuffixReverseComparator()); |
||||||
|
|
||||||
|
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { |
||||||
|
Options* opt = GetOptions(); |
||||||
|
opt->comparator = comparator; |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rnd_seed); |
||||||
|
|
||||||
|
std::vector<std::string> source_strings; |
||||||
|
std::vector<std::string> source_prefixes; |
||||||
|
// Randomly generate 5 prefixes
|
||||||
|
for (int i = 0; i < 5; i++) { |
||||||
|
source_prefixes.push_back(test::RandomHumanReadableString(&rnd, 8)); |
||||||
|
} |
||||||
|
for (int j = 0; j < 20; j++) { |
||||||
|
int prefix_index = rnd.Uniform(static_cast<int>(source_prefixes.size())); |
||||||
|
std::string key = source_prefixes[prefix_index] + |
||||||
|
test::RandomHumanReadableString(&rnd, rnd.Uniform(8)); |
||||||
|
source_strings.push_back(key); |
||||||
|
} |
||||||
|
|
||||||
|
DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 30, 600, 66); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, Uint64Comparator) { |
||||||
|
SetOwnedComparator(test::Uint64Comparator()); |
||||||
|
|
||||||
|
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { |
||||||
|
Options* opt = GetOptions(); |
||||||
|
opt->comparator = comparator; |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rnd_seed); |
||||||
|
Random64 rnd64(rnd_seed); |
||||||
|
|
||||||
|
std::vector<std::string> source_strings; |
||||||
|
// Randomly generate source keys
|
||||||
|
for (int i = 0; i < 100; i++) { |
||||||
|
uint64_t r = rnd64.Next(); |
||||||
|
std::string str; |
||||||
|
str.resize(8); |
||||||
|
memcpy(&str[0], static_cast<void*>(&r), 8); |
||||||
|
source_strings.push_back(str); |
||||||
|
} |
||||||
|
|
||||||
|
DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, DoubleComparator) { |
||||||
|
SetOwnedComparator(new DoubleComparator()); |
||||||
|
|
||||||
|
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { |
||||||
|
Options* opt = GetOptions(); |
||||||
|
opt->comparator = comparator; |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rnd_seed); |
||||||
|
|
||||||
|
std::vector<std::string> source_strings; |
||||||
|
// Randomly generate source keys
|
||||||
|
for (int i = 0; i < 100; i++) { |
||||||
|
uint32_t r = rnd.Next(); |
||||||
|
uint32_t divide_order = rnd.Uniform(8); |
||||||
|
double to_divide = 1.0; |
||||||
|
for (uint32_t j = 0; j < divide_order; j++) { |
||||||
|
to_divide *= 10.0; |
||||||
|
} |
||||||
|
source_strings.push_back(ToString(r / to_divide)); |
||||||
|
} |
||||||
|
|
||||||
|
DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, HashComparator) { |
||||||
|
SetOwnedComparator(new HashComparator()); |
||||||
|
|
||||||
|
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { |
||||||
|
Options* opt = GetOptions(); |
||||||
|
opt->comparator = comparator; |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rnd_seed); |
||||||
|
|
||||||
|
std::vector<std::string> source_strings; |
||||||
|
// Randomly generate source keys
|
||||||
|
for (int i = 0; i < 100; i++) { |
||||||
|
source_strings.push_back(test::RandomKey(&rnd, 8)); |
||||||
|
} |
||||||
|
|
||||||
|
DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(ComparatorDBTest, TwoStrComparator) { |
||||||
|
SetOwnedComparator(new TwoStrComparator()); |
||||||
|
|
||||||
|
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) { |
||||||
|
Options* opt = GetOptions(); |
||||||
|
opt->comparator = comparator; |
||||||
|
DestroyAndReopen(); |
||||||
|
Random rnd(rnd_seed); |
||||||
|
|
||||||
|
std::vector<std::string> source_strings; |
||||||
|
// Randomly generate source keys
|
||||||
|
for (int i = 0; i < 100; i++) { |
||||||
|
std::string str; |
||||||
|
uint32_t size1 = rnd.Uniform(8); |
||||||
|
uint32_t size2 = rnd.Uniform(8); |
||||||
|
str.append(1, static_cast<char>(size1)); |
||||||
|
str.append(1, static_cast<char>(size2)); |
||||||
|
str.append(test::RandomKey(&rnd, size1)); |
||||||
|
str.append(test::RandomKey(&rnd, size2)); |
||||||
|
source_strings.push_back(str); |
||||||
|
} |
||||||
|
|
||||||
|
DoRandomIteraratorTest(GetDB(), source_strings, &rnd, 200, 1000, 66); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,742 @@ |
|||||||
|
// Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright 2014 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
// This test uses a custom Env to keep track of the state of a filesystem as of
|
||||||
|
// the last "sync". It then checks for data loss errors by purposely dropping
|
||||||
|
// file data (or entire files) not protected by a "sync".
|
||||||
|
|
||||||
|
#include <map> |
||||||
|
#include <set> |
||||||
|
#include "db/db_impl.h" |
||||||
|
#include "db/filename.h" |
||||||
|
#include "db/log_format.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/table.h" |
||||||
|
#include "rocksdb/write_batch.h" |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/mock_env.h" |
||||||
|
#include "util/mutexlock.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
static const int kValueSize = 1000; |
||||||
|
static const int kMaxNumValues = 2000; |
||||||
|
static const size_t kNumIterations = 3; |
||||||
|
|
||||||
|
class TestWritableFile; |
||||||
|
class FaultInjectionTestEnv; |
||||||
|
|
||||||
|
namespace { |
||||||
|
|
||||||
|
// Assume a filename, and not a directory name like "/foo/bar/"
|
||||||
|
static std::string GetDirName(const std::string filename) { |
||||||
|
size_t found = filename.find_last_of("/\\"); |
||||||
|
if (found == std::string::npos) { |
||||||
|
return ""; |
||||||
|
} else { |
||||||
|
return filename.substr(0, found); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Trim the tailing "/" in the end of `str`
|
||||||
|
static std::string TrimDirname(const std::string& str) { |
||||||
|
size_t found = str.find_last_not_of("/"); |
||||||
|
if (found == std::string::npos) { |
||||||
|
return str; |
||||||
|
} |
||||||
|
return str.substr(0, found + 1); |
||||||
|
} |
||||||
|
|
||||||
|
// Return pair <parent directory name, file name> of a full path.
|
||||||
|
static std::pair<std::string, std::string> GetDirAndName( |
||||||
|
const std::string& name) { |
||||||
|
std::string dirname = GetDirName(name); |
||||||
|
std::string fname = name.substr(dirname.size() + 1); |
||||||
|
return std::make_pair(dirname, fname); |
||||||
|
} |
||||||
|
|
||||||
|
// A basic file truncation function suitable for this test.
|
||||||
|
Status Truncate(Env* env, const std::string& filename, uint64_t length) { |
||||||
|
unique_ptr<SequentialFile> orig_file; |
||||||
|
const EnvOptions options; |
||||||
|
Status s = env->NewSequentialFile(filename, &orig_file, options); |
||||||
|
if (!s.ok()) { |
||||||
|
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(), |
||||||
|
s.ToString().c_str()); |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
char* scratch = new char[length]; |
||||||
|
rocksdb::Slice result; |
||||||
|
s = orig_file->Read(length, &result, scratch); |
||||||
|
if (s.ok()) { |
||||||
|
std::string tmp_name = GetDirName(filename) + "/truncate.tmp"; |
||||||
|
unique_ptr<WritableFile> tmp_file; |
||||||
|
s = env->NewWritableFile(tmp_name, &tmp_file, options); |
||||||
|
if (s.ok()) { |
||||||
|
s = tmp_file->Append(result); |
||||||
|
if (s.ok()) { |
||||||
|
s = env->RenameFile(tmp_name, filename); |
||||||
|
} else { |
||||||
|
fprintf(stderr, "Cannot rename file %s to %s: %s\n", tmp_name.c_str(), |
||||||
|
filename.c_str(), s.ToString().c_str()); |
||||||
|
env->DeleteFile(tmp_name); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if (!s.ok()) { |
||||||
|
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(), |
||||||
|
s.ToString().c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
delete[] scratch; |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
struct FileState { |
||||||
|
std::string filename_; |
||||||
|
ssize_t pos_; |
||||||
|
ssize_t pos_at_last_sync_; |
||||||
|
ssize_t pos_at_last_flush_; |
||||||
|
|
||||||
|
explicit FileState(const std::string& filename) |
||||||
|
: filename_(filename), |
||||||
|
pos_(-1), |
||||||
|
pos_at_last_sync_(-1), |
||||||
|
pos_at_last_flush_(-1) { } |
||||||
|
|
||||||
|
FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {} |
||||||
|
|
||||||
|
bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; } |
||||||
|
|
||||||
|
Status DropUnsyncedData(Env* env) const; |
||||||
|
|
||||||
|
Status DropRandomUnsyncedData(Env* env, Random* rand) const; |
||||||
|
}; |
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
// A wrapper around WritableFile which informs another Env whenever this file
|
||||||
|
// is written to or sync'ed.
|
||||||
|
class TestWritableFile : public WritableFile { |
||||||
|
public: |
||||||
|
explicit TestWritableFile(const std::string& fname, |
||||||
|
unique_ptr<WritableFile>&& f, |
||||||
|
FaultInjectionTestEnv* env); |
||||||
|
virtual ~TestWritableFile(); |
||||||
|
virtual Status Append(const Slice& data); |
||||||
|
virtual Status Close(); |
||||||
|
virtual Status Flush(); |
||||||
|
virtual Status Sync(); |
||||||
|
|
||||||
|
private: |
||||||
|
FileState state_; |
||||||
|
unique_ptr<WritableFile> target_; |
||||||
|
bool writable_file_opened_; |
||||||
|
FaultInjectionTestEnv* env_; |
||||||
|
}; |
||||||
|
|
||||||
|
class TestDirectory : public Directory { |
||||||
|
public: |
||||||
|
explicit TestDirectory(FaultInjectionTestEnv* env, std::string dirname, |
||||||
|
Directory* dir) |
||||||
|
: env_(env), dirname_(dirname), dir_(dir) {} |
||||||
|
~TestDirectory() {} |
||||||
|
|
||||||
|
virtual Status Fsync() override; |
||||||
|
|
||||||
|
private: |
||||||
|
FaultInjectionTestEnv* env_; |
||||||
|
std::string dirname_; |
||||||
|
unique_ptr<Directory> dir_; |
||||||
|
}; |
||||||
|
|
||||||
|
class FaultInjectionTestEnv : public EnvWrapper { |
||||||
|
public: |
||||||
|
explicit FaultInjectionTestEnv(Env* base) |
||||||
|
: EnvWrapper(base), |
||||||
|
filesystem_active_(true) {} |
||||||
|
virtual ~FaultInjectionTestEnv() { } |
||||||
|
|
||||||
|
Status NewDirectory(const std::string& name, |
||||||
|
unique_ptr<Directory>* result) override { |
||||||
|
unique_ptr<Directory> r; |
||||||
|
Status s = target()->NewDirectory(name, &r); |
||||||
|
ASSERT_OK(s); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
result->reset(new TestDirectory(this, TrimDirname(name), r.release())); |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
Status NewWritableFile(const std::string& fname, |
||||||
|
unique_ptr<WritableFile>* result, |
||||||
|
const EnvOptions& soptions) { |
||||||
|
Status s = target()->NewWritableFile(fname, result, soptions); |
||||||
|
if (s.ok()) { |
||||||
|
result->reset(new TestWritableFile(fname, std::move(*result), this)); |
||||||
|
// WritableFile doesn't append to files, so if the same file is opened
|
||||||
|
// again then it will be truncated - so forget our saved state.
|
||||||
|
UntrackFile(fname); |
||||||
|
MutexLock l(&mutex_); |
||||||
|
open_files_.insert(fname); |
||||||
|
auto dir_and_name = GetDirAndName(fname); |
||||||
|
auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first]; |
||||||
|
list.insert(dir_and_name.second); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
virtual Status DeleteFile(const std::string& f) { |
||||||
|
Status s = EnvWrapper::DeleteFile(f); |
||||||
|
if (!s.ok()) { |
||||||
|
fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(), |
||||||
|
s.ToString().c_str()); |
||||||
|
} |
||||||
|
ASSERT_OK(s); |
||||||
|
if (s.ok()) { |
||||||
|
UntrackFile(f); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
virtual Status RenameFile(const std::string& s, const std::string& t) { |
||||||
|
Status ret = EnvWrapper::RenameFile(s, t); |
||||||
|
|
||||||
|
if (ret.ok()) { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
if (db_file_state_.find(s) != db_file_state_.end()) { |
||||||
|
db_file_state_[t] = db_file_state_[s]; |
||||||
|
db_file_state_.erase(s); |
||||||
|
} |
||||||
|
|
||||||
|
auto sdn = GetDirAndName(s); |
||||||
|
auto tdn = GetDirAndName(t); |
||||||
|
if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) { |
||||||
|
auto& tlist = dir_to_new_files_since_last_sync_[tdn.first]; |
||||||
|
assert(tlist.find(tdn.second) == tlist.end()); |
||||||
|
tlist.insert(tdn.second); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
void WritableFileClosed(const FileState& state) { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
if (open_files_.find(state.filename_) != open_files_.end()) { |
||||||
|
db_file_state_[state.filename_] = state; |
||||||
|
open_files_.erase(state.filename_); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// For every file that is not fully synced, make a call to `func` with
|
||||||
|
// FileState of the file as the parameter.
|
||||||
|
Status DropFileData(std::function<Status(Env*, FileState)> func) { |
||||||
|
Status s; |
||||||
|
MutexLock l(&mutex_); |
||||||
|
for (std::map<std::string, FileState>::const_iterator it = |
||||||
|
db_file_state_.begin(); |
||||||
|
s.ok() && it != db_file_state_.end(); ++it) { |
||||||
|
const FileState& state = it->second; |
||||||
|
if (!state.IsFullySynced()) { |
||||||
|
s = func(target(), state); |
||||||
|
} |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status DropUnsyncedFileData() { |
||||||
|
return DropFileData([&](Env* env, const FileState& state) { |
||||||
|
return state.DropUnsyncedData(env); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
Status DropRandomUnsyncedFileData(Random* rnd) { |
||||||
|
return DropFileData([&](Env* env, const FileState& state) { |
||||||
|
return state.DropRandomUnsyncedData(env, rnd); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
Status DeleteFilesCreatedAfterLastDirSync() { |
||||||
|
// Because DeleteFile access this container make a copy to avoid deadlock
|
||||||
|
std::map<std::string, std::set<std::string>> map_copy; |
||||||
|
{ |
||||||
|
MutexLock l(&mutex_); |
||||||
|
map_copy.insert(dir_to_new_files_since_last_sync_.begin(), |
||||||
|
dir_to_new_files_since_last_sync_.end()); |
||||||
|
} |
||||||
|
|
||||||
|
for (auto& pair : map_copy) { |
||||||
|
for (std::string name : pair.second) { |
||||||
|
Status s = DeleteFile(pair.first + "/" + name); |
||||||
|
if (!s.ok()) { |
||||||
|
return s; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
void ResetState() { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
db_file_state_.clear(); |
||||||
|
dir_to_new_files_since_last_sync_.clear(); |
||||||
|
SetFilesystemActiveNoLock(true); |
||||||
|
} |
||||||
|
|
||||||
|
void UntrackFile(const std::string& f) { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
auto dir_and_name = GetDirAndName(f); |
||||||
|
dir_to_new_files_since_last_sync_[dir_and_name.first].erase( |
||||||
|
dir_and_name.second); |
||||||
|
db_file_state_.erase(f); |
||||||
|
open_files_.erase(f); |
||||||
|
} |
||||||
|
|
||||||
|
void SyncDir(const std::string& dirname) { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
dir_to_new_files_since_last_sync_.erase(dirname); |
||||||
|
} |
||||||
|
|
||||||
|
// Setting the filesystem to inactive is the test equivalent to simulating a
|
||||||
|
// system reset. Setting to inactive will freeze our saved filesystem state so
|
||||||
|
// that it will stop being recorded. It can then be reset back to the state at
|
||||||
|
// the time of the reset.
|
||||||
|
bool IsFilesystemActive() { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
return filesystem_active_; |
||||||
|
} |
||||||
|
void SetFilesystemActiveNoLock(bool active) { filesystem_active_ = active; } |
||||||
|
void SetFilesystemActive(bool active) { |
||||||
|
MutexLock l(&mutex_); |
||||||
|
SetFilesystemActiveNoLock(active); |
||||||
|
} |
||||||
|
void AssertNoOpenFile() { ASSERT_TRUE(open_files_.empty()); } |
||||||
|
|
||||||
|
private: |
||||||
|
port::Mutex mutex_; |
||||||
|
std::map<std::string, FileState> db_file_state_; |
||||||
|
std::set<std::string> open_files_; |
||||||
|
std::unordered_map<std::string, std::set<std::string>> |
||||||
|
dir_to_new_files_since_last_sync_; |
||||||
|
bool filesystem_active_; // Record flushes, syncs, writes
|
||||||
|
}; |
||||||
|
|
||||||
|
Status FileState::DropUnsyncedData(Env* env) const { |
||||||
|
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_; |
||||||
|
return Truncate(env, filename_, sync_pos); |
||||||
|
} |
||||||
|
|
||||||
|
Status FileState::DropRandomUnsyncedData(Env* env, Random* rand) const { |
||||||
|
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_; |
||||||
|
assert(pos_ >= sync_pos); |
||||||
|
int range = static_cast<int>(pos_ - sync_pos); |
||||||
|
uint64_t truncated_size = |
||||||
|
static_cast<uint64_t>(sync_pos) + rand->Uniform(range); |
||||||
|
return Truncate(env, filename_, truncated_size); |
||||||
|
} |
||||||
|
|
||||||
|
Status TestDirectory::Fsync() { |
||||||
|
env_->SyncDir(dirname_); |
||||||
|
return dir_->Fsync(); |
||||||
|
} |
||||||
|
|
||||||
|
TestWritableFile::TestWritableFile(const std::string& fname, |
||||||
|
unique_ptr<WritableFile>&& f, |
||||||
|
FaultInjectionTestEnv* env) |
||||||
|
: state_(fname), |
||||||
|
target_(std::move(f)), |
||||||
|
writable_file_opened_(true), |
||||||
|
env_(env) { |
||||||
|
assert(target_ != nullptr); |
||||||
|
state_.pos_ = 0; |
||||||
|
} |
||||||
|
|
||||||
|
TestWritableFile::~TestWritableFile() { |
||||||
|
if (writable_file_opened_) { |
||||||
|
Close(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Status TestWritableFile::Append(const Slice& data) { |
||||||
|
Status s = target_->Append(data); |
||||||
|
if (s.ok() && env_->IsFilesystemActive()) { |
||||||
|
state_.pos_ += data.size(); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status TestWritableFile::Close() { |
||||||
|
writable_file_opened_ = false; |
||||||
|
Status s = target_->Close(); |
||||||
|
if (s.ok()) { |
||||||
|
env_->WritableFileClosed(state_); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status TestWritableFile::Flush() { |
||||||
|
Status s = target_->Flush(); |
||||||
|
if (s.ok() && env_->IsFilesystemActive()) { |
||||||
|
state_.pos_at_last_flush_ = state_.pos_; |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status TestWritableFile::Sync() { |
||||||
|
if (!env_->IsFilesystemActive()) { |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
// No need to actual sync.
|
||||||
|
state_.pos_at_last_sync_ = state_.pos_; |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
class FaultInjectionTest { |
||||||
|
protected: |
||||||
|
enum OptionConfig { |
||||||
|
kDefault, |
||||||
|
kDifferentDataDir, |
||||||
|
kWalDir, |
||||||
|
kSyncWal, |
||||||
|
kWalDirSyncWal, |
||||||
|
kMultiLevels, |
||||||
|
kEnd, |
||||||
|
}; |
||||||
|
int option_config_; |
||||||
|
// When need to make sure data is persistent, sync WAL
|
||||||
|
bool sync_use_wal_; |
||||||
|
// When need to make sure data is persistent, call DB::CompactRange()
|
||||||
|
bool sync_use_compact_; |
||||||
|
|
||||||
|
protected: |
||||||
|
public: |
||||||
|
enum ExpectedVerifResult { kValExpectFound, kValExpectNoError }; |
||||||
|
enum ResetMethod { |
||||||
|
kResetDropUnsyncedData, |
||||||
|
kResetDropRandomUnsyncedData, |
||||||
|
kResetDeleteUnsyncedFiles, |
||||||
|
kResetDropAndDeleteUnsynced |
||||||
|
}; |
||||||
|
|
||||||
|
std::unique_ptr<Env> base_env_; |
||||||
|
FaultInjectionTestEnv* env_; |
||||||
|
std::string dbname_; |
||||||
|
shared_ptr<Cache> tiny_cache_; |
||||||
|
Options options_; |
||||||
|
DB* db_; |
||||||
|
|
||||||
|
FaultInjectionTest() |
||||||
|
: option_config_(kDefault), |
||||||
|
sync_use_wal_(false), |
||||||
|
sync_use_compact_(true), |
||||||
|
base_env_(nullptr), |
||||||
|
env_(NULL), |
||||||
|
db_(NULL) { |
||||||
|
NewDB(); |
||||||
|
} |
||||||
|
|
||||||
|
~FaultInjectionTest() { ASSERT_OK(TearDown()); } |
||||||
|
|
||||||
|
bool ChangeOptions() { |
||||||
|
option_config_++; |
||||||
|
if (option_config_ >= kEnd) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
if (option_config_ == kMultiLevels) { |
||||||
|
base_env_.reset(new MockEnv(Env::Default())); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Return the current option configuration.
|
||||||
|
Options CurrentOptions() { |
||||||
|
sync_use_wal_ = false; |
||||||
|
sync_use_compact_ = true; |
||||||
|
Options options; |
||||||
|
switch (option_config_) { |
||||||
|
case kWalDir: |
||||||
|
options.wal_dir = test::TmpDir(env_) + "/fault_test_wal"; |
||||||
|
break; |
||||||
|
case kDifferentDataDir: |
||||||
|
options.db_paths.emplace_back(test::TmpDir(env_) + "/fault_test_data", |
||||||
|
1000000U); |
||||||
|
break; |
||||||
|
case kSyncWal: |
||||||
|
sync_use_wal_ = true; |
||||||
|
sync_use_compact_ = false; |
||||||
|
break; |
||||||
|
case kWalDirSyncWal: |
||||||
|
options.wal_dir = test::TmpDir(env_) + "/fault_test_wal"; |
||||||
|
sync_use_wal_ = true; |
||||||
|
sync_use_compact_ = false; |
||||||
|
break; |
||||||
|
case kMultiLevels: |
||||||
|
options.write_buffer_size = 64 * 1024; |
||||||
|
options.target_file_size_base = 64 * 1024; |
||||||
|
options.level0_file_num_compaction_trigger = 2; |
||||||
|
options.level0_slowdown_writes_trigger = 2; |
||||||
|
options.level0_stop_writes_trigger = 4; |
||||||
|
options.max_bytes_for_level_base = 128 * 1024; |
||||||
|
options.max_write_buffer_number = 2; |
||||||
|
options.max_background_compactions = 8; |
||||||
|
options.max_background_flushes = 8; |
||||||
|
sync_use_wal_ = true; |
||||||
|
sync_use_compact_ = false; |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
return options; |
||||||
|
} |
||||||
|
|
||||||
|
Status NewDB() { |
||||||
|
assert(db_ == NULL); |
||||||
|
assert(tiny_cache_ == nullptr); |
||||||
|
assert(env_ == NULL); |
||||||
|
|
||||||
|
env_ = |
||||||
|
new FaultInjectionTestEnv(base_env_ ? base_env_.get() : Env::Default()); |
||||||
|
|
||||||
|
options_ = CurrentOptions(); |
||||||
|
options_.env = env_; |
||||||
|
options_.paranoid_checks = true; |
||||||
|
|
||||||
|
BlockBasedTableOptions table_options; |
||||||
|
tiny_cache_ = NewLRUCache(100); |
||||||
|
table_options.block_cache = tiny_cache_; |
||||||
|
options_.table_factory.reset(NewBlockBasedTableFactory(table_options)); |
||||||
|
|
||||||
|
dbname_ = test::TmpDir() + "/fault_test"; |
||||||
|
|
||||||
|
ASSERT_OK(DestroyDB(dbname_, options_)); |
||||||
|
|
||||||
|
options_.create_if_missing = true; |
||||||
|
Status s = OpenDB(); |
||||||
|
options_.create_if_missing = false; |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status SetUp() { |
||||||
|
Status s = TearDown(); |
||||||
|
if (s.ok()) { |
||||||
|
s = NewDB(); |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status TearDown() { |
||||||
|
CloseDB(); |
||||||
|
|
||||||
|
Status s = DestroyDB(dbname_, options_); |
||||||
|
|
||||||
|
delete env_; |
||||||
|
env_ = NULL; |
||||||
|
|
||||||
|
tiny_cache_.reset(); |
||||||
|
|
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
void Build(const WriteOptions& write_options, int start_idx, int num_vals) { |
||||||
|
std::string key_space, value_space; |
||||||
|
WriteBatch batch; |
||||||
|
for (int i = start_idx; i < start_idx + num_vals; i++) { |
||||||
|
Slice key = Key(i, &key_space); |
||||||
|
batch.Clear(); |
||||||
|
batch.Put(key, Value(i, &value_space)); |
||||||
|
ASSERT_OK(db_->Write(write_options, &batch)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Status ReadValue(int i, std::string* val) const { |
||||||
|
std::string key_space, value_space; |
||||||
|
Slice key = Key(i, &key_space); |
||||||
|
Value(i, &value_space); |
||||||
|
ReadOptions options; |
||||||
|
return db_->Get(options, key, val); |
||||||
|
} |
||||||
|
|
||||||
|
Status Verify(int start_idx, int num_vals, |
||||||
|
ExpectedVerifResult expected) const { |
||||||
|
std::string val; |
||||||
|
std::string value_space; |
||||||
|
Status s; |
||||||
|
for (int i = start_idx; i < start_idx + num_vals && s.ok(); i++) { |
||||||
|
Value(i, &value_space); |
||||||
|
s = ReadValue(i, &val); |
||||||
|
if (s.ok()) { |
||||||
|
ASSERT_EQ(value_space, val); |
||||||
|
} |
||||||
|
if (expected == kValExpectFound) { |
||||||
|
if (!s.ok()) { |
||||||
|
fprintf(stderr, "Error when read %dth record (expect found): %s\n", i, |
||||||
|
s.ToString().c_str()); |
||||||
|
return s; |
||||||
|
} |
||||||
|
} else if (!s.ok() && !s.IsNotFound()) { |
||||||
|
fprintf(stderr, "Error when read %dth record: %s\n", i, |
||||||
|
s.ToString().c_str()); |
||||||
|
return s; |
||||||
|
} |
||||||
|
} |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
// Return the ith key
|
||||||
|
Slice Key(int i, std::string* storage) const { |
||||||
|
char buf[100]; |
||||||
|
snprintf(buf, sizeof(buf), "%016d", i); |
||||||
|
storage->assign(buf, strlen(buf)); |
||||||
|
return Slice(*storage); |
||||||
|
} |
||||||
|
|
||||||
|
// Return the value to associate with the specified key
|
||||||
|
Slice Value(int k, std::string* storage) const { |
||||||
|
Random r(k); |
||||||
|
return test::RandomString(&r, kValueSize, storage); |
||||||
|
} |
||||||
|
|
||||||
|
Status OpenDB() { |
||||||
|
delete db_; |
||||||
|
db_ = NULL; |
||||||
|
env_->ResetState(); |
||||||
|
return DB::Open(options_, dbname_, &db_); |
||||||
|
} |
||||||
|
|
||||||
|
void CloseDB() { |
||||||
|
delete db_; |
||||||
|
db_ = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
void DeleteAllData() { |
||||||
|
Iterator* iter = db_->NewIterator(ReadOptions()); |
||||||
|
WriteOptions options; |
||||||
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
||||||
|
ASSERT_OK(db_->Delete(WriteOptions(), iter->key())); |
||||||
|
} |
||||||
|
|
||||||
|
delete iter; |
||||||
|
|
||||||
|
FlushOptions flush_options; |
||||||
|
flush_options.wait = true; |
||||||
|
db_->Flush(flush_options); |
||||||
|
} |
||||||
|
|
||||||
|
// rnd cannot be null for kResetDropRandomUnsyncedData
|
||||||
|
void ResetDBState(ResetMethod reset_method, Random* rnd = nullptr) { |
||||||
|
env_->AssertNoOpenFile(); |
||||||
|
switch (reset_method) { |
||||||
|
case kResetDropUnsyncedData: |
||||||
|
ASSERT_OK(env_->DropUnsyncedFileData()); |
||||||
|
break; |
||||||
|
case kResetDropRandomUnsyncedData: |
||||||
|
ASSERT_OK(env_->DropRandomUnsyncedFileData(rnd)); |
||||||
|
break; |
||||||
|
case kResetDeleteUnsyncedFiles: |
||||||
|
ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync()); |
||||||
|
break; |
||||||
|
case kResetDropAndDeleteUnsynced: |
||||||
|
ASSERT_OK(env_->DropUnsyncedFileData()); |
||||||
|
ASSERT_OK(env_->DeleteFilesCreatedAfterLastDirSync()); |
||||||
|
break; |
||||||
|
default: |
||||||
|
assert(false); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void PartialCompactTestPreFault(int num_pre_sync, int num_post_sync) { |
||||||
|
DeleteAllData(); |
||||||
|
|
||||||
|
WriteOptions write_options; |
||||||
|
write_options.sync = sync_use_wal_; |
||||||
|
|
||||||
|
Build(write_options, 0, num_pre_sync); |
||||||
|
if (sync_use_compact_) { |
||||||
|
db_->CompactRange(nullptr, nullptr); |
||||||
|
} |
||||||
|
write_options.sync = false; |
||||||
|
Build(write_options, num_pre_sync, num_post_sync); |
||||||
|
} |
||||||
|
|
||||||
|
void PartialCompactTestReopenWithFault(ResetMethod reset_method, |
||||||
|
int num_pre_sync, int num_post_sync, |
||||||
|
Random* rnd = nullptr) { |
||||||
|
env_->SetFilesystemActive(false); |
||||||
|
CloseDB(); |
||||||
|
ResetDBState(reset_method, rnd); |
||||||
|
ASSERT_OK(OpenDB()); |
||||||
|
ASSERT_OK(Verify(0, num_pre_sync, FaultInjectionTest::kValExpectFound)); |
||||||
|
ASSERT_OK(Verify(num_pre_sync, num_post_sync, |
||||||
|
FaultInjectionTest::kValExpectNoError)); |
||||||
|
} |
||||||
|
|
||||||
|
void NoWriteTestPreFault() { |
||||||
|
} |
||||||
|
|
||||||
|
void NoWriteTestReopenWithFault(ResetMethod reset_method) { |
||||||
|
CloseDB(); |
||||||
|
ResetDBState(reset_method); |
||||||
|
ASSERT_OK(OpenDB()); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(FaultInjectionTest, FaultTest) { |
||||||
|
do { |
||||||
|
Random rnd(301); |
||||||
|
ASSERT_OK(SetUp()); |
||||||
|
|
||||||
|
for (size_t idx = 0; idx < kNumIterations; idx++) { |
||||||
|
int num_pre_sync = rnd.Uniform(kMaxNumValues); |
||||||
|
int num_post_sync = rnd.Uniform(kMaxNumValues); |
||||||
|
|
||||||
|
PartialCompactTestPreFault(num_pre_sync, num_post_sync); |
||||||
|
PartialCompactTestReopenWithFault(kResetDropUnsyncedData, num_pre_sync, |
||||||
|
num_post_sync); |
||||||
|
NoWriteTestPreFault(); |
||||||
|
NoWriteTestReopenWithFault(kResetDropUnsyncedData); |
||||||
|
|
||||||
|
PartialCompactTestPreFault(num_pre_sync, num_post_sync); |
||||||
|
PartialCompactTestReopenWithFault(kResetDropRandomUnsyncedData, |
||||||
|
num_pre_sync, num_post_sync, &rnd); |
||||||
|
NoWriteTestPreFault(); |
||||||
|
NoWriteTestReopenWithFault(kResetDropUnsyncedData); |
||||||
|
|
||||||
|
// Setting a separate data path won't pass the test as we don't sync
|
||||||
|
// it after creating new files,
|
||||||
|
PartialCompactTestPreFault(num_pre_sync, num_post_sync); |
||||||
|
PartialCompactTestReopenWithFault(kResetDropAndDeleteUnsynced, |
||||||
|
num_pre_sync, num_post_sync); |
||||||
|
NoWriteTestPreFault(); |
||||||
|
NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced); |
||||||
|
|
||||||
|
PartialCompactTestPreFault(num_pre_sync, num_post_sync); |
||||||
|
// No new files created so we expect all values since no files will be
|
||||||
|
// dropped.
|
||||||
|
PartialCompactTestReopenWithFault(kResetDeleteUnsyncedFiles, num_pre_sync, |
||||||
|
num_post_sync); |
||||||
|
NoWriteTestPreFault(); |
||||||
|
NoWriteTestReopenWithFault(kResetDeleteUnsyncedFiles); |
||||||
|
} |
||||||
|
} while (ChangeOptions()); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
return rocksdb::test::RunAllTests(); |
||||||
|
} |
@ -0,0 +1,221 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#include "db/flush_job.h" |
||||||
|
|
||||||
|
#ifndef __STDC_FORMAT_MACROS |
||||||
|
#define __STDC_FORMAT_MACROS |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
#include <algorithm> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "db/builder.h" |
||||||
|
#include "db/db_iter.h" |
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/filename.h" |
||||||
|
#include "db/log_reader.h" |
||||||
|
#include "db/log_writer.h" |
||||||
|
#include "db/memtable.h" |
||||||
|
#include "db/memtable_list.h" |
||||||
|
#include "db/merge_context.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "port/port.h" |
||||||
|
#include "port/likely.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/statistics.h" |
||||||
|
#include "rocksdb/status.h" |
||||||
|
#include "rocksdb/table.h" |
||||||
|
#include "table/block.h" |
||||||
|
#include "table/block_based_table_factory.h" |
||||||
|
#include "table/merger.h" |
||||||
|
#include "table/table_builder.h" |
||||||
|
#include "table/two_level_iterator.h" |
||||||
|
#include "util/coding.h" |
||||||
|
#include "util/file_util.h" |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/log_buffer.h" |
||||||
|
#include "util/mutexlock.h" |
||||||
|
#include "util/perf_context_imp.h" |
||||||
|
#include "util/iostats_context_imp.h" |
||||||
|
#include "util/stop_watch.h" |
||||||
|
#include "util/sync_point.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd, |
||||||
|
const DBOptions& db_options, |
||||||
|
const MutableCFOptions& mutable_cf_options, |
||||||
|
const EnvOptions& env_options, VersionSet* versions, |
||||||
|
InstrumentedMutex* db_mutex, |
||||||
|
std::atomic<bool>* shutting_down, |
||||||
|
SequenceNumber newest_snapshot, JobContext* job_context, |
||||||
|
LogBuffer* log_buffer, Directory* db_directory, |
||||||
|
Directory* output_file_directory, |
||||||
|
CompressionType output_compression, Statistics* stats) |
||||||
|
: dbname_(dbname), |
||||||
|
cfd_(cfd), |
||||||
|
db_options_(db_options), |
||||||
|
mutable_cf_options_(mutable_cf_options), |
||||||
|
env_options_(env_options), |
||||||
|
versions_(versions), |
||||||
|
db_mutex_(db_mutex), |
||||||
|
shutting_down_(shutting_down), |
||||||
|
newest_snapshot_(newest_snapshot), |
||||||
|
job_context_(job_context), |
||||||
|
log_buffer_(log_buffer), |
||||||
|
db_directory_(db_directory), |
||||||
|
output_file_directory_(output_file_directory), |
||||||
|
output_compression_(output_compression), |
||||||
|
stats_(stats) {} |
||||||
|
|
||||||
|
Status FlushJob::Run(uint64_t* file_number) { |
||||||
|
// Save the contents of the earliest memtable as a new Table
|
||||||
|
uint64_t fn; |
||||||
|
autovector<MemTable*> mems; |
||||||
|
cfd_->imm()->PickMemtablesToFlush(&mems); |
||||||
|
if (mems.empty()) { |
||||||
|
LogToBuffer(log_buffer_, "[%s] Nothing in memtable to flush", |
||||||
|
cfd_->GetName().c_str()); |
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
// entries mems are (implicitly) sorted in ascending order by their created
|
||||||
|
// time. We will use the first memtable's `edit` to keep the meta info for
|
||||||
|
// this flush.
|
||||||
|
MemTable* m = mems[0]; |
||||||
|
VersionEdit* edit = m->GetEdits(); |
||||||
|
edit->SetPrevLogNumber(0); |
||||||
|
// SetLogNumber(log_num) indicates logs with number smaller than log_num
|
||||||
|
// will no longer be picked up for recovery.
|
||||||
|
edit->SetLogNumber(mems.back()->GetNextLogNumber()); |
||||||
|
edit->SetColumnFamily(cfd_->GetID()); |
||||||
|
|
||||||
|
// This will release and re-acquire the mutex.
|
||||||
|
Status s = WriteLevel0Table(mems, edit, &fn); |
||||||
|
|
||||||
|
if (s.ok() && |
||||||
|
(shutting_down_->load(std::memory_order_acquire) || cfd_->IsDropped())) { |
||||||
|
s = Status::ShutdownInProgress( |
||||||
|
"Database shutdown or Column family drop during flush"); |
||||||
|
} |
||||||
|
|
||||||
|
if (!s.ok()) { |
||||||
|
cfd_->imm()->RollbackMemtableFlush(mems, fn); |
||||||
|
} else { |
||||||
|
// Replace immutable memtable with the generated Table
|
||||||
|
s = cfd_->imm()->InstallMemtableFlushResults( |
||||||
|
cfd_, mutable_cf_options_, mems, versions_, db_mutex_, fn, |
||||||
|
&job_context_->memtables_to_free, db_directory_, log_buffer_); |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok() && file_number != nullptr) { |
||||||
|
*file_number = fn; |
||||||
|
} |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems, |
||||||
|
VersionEdit* edit, uint64_t* filenumber) { |
||||||
|
db_mutex_->AssertHeld(); |
||||||
|
const uint64_t start_micros = db_options_.env->NowMicros(); |
||||||
|
FileMetaData meta; |
||||||
|
// path 0 for level 0 file.
|
||||||
|
meta.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0); |
||||||
|
*filenumber = meta.fd.GetNumber(); |
||||||
|
|
||||||
|
const SequenceNumber earliest_seqno_in_memtable = |
||||||
|
mems[0]->GetFirstSequenceNumber(); |
||||||
|
Version* base = cfd_->current(); |
||||||
|
base->Ref(); // it is likely that we do not need this reference
|
||||||
|
Status s; |
||||||
|
{ |
||||||
|
db_mutex_->Unlock(); |
||||||
|
if (log_buffer_) { |
||||||
|
log_buffer_->FlushBufferToLog(); |
||||||
|
} |
||||||
|
std::vector<Iterator*> memtables; |
||||||
|
ReadOptions ro; |
||||||
|
ro.total_order_seek = true; |
||||||
|
Arena arena; |
||||||
|
for (MemTable* m : mems) { |
||||||
|
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log, |
||||||
|
"[%s] Flushing memtable with next log file: %" PRIu64 "\n", |
||||||
|
cfd_->GetName().c_str(), m->GetNextLogNumber()); |
||||||
|
memtables.push_back(m->NewIterator(ro, &arena)); |
||||||
|
} |
||||||
|
{ |
||||||
|
ScopedArenaIterator iter( |
||||||
|
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0], |
||||||
|
static_cast<int>(memtables.size()), &arena)); |
||||||
|
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log, |
||||||
|
"[%s] Level-0 flush table #%" PRIu64 ": started", |
||||||
|
cfd_->GetName().c_str(), meta.fd.GetNumber()); |
||||||
|
|
||||||
|
s = BuildTable(dbname_, db_options_.env, *cfd_->ioptions(), env_options_, |
||||||
|
cfd_->table_cache(), iter.get(), &meta, |
||||||
|
cfd_->internal_comparator(), newest_snapshot_, |
||||||
|
earliest_seqno_in_memtable, output_compression_, |
||||||
|
cfd_->ioptions()->compression_opts, Env::IO_HIGH); |
||||||
|
LogFlush(db_options_.info_log); |
||||||
|
} |
||||||
|
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log, |
||||||
|
"[%s] Level-0 flush table #%" PRIu64 ": %" PRIu64 " bytes %s", |
||||||
|
cfd_->GetName().c_str(), meta.fd.GetNumber(), meta.fd.GetFileSize(), |
||||||
|
s.ToString().c_str()); |
||||||
|
if (!db_options_.disableDataSync && output_file_directory_ != nullptr) { |
||||||
|
output_file_directory_->Fsync(); |
||||||
|
} |
||||||
|
db_mutex_->Lock(); |
||||||
|
} |
||||||
|
base->Unref(); |
||||||
|
|
||||||
|
// re-acquire the most current version
|
||||||
|
base = cfd_->current(); |
||||||
|
|
||||||
|
// Note that if file_size is zero, the file has been deleted and
|
||||||
|
// should not be added to the manifest.
|
||||||
|
int level = 0; |
||||||
|
if (s.ok() && meta.fd.GetFileSize() > 0) { |
||||||
|
const Slice min_user_key = meta.smallest.user_key(); |
||||||
|
const Slice max_user_key = meta.largest.user_key(); |
||||||
|
// if we have more than 1 background thread, then we cannot
|
||||||
|
// insert files directly into higher levels because some other
|
||||||
|
// threads could be concurrently producing compacted files for
|
||||||
|
// that key range.
|
||||||
|
if (base != nullptr && db_options_.max_background_compactions <= 1 && |
||||||
|
db_options_.max_background_flushes == 0 && |
||||||
|
cfd_->ioptions()->compaction_style == kCompactionStyleLevel) { |
||||||
|
level = base->storage_info()->PickLevelForMemTableOutput( |
||||||
|
mutable_cf_options_, min_user_key, max_user_key); |
||||||
|
// If level does not match path id, reset level back to 0
|
||||||
|
uint32_t fdpath = LevelCompactionPicker::GetPathId( |
||||||
|
*cfd_->ioptions(), mutable_cf_options_, level); |
||||||
|
if (fdpath != 0) { |
||||||
|
level = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
edit->AddFile(level, meta.fd.GetNumber(), meta.fd.GetPathId(), |
||||||
|
meta.fd.GetFileSize(), meta.smallest, meta.largest, |
||||||
|
meta.smallest_seqno, meta.largest_seqno); |
||||||
|
} |
||||||
|
|
||||||
|
InternalStats::CompactionStats stats(1); |
||||||
|
stats.micros = db_options_.env->NowMicros() - start_micros; |
||||||
|
stats.bytes_written = meta.fd.GetFileSize(); |
||||||
|
cfd_->internal_stats()->AddCompactionStats(level, stats); |
||||||
|
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED, |
||||||
|
meta.fd.GetFileSize()); |
||||||
|
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize()); |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,87 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
#include <deque> |
||||||
|
#include <limits> |
||||||
|
#include <set> |
||||||
|
#include <utility> |
||||||
|
#include <vector> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/log_writer.h" |
||||||
|
#include "db/snapshot.h" |
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/version_edit.h" |
||||||
|
#include "db/memtable_list.h" |
||||||
|
#include "port/port.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/memtablerep.h" |
||||||
|
#include "rocksdb/transaction_log.h" |
||||||
|
#include "util/autovector.h" |
||||||
|
#include "util/instrumented_mutex.h" |
||||||
|
#include "util/stop_watch.h" |
||||||
|
#include "util/thread_local.h" |
||||||
|
#include "util/scoped_arena_iterator.h" |
||||||
|
#include "db/internal_stats.h" |
||||||
|
#include "db/write_controller.h" |
||||||
|
#include "db/flush_scheduler.h" |
||||||
|
#include "db/write_thread.h" |
||||||
|
#include "db/job_context.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class MemTable; |
||||||
|
class TableCache; |
||||||
|
class Version; |
||||||
|
class VersionEdit; |
||||||
|
class VersionSet; |
||||||
|
class Arena; |
||||||
|
|
||||||
|
class FlushJob { |
||||||
|
public: |
||||||
|
// TODO(icanadi) make effort to reduce number of parameters here
|
||||||
|
// IMPORTANT: mutable_cf_options needs to be alive while FlushJob is alive
|
||||||
|
FlushJob(const std::string& dbname, ColumnFamilyData* cfd, |
||||||
|
const DBOptions& db_options, |
||||||
|
const MutableCFOptions& mutable_cf_options, |
||||||
|
const EnvOptions& env_options, VersionSet* versions, |
||||||
|
InstrumentedMutex* db_mutex, std::atomic<bool>* shutting_down, |
||||||
|
SequenceNumber newest_snapshot, JobContext* job_context, |
||||||
|
LogBuffer* log_buffer, Directory* db_directory, |
||||||
|
Directory* output_file_directory, CompressionType output_compression, |
||||||
|
Statistics* stats); |
||||||
|
~FlushJob() {} |
||||||
|
|
||||||
|
Status Run(uint64_t* file_number = nullptr); |
||||||
|
|
||||||
|
private: |
||||||
|
Status WriteLevel0Table(const autovector<MemTable*>& mems, VersionEdit* edit, |
||||||
|
uint64_t* filenumber); |
||||||
|
const std::string& dbname_; |
||||||
|
ColumnFamilyData* cfd_; |
||||||
|
const DBOptions& db_options_; |
||||||
|
const MutableCFOptions& mutable_cf_options_; |
||||||
|
const EnvOptions& env_options_; |
||||||
|
VersionSet* versions_; |
||||||
|
InstrumentedMutex* db_mutex_; |
||||||
|
std::atomic<bool>* shutting_down_; |
||||||
|
SequenceNumber newest_snapshot_; |
||||||
|
JobContext* job_context_; |
||||||
|
LogBuffer* log_buffer_; |
||||||
|
Directory* db_directory_; |
||||||
|
Directory* output_file_directory_; |
||||||
|
CompressionType output_compression_; |
||||||
|
Statistics* stats_; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,124 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include <map> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "db/flush_job.h" |
||||||
|
#include "db/column_family.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "db/writebuffer.h" |
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
#include "table/mock_table.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
// TODO(icanadi) Mock out everything else:
|
||||||
|
// 1. VersionSet
|
||||||
|
// 2. Memtable
|
||||||
|
class FlushJobTest { |
||||||
|
public: |
||||||
|
FlushJobTest() |
||||||
|
: env_(Env::Default()), |
||||||
|
dbname_(test::TmpDir() + "/flush_job_test"), |
||||||
|
table_cache_(NewLRUCache(50000, 16, 8)), |
||||||
|
write_buffer_(db_options_.db_write_buffer_size), |
||||||
|
versions_(new VersionSet(dbname_, &db_options_, env_options_, |
||||||
|
table_cache_.get(), &write_buffer_, |
||||||
|
&write_controller_)), |
||||||
|
shutting_down_(false), |
||||||
|
mock_table_factory_(new mock::MockTableFactory()) { |
||||||
|
ASSERT_OK(env_->CreateDirIfMissing(dbname_)); |
||||||
|
db_options_.db_paths.emplace_back(dbname_, |
||||||
|
std::numeric_limits<uint64_t>::max()); |
||||||
|
// TODO(icanadi) Remove this once we mock out VersionSet
|
||||||
|
NewDB(); |
||||||
|
std::vector<ColumnFamilyDescriptor> column_families; |
||||||
|
cf_options_.table_factory = mock_table_factory_; |
||||||
|
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_); |
||||||
|
|
||||||
|
ASSERT_OK(versions_->Recover(column_families, false)); |
||||||
|
} |
||||||
|
|
||||||
|
void NewDB() { |
||||||
|
VersionEdit new_db; |
||||||
|
new_db.SetLogNumber(0); |
||||||
|
new_db.SetNextFile(2); |
||||||
|
new_db.SetLastSequence(0); |
||||||
|
|
||||||
|
const std::string manifest = DescriptorFileName(dbname_, 1); |
||||||
|
unique_ptr<WritableFile> file; |
||||||
|
Status s = env_->NewWritableFile( |
||||||
|
manifest, &file, env_->OptimizeForManifestWrite(env_options_)); |
||||||
|
ASSERT_OK(s); |
||||||
|
{ |
||||||
|
log::Writer log(std::move(file)); |
||||||
|
std::string record; |
||||||
|
new_db.EncodeTo(&record); |
||||||
|
s = log.AddRecord(record); |
||||||
|
} |
||||||
|
ASSERT_OK(s); |
||||||
|
// Make "CURRENT" file that points to the new manifest file.
|
||||||
|
s = SetCurrentFile(env_, dbname_, 1, nullptr); |
||||||
|
} |
||||||
|
|
||||||
|
Env* env_; |
||||||
|
std::string dbname_; |
||||||
|
EnvOptions env_options_; |
||||||
|
std::shared_ptr<Cache> table_cache_; |
||||||
|
WriteController write_controller_; |
||||||
|
DBOptions db_options_; |
||||||
|
WriteBuffer write_buffer_; |
||||||
|
ColumnFamilyOptions cf_options_; |
||||||
|
std::unique_ptr<VersionSet> versions_; |
||||||
|
InstrumentedMutex mutex_; |
||||||
|
std::atomic<bool> shutting_down_; |
||||||
|
std::shared_ptr<mock::MockTableFactory> mock_table_factory_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(FlushJobTest, Empty) { |
||||||
|
JobContext job_context; |
||||||
|
auto cfd = versions_->GetColumnFamilySet()->GetDefault(); |
||||||
|
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), |
||||||
|
db_options_, *cfd->GetLatestMutableCFOptions(), |
||||||
|
env_options_, versions_.get(), &mutex_, &shutting_down_, |
||||||
|
SequenceNumber(), &job_context, nullptr, nullptr, nullptr, |
||||||
|
kNoCompression, nullptr); |
||||||
|
ASSERT_OK(flush_job.Run()); |
||||||
|
job_context.Clean(); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(FlushJobTest, NonEmpty) { |
||||||
|
JobContext job_context; |
||||||
|
auto cfd = versions_->GetColumnFamilySet()->GetDefault(); |
||||||
|
auto new_mem = cfd->ConstructNewMemtable(*cfd->GetLatestMutableCFOptions()); |
||||||
|
new_mem->Ref(); |
||||||
|
std::map<std::string, std::string> inserted_keys; |
||||||
|
for (int i = 1; i < 10000; ++i) { |
||||||
|
std::string key(ToString(i)); |
||||||
|
std::string value("value" + ToString(i)); |
||||||
|
new_mem->Add(SequenceNumber(i), kTypeValue, key, value); |
||||||
|
InternalKey internal_key(key, SequenceNumber(i), kTypeValue); |
||||||
|
inserted_keys.insert({internal_key.Encode().ToString(), value}); |
||||||
|
} |
||||||
|
cfd->imm()->Add(new_mem); |
||||||
|
|
||||||
|
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(), |
||||||
|
db_options_, *cfd->GetLatestMutableCFOptions(), |
||||||
|
env_options_, versions_.get(), &mutex_, &shutting_down_, |
||||||
|
SequenceNumber(), &job_context, nullptr, nullptr, nullptr, |
||||||
|
kNoCompression, nullptr); |
||||||
|
mutex_.Lock(); |
||||||
|
ASSERT_OK(flush_job.Run()); |
||||||
|
mutex_.Unlock(); |
||||||
|
mock_table_factory_->AssertSingleFile(inserted_keys); |
||||||
|
job_context.Clean(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
@ -0,0 +1,63 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include "db/flush_scheduler.h" |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
|
||||||
|
#include "db/column_family.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) { |
||||||
|
#ifndef NDEBUG |
||||||
|
assert(column_families_set_.find(cfd) == column_families_set_.end()); |
||||||
|
column_families_set_.insert(cfd); |
||||||
|
#endif // NDEBUG
|
||||||
|
cfd->Ref(); |
||||||
|
column_families_.push_back(cfd); |
||||||
|
} |
||||||
|
|
||||||
|
ColumnFamilyData* FlushScheduler::GetNextColumnFamily() { |
||||||
|
ColumnFamilyData* cfd = nullptr; |
||||||
|
while (column_families_.size() > 0) { |
||||||
|
cfd = column_families_.front(); |
||||||
|
column_families_.pop_front(); |
||||||
|
if (cfd->IsDropped()) { |
||||||
|
if (cfd->Unref()) { |
||||||
|
delete cfd; |
||||||
|
cfd = nullptr; |
||||||
|
} |
||||||
|
} else { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
#ifndef NDEBUG |
||||||
|
if (cfd != nullptr) { |
||||||
|
auto itr = column_families_set_.find(cfd); |
||||||
|
assert(itr != column_families_set_.end()); |
||||||
|
column_families_set_.erase(itr); |
||||||
|
} |
||||||
|
#endif // NDEBUG
|
||||||
|
return cfd; |
||||||
|
} |
||||||
|
|
||||||
|
bool FlushScheduler::Empty() { return column_families_.empty(); } |
||||||
|
|
||||||
|
void FlushScheduler::Clear() { |
||||||
|
for (auto cfd : column_families_) { |
||||||
|
#ifndef NDEBUG |
||||||
|
auto itr = column_families_set_.find(cfd); |
||||||
|
assert(itr != column_families_set_.end()); |
||||||
|
column_families_set_.erase(itr); |
||||||
|
#endif // NDEBUG
|
||||||
|
if (cfd->Unref()) { |
||||||
|
delete cfd; |
||||||
|
} |
||||||
|
} |
||||||
|
column_families_.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,40 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <deque> |
||||||
|
#include <set> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class ColumnFamilyData; |
||||||
|
|
||||||
|
// This class is thread-compatible. It's should only be accessed from single
|
||||||
|
// write thread (between BeginWrite() and EndWrite())
|
||||||
|
class FlushScheduler { |
||||||
|
public: |
||||||
|
FlushScheduler() = default; |
||||||
|
~FlushScheduler() = default; |
||||||
|
|
||||||
|
void ScheduleFlush(ColumnFamilyData* cfd); |
||||||
|
// Returns Ref()-ed column family. Client needs to Unref()
|
||||||
|
// REQUIRES: db mutex is held (exception is single-threaded recovery)
|
||||||
|
ColumnFamilyData* GetNextColumnFamily(); |
||||||
|
|
||||||
|
bool Empty(); |
||||||
|
|
||||||
|
void Clear(); |
||||||
|
|
||||||
|
private: |
||||||
|
std::deque<ColumnFamilyData*> column_families_; |
||||||
|
#ifndef NDEBUG |
||||||
|
std::set<ColumnFamilyData*> column_families_set_; |
||||||
|
#endif // NDEBUG
|
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,103 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "db/column_family.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class MemTable; |
||||||
|
|
||||||
|
struct JobContext { |
||||||
|
inline bool HaveSomethingToDelete() const { |
||||||
|
return full_scan_candidate_files.size() || sst_delete_files.size() || |
||||||
|
log_delete_files.size() || new_superversion != nullptr || |
||||||
|
superversions_to_free.size() > 0 || memtables_to_free.size() > 0; |
||||||
|
} |
||||||
|
|
||||||
|
// Structure to store information for candidate files to delete.
|
||||||
|
struct CandidateFileInfo { |
||||||
|
std::string file_name; |
||||||
|
uint32_t path_id; |
||||||
|
CandidateFileInfo(std::string name, uint32_t path) |
||||||
|
: file_name(std::move(name)), path_id(path) {} |
||||||
|
bool operator==(const CandidateFileInfo& other) const { |
||||||
|
return file_name == other.file_name && path_id == other.path_id; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// a list of all files that we'll consider deleting
|
||||||
|
// (every once in a while this is filled up with all files
|
||||||
|
// in the DB directory)
|
||||||
|
// (filled only if we're doing full scan)
|
||||||
|
std::vector<CandidateFileInfo> full_scan_candidate_files; |
||||||
|
|
||||||
|
// the list of all live sst files that cannot be deleted
|
||||||
|
std::vector<FileDescriptor> sst_live; |
||||||
|
|
||||||
|
// a list of sst files that we need to delete
|
||||||
|
std::vector<FileMetaData*> sst_delete_files; |
||||||
|
|
||||||
|
// a list of log files that we need to delete
|
||||||
|
std::vector<uint64_t> log_delete_files; |
||||||
|
|
||||||
|
// a list of memtables to be free
|
||||||
|
autovector<MemTable*> memtables_to_free; |
||||||
|
|
||||||
|
autovector<SuperVersion*> superversions_to_free; |
||||||
|
|
||||||
|
SuperVersion* new_superversion; // if nullptr no new superversion
|
||||||
|
|
||||||
|
// the current manifest_file_number, log_number and prev_log_number
|
||||||
|
// that corresponds to the set of files in 'live'.
|
||||||
|
uint64_t manifest_file_number; |
||||||
|
uint64_t pending_manifest_file_number; |
||||||
|
uint64_t log_number; |
||||||
|
uint64_t prev_log_number; |
||||||
|
|
||||||
|
uint64_t min_pending_output = 0; |
||||||
|
|
||||||
|
explicit JobContext(bool create_superversion = false) { |
||||||
|
manifest_file_number = 0; |
||||||
|
pending_manifest_file_number = 0; |
||||||
|
log_number = 0; |
||||||
|
prev_log_number = 0; |
||||||
|
new_superversion = create_superversion ? new SuperVersion() : nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void Clean() { |
||||||
|
// free pending memtables
|
||||||
|
for (auto m : memtables_to_free) { |
||||||
|
delete m; |
||||||
|
} |
||||||
|
// free superversions
|
||||||
|
for (auto s : superversions_to_free) { |
||||||
|
delete s; |
||||||
|
} |
||||||
|
// if new_superversion was not used, it will be non-nullptr and needs
|
||||||
|
// to be freed here
|
||||||
|
delete new_superversion; |
||||||
|
|
||||||
|
memtables_to_free.clear(); |
||||||
|
superversions_to_free.clear(); |
||||||
|
new_superversion = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
~JobContext() { |
||||||
|
assert(memtables_to_free.size() == 0); |
||||||
|
assert(superversions_to_free.size() == 0); |
||||||
|
assert(new_superversion == nullptr); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,401 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/db_impl.h" |
||||||
|
#include "db/filename.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "db/write_batch_internal.h" |
||||||
|
#include "rocksdb/cache.h" |
||||||
|
#include "rocksdb/compaction_filter.h" |
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/env.h" |
||||||
|
#include "rocksdb/filter_policy.h" |
||||||
|
#include "rocksdb/perf_context.h" |
||||||
|
#include "rocksdb/slice.h" |
||||||
|
#include "rocksdb/slice_transform.h" |
||||||
|
#include "rocksdb/table.h" |
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/table_properties.h" |
||||||
|
#include "table/block_based_table_factory.h" |
||||||
|
#include "table/plain_table_factory.h" |
||||||
|
#include "util/hash.h" |
||||||
|
#include "util/hash_linklist_rep.h" |
||||||
|
#include "utilities/merge_operators.h" |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/mutexlock.h" |
||||||
|
#include "util/rate_limiter.h" |
||||||
|
#include "util/statistics.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/sync_point.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class EventListenerTest { |
||||||
|
public: |
||||||
|
EventListenerTest() { |
||||||
|
dbname_ = test::TmpDir() + "/listener_test"; |
||||||
|
ASSERT_OK(DestroyDB(dbname_, Options())); |
||||||
|
db_ = nullptr; |
||||||
|
Reopen(); |
||||||
|
} |
||||||
|
|
||||||
|
~EventListenerTest() { |
||||||
|
Close(); |
||||||
|
Options options; |
||||||
|
options.db_paths.emplace_back(dbname_, 0); |
||||||
|
options.db_paths.emplace_back(dbname_ + "_2", 0); |
||||||
|
options.db_paths.emplace_back(dbname_ + "_3", 0); |
||||||
|
options.db_paths.emplace_back(dbname_ + "_4", 0); |
||||||
|
ASSERT_OK(DestroyDB(dbname_, options)); |
||||||
|
} |
||||||
|
|
||||||
|
void CreateColumnFamilies(const std::vector<std::string>& cfs, |
||||||
|
const ColumnFamilyOptions* options = nullptr) { |
||||||
|
ColumnFamilyOptions cf_opts; |
||||||
|
cf_opts = ColumnFamilyOptions(Options()); |
||||||
|
size_t cfi = handles_.size(); |
||||||
|
handles_.resize(cfi + cfs.size()); |
||||||
|
for (auto cf : cfs) { |
||||||
|
ASSERT_OK(db_->CreateColumnFamily(cf_opts, cf, &handles_[cfi++])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Close() { |
||||||
|
for (auto h : handles_) { |
||||||
|
delete h; |
||||||
|
} |
||||||
|
handles_.clear(); |
||||||
|
delete db_; |
||||||
|
db_ = nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
void ReopenWithColumnFamilies(const std::vector<std::string>& cfs, |
||||||
|
const Options* options = nullptr) { |
||||||
|
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options)); |
||||||
|
} |
||||||
|
|
||||||
|
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs, |
||||||
|
const Options* options = nullptr) { |
||||||
|
Close(); |
||||||
|
Options opts = (options == nullptr) ? Options() : *options; |
||||||
|
std::vector<const Options*> v_opts(cfs.size(), &opts); |
||||||
|
return TryReopenWithColumnFamilies(cfs, v_opts); |
||||||
|
} |
||||||
|
|
||||||
|
Status TryReopenWithColumnFamilies( |
||||||
|
const std::vector<std::string>& cfs, |
||||||
|
const std::vector<const Options*>& options) { |
||||||
|
Close(); |
||||||
|
ASSERT_EQ(cfs.size(), options.size()); |
||||||
|
std::vector<ColumnFamilyDescriptor> column_families; |
||||||
|
for (size_t i = 0; i < cfs.size(); ++i) { |
||||||
|
column_families.push_back(ColumnFamilyDescriptor(cfs[i], *options[i])); |
||||||
|
} |
||||||
|
DBOptions db_opts = DBOptions(*options[0]); |
||||||
|
return DB::Open(db_opts, dbname_, column_families, &handles_, &db_); |
||||||
|
} |
||||||
|
|
||||||
|
Status TryReopen(Options* options = nullptr) { |
||||||
|
Close(); |
||||||
|
Options opts; |
||||||
|
if (options != nullptr) { |
||||||
|
opts = *options; |
||||||
|
} else { |
||||||
|
opts.create_if_missing = true; |
||||||
|
} |
||||||
|
|
||||||
|
return DB::Open(opts, dbname_, &db_); |
||||||
|
} |
||||||
|
|
||||||
|
void Reopen(Options* options = nullptr) { |
||||||
|
ASSERT_OK(TryReopen(options)); |
||||||
|
} |
||||||
|
|
||||||
|
void CreateAndReopenWithCF(const std::vector<std::string>& cfs, |
||||||
|
const Options* options = nullptr) { |
||||||
|
CreateColumnFamilies(cfs, options); |
||||||
|
std::vector<std::string> cfs_plus_default = cfs; |
||||||
|
cfs_plus_default.insert(cfs_plus_default.begin(), kDefaultColumnFamilyName); |
||||||
|
ReopenWithColumnFamilies(cfs_plus_default, options); |
||||||
|
} |
||||||
|
|
||||||
|
DBImpl* dbfull() { |
||||||
|
return reinterpret_cast<DBImpl*>(db_); |
||||||
|
} |
||||||
|
|
||||||
|
Status Put(int cf, const Slice& k, const Slice& v, |
||||||
|
WriteOptions wo = WriteOptions()) { |
||||||
|
return db_->Put(wo, handles_[cf], k, v); |
||||||
|
} |
||||||
|
|
||||||
|
Status Flush(int cf = 0) { |
||||||
|
FlushOptions opt = FlushOptions(); |
||||||
|
opt.wait = true; |
||||||
|
if (cf == 0) { |
||||||
|
return db_->Flush(opt); |
||||||
|
} else { |
||||||
|
return db_->Flush(opt, handles_[cf]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
DB* db_; |
||||||
|
std::string dbname_; |
||||||
|
std::vector<ColumnFamilyHandle*> handles_; |
||||||
|
}; |
||||||
|
|
||||||
|
class TestCompactionListener : public EventListener { |
||||||
|
public: |
||||||
|
void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) override { |
||||||
|
compacted_dbs_.push_back(db); |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<DB*> compacted_dbs_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(EventListenerTest, OnSingleDBCompactionTest) { |
||||||
|
const int kTestKeySize = 16; |
||||||
|
const int kTestValueSize = 984; |
||||||
|
const int kEntrySize = kTestKeySize + kTestValueSize; |
||||||
|
const int kEntriesPerBuffer = 100; |
||||||
|
const int kNumL0Files = 4; |
||||||
|
|
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
options.write_buffer_size = kEntrySize * kEntriesPerBuffer; |
||||||
|
options.compaction_style = kCompactionStyleLevel; |
||||||
|
options.target_file_size_base = options.write_buffer_size; |
||||||
|
options.max_bytes_for_level_base = options.target_file_size_base * 2; |
||||||
|
options.max_bytes_for_level_multiplier = 2; |
||||||
|
options.compression = kNoCompression; |
||||||
|
options.enable_thread_tracking = true; |
||||||
|
options.level0_file_num_compaction_trigger = kNumL0Files; |
||||||
|
|
||||||
|
TestCompactionListener* listener = new TestCompactionListener(); |
||||||
|
options.listeners.emplace_back(listener); |
||||||
|
std::vector<std::string> cf_names = { |
||||||
|
"pikachu", "ilya", "muromec", "dobrynia", |
||||||
|
"nikitich", "alyosha", "popovich"}; |
||||||
|
CreateAndReopenWithCF(cf_names, &options); |
||||||
|
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); |
||||||
|
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); |
||||||
|
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); |
||||||
|
ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); |
||||||
|
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); |
||||||
|
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); |
||||||
|
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); |
||||||
|
for (size_t i = 1; i < 8; ++i) { |
||||||
|
ASSERT_OK(Flush(static_cast<int>(i))); |
||||||
|
const Slice kStart = "a"; |
||||||
|
const Slice kEnd = "z"; |
||||||
|
ASSERT_OK(dbfull()->CompactRange(handles_[i], &kStart, &kEnd)); |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
dbfull()->TEST_WaitForCompact(); |
||||||
|
} |
||||||
|
|
||||||
|
ASSERT_EQ(listener->compacted_dbs_.size(), cf_names.size()); |
||||||
|
for (size_t i = 0; i < cf_names.size(); ++i) { |
||||||
|
ASSERT_EQ(listener->compacted_dbs_[i], db_); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestFlushListener : public EventListener { |
||||||
|
public: |
||||||
|
void OnFlushCompleted( |
||||||
|
DB* db, const std::string& name, |
||||||
|
const std::string& file_path, |
||||||
|
bool triggered_writes_slowdown, |
||||||
|
bool triggered_writes_stop) override { |
||||||
|
flushed_dbs_.push_back(db); |
||||||
|
flushed_column_family_names_.push_back(name); |
||||||
|
if (triggered_writes_slowdown) { |
||||||
|
slowdown_count++; |
||||||
|
} |
||||||
|
if (triggered_writes_stop) { |
||||||
|
stop_count++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<std::string> flushed_column_family_names_; |
||||||
|
std::vector<DB*> flushed_dbs_; |
||||||
|
int slowdown_count; |
||||||
|
int stop_count; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(EventListenerTest, OnSingleDBFlushTest) { |
||||||
|
Options options; |
||||||
|
options.write_buffer_size = 100000; |
||||||
|
TestFlushListener* listener = new TestFlushListener(); |
||||||
|
options.listeners.emplace_back(listener); |
||||||
|
std::vector<std::string> cf_names = { |
||||||
|
"pikachu", "ilya", "muromec", "dobrynia", |
||||||
|
"nikitich", "alyosha", "popovich"}; |
||||||
|
CreateAndReopenWithCF(cf_names, &options); |
||||||
|
|
||||||
|
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); |
||||||
|
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); |
||||||
|
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); |
||||||
|
ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); |
||||||
|
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); |
||||||
|
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); |
||||||
|
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); |
||||||
|
for (size_t i = 1; i < 8; ++i) { |
||||||
|
ASSERT_OK(Flush(static_cast<int>(i))); |
||||||
|
dbfull()->TEST_WaitForFlushMemTable(); |
||||||
|
ASSERT_EQ(listener->flushed_dbs_.size(), i); |
||||||
|
ASSERT_EQ(listener->flushed_column_family_names_.size(), i); |
||||||
|
} |
||||||
|
|
||||||
|
// make sure call-back functions are called in the right order
|
||||||
|
for (size_t i = 0; i < cf_names.size(); ++i) { |
||||||
|
ASSERT_EQ(listener->flushed_dbs_[i], db_); |
||||||
|
ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(EventListenerTest, MultiCF) { |
||||||
|
Options options; |
||||||
|
options.write_buffer_size = 100000; |
||||||
|
TestFlushListener* listener = new TestFlushListener(); |
||||||
|
options.listeners.emplace_back(listener); |
||||||
|
std::vector<std::string> cf_names = { |
||||||
|
"pikachu", "ilya", "muromec", "dobrynia", |
||||||
|
"nikitich", "alyosha", "popovich"}; |
||||||
|
CreateAndReopenWithCF(cf_names, &options); |
||||||
|
|
||||||
|
ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p'))); |
||||||
|
ASSERT_OK(Put(2, "ilya", std::string(90000, 'i'))); |
||||||
|
ASSERT_OK(Put(3, "muromec", std::string(90000, 'm'))); |
||||||
|
ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd'))); |
||||||
|
ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n'))); |
||||||
|
ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a'))); |
||||||
|
ASSERT_OK(Put(7, "popovich", std::string(90000, 'p'))); |
||||||
|
for (size_t i = 1; i < 8; ++i) { |
||||||
|
ASSERT_OK(Flush(static_cast<int>(i))); |
||||||
|
ASSERT_EQ(listener->flushed_dbs_.size(), i); |
||||||
|
ASSERT_EQ(listener->flushed_column_family_names_.size(), i); |
||||||
|
} |
||||||
|
|
||||||
|
// make sure call-back functions are called in the right order
|
||||||
|
for (size_t i = 0; i < cf_names.size(); i++) { |
||||||
|
ASSERT_EQ(listener->flushed_dbs_[i], db_); |
||||||
|
ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(EventListenerTest, MultiDBMultiListeners) { |
||||||
|
std::vector<TestFlushListener*> listeners; |
||||||
|
const int kNumDBs = 5; |
||||||
|
const int kNumListeners = 10; |
||||||
|
for (int i = 0; i < kNumListeners; ++i) { |
||||||
|
listeners.emplace_back(new TestFlushListener()); |
||||||
|
} |
||||||
|
|
||||||
|
std::vector<std::string> cf_names = { |
||||||
|
"pikachu", "ilya", "muromec", "dobrynia", |
||||||
|
"nikitich", "alyosha", "popovich"}; |
||||||
|
|
||||||
|
Options options; |
||||||
|
options.create_if_missing = true; |
||||||
|
for (int i = 0; i < kNumListeners; ++i) { |
||||||
|
options.listeners.emplace_back(listeners[i]); |
||||||
|
} |
||||||
|
DBOptions db_opts(options); |
||||||
|
ColumnFamilyOptions cf_opts(options); |
||||||
|
|
||||||
|
std::vector<DB*> dbs; |
||||||
|
std::vector<std::vector<ColumnFamilyHandle *>> vec_handles; |
||||||
|
|
||||||
|
for (int d = 0; d < kNumDBs; ++d) { |
||||||
|
ASSERT_OK(DestroyDB(dbname_ + ToString(d), options)); |
||||||
|
DB* db; |
||||||
|
std::vector<ColumnFamilyHandle*> handles; |
||||||
|
ASSERT_OK(DB::Open(options, dbname_ + ToString(d), &db)); |
||||||
|
for (size_t c = 0; c < cf_names.size(); ++c) { |
||||||
|
ColumnFamilyHandle* handle; |
||||||
|
db->CreateColumnFamily(cf_opts, cf_names[c], &handle); |
||||||
|
handles.push_back(handle); |
||||||
|
} |
||||||
|
|
||||||
|
vec_handles.push_back(std::move(handles)); |
||||||
|
dbs.push_back(db); |
||||||
|
} |
||||||
|
|
||||||
|
for (int d = 0; d < kNumDBs; ++d) { |
||||||
|
for (size_t c = 0; c < cf_names.size(); ++c) { |
||||||
|
ASSERT_OK(dbs[d]->Put(WriteOptions(), vec_handles[d][c], |
||||||
|
cf_names[c], cf_names[c])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (size_t c = 0; c < cf_names.size(); ++c) { |
||||||
|
for (int d = 0; d < kNumDBs; ++d) { |
||||||
|
ASSERT_OK(dbs[d]->Flush(FlushOptions(), vec_handles[d][c])); |
||||||
|
reinterpret_cast<DBImpl*>(dbs[d])->TEST_WaitForFlushMemTable(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (auto* listener : listeners) { |
||||||
|
int pos = 0; |
||||||
|
for (size_t c = 0; c < cf_names.size(); ++c) { |
||||||
|
for (int d = 0; d < kNumDBs; ++d) { |
||||||
|
ASSERT_EQ(listener->flushed_dbs_[pos], dbs[d]); |
||||||
|
ASSERT_EQ(listener->flushed_column_family_names_[pos], cf_names[c]); |
||||||
|
pos++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (auto handles : vec_handles) { |
||||||
|
for (auto h : handles) { |
||||||
|
delete h; |
||||||
|
} |
||||||
|
handles.clear(); |
||||||
|
} |
||||||
|
vec_handles.clear(); |
||||||
|
|
||||||
|
for (auto db : dbs) { |
||||||
|
delete db; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(EventListenerTest, DisableBGCompaction) { |
||||||
|
Options options; |
||||||
|
TestFlushListener* listener = new TestFlushListener(); |
||||||
|
const int kSlowdownTrigger = 5; |
||||||
|
const int kStopTrigger = 10; |
||||||
|
options.level0_slowdown_writes_trigger = kSlowdownTrigger; |
||||||
|
options.level0_stop_writes_trigger = kStopTrigger; |
||||||
|
options.listeners.emplace_back(listener); |
||||||
|
// BG compaction is disabled. Number of L0 files will simply keeps
|
||||||
|
// increasing in this test.
|
||||||
|
options.compaction_style = kCompactionStyleNone; |
||||||
|
options.compression = kNoCompression; |
||||||
|
options.write_buffer_size = 100000; // Small write buffer
|
||||||
|
|
||||||
|
CreateAndReopenWithCF({"pikachu"}, &options); |
||||||
|
WriteOptions wopts; |
||||||
|
wopts.timeout_hint_us = 100000; |
||||||
|
ColumnFamilyMetaData cf_meta; |
||||||
|
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta); |
||||||
|
// keep writing until writes are forced to stop.
|
||||||
|
for (int i = 0; static_cast<int>(cf_meta.file_count) < kStopTrigger; ++i) { |
||||||
|
Put(1, ToString(i), std::string(100000, 'x'), wopts); |
||||||
|
db_->GetColumnFamilyMetaData(handles_[1], &cf_meta); |
||||||
|
} |
||||||
|
ASSERT_GE(listener->slowdown_count, kStopTrigger - kSlowdownTrigger); |
||||||
|
ASSERT_GE(listener->stop_count, 1); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
return rocksdb::test::RunAllTests(); |
||||||
|
} |
||||||
|
|
@ -0,0 +1,52 @@ |
|||||||
|
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#include <assert.h> |
||||||
|
|
||||||
|
#include "db/memtable_allocator.h" |
||||||
|
#include "db/writebuffer.h" |
||||||
|
#include "util/arena.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
MemTableAllocator::MemTableAllocator(Arena* arena, WriteBuffer* write_buffer) |
||||||
|
: arena_(arena), write_buffer_(write_buffer), bytes_allocated_(0) { |
||||||
|
} |
||||||
|
|
||||||
|
MemTableAllocator::~MemTableAllocator() { |
||||||
|
DoneAllocating(); |
||||||
|
} |
||||||
|
|
||||||
|
char* MemTableAllocator::Allocate(size_t bytes) { |
||||||
|
assert(write_buffer_ != nullptr); |
||||||
|
bytes_allocated_ += bytes; |
||||||
|
write_buffer_->ReserveMem(bytes); |
||||||
|
return arena_->Allocate(bytes); |
||||||
|
} |
||||||
|
|
||||||
|
char* MemTableAllocator::AllocateAligned(size_t bytes, size_t huge_page_size, |
||||||
|
Logger* logger) { |
||||||
|
assert(write_buffer_ != nullptr); |
||||||
|
bytes_allocated_ += bytes; |
||||||
|
write_buffer_->ReserveMem(bytes); |
||||||
|
return arena_->AllocateAligned(bytes, huge_page_size, logger); |
||||||
|
} |
||||||
|
|
||||||
|
void MemTableAllocator::DoneAllocating() { |
||||||
|
if (write_buffer_ != nullptr) { |
||||||
|
write_buffer_->FreeMem(bytes_allocated_); |
||||||
|
write_buffer_ = nullptr; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
size_t MemTableAllocator::BlockSize() const { |
||||||
|
return arena_->BlockSize(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,47 @@ |
|||||||
|
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
//
|
||||||
|
// This is used by the MemTable to allocate write buffer memory. It connects
|
||||||
|
// to WriteBuffer so we can track and enforce overall write buffer limits.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
#include "util/allocator.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class Arena; |
||||||
|
class Logger; |
||||||
|
class WriteBuffer; |
||||||
|
|
||||||
|
class MemTableAllocator : public Allocator { |
||||||
|
public: |
||||||
|
explicit MemTableAllocator(Arena* arena, WriteBuffer* write_buffer); |
||||||
|
~MemTableAllocator(); |
||||||
|
|
||||||
|
// Allocator interface
|
||||||
|
char* Allocate(size_t bytes) override; |
||||||
|
char* AllocateAligned(size_t bytes, size_t huge_page_size = 0, |
||||||
|
Logger* logger = nullptr) override; |
||||||
|
size_t BlockSize() const override; |
||||||
|
|
||||||
|
// Call when we're finished allocating memory so we can free it from
|
||||||
|
// the write buffer's limit.
|
||||||
|
void DoneAllocating(); |
||||||
|
|
||||||
|
private: |
||||||
|
Arena* arena_; |
||||||
|
WriteBuffer* write_buffer_; |
||||||
|
size_t bytes_allocated_; |
||||||
|
|
||||||
|
// No copying allowed
|
||||||
|
MemTableAllocator(const MemTableAllocator&); |
||||||
|
void operator=(const MemTableAllocator&); |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,696 @@ |
|||||||
|
// Copyright (c) 2014, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#define __STDC_FORMAT_MACROS |
||||||
|
|
||||||
|
#ifndef GFLAGS |
||||||
|
#include <cstdio> |
||||||
|
int main() { |
||||||
|
fprintf(stderr, "Please install gflags to run rocksdb tools\n"); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
#else |
||||||
|
|
||||||
|
#include <gflags/gflags.h> |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
#include <iostream> |
||||||
|
#include <memory> |
||||||
|
#include <thread> |
||||||
|
#include <type_traits> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/memtable.h" |
||||||
|
#include "db/writebuffer.h" |
||||||
|
#include "port/port.h" |
||||||
|
#include "port/stack_trace.h" |
||||||
|
#include "rocksdb/comparator.h" |
||||||
|
#include "rocksdb/memtablerep.h" |
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/slice_transform.h" |
||||||
|
#include "util/arena.h" |
||||||
|
#include "util/mutexlock.h" |
||||||
|
#include "util/stop_watch.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
using GFLAGS::ParseCommandLineFlags; |
||||||
|
using GFLAGS::RegisterFlagValidator; |
||||||
|
using GFLAGS::SetUsageMessage; |
||||||
|
|
||||||
|
DEFINE_string(benchmarks, "fillrandom", |
||||||
|
"Comma-separated list of benchmarks to run. Options:\n" |
||||||
|
"\tfillrandom -- write N random values\n" |
||||||
|
"\tfillseq -- write N values in sequential order\n" |
||||||
|
"\treadrandom -- read N values in random order\n" |
||||||
|
"\treadseq -- scan the DB\n" |
||||||
|
"\treadwrite -- 1 thread writes while N - 1 threads " |
||||||
|
"do random\n" |
||||||
|
"\t reads\n" |
||||||
|
"\tseqreadwrite -- 1 thread writes while N - 1 threads " |
||||||
|
"do scans\n"); |
||||||
|
|
||||||
|
DEFINE_string(memtablerep, "skiplist", |
||||||
|
"Which implementation of memtablerep to use. See " |
||||||
|
"include/memtablerep.h for\n" |
||||||
|
" more details. Options:\n" |
||||||
|
"\tskiplist -- backed by a skiplist\n" |
||||||
|
"\tvector -- backed by an std::vector\n" |
||||||
|
"\thashskiplist -- backed by a hash skip list\n" |
||||||
|
"\thashlinklist -- backed by a hash linked list\n" |
||||||
|
"\tcuckoo -- backed by a cuckoo hash table"); |
||||||
|
|
||||||
|
DEFINE_int64(bucket_count, 1000000, |
||||||
|
"bucket_count parameter to pass into NewHashSkiplistRepFactory or " |
||||||
|
"NewHashLinkListRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32( |
||||||
|
hashskiplist_height, 4, |
||||||
|
"skiplist_height parameter to pass into NewHashSkiplistRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32( |
||||||
|
hashskiplist_branching_factor, 4, |
||||||
|
"branching_factor parameter to pass into NewHashSkiplistRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32( |
||||||
|
huge_page_tlb_size, 0, |
||||||
|
"huge_page_tlb_size parameter to pass into NewHashLinkListRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32(bucket_entries_logging_threshold, 4096, |
||||||
|
"bucket_entries_logging_threshold parameter to pass into " |
||||||
|
"NewHashLinkListRepFactory"); |
||||||
|
|
||||||
|
DEFINE_bool(if_log_bucket_dist_when_flash, true, |
||||||
|
"if_log_bucket_dist_when_flash parameter to pass into " |
||||||
|
"NewHashLinkListRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32( |
||||||
|
threshold_use_skiplist, 256, |
||||||
|
"threshold_use_skiplist parameter to pass into NewHashLinkListRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int64( |
||||||
|
write_buffer_size, 256, |
||||||
|
"write_buffer_size parameter to pass into NewHashCuckooRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int64( |
||||||
|
average_data_size, 64, |
||||||
|
"average_data_size parameter to pass into NewHashCuckooRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int64( |
||||||
|
hash_function_count, 4, |
||||||
|
"hash_function_count parameter to pass into NewHashCuckooRepFactory"); |
||||||
|
|
||||||
|
DEFINE_int32( |
||||||
|
num_threads, 1, |
||||||
|
"Number of concurrent threads to run. If the benchmark includes writes,\n" |
||||||
|
"then at most one thread will be a writer"); |
||||||
|
|
||||||
|
DEFINE_int32(num_operations, 1000000, |
||||||
|
"Number of operations to do for write and random read benchmarks"); |
||||||
|
|
||||||
|
DEFINE_int32(num_scans, 10, |
||||||
|
"Number of times for each thread to scan the memtablerep for " |
||||||
|
"sequential read " |
||||||
|
"benchmarks"); |
||||||
|
|
||||||
|
DEFINE_int32(item_size, 100, "Number of bytes each item should be"); |
||||||
|
|
||||||
|
DEFINE_int32(prefix_length, 8, |
||||||
|
"Prefix length to pass into NewFixedPrefixTransform"); |
||||||
|
|
||||||
|
/* VectorRep settings */ |
||||||
|
DEFINE_int64(vectorrep_count, 0, |
||||||
|
"Number of entries to reserve on VectorRep initialization"); |
||||||
|
|
||||||
|
DEFINE_int64(seed, 0, |
||||||
|
"Seed base for random number generators. " |
||||||
|
"When 0 it is deterministic."); |
||||||
|
|
||||||
|
static rocksdb::Env* FLAGS_env = rocksdb::Env::Default(); |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
namespace { |
||||||
|
struct CallbackVerifyArgs { |
||||||
|
bool found; |
||||||
|
LookupKey* key; |
||||||
|
MemTableRep* table; |
||||||
|
InternalKeyComparator* comparator; |
||||||
|
}; |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Helper for quickly generating random data.
|
||||||
|
class RandomGenerator { |
||||||
|
private: |
||||||
|
std::string data_; |
||||||
|
unsigned int pos_; |
||||||
|
|
||||||
|
public: |
||||||
|
RandomGenerator() { |
||||||
|
Random rnd(301); |
||||||
|
auto size = (unsigned)std::max(1048576, FLAGS_item_size); |
||||||
|
test::RandomString(&rnd, size, &data_); |
||||||
|
pos_ = 0; |
||||||
|
} |
||||||
|
|
||||||
|
Slice Generate(unsigned int len) { |
||||||
|
assert(len <= data_.size()); |
||||||
|
if (pos_ + len > data_.size()) { |
||||||
|
pos_ = 0; |
||||||
|
} |
||||||
|
pos_ += len; |
||||||
|
return Slice(data_.data() + pos_ - len, len); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
enum WriteMode { SEQUENTIAL, RANDOM, UNIQUE_RANDOM }; |
||||||
|
|
||||||
|
class KeyGenerator { |
||||||
|
public: |
||||||
|
KeyGenerator(Random64* rand, WriteMode mode, uint64_t num) |
||||||
|
: rand_(rand), mode_(mode), num_(num), next_(0) { |
||||||
|
if (mode_ == UNIQUE_RANDOM) { |
||||||
|
// NOTE: if memory consumption of this approach becomes a concern,
|
||||||
|
// we can either break it into pieces and only random shuffle a section
|
||||||
|
// each time. Alternatively, use a bit map implementation
|
||||||
|
// (https://reviews.facebook.net/differential/diff/54627/)
|
||||||
|
values_.resize(num_); |
||||||
|
for (uint64_t i = 0; i < num_; ++i) { |
||||||
|
values_[i] = i; |
||||||
|
} |
||||||
|
std::shuffle( |
||||||
|
values_.begin(), values_.end(), |
||||||
|
std::default_random_engine(static_cast<unsigned int>(FLAGS_seed))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
uint64_t Next() { |
||||||
|
switch (mode_) { |
||||||
|
case SEQUENTIAL: |
||||||
|
return next_++; |
||||||
|
case RANDOM: |
||||||
|
return rand_->Next() % num_; |
||||||
|
case UNIQUE_RANDOM: |
||||||
|
return values_[next_++]; |
||||||
|
} |
||||||
|
assert(false); |
||||||
|
return std::numeric_limits<uint64_t>::max(); |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
Random64* rand_; |
||||||
|
WriteMode mode_; |
||||||
|
const uint64_t num_; |
||||||
|
uint64_t next_; |
||||||
|
std::vector<uint64_t> values_; |
||||||
|
}; |
||||||
|
|
||||||
|
class BenchmarkThread { |
||||||
|
public: |
||||||
|
explicit BenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, |
||||||
|
uint64_t* read_hits) |
||||||
|
: table_(table), |
||||||
|
key_gen_(key_gen), |
||||||
|
bytes_written_(bytes_written), |
||||||
|
bytes_read_(bytes_read), |
||||||
|
sequence_(sequence), |
||||||
|
num_ops_(num_ops), |
||||||
|
read_hits_(read_hits) {} |
||||||
|
|
||||||
|
virtual void operator()() = 0; |
||||||
|
virtual ~BenchmarkThread() {} |
||||||
|
|
||||||
|
protected: |
||||||
|
MemTableRep* table_; |
||||||
|
KeyGenerator* key_gen_; |
||||||
|
uint64_t* bytes_written_; |
||||||
|
uint64_t* bytes_read_; |
||||||
|
uint64_t* sequence_; |
||||||
|
uint64_t num_ops_; |
||||||
|
uint64_t* read_hits_; |
||||||
|
RandomGenerator generator_; |
||||||
|
}; |
||||||
|
|
||||||
|
class FillBenchmarkThread : public BenchmarkThread { |
||||||
|
public: |
||||||
|
FillBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, uint64_t* read_hits) |
||||||
|
: BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence, |
||||||
|
num_ops, read_hits) {} |
||||||
|
|
||||||
|
void FillOne() { |
||||||
|
char* buf = nullptr; |
||||||
|
auto internal_key_size = 16; |
||||||
|
auto encoded_len = |
||||||
|
FLAGS_item_size + VarintLength(internal_key_size) + internal_key_size; |
||||||
|
KeyHandle handle = table_->Allocate(encoded_len, &buf); |
||||||
|
assert(buf != nullptr); |
||||||
|
char* p = EncodeVarint32(buf, internal_key_size); |
||||||
|
auto key = key_gen_->Next(); |
||||||
|
EncodeFixed64(p, key); |
||||||
|
p += 8; |
||||||
|
EncodeFixed64(p, ++(*sequence_)); |
||||||
|
p += 8; |
||||||
|
Slice bytes = generator_.Generate(FLAGS_item_size); |
||||||
|
memcpy(p, bytes.data(), FLAGS_item_size); |
||||||
|
p += FLAGS_item_size; |
||||||
|
assert(p == buf + encoded_len); |
||||||
|
table_->Insert(handle); |
||||||
|
*bytes_written_ += encoded_len; |
||||||
|
} |
||||||
|
|
||||||
|
void operator()() override { |
||||||
|
for (unsigned int i = 0; i < num_ops_; ++i) { |
||||||
|
FillOne(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class ConcurrentFillBenchmarkThread : public FillBenchmarkThread { |
||||||
|
public: |
||||||
|
ConcurrentFillBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, |
||||||
|
uint64_t* read_hits, |
||||||
|
std::atomic_int* threads_done) |
||||||
|
: FillBenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence, |
||||||
|
num_ops, read_hits) { |
||||||
|
threads_done_ = threads_done; |
||||||
|
} |
||||||
|
|
||||||
|
void operator()() override { |
||||||
|
// # of read threads will be total threads - write threads (always 1). Loop
|
||||||
|
// while all reads complete.
|
||||||
|
while ((*threads_done_).load() < (FLAGS_num_threads - 1)) { |
||||||
|
FillOne(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::atomic_int* threads_done_; |
||||||
|
}; |
||||||
|
|
||||||
|
class ReadBenchmarkThread : public BenchmarkThread { |
||||||
|
public: |
||||||
|
ReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, uint64_t* read_hits) |
||||||
|
: BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence, |
||||||
|
num_ops, read_hits) {} |
||||||
|
|
||||||
|
static bool callback(void* arg, const char* entry) { |
||||||
|
CallbackVerifyArgs* callback_args = static_cast<CallbackVerifyArgs*>(arg); |
||||||
|
assert(callback_args != nullptr); |
||||||
|
uint32_t key_length; |
||||||
|
const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length); |
||||||
|
if ((callback_args->comparator)->user_comparator()->Compare( |
||||||
|
Slice(key_ptr, key_length - 8), callback_args->key->user_key()) == |
||||||
|
0) { |
||||||
|
callback_args->found = true; |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
void ReadOne() { |
||||||
|
std::string user_key; |
||||||
|
auto key = key_gen_->Next(); |
||||||
|
PutFixed64(&user_key, key); |
||||||
|
LookupKey lookup_key(user_key, *sequence_); |
||||||
|
InternalKeyComparator internal_key_comp(BytewiseComparator()); |
||||||
|
CallbackVerifyArgs verify_args; |
||||||
|
verify_args.found = false; |
||||||
|
verify_args.key = &lookup_key; |
||||||
|
verify_args.table = table_; |
||||||
|
verify_args.comparator = &internal_key_comp; |
||||||
|
table_->Get(lookup_key, &verify_args, callback); |
||||||
|
if (verify_args.found) { |
||||||
|
*bytes_read_ += VarintLength(16) + 16 + FLAGS_item_size; |
||||||
|
++*read_hits_; |
||||||
|
} |
||||||
|
} |
||||||
|
void operator()() override { |
||||||
|
for (unsigned int i = 0; i < num_ops_; ++i) { |
||||||
|
ReadOne(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class SeqReadBenchmarkThread : public BenchmarkThread { |
||||||
|
public: |
||||||
|
SeqReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, |
||||||
|
uint64_t* read_hits) |
||||||
|
: BenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence, |
||||||
|
num_ops, read_hits) {} |
||||||
|
|
||||||
|
void ReadOneSeq() { |
||||||
|
std::unique_ptr<MemTableRep::Iterator> iter(table_->GetIterator()); |
||||||
|
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
||||||
|
// pretend to read the value
|
||||||
|
*bytes_read_ += VarintLength(16) + 16 + FLAGS_item_size; |
||||||
|
} |
||||||
|
++*read_hits_; |
||||||
|
} |
||||||
|
|
||||||
|
void operator()() override { |
||||||
|
for (unsigned int i = 0; i < num_ops_; ++i) { |
||||||
|
{ ReadOneSeq(); } |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class ConcurrentReadBenchmarkThread : public ReadBenchmarkThread { |
||||||
|
public: |
||||||
|
ConcurrentReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
uint64_t* sequence, uint64_t num_ops, |
||||||
|
uint64_t* read_hits, |
||||||
|
std::atomic_int* threads_done) |
||||||
|
: ReadBenchmarkThread(table, key_gen, bytes_written, bytes_read, sequence, |
||||||
|
num_ops, read_hits) { |
||||||
|
threads_done_ = threads_done; |
||||||
|
} |
||||||
|
|
||||||
|
void operator()() override { |
||||||
|
for (unsigned int i = 0; i < num_ops_; ++i) { |
||||||
|
ReadOne(); |
||||||
|
} |
||||||
|
++*threads_done_; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::atomic_int* threads_done_; |
||||||
|
}; |
||||||
|
|
||||||
|
class SeqConcurrentReadBenchmarkThread : public SeqReadBenchmarkThread { |
||||||
|
public: |
||||||
|
SeqConcurrentReadBenchmarkThread(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* bytes_written, |
||||||
|
uint64_t* bytes_read, uint64_t* sequence, |
||||||
|
uint64_t num_ops, uint64_t* read_hits, |
||||||
|
std::atomic_int* threads_done) |
||||||
|
: SeqReadBenchmarkThread(table, key_gen, bytes_written, bytes_read, |
||||||
|
sequence, num_ops, read_hits) { |
||||||
|
threads_done_ = threads_done; |
||||||
|
} |
||||||
|
|
||||||
|
void operator()() override { |
||||||
|
for (unsigned int i = 0; i < num_ops_; ++i) { |
||||||
|
ReadOneSeq(); |
||||||
|
} |
||||||
|
++*threads_done_; |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::atomic_int* threads_done_; |
||||||
|
}; |
||||||
|
|
||||||
|
class Benchmark { |
||||||
|
public: |
||||||
|
explicit Benchmark(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* sequence, uint32_t num_threads) |
||||||
|
: table_(table), |
||||||
|
key_gen_(key_gen), |
||||||
|
sequence_(sequence), |
||||||
|
num_threads_(num_threads) {} |
||||||
|
|
||||||
|
virtual ~Benchmark() {} |
||||||
|
virtual void Run() { |
||||||
|
std::cout << "Number of threads: " << num_threads_ << std::endl; |
||||||
|
std::vector<std::thread> threads; |
||||||
|
uint64_t bytes_written = 0; |
||||||
|
uint64_t bytes_read = 0; |
||||||
|
uint64_t read_hits = 0; |
||||||
|
StopWatchNano timer(Env::Default(), true); |
||||||
|
RunThreads(&threads, &bytes_written, &bytes_read, true, &read_hits); |
||||||
|
auto elapsed_time = static_cast<double>(timer.ElapsedNanos() / 1000); |
||||||
|
std::cout << "Elapsed time: " << static_cast<int>(elapsed_time) << " us" |
||||||
|
<< std::endl; |
||||||
|
|
||||||
|
if (bytes_written > 0) { |
||||||
|
auto MiB_written = static_cast<double>(bytes_written) / (1 << 20); |
||||||
|
auto write_throughput = MiB_written / (elapsed_time / 1000000); |
||||||
|
std::cout << "Total bytes written: " << MiB_written << " MiB" |
||||||
|
<< std::endl; |
||||||
|
std::cout << "Write throughput: " << write_throughput << " MiB/s" |
||||||
|
<< std::endl; |
||||||
|
auto us_per_op = elapsed_time / num_write_ops_per_thread_; |
||||||
|
std::cout << "write us/op: " << us_per_op << std::endl; |
||||||
|
} |
||||||
|
if (bytes_read > 0) { |
||||||
|
auto MiB_read = static_cast<double>(bytes_read) / (1 << 20); |
||||||
|
auto read_throughput = MiB_read / (elapsed_time / 1000000); |
||||||
|
std::cout << "Total bytes read: " << MiB_read << " MiB" << std::endl; |
||||||
|
std::cout << "Read throughput: " << read_throughput << " MiB/s" |
||||||
|
<< std::endl; |
||||||
|
auto us_per_op = elapsed_time / num_read_ops_per_thread_; |
||||||
|
std::cout << "read us/op: " << us_per_op << std::endl; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
virtual void RunThreads(std::vector<std::thread>* threads, |
||||||
|
uint64_t* bytes_written, uint64_t* bytes_read, |
||||||
|
bool write, uint64_t* read_hits) = 0; |
||||||
|
|
||||||
|
protected: |
||||||
|
MemTableRep* table_; |
||||||
|
KeyGenerator* key_gen_; |
||||||
|
uint64_t* sequence_; |
||||||
|
uint64_t num_write_ops_per_thread_; |
||||||
|
uint64_t num_read_ops_per_thread_; |
||||||
|
const uint32_t num_threads_; |
||||||
|
}; |
||||||
|
|
||||||
|
class FillBenchmark : public Benchmark { |
||||||
|
public: |
||||||
|
explicit FillBenchmark(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* sequence) |
||||||
|
: Benchmark(table, key_gen, sequence, 1) { |
||||||
|
num_write_ops_per_thread_ = FLAGS_num_operations; |
||||||
|
} |
||||||
|
|
||||||
|
void RunThreads(std::vector<std::thread>* threads, uint64_t* bytes_written, |
||||||
|
uint64_t* bytes_read, bool write, |
||||||
|
uint64_t* read_hits) override { |
||||||
|
FillBenchmarkThread(table_, key_gen_, bytes_written, bytes_read, sequence_, |
||||||
|
num_write_ops_per_thread_, read_hits)(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class ReadBenchmark : public Benchmark { |
||||||
|
public: |
||||||
|
explicit ReadBenchmark(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* sequence) |
||||||
|
: Benchmark(table, key_gen, sequence, FLAGS_num_threads) { |
||||||
|
num_read_ops_per_thread_ = FLAGS_num_operations / FLAGS_num_threads; |
||||||
|
} |
||||||
|
|
||||||
|
void RunThreads(std::vector<std::thread>* threads, uint64_t* bytes_written, |
||||||
|
uint64_t* bytes_read, bool write, |
||||||
|
uint64_t* read_hits) override { |
||||||
|
for (int i = 0; i < FLAGS_num_threads; ++i) { |
||||||
|
threads->emplace_back( |
||||||
|
ReadBenchmarkThread(table_, key_gen_, bytes_written, bytes_read, |
||||||
|
sequence_, num_read_ops_per_thread_, read_hits)); |
||||||
|
} |
||||||
|
for (auto& thread : *threads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
std::cout << "read hit%: " |
||||||
|
<< (static_cast<double>(*read_hits) / FLAGS_num_operations) * 100 |
||||||
|
<< std::endl; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class SeqReadBenchmark : public Benchmark { |
||||||
|
public: |
||||||
|
explicit SeqReadBenchmark(MemTableRep* table, uint64_t* sequence) |
||||||
|
: Benchmark(table, nullptr, sequence, FLAGS_num_threads) { |
||||||
|
num_read_ops_per_thread_ = FLAGS_num_scans; |
||||||
|
} |
||||||
|
|
||||||
|
void RunThreads(std::vector<std::thread>* threads, uint64_t* bytes_written, |
||||||
|
uint64_t* bytes_read, bool write, |
||||||
|
uint64_t* read_hits) override { |
||||||
|
for (int i = 0; i < FLAGS_num_threads; ++i) { |
||||||
|
threads->emplace_back(SeqReadBenchmarkThread( |
||||||
|
table_, key_gen_, bytes_written, bytes_read, sequence_, |
||||||
|
num_read_ops_per_thread_, read_hits)); |
||||||
|
} |
||||||
|
for (auto& thread : *threads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
template <class ReadThreadType> |
||||||
|
class ReadWriteBenchmark : public Benchmark { |
||||||
|
public: |
||||||
|
explicit ReadWriteBenchmark(MemTableRep* table, KeyGenerator* key_gen, |
||||||
|
uint64_t* sequence) |
||||||
|
: Benchmark(table, key_gen, sequence, FLAGS_num_threads) { |
||||||
|
num_read_ops_per_thread_ = |
||||||
|
FLAGS_num_threads <= 1 |
||||||
|
? 0 |
||||||
|
: (FLAGS_num_operations / (FLAGS_num_threads - 1)); |
||||||
|
num_write_ops_per_thread_ = FLAGS_num_operations; |
||||||
|
} |
||||||
|
|
||||||
|
void RunThreads(std::vector<std::thread>* threads, uint64_t* bytes_written, |
||||||
|
uint64_t* bytes_read, bool write, |
||||||
|
uint64_t* read_hits) override { |
||||||
|
std::atomic_int threads_done; |
||||||
|
threads_done.store(0); |
||||||
|
threads->emplace_back(ConcurrentFillBenchmarkThread( |
||||||
|
table_, key_gen_, bytes_written, bytes_read, sequence_, |
||||||
|
num_write_ops_per_thread_, read_hits, &threads_done)); |
||||||
|
for (int i = 1; i < FLAGS_num_threads; ++i) { |
||||||
|
threads->emplace_back( |
||||||
|
ReadThreadType(table_, key_gen_, bytes_written, bytes_read, sequence_, |
||||||
|
num_read_ops_per_thread_, read_hits, &threads_done)); |
||||||
|
} |
||||||
|
for (auto& thread : *threads) { |
||||||
|
thread.join(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
void PrintWarnings() { |
||||||
|
#if defined(__GNUC__) && !defined(__OPTIMIZE__) |
||||||
|
fprintf(stdout, |
||||||
|
"WARNING: Optimization is disabled: benchmarks unnecessarily slow\n"); |
||||||
|
#endif |
||||||
|
#ifndef NDEBUG |
||||||
|
fprintf(stdout, |
||||||
|
"WARNING: Assertions are enabled; benchmarks unnecessarily slow\n"); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
int main(int argc, char** argv) { |
||||||
|
rocksdb::port::InstallStackTraceHandler(); |
||||||
|
SetUsageMessage(std::string("\nUSAGE:\n") + std::string(argv[0]) + |
||||||
|
" [OPTIONS]..."); |
||||||
|
ParseCommandLineFlags(&argc, &argv, true); |
||||||
|
|
||||||
|
PrintWarnings(); |
||||||
|
|
||||||
|
rocksdb::Options options; |
||||||
|
|
||||||
|
std::unique_ptr<rocksdb::MemTableRepFactory> factory; |
||||||
|
if (FLAGS_memtablerep == "skiplist") { |
||||||
|
factory.reset(new rocksdb::SkipListFactory); |
||||||
|
} else if (FLAGS_memtablerep == "vector") { |
||||||
|
factory.reset(new rocksdb::VectorRepFactory); |
||||||
|
} else if (FLAGS_memtablerep == "hashskiplist") { |
||||||
|
factory.reset(rocksdb::NewHashSkipListRepFactory( |
||||||
|
FLAGS_bucket_count, FLAGS_hashskiplist_height, |
||||||
|
FLAGS_hashskiplist_branching_factor)); |
||||||
|
options.prefix_extractor.reset( |
||||||
|
rocksdb::NewFixedPrefixTransform(FLAGS_prefix_length)); |
||||||
|
} else if (FLAGS_memtablerep == "hashlinklist") { |
||||||
|
factory.reset(rocksdb::NewHashLinkListRepFactory( |
||||||
|
FLAGS_bucket_count, FLAGS_huge_page_tlb_size, |
||||||
|
FLAGS_bucket_entries_logging_threshold, |
||||||
|
FLAGS_if_log_bucket_dist_when_flash, FLAGS_threshold_use_skiplist)); |
||||||
|
options.prefix_extractor.reset( |
||||||
|
rocksdb::NewFixedPrefixTransform(FLAGS_prefix_length)); |
||||||
|
} else if (FLAGS_memtablerep == "cuckoo") { |
||||||
|
factory.reset(rocksdb::NewHashCuckooRepFactory( |
||||||
|
FLAGS_write_buffer_size, FLAGS_average_data_size, |
||||||
|
static_cast<uint32_t>(FLAGS_hash_function_count))); |
||||||
|
options.prefix_extractor.reset( |
||||||
|
rocksdb::NewFixedPrefixTransform(FLAGS_prefix_length)); |
||||||
|
} else { |
||||||
|
fprintf(stdout, "Unknown memtablerep: %s\n", FLAGS_memtablerep.c_str()); |
||||||
|
exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
rocksdb::InternalKeyComparator internal_key_comp( |
||||||
|
rocksdb::BytewiseComparator()); |
||||||
|
rocksdb::MemTable::KeyComparator key_comp(internal_key_comp); |
||||||
|
rocksdb::Arena arena; |
||||||
|
rocksdb::WriteBuffer wb(FLAGS_write_buffer_size); |
||||||
|
rocksdb::MemTableAllocator memtable_allocator(&arena, &wb); |
||||||
|
uint64_t sequence; |
||||||
|
auto createMemtableRep = [&] { |
||||||
|
sequence = 0; |
||||||
|
return factory->CreateMemTableRep(key_comp, &memtable_allocator, |
||||||
|
options.prefix_extractor.get(), |
||||||
|
options.info_log.get()); |
||||||
|
}; |
||||||
|
std::unique_ptr<rocksdb::MemTableRep> memtablerep; |
||||||
|
rocksdb::Random64 rng(FLAGS_seed); |
||||||
|
const char* benchmarks = FLAGS_benchmarks.c_str(); |
||||||
|
while (benchmarks != nullptr) { |
||||||
|
std::unique_ptr<rocksdb::KeyGenerator> key_gen; |
||||||
|
const char* sep = strchr(benchmarks, ','); |
||||||
|
rocksdb::Slice name; |
||||||
|
if (sep == nullptr) { |
||||||
|
name = benchmarks; |
||||||
|
benchmarks = nullptr; |
||||||
|
} else { |
||||||
|
name = rocksdb::Slice(benchmarks, sep - benchmarks); |
||||||
|
benchmarks = sep + 1; |
||||||
|
} |
||||||
|
std::unique_ptr<rocksdb::Benchmark> benchmark; |
||||||
|
if (name == rocksdb::Slice("fillseq")) { |
||||||
|
memtablerep.reset(createMemtableRep()); |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::SEQUENTIAL, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset(new rocksdb::FillBenchmark(memtablerep.get(), |
||||||
|
key_gen.get(), &sequence)); |
||||||
|
} else if (name == rocksdb::Slice("fillrandom")) { |
||||||
|
memtablerep.reset(createMemtableRep()); |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::UNIQUE_RANDOM, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset(new rocksdb::FillBenchmark(memtablerep.get(), |
||||||
|
key_gen.get(), &sequence)); |
||||||
|
} else if (name == rocksdb::Slice("readrandom")) { |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::RANDOM, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset(new rocksdb::ReadBenchmark(memtablerep.get(), |
||||||
|
key_gen.get(), &sequence)); |
||||||
|
} else if (name == rocksdb::Slice("readseq")) { |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::SEQUENTIAL, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset( |
||||||
|
new rocksdb::SeqReadBenchmark(memtablerep.get(), &sequence)); |
||||||
|
} else if (name == rocksdb::Slice("readwrite")) { |
||||||
|
memtablerep.reset(createMemtableRep()); |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::RANDOM, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset(new rocksdb::ReadWriteBenchmark< |
||||||
|
rocksdb::ConcurrentReadBenchmarkThread>(memtablerep.get(), |
||||||
|
key_gen.get(), &sequence)); |
||||||
|
} else if (name == rocksdb::Slice("seqreadwrite")) { |
||||||
|
memtablerep.reset(createMemtableRep()); |
||||||
|
key_gen.reset(new rocksdb::KeyGenerator(&rng, rocksdb::RANDOM, |
||||||
|
FLAGS_num_operations)); |
||||||
|
benchmark.reset(new rocksdb::ReadWriteBenchmark< |
||||||
|
rocksdb::SeqConcurrentReadBenchmarkThread>(memtablerep.get(), |
||||||
|
key_gen.get(), &sequence)); |
||||||
|
} else { |
||||||
|
std::cout << "WARNING: skipping unknown benchmark '" << name.ToString() |
||||||
|
<< std::endl; |
||||||
|
continue; |
||||||
|
} |
||||||
|
std::cout << "Running " << name.ToString() << std::endl; |
||||||
|
benchmark->Run(); |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#endif // GFLAGS
|
@ -1,810 +0,0 @@ |
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
||||||
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
|
||||||
// This source code is licensed under the BSD-style license found in the
|
|
||||||
// LICENSE file in the root directory of this source tree. An additional grant
|
|
||||||
// of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
//
|
|
||||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
||||||
#include <algorithm> |
|
||||||
#include <set> |
|
||||||
|
|
||||||
#include "rocksdb/db.h" |
|
||||||
#include "rocksdb/filter_policy.h" |
|
||||||
#include "db/db_impl.h" |
|
||||||
#include "db/filename.h" |
|
||||||
#include "db/version_set.h" |
|
||||||
#include "db/write_batch_internal.h" |
|
||||||
#include "rocksdb/statistics.h" |
|
||||||
#include "rocksdb/cache.h" |
|
||||||
#include "rocksdb/compaction_filter.h" |
|
||||||
#include "rocksdb/env.h" |
|
||||||
#include "rocksdb/table.h" |
|
||||||
#include "rocksdb/table_properties.h" |
|
||||||
#include "table/table_builder.h" |
|
||||||
#include "util/hash.h" |
|
||||||
#include "util/logging.h" |
|
||||||
#include "util/mutexlock.h" |
|
||||||
#include "util/testharness.h" |
|
||||||
#include "util/testutil.h" |
|
||||||
#include "utilities/merge_operators.h" |
|
||||||
|
|
||||||
using std::unique_ptr; |
|
||||||
|
|
||||||
// IS THIS FILE STILL NEEDED?
|
|
||||||
namespace rocksdb { |
|
||||||
|
|
||||||
// SimpleTable is a simple table format for UNIT TEST ONLY. It is not built
|
|
||||||
// as production quality.
|
|
||||||
// SimpleTable requires the input key size to be fixed 16 bytes, value cannot
|
|
||||||
// be longer than 150000 bytes and stored data on disk in this format:
|
|
||||||
// +--------------------------------------------+ <= key1 offset
|
|
||||||
// | key1 | value_size (4 bytes) | |
|
|
||||||
// +----------------------------------------+ |
|
|
||||||
// | value1 |
|
|
||||||
// | |
|
|
||||||
// +----------------------------------------+---+ <= key2 offset
|
|
||||||
// | key2 | value_size (4 bytes) | |
|
|
||||||
// +----------------------------------------+ |
|
|
||||||
// | value2 |
|
|
||||||
// | |
|
|
||||||
// | ...... |
|
|
||||||
// +-----------------+--------------------------+ <= index_block_offset
|
|
||||||
// | key1 | key1 offset (8 bytes) |
|
|
||||||
// +-----------------+--------------------------+
|
|
||||||
// | key2 | key2 offset (8 bytes) |
|
|
||||||
// +-----------------+--------------------------+
|
|
||||||
// | key3 | key3 offset (8 bytes) |
|
|
||||||
// +-----------------+--------------------------+
|
|
||||||
// | ...... |
|
|
||||||
// +-----------------+------------+-------------+
|
|
||||||
// | index_block_offset (8 bytes) |
|
|
||||||
// +------------------------------+
|
|
||||||
|
|
||||||
// SimpleTable is a simple table format for UNIT TEST ONLY. It is not built
|
|
||||||
// as production quality.
|
|
||||||
class SimpleTableReader: public TableReader { |
|
||||||
public: |
|
||||||
// Attempt to open the table that is stored in bytes [0..file_size)
|
|
||||||
// of "file", and read the metadata entries necessary to allow
|
|
||||||
// retrieving data from the table.
|
|
||||||
//
|
|
||||||
// If successful, returns ok and sets "*table" to the newly opened
|
|
||||||
// table. The client should delete "*table" when no longer needed.
|
|
||||||
// If there was an error while initializing the table, sets "*table"
|
|
||||||
// to nullptr and returns a non-ok status. Does not take ownership of
|
|
||||||
// "*source", but the client must ensure that "source" remains live
|
|
||||||
// for the duration of the returned table's lifetime.
|
|
||||||
//
|
|
||||||
// *file must remain live while this Table is in use.
|
|
||||||
static Status Open(const Options& options, const EnvOptions& soptions, |
|
||||||
unique_ptr<RandomAccessFile> && file, uint64_t file_size, |
|
||||||
unique_ptr<TableReader>* table_reader); |
|
||||||
|
|
||||||
Iterator* NewIterator(const ReadOptions&, Arena* arena) override; |
|
||||||
|
|
||||||
Status Get(const ReadOptions&, const Slice& key, void* arg, |
|
||||||
bool (*handle_result)(void* arg, const ParsedInternalKey& k, |
|
||||||
const Slice& v), |
|
||||||
void (*mark_key_may_exist)(void*) = nullptr) override; |
|
||||||
|
|
||||||
uint64_t ApproximateOffsetOf(const Slice& key) override; |
|
||||||
|
|
||||||
virtual size_t ApproximateMemoryUsage() const override { return 0; } |
|
||||||
|
|
||||||
void SetupForCompaction() override; |
|
||||||
|
|
||||||
std::shared_ptr<const TableProperties> GetTableProperties() const override; |
|
||||||
|
|
||||||
~SimpleTableReader(); |
|
||||||
|
|
||||||
private: |
|
||||||
struct Rep; |
|
||||||
Rep* rep_; |
|
||||||
|
|
||||||
explicit SimpleTableReader(Rep* rep) { |
|
||||||
rep_ = rep; |
|
||||||
} |
|
||||||
friend class TableCache; |
|
||||||
friend class SimpleTableIterator; |
|
||||||
|
|
||||||
Status GetOffset(const Slice& target, uint64_t* offset); |
|
||||||
|
|
||||||
// No copying allowed
|
|
||||||
explicit SimpleTableReader(const TableReader&) = delete; |
|
||||||
void operator=(const TableReader&) = delete; |
|
||||||
}; |
|
||||||
|
|
||||||
// Iterator to iterate SimpleTable
|
|
||||||
class SimpleTableIterator: public Iterator { |
|
||||||
public: |
|
||||||
explicit SimpleTableIterator(SimpleTableReader* table); |
|
||||||
~SimpleTableIterator(); |
|
||||||
|
|
||||||
bool Valid() const; |
|
||||||
|
|
||||||
void SeekToFirst(); |
|
||||||
|
|
||||||
void SeekToLast(); |
|
||||||
|
|
||||||
void Seek(const Slice& target); |
|
||||||
|
|
||||||
void Next(); |
|
||||||
|
|
||||||
void Prev(); |
|
||||||
|
|
||||||
Slice key() const; |
|
||||||
|
|
||||||
Slice value() const; |
|
||||||
|
|
||||||
Status status() const; |
|
||||||
|
|
||||||
private: |
|
||||||
SimpleTableReader* table_; |
|
||||||
uint64_t offset_; |
|
||||||
uint64_t next_offset_; |
|
||||||
Slice key_; |
|
||||||
Slice value_; |
|
||||||
char tmp_str_[4]; |
|
||||||
char* key_str_; |
|
||||||
char* value_str_; |
|
||||||
int value_str_len_; |
|
||||||
Status status_; |
|
||||||
// No copying allowed
|
|
||||||
SimpleTableIterator(const SimpleTableIterator&) = delete; |
|
||||||
void operator=(const Iterator&) = delete; |
|
||||||
}; |
|
||||||
|
|
||||||
struct SimpleTableReader::Rep { |
|
||||||
~Rep() { |
|
||||||
} |
|
||||||
Rep(const EnvOptions& storage_options, uint64_t index_start_offset, |
|
||||||
int num_entries) : |
|
||||||
soptions(storage_options), index_start_offset(index_start_offset), |
|
||||||
num_entries(num_entries) { |
|
||||||
} |
|
||||||
|
|
||||||
Options options; |
|
||||||
const EnvOptions& soptions; |
|
||||||
Status status; |
|
||||||
unique_ptr<RandomAccessFile> file; |
|
||||||
uint64_t index_start_offset; |
|
||||||
int num_entries; |
|
||||||
std::shared_ptr<TableProperties> table_properties; |
|
||||||
|
|
||||||
const static int user_key_size = 16; |
|
||||||
const static int offset_length = 8; |
|
||||||
const static int key_footer_len = 8; |
|
||||||
|
|
||||||
static int GetInternalKeyLength() { |
|
||||||
return user_key_size + key_footer_len; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
SimpleTableReader::~SimpleTableReader() { |
|
||||||
delete rep_; |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableReader::Open(const Options& options, |
|
||||||
const EnvOptions& soptions, |
|
||||||
unique_ptr<RandomAccessFile> && file, |
|
||||||
uint64_t size, |
|
||||||
unique_ptr<TableReader>* table_reader) { |
|
||||||
char footer_space[Rep::offset_length]; |
|
||||||
Slice footer_input; |
|
||||||
Status s = file->Read(size - Rep::offset_length, Rep::offset_length, |
|
||||||
&footer_input, footer_space); |
|
||||||
if (s.ok()) { |
|
||||||
uint64_t index_start_offset = DecodeFixed64(footer_space); |
|
||||||
|
|
||||||
int num_entries = (size - Rep::offset_length - index_start_offset) |
|
||||||
/ (Rep::GetInternalKeyLength() + Rep::offset_length); |
|
||||||
SimpleTableReader::Rep* rep = new SimpleTableReader::Rep(soptions, |
|
||||||
index_start_offset, |
|
||||||
num_entries); |
|
||||||
|
|
||||||
rep->file = std::move(file); |
|
||||||
rep->options = options; |
|
||||||
table_reader->reset(new SimpleTableReader(rep)); |
|
||||||
} |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableReader::SetupForCompaction() { |
|
||||||
} |
|
||||||
|
|
||||||
std::shared_ptr<const TableProperties> SimpleTableReader::GetTableProperties() |
|
||||||
const { |
|
||||||
return rep_->table_properties; |
|
||||||
} |
|
||||||
|
|
||||||
Iterator* SimpleTableReader::NewIterator(const ReadOptions& options, |
|
||||||
Arena* arena) { |
|
||||||
if (arena == nullptr) { |
|
||||||
return new SimpleTableIterator(this); |
|
||||||
} else { |
|
||||||
auto mem = arena->AllocateAligned(sizeof(SimpleTableIterator)); |
|
||||||
return new (mem) SimpleTableIterator(this); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableReader::GetOffset(const Slice& target, uint64_t* offset) { |
|
||||||
uint32_t left = 0; |
|
||||||
uint32_t right = rep_->num_entries - 1; |
|
||||||
char key_chars[Rep::GetInternalKeyLength()]; |
|
||||||
Slice tmp_slice; |
|
||||||
|
|
||||||
uint32_t target_offset = 0; |
|
||||||
while (left <= right) { |
|
||||||
uint32_t mid = (left + right + 1) / 2; |
|
||||||
|
|
||||||
uint64_t offset_to_read = rep_->index_start_offset |
|
||||||
+ (Rep::GetInternalKeyLength() + Rep::offset_length) * mid; |
|
||||||
Status s = rep_->file->Read(offset_to_read, Rep::GetInternalKeyLength(), |
|
||||||
&tmp_slice, key_chars); |
|
||||||
if (!s.ok()) { |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
InternalKeyComparator ikc(rep_->options.comparator); |
|
||||||
int compare_result = ikc.Compare(tmp_slice, target); |
|
||||||
|
|
||||||
if (compare_result < 0) { |
|
||||||
if (left == right) { |
|
||||||
target_offset = right + 1; |
|
||||||
break; |
|
||||||
} |
|
||||||
left = mid; |
|
||||||
} else { |
|
||||||
if (left == right) { |
|
||||||
target_offset = left; |
|
||||||
break; |
|
||||||
} |
|
||||||
right = mid - 1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (target_offset >= (uint32_t) rep_->num_entries) { |
|
||||||
*offset = rep_->index_start_offset; |
|
||||||
return Status::OK(); |
|
||||||
} |
|
||||||
|
|
||||||
char value_offset_chars[Rep::offset_length]; |
|
||||||
|
|
||||||
int64_t offset_for_value_offset = rep_->index_start_offset |
|
||||||
+ (Rep::GetInternalKeyLength() + Rep::offset_length) * target_offset |
|
||||||
+ Rep::GetInternalKeyLength(); |
|
||||||
Status s = rep_->file->Read(offset_for_value_offset, Rep::offset_length, |
|
||||||
&tmp_slice, value_offset_chars); |
|
||||||
if (s.ok()) { |
|
||||||
*offset = DecodeFixed64(value_offset_chars); |
|
||||||
} |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableReader::Get(const ReadOptions& options, const Slice& k, |
|
||||||
void* arg, |
|
||||||
bool (*saver)(void*, const ParsedInternalKey&, |
|
||||||
const Slice&), |
|
||||||
void (*mark_key_may_exist)(void*)) { |
|
||||||
Status s; |
|
||||||
SimpleTableIterator* iter = new SimpleTableIterator(this); |
|
||||||
for (iter->Seek(k); iter->Valid(); iter->Next()) { |
|
||||||
ParsedInternalKey parsed_key; |
|
||||||
if (!ParseInternalKey(iter->key(), &parsed_key)) { |
|
||||||
return Status::Corruption(Slice()); |
|
||||||
} |
|
||||||
|
|
||||||
if (!(*saver)(arg, parsed_key, iter->value())) { |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
s = iter->status(); |
|
||||||
delete iter; |
|
||||||
return s; |
|
||||||
} |
|
||||||
|
|
||||||
uint64_t SimpleTableReader::ApproximateOffsetOf(const Slice& key) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
SimpleTableIterator::SimpleTableIterator(SimpleTableReader* table) : |
|
||||||
table_(table) { |
|
||||||
key_str_ = new char[SimpleTableReader::Rep::GetInternalKeyLength()]; |
|
||||||
value_str_len_ = -1; |
|
||||||
SeekToFirst(); |
|
||||||
} |
|
||||||
|
|
||||||
SimpleTableIterator::~SimpleTableIterator() { |
|
||||||
delete[] key_str_; |
|
||||||
if (value_str_len_ >= 0) { |
|
||||||
delete[] value_str_; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
bool SimpleTableIterator::Valid() const { |
|
||||||
return offset_ < table_->rep_->index_start_offset; |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableIterator::SeekToFirst() { |
|
||||||
next_offset_ = 0; |
|
||||||
Next(); |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableIterator::SeekToLast() { |
|
||||||
assert(false); |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableIterator::Seek(const Slice& target) { |
|
||||||
Status s = table_->GetOffset(target, &next_offset_); |
|
||||||
if (!s.ok()) { |
|
||||||
status_ = s; |
|
||||||
} |
|
||||||
Next(); |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableIterator::Next() { |
|
||||||
offset_ = next_offset_; |
|
||||||
if (offset_ >= table_->rep_->index_start_offset) { |
|
||||||
return; |
|
||||||
} |
|
||||||
Slice result; |
|
||||||
int internal_key_size = SimpleTableReader::Rep::GetInternalKeyLength(); |
|
||||||
|
|
||||||
Status s = table_->rep_->file->Read(next_offset_, internal_key_size, &result, |
|
||||||
key_str_); |
|
||||||
next_offset_ += internal_key_size; |
|
||||||
key_ = result; |
|
||||||
|
|
||||||
Slice value_size_slice; |
|
||||||
s = table_->rep_->file->Read(next_offset_, 4, &value_size_slice, tmp_str_); |
|
||||||
next_offset_ += 4; |
|
||||||
uint32_t value_size = DecodeFixed32(tmp_str_); |
|
||||||
|
|
||||||
Slice value_slice; |
|
||||||
if ((int) value_size > value_str_len_) { |
|
||||||
if (value_str_len_ >= 0) { |
|
||||||
delete[] value_str_; |
|
||||||
} |
|
||||||
value_str_ = new char[value_size]; |
|
||||||
value_str_len_ = value_size; |
|
||||||
} |
|
||||||
s = table_->rep_->file->Read(next_offset_, value_size, &value_slice, |
|
||||||
value_str_); |
|
||||||
next_offset_ += value_size; |
|
||||||
value_ = value_slice; |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableIterator::Prev() { |
|
||||||
assert(false); |
|
||||||
} |
|
||||||
|
|
||||||
Slice SimpleTableIterator::key() const { |
|
||||||
Log(table_->rep_->options.info_log, "key!!!!"); |
|
||||||
return key_; |
|
||||||
} |
|
||||||
|
|
||||||
Slice SimpleTableIterator::value() const { |
|
||||||
return value_; |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableIterator::status() const { |
|
||||||
return status_; |
|
||||||
} |
|
||||||
|
|
||||||
class SimpleTableBuilder: public TableBuilder { |
|
||||||
public: |
|
||||||
// Create a builder that will store the contents of the table it is
|
|
||||||
// building in *file. Does not close the file. It is up to the
|
|
||||||
// caller to close the file after calling Finish(). The output file
|
|
||||||
// will be part of level specified by 'level'. A value of -1 means
|
|
||||||
// that the caller does not know which level the output file will reside.
|
|
||||||
SimpleTableBuilder(const Options& options, WritableFile* file, |
|
||||||
CompressionType compression_type); |
|
||||||
|
|
||||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
|
||||||
~SimpleTableBuilder(); |
|
||||||
|
|
||||||
// Add key,value to the table being constructed.
|
|
||||||
// REQUIRES: key is after any previously added key according to comparator.
|
|
||||||
// REQUIRES: Finish(), Abandon() have not been called
|
|
||||||
void Add(const Slice& key, const Slice& value) override; |
|
||||||
|
|
||||||
// Return non-ok iff some error has been detected.
|
|
||||||
Status status() const override; |
|
||||||
|
|
||||||
// Finish building the table. Stops using the file passed to the
|
|
||||||
// constructor after this function returns.
|
|
||||||
// REQUIRES: Finish(), Abandon() have not been called
|
|
||||||
Status Finish() override; |
|
||||||
|
|
||||||
// Indicate that the contents of this builder should be abandoned. Stops
|
|
||||||
// using the file passed to the constructor after this function returns.
|
|
||||||
// If the caller is not going to call Finish(), it must call Abandon()
|
|
||||||
// before destroying this builder.
|
|
||||||
// REQUIRES: Finish(), Abandon() have not been called
|
|
||||||
void Abandon() override; |
|
||||||
|
|
||||||
// Number of calls to Add() so far.
|
|
||||||
uint64_t NumEntries() const override; |
|
||||||
|
|
||||||
// Size of the file generated so far. If invoked after a successful
|
|
||||||
// Finish() call, returns the size of the final generated file.
|
|
||||||
uint64_t FileSize() const override; |
|
||||||
|
|
||||||
private: |
|
||||||
struct Rep; |
|
||||||
Rep* rep_; |
|
||||||
|
|
||||||
// No copying allowed
|
|
||||||
SimpleTableBuilder(const SimpleTableBuilder&) = delete; |
|
||||||
void operator=(const SimpleTableBuilder&) = delete; |
|
||||||
}; |
|
||||||
|
|
||||||
struct SimpleTableBuilder::Rep { |
|
||||||
Options options; |
|
||||||
WritableFile* file; |
|
||||||
uint64_t offset = 0; |
|
||||||
Status status; |
|
||||||
|
|
||||||
uint64_t num_entries = 0; |
|
||||||
|
|
||||||
bool closed = false; // Either Finish() or Abandon() has been called.
|
|
||||||
|
|
||||||
const static int user_key_size = 16; |
|
||||||
const static int offset_length = 8; |
|
||||||
const static int key_footer_len = 8; |
|
||||||
|
|
||||||
static int GetInternalKeyLength() { |
|
||||||
return user_key_size + key_footer_len; |
|
||||||
} |
|
||||||
|
|
||||||
std::string index; |
|
||||||
|
|
||||||
Rep(const Options& opt, WritableFile* f) : |
|
||||||
options(opt), file(f) { |
|
||||||
} |
|
||||||
~Rep() { |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
SimpleTableBuilder::SimpleTableBuilder(const Options& options, |
|
||||||
WritableFile* file, |
|
||||||
CompressionType compression_type) : |
|
||||||
rep_(new SimpleTableBuilder::Rep(options, file)) { |
|
||||||
} |
|
||||||
|
|
||||||
SimpleTableBuilder::~SimpleTableBuilder() { |
|
||||||
delete (rep_); |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableBuilder::Add(const Slice& key, const Slice& value) { |
|
||||||
assert((int ) key.size() == Rep::GetInternalKeyLength()); |
|
||||||
|
|
||||||
// Update index
|
|
||||||
rep_->index.append(key.data(), key.size()); |
|
||||||
PutFixed64(&(rep_->index), rep_->offset); |
|
||||||
|
|
||||||
// Write key-value pair
|
|
||||||
rep_->file->Append(key); |
|
||||||
rep_->offset += Rep::GetInternalKeyLength(); |
|
||||||
|
|
||||||
std::string size; |
|
||||||
int value_size = value.size(); |
|
||||||
PutFixed32(&size, value_size); |
|
||||||
Slice sizeSlice(size); |
|
||||||
rep_->file->Append(sizeSlice); |
|
||||||
rep_->file->Append(value); |
|
||||||
rep_->offset += value_size + 4; |
|
||||||
|
|
||||||
rep_->num_entries++; |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableBuilder::status() const { |
|
||||||
return Status::OK(); |
|
||||||
} |
|
||||||
|
|
||||||
Status SimpleTableBuilder::Finish() { |
|
||||||
Rep* r = rep_; |
|
||||||
assert(!r->closed); |
|
||||||
r->closed = true; |
|
||||||
|
|
||||||
uint64_t index_offset = rep_->offset; |
|
||||||
Slice index_slice(rep_->index); |
|
||||||
rep_->file->Append(index_slice); |
|
||||||
rep_->offset += index_slice.size(); |
|
||||||
|
|
||||||
std::string index_offset_str; |
|
||||||
PutFixed64(&index_offset_str, index_offset); |
|
||||||
Slice foot_slice(index_offset_str); |
|
||||||
rep_->file->Append(foot_slice); |
|
||||||
rep_->offset += foot_slice.size(); |
|
||||||
|
|
||||||
return Status::OK(); |
|
||||||
} |
|
||||||
|
|
||||||
void SimpleTableBuilder::Abandon() { |
|
||||||
rep_->closed = true; |
|
||||||
} |
|
||||||
|
|
||||||
uint64_t SimpleTableBuilder::NumEntries() const { |
|
||||||
return rep_->num_entries; |
|
||||||
} |
|
||||||
|
|
||||||
uint64_t SimpleTableBuilder::FileSize() const { |
|
||||||
return rep_->offset; |
|
||||||
} |
|
||||||
|
|
||||||
class SimpleTableFactory: public TableFactory { |
|
||||||
public: |
|
||||||
~SimpleTableFactory() { |
|
||||||
} |
|
||||||
SimpleTableFactory() { |
|
||||||
} |
|
||||||
const char* Name() const override { |
|
||||||
return "SimpleTable"; |
|
||||||
} |
|
||||||
Status NewTableReader(const Options& options, const EnvOptions& soptions, |
|
||||||
const InternalKeyComparator& internal_key, |
|
||||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size, |
|
||||||
unique_ptr<TableReader>* table_reader) const; |
|
||||||
|
|
||||||
TableBuilder* NewTableBuilder(const Options& options, |
|
||||||
const InternalKeyComparator& internal_key, |
|
||||||
WritableFile* file, |
|
||||||
CompressionType compression_type) const; |
|
||||||
|
|
||||||
virtual Status SanitizeDBOptions(DBOptions* db_opts) const override { |
|
||||||
return Status::OK(); |
|
||||||
} |
|
||||||
|
|
||||||
virtual std::string GetPrintableTableOptions() const override { |
|
||||||
return std::string(); |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
Status SimpleTableFactory::NewTableReader( |
|
||||||
const Options& options, const EnvOptions& soptions, |
|
||||||
const InternalKeyComparator& internal_key, |
|
||||||
unique_ptr<RandomAccessFile>&& file, uint64_t file_size, |
|
||||||
unique_ptr<TableReader>* table_reader) const { |
|
||||||
|
|
||||||
return SimpleTableReader::Open(options, soptions, std::move(file), file_size, |
|
||||||
table_reader); |
|
||||||
} |
|
||||||
|
|
||||||
TableBuilder* SimpleTableFactory::NewTableBuilder( |
|
||||||
const Options& options, const InternalKeyComparator& internal_key, |
|
||||||
WritableFile* file, CompressionType compression_type) const { |
|
||||||
return new SimpleTableBuilder(options, file, compression_type); |
|
||||||
} |
|
||||||
|
|
||||||
class SimpleTableDBTest { |
|
||||||
protected: |
|
||||||
public: |
|
||||||
std::string dbname_; |
|
||||||
Env* env_; |
|
||||||
DB* db_; |
|
||||||
|
|
||||||
Options last_options_; |
|
||||||
|
|
||||||
SimpleTableDBTest() : |
|
||||||
env_(Env::Default()) { |
|
||||||
dbname_ = test::TmpDir() + "/simple_table_db_test"; |
|
||||||
ASSERT_OK(DestroyDB(dbname_, Options())); |
|
||||||
db_ = nullptr; |
|
||||||
Reopen(); |
|
||||||
} |
|
||||||
|
|
||||||
~SimpleTableDBTest() { |
|
||||||
delete db_; |
|
||||||
ASSERT_OK(DestroyDB(dbname_, Options())); |
|
||||||
} |
|
||||||
|
|
||||||
// Return the current option configuration.
|
|
||||||
Options CurrentOptions() { |
|
||||||
Options options; |
|
||||||
options.table_factory.reset(new SimpleTableFactory()); |
|
||||||
return options; |
|
||||||
} |
|
||||||
|
|
||||||
DBImpl* dbfull() { |
|
||||||
return reinterpret_cast<DBImpl*>(db_); |
|
||||||
} |
|
||||||
|
|
||||||
void Reopen(Options* options = nullptr) { |
|
||||||
ASSERT_OK(TryReopen(options)); |
|
||||||
} |
|
||||||
|
|
||||||
void Close() { |
|
||||||
delete db_; |
|
||||||
db_ = nullptr; |
|
||||||
} |
|
||||||
|
|
||||||
void DestroyAndReopen(Options* options = nullptr) { |
|
||||||
//Destroy using last options
|
|
||||||
Destroy(&last_options_); |
|
||||||
ASSERT_OK(TryReopen(options)); |
|
||||||
} |
|
||||||
|
|
||||||
void Destroy(Options* options) { |
|
||||||
delete db_; |
|
||||||
db_ = nullptr; |
|
||||||
ASSERT_OK(DestroyDB(dbname_, *options)); |
|
||||||
} |
|
||||||
|
|
||||||
Status PureReopen(Options* options, DB** db) { |
|
||||||
return DB::Open(*options, dbname_, db); |
|
||||||
} |
|
||||||
|
|
||||||
Status TryReopen(Options* options = nullptr) { |
|
||||||
delete db_; |
|
||||||
db_ = nullptr; |
|
||||||
Options opts; |
|
||||||
if (options != nullptr) { |
|
||||||
opts = *options; |
|
||||||
} else { |
|
||||||
opts = CurrentOptions(); |
|
||||||
opts.create_if_missing = true; |
|
||||||
} |
|
||||||
last_options_ = opts; |
|
||||||
|
|
||||||
return DB::Open(opts, dbname_, &db_); |
|
||||||
} |
|
||||||
|
|
||||||
Status Put(const Slice& k, const Slice& v) { |
|
||||||
return db_->Put(WriteOptions(), k, v); |
|
||||||
} |
|
||||||
|
|
||||||
Status Delete(const std::string& k) { |
|
||||||
return db_->Delete(WriteOptions(), k); |
|
||||||
} |
|
||||||
|
|
||||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr) { |
|
||||||
ReadOptions options; |
|
||||||
options.snapshot = snapshot; |
|
||||||
std::string result; |
|
||||||
Status s = db_->Get(options, k, &result); |
|
||||||
if (s.IsNotFound()) { |
|
||||||
result = "NOT_FOUND"; |
|
||||||
} else if (!s.ok()) { |
|
||||||
result = s.ToString(); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
int NumTableFilesAtLevel(int level) { |
|
||||||
std::string property; |
|
||||||
ASSERT_TRUE( |
|
||||||
db_->GetProperty("rocksdb.num-files-at-level" + NumberToString(level), |
|
||||||
&property)); |
|
||||||
return atoi(property.c_str()); |
|
||||||
} |
|
||||||
|
|
||||||
// Return spread of files per level
|
|
||||||
std::string FilesPerLevel() { |
|
||||||
std::string result; |
|
||||||
int last_non_zero_offset = 0; |
|
||||||
for (int level = 0; level < db_->NumberLevels(); level++) { |
|
||||||
int f = NumTableFilesAtLevel(level); |
|
||||||
char buf[100]; |
|
||||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f); |
|
||||||
result += buf; |
|
||||||
if (f > 0) { |
|
||||||
last_non_zero_offset = result.size(); |
|
||||||
} |
|
||||||
} |
|
||||||
result.resize(last_non_zero_offset); |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
std::string IterStatus(Iterator* iter) { |
|
||||||
std::string result; |
|
||||||
if (iter->Valid()) { |
|
||||||
result = iter->key().ToString() + "->" + iter->value().ToString(); |
|
||||||
} else { |
|
||||||
result = "(invalid)"; |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
TEST(SimpleTableDBTest, Empty) { |
|
||||||
ASSERT_TRUE(db_ != nullptr); |
|
||||||
ASSERT_EQ("NOT_FOUND", Get("0000000000000foo")); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(SimpleTableDBTest, ReadWrite) { |
|
||||||
ASSERT_OK(Put("0000000000000foo", "v1")); |
|
||||||
ASSERT_EQ("v1", Get("0000000000000foo")); |
|
||||||
ASSERT_OK(Put("0000000000000bar", "v2")); |
|
||||||
ASSERT_OK(Put("0000000000000foo", "v3")); |
|
||||||
ASSERT_EQ("v3", Get("0000000000000foo")); |
|
||||||
ASSERT_EQ("v2", Get("0000000000000bar")); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(SimpleTableDBTest, Flush) { |
|
||||||
ASSERT_OK(Put("0000000000000foo", "v1")); |
|
||||||
ASSERT_OK(Put("0000000000000bar", "v2")); |
|
||||||
ASSERT_OK(Put("0000000000000foo", "v3")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
ASSERT_EQ("v3", Get("0000000000000foo")); |
|
||||||
ASSERT_EQ("v2", Get("0000000000000bar")); |
|
||||||
} |
|
||||||
|
|
||||||
TEST(SimpleTableDBTest, Flush2) { |
|
||||||
ASSERT_OK(Put("0000000000000bar", "b")); |
|
||||||
ASSERT_OK(Put("0000000000000foo", "v1")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
|
|
||||||
ASSERT_OK(Put("0000000000000foo", "v2")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
ASSERT_EQ("v2", Get("0000000000000foo")); |
|
||||||
|
|
||||||
ASSERT_OK(Put("0000000000000eee", "v3")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
ASSERT_EQ("v3", Get("0000000000000eee")); |
|
||||||
|
|
||||||
ASSERT_OK(Delete("0000000000000bar")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
ASSERT_EQ("NOT_FOUND", Get("0000000000000bar")); |
|
||||||
|
|
||||||
ASSERT_OK(Put("0000000000000eee", "v5")); |
|
||||||
dbfull()->TEST_FlushMemTable(); |
|
||||||
ASSERT_EQ("v5", Get("0000000000000eee")); |
|
||||||
} |
|
||||||
|
|
||||||
static std::string Key(int i) { |
|
||||||
char buf[100]; |
|
||||||
snprintf(buf, sizeof(buf), "key_______%06d", i); |
|
||||||
return std::string(buf); |
|
||||||
} |
|
||||||
|
|
||||||
static std::string RandomString(Random* rnd, int len) { |
|
||||||
std::string r; |
|
||||||
test::RandomString(rnd, len, &r); |
|
||||||
return r; |
|
||||||
} |
|
||||||
|
|
||||||
TEST(SimpleTableDBTest, CompactionTrigger) { |
|
||||||
Options options = CurrentOptions(); |
|
||||||
options.write_buffer_size = 100 << 10; //100KB
|
|
||||||
options.num_levels = 3; |
|
||||||
options.max_mem_compaction_level = 0; |
|
||||||
options.level0_file_num_compaction_trigger = 3; |
|
||||||
Reopen(&options); |
|
||||||
|
|
||||||
Random rnd(301); |
|
||||||
|
|
||||||
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1; |
|
||||||
num++) { |
|
||||||
std::vector<std::string> values; |
|
||||||
// Write 120KB (12 values, each 10K)
|
|
||||||
for (int i = 0; i < 12; i++) { |
|
||||||
values.push_back(RandomString(&rnd, 10000)); |
|
||||||
ASSERT_OK(Put(Key(i), values[i])); |
|
||||||
} |
|
||||||
dbfull()->TEST_WaitForFlushMemTable(); |
|
||||||
ASSERT_EQ(NumTableFilesAtLevel(0), num + 1); |
|
||||||
} |
|
||||||
|
|
||||||
//generate one more file in level-0, and should trigger level-0 compaction
|
|
||||||
std::vector<std::string> values; |
|
||||||
for (int i = 0; i < 12; i++) { |
|
||||||
values.push_back(RandomString(&rnd, 10000)); |
|
||||||
ASSERT_OK(Put(Key(i), values[i])); |
|
||||||
} |
|
||||||
dbfull()->TEST_WaitForCompact(); |
|
||||||
|
|
||||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0); |
|
||||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1); |
|
||||||
} |
|
||||||
|
|
||||||
} // namespace rocksdb
|
|
||||||
|
|
||||||
int main(int argc, char** argv) { |
|
||||||
return rocksdb::test::RunAllTests(); |
|
||||||
} |
|
@ -0,0 +1,330 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
|
||||||
|
#include "db/version_builder.h" |
||||||
|
|
||||||
|
#ifndef __STDC_FORMAT_MACROS |
||||||
|
#define __STDC_FORMAT_MACROS |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
#include <algorithm> |
||||||
|
#include <set> |
||||||
|
#include <unordered_map> |
||||||
|
#include <unordered_set> |
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "db/dbformat.h" |
||||||
|
#include "db/table_cache.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "table/table_reader.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b) { |
||||||
|
if (a->smallest_seqno != b->smallest_seqno) { |
||||||
|
return a->smallest_seqno > b->smallest_seqno; |
||||||
|
} |
||||||
|
if (a->largest_seqno != b->largest_seqno) { |
||||||
|
return a->largest_seqno > b->largest_seqno; |
||||||
|
} |
||||||
|
// Break ties by file number
|
||||||
|
return a->fd.GetNumber() > b->fd.GetNumber(); |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
bool BySmallestKey(FileMetaData* a, FileMetaData* b, |
||||||
|
const InternalKeyComparator* cmp) { |
||||||
|
int r = cmp->Compare(a->smallest, b->smallest); |
||||||
|
if (r != 0) { |
||||||
|
return (r < 0); |
||||||
|
} |
||||||
|
// Break ties by file number
|
||||||
|
return (a->fd.GetNumber() < b->fd.GetNumber()); |
||||||
|
} |
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class VersionBuilder::Rep { |
||||||
|
private: |
||||||
|
// Helper to sort files_ in v
|
||||||
|
// kLevel0 -- NewestFirstBySeqNo
|
||||||
|
// kLevelNon0 -- BySmallestKey
|
||||||
|
struct FileComparator { |
||||||
|
enum SortMethod { kLevel0 = 0, kLevelNon0 = 1, } sort_method; |
||||||
|
const InternalKeyComparator* internal_comparator; |
||||||
|
|
||||||
|
bool operator()(FileMetaData* f1, FileMetaData* f2) const { |
||||||
|
switch (sort_method) { |
||||||
|
case kLevel0: |
||||||
|
return NewestFirstBySeqNo(f1, f2); |
||||||
|
case kLevelNon0: |
||||||
|
return BySmallestKey(f1, f2, internal_comparator); |
||||||
|
} |
||||||
|
assert(false); |
||||||
|
return false; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
struct LevelState { |
||||||
|
std::unordered_set<uint64_t> deleted_files; |
||||||
|
// Map from file number to file meta data.
|
||||||
|
std::unordered_map<uint64_t, FileMetaData*> added_files; |
||||||
|
}; |
||||||
|
|
||||||
|
const EnvOptions& env_options_; |
||||||
|
TableCache* table_cache_; |
||||||
|
VersionStorageInfo* base_vstorage_; |
||||||
|
LevelState* levels_; |
||||||
|
FileComparator level_zero_cmp_; |
||||||
|
FileComparator level_nonzero_cmp_; |
||||||
|
|
||||||
|
public: |
||||||
|
Rep(const EnvOptions& env_options, TableCache* table_cache, |
||||||
|
VersionStorageInfo* base_vstorage) |
||||||
|
: env_options_(env_options), |
||||||
|
table_cache_(table_cache), |
||||||
|
base_vstorage_(base_vstorage) { |
||||||
|
levels_ = new LevelState[base_vstorage_->num_levels()]; |
||||||
|
level_zero_cmp_.sort_method = FileComparator::kLevel0; |
||||||
|
level_nonzero_cmp_.sort_method = FileComparator::kLevelNon0; |
||||||
|
level_nonzero_cmp_.internal_comparator = |
||||||
|
base_vstorage_->InternalComparator(); |
||||||
|
} |
||||||
|
|
||||||
|
~Rep() { |
||||||
|
for (int level = 0; level < base_vstorage_->num_levels(); level++) { |
||||||
|
const auto& added = levels_[level].added_files; |
||||||
|
for (auto& pair : added) { |
||||||
|
UnrefFile(pair.second); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
delete[] levels_; |
||||||
|
} |
||||||
|
|
||||||
|
void UnrefFile(FileMetaData* f) { |
||||||
|
f->refs--; |
||||||
|
if (f->refs <= 0) { |
||||||
|
if (f->table_reader_handle) { |
||||||
|
assert(table_cache_ != nullptr); |
||||||
|
table_cache_->ReleaseHandle(f->table_reader_handle); |
||||||
|
f->table_reader_handle = nullptr; |
||||||
|
} |
||||||
|
delete f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void CheckConsistency(VersionStorageInfo* vstorage) { |
||||||
|
#ifndef NDEBUG |
||||||
|
// make sure the files are sorted correctly
|
||||||
|
for (int level = 0; level < vstorage->num_levels(); level++) { |
||||||
|
auto& level_files = vstorage->LevelFiles(level); |
||||||
|
for (size_t i = 1; i < level_files.size(); i++) { |
||||||
|
auto f1 = level_files[i - 1]; |
||||||
|
auto f2 = level_files[i]; |
||||||
|
if (level == 0) { |
||||||
|
assert(level_zero_cmp_(f1, f2)); |
||||||
|
assert(f1->largest_seqno > f2->largest_seqno); |
||||||
|
} else { |
||||||
|
assert(level_nonzero_cmp_(f1, f2)); |
||||||
|
|
||||||
|
// Make sure there is no overlap in levels > 0
|
||||||
|
if (vstorage->InternalComparator()->Compare(f1->largest, |
||||||
|
f2->smallest) >= 0) { |
||||||
|
fprintf(stderr, "overlapping ranges in same level %s vs. %s\n", |
||||||
|
(f1->largest).DebugString().c_str(), |
||||||
|
(f2->smallest).DebugString().c_str()); |
||||||
|
abort(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number, |
||||||
|
int level) { |
||||||
|
#ifndef NDEBUG |
||||||
|
// a file to be deleted better exist in the previous version
|
||||||
|
bool found = false; |
||||||
|
for (int l = 0; !found && l < base_vstorage_->num_levels(); l++) { |
||||||
|
const std::vector<FileMetaData*>& base_files = |
||||||
|
base_vstorage_->LevelFiles(l); |
||||||
|
for (unsigned int i = 0; i < base_files.size(); i++) { |
||||||
|
FileMetaData* f = base_files[i]; |
||||||
|
if (f->fd.GetNumber() == number) { |
||||||
|
found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
// if the file did not exist in the previous version, then it
|
||||||
|
// is possibly moved from lower level to higher level in current
|
||||||
|
// version
|
||||||
|
for (int l = level + 1; !found && l < base_vstorage_->num_levels(); l++) { |
||||||
|
auto& level_added = levels_[l].added_files; |
||||||
|
auto got = level_added.find(number); |
||||||
|
if (got != level_added.end()) { |
||||||
|
found = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// maybe this file was added in a previous edit that was Applied
|
||||||
|
if (!found) { |
||||||
|
auto& level_added = levels_[level].added_files; |
||||||
|
auto got = level_added.find(number); |
||||||
|
if (got != level_added.end()) { |
||||||
|
found = true; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!found) { |
||||||
|
fprintf(stderr, "not found %" PRIu64 "\n", number); |
||||||
|
} |
||||||
|
assert(found); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
// Apply all of the edits in *edit to the current state.
|
||||||
|
void Apply(VersionEdit* edit) { |
||||||
|
CheckConsistency(base_vstorage_); |
||||||
|
|
||||||
|
// Delete files
|
||||||
|
const VersionEdit::DeletedFileSet& del = edit->GetDeletedFiles(); |
||||||
|
for (const auto& del_file : del) { |
||||||
|
const auto level = del_file.first; |
||||||
|
const auto number = del_file.second; |
||||||
|
levels_[level].deleted_files.insert(number); |
||||||
|
CheckConsistencyForDeletes(edit, number, level); |
||||||
|
|
||||||
|
auto exising = levels_[level].added_files.find(number); |
||||||
|
if (exising != levels_[level].added_files.end()) { |
||||||
|
UnrefFile(exising->second); |
||||||
|
levels_[level].added_files.erase(number); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Add new files
|
||||||
|
for (const auto& new_file : edit->GetNewFiles()) { |
||||||
|
const int level = new_file.first; |
||||||
|
FileMetaData* f = new FileMetaData(new_file.second); |
||||||
|
f->refs = 1; |
||||||
|
|
||||||
|
assert(levels_[level].added_files.find(f->fd.GetNumber()) == |
||||||
|
levels_[level].added_files.end()); |
||||||
|
levels_[level].deleted_files.erase(f->fd.GetNumber()); |
||||||
|
levels_[level].added_files[f->fd.GetNumber()] = f; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Save the current state in *v.
|
||||||
|
void SaveTo(VersionStorageInfo* vstorage) { |
||||||
|
CheckConsistency(base_vstorage_); |
||||||
|
CheckConsistency(vstorage); |
||||||
|
|
||||||
|
for (int level = 0; level < base_vstorage_->num_levels(); level++) { |
||||||
|
const auto& cmp = (level == 0) ? level_zero_cmp_ : level_nonzero_cmp_; |
||||||
|
// Merge the set of added files with the set of pre-existing files.
|
||||||
|
// Drop any deleted files. Store the result in *v.
|
||||||
|
const auto& base_files = base_vstorage_->LevelFiles(level); |
||||||
|
auto base_iter = base_files.begin(); |
||||||
|
auto base_end = base_files.end(); |
||||||
|
const auto& unordered_added_files = levels_[level].added_files; |
||||||
|
vstorage->Reserve(level, |
||||||
|
base_files.size() + unordered_added_files.size()); |
||||||
|
|
||||||
|
// Sort added files for the level.
|
||||||
|
std::vector<FileMetaData*> added_files; |
||||||
|
added_files.reserve(unordered_added_files.size()); |
||||||
|
for (const auto& pair : unordered_added_files) { |
||||||
|
added_files.push_back(pair.second); |
||||||
|
} |
||||||
|
std::sort(added_files.begin(), added_files.end(), cmp); |
||||||
|
|
||||||
|
#ifndef NDEBUG |
||||||
|
FileMetaData* prev_file = nullptr; |
||||||
|
#endif |
||||||
|
|
||||||
|
for (const auto& added : added_files) { |
||||||
|
#ifndef NDEBUG |
||||||
|
if (level > 0 && prev_file != nullptr) { |
||||||
|
assert(base_vstorage_->InternalComparator()->Compare( |
||||||
|
prev_file->smallest, added->smallest) <= 0); |
||||||
|
} |
||||||
|
prev_file = added; |
||||||
|
#endif |
||||||
|
|
||||||
|
// Add all smaller files listed in base_
|
||||||
|
for (auto bpos = std::upper_bound(base_iter, base_end, added, cmp); |
||||||
|
base_iter != bpos; ++base_iter) { |
||||||
|
MaybeAddFile(vstorage, level, *base_iter); |
||||||
|
} |
||||||
|
|
||||||
|
MaybeAddFile(vstorage, level, added); |
||||||
|
} |
||||||
|
|
||||||
|
// Add remaining base files
|
||||||
|
for (; base_iter != base_end; ++base_iter) { |
||||||
|
MaybeAddFile(vstorage, level, *base_iter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
CheckConsistency(vstorage); |
||||||
|
} |
||||||
|
|
||||||
|
void LoadTableHandlers() { |
||||||
|
assert(table_cache_ != nullptr); |
||||||
|
for (int level = 0; level < base_vstorage_->num_levels(); level++) { |
||||||
|
for (auto& file_meta_pair : levels_[level].added_files) { |
||||||
|
auto* file_meta = file_meta_pair.second; |
||||||
|
assert(!file_meta->table_reader_handle); |
||||||
|
table_cache_->FindTable( |
||||||
|
env_options_, *(base_vstorage_->InternalComparator()), |
||||||
|
file_meta->fd, &file_meta->table_reader_handle, false); |
||||||
|
if (file_meta->table_reader_handle != nullptr) { |
||||||
|
// Load table_reader
|
||||||
|
file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle( |
||||||
|
file_meta->table_reader_handle); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f) { |
||||||
|
if (levels_[level].deleted_files.count(f->fd.GetNumber()) > 0) { |
||||||
|
// File is deleted: do nothing
|
||||||
|
} else { |
||||||
|
vstorage->AddFile(level, f); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
VersionBuilder::VersionBuilder(const EnvOptions& env_options, |
||||||
|
TableCache* table_cache, |
||||||
|
VersionStorageInfo* base_vstorage) |
||||||
|
: rep_(new Rep(env_options, table_cache, base_vstorage)) {} |
||||||
|
VersionBuilder::~VersionBuilder() { delete rep_; } |
||||||
|
void VersionBuilder::CheckConsistency(VersionStorageInfo* vstorage) { |
||||||
|
rep_->CheckConsistency(vstorage); |
||||||
|
} |
||||||
|
void VersionBuilder::CheckConsistencyForDeletes(VersionEdit* edit, |
||||||
|
uint64_t number, int level) { |
||||||
|
rep_->CheckConsistencyForDeletes(edit, number, level); |
||||||
|
} |
||||||
|
void VersionBuilder::Apply(VersionEdit* edit) { rep_->Apply(edit); } |
||||||
|
void VersionBuilder::SaveTo(VersionStorageInfo* vstorage) { |
||||||
|
rep_->SaveTo(vstorage); |
||||||
|
} |
||||||
|
void VersionBuilder::LoadTableHandlers() { rep_->LoadTableHandlers(); } |
||||||
|
void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level, |
||||||
|
FileMetaData* f) { |
||||||
|
rep_->MaybeAddFile(vstorage, level, f); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,42 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||||
|
//
|
||||||
|
#pragma once |
||||||
|
#include "rocksdb/env.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class TableCache; |
||||||
|
class VersionStorageInfo; |
||||||
|
class VersionEdit; |
||||||
|
struct FileMetaData; |
||||||
|
|
||||||
|
// A helper class so we can efficiently apply a whole sequence
|
||||||
|
// of edits to a particular state without creating intermediate
|
||||||
|
// Versions that contain full copies of the intermediate state.
|
||||||
|
class VersionBuilder { |
||||||
|
public: |
||||||
|
VersionBuilder(const EnvOptions& env_options, TableCache* table_cache, |
||||||
|
VersionStorageInfo* base_vstorage); |
||||||
|
~VersionBuilder(); |
||||||
|
void CheckConsistency(VersionStorageInfo* vstorage); |
||||||
|
void CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number, |
||||||
|
int level); |
||||||
|
void Apply(VersionEdit* edit); |
||||||
|
void SaveTo(VersionStorageInfo* vstorage); |
||||||
|
void LoadTableHandlers(); |
||||||
|
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f); |
||||||
|
|
||||||
|
private: |
||||||
|
class Rep; |
||||||
|
Rep* rep_; |
||||||
|
}; |
||||||
|
|
||||||
|
extern bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b); |
||||||
|
} // namespace rocksdb
|
@ -0,0 +1,229 @@ |
|||||||
|
// Copyright (c) 2013, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#include <string> |
||||||
|
#include "db/version_edit.h" |
||||||
|
#include "db/version_set.h" |
||||||
|
#include "util/logging.h" |
||||||
|
#include "util/testharness.h" |
||||||
|
#include "util/testutil.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class VersionBuilderTest { |
||||||
|
public: |
||||||
|
const Comparator* ucmp_; |
||||||
|
InternalKeyComparator icmp_; |
||||||
|
Options options_; |
||||||
|
ImmutableCFOptions ioptions_; |
||||||
|
MutableCFOptions mutable_cf_options_; |
||||||
|
VersionStorageInfo vstorage_; |
||||||
|
uint32_t file_num_; |
||||||
|
CompactionOptionsFIFO fifo_options_; |
||||||
|
std::vector<uint64_t> size_being_compacted_; |
||||||
|
|
||||||
|
VersionBuilderTest() |
||||||
|
: ucmp_(BytewiseComparator()), |
||||||
|
icmp_(ucmp_), |
||||||
|
ioptions_(options_), |
||||||
|
mutable_cf_options_(options_, ioptions_), |
||||||
|
vstorage_(&icmp_, ucmp_, options_.num_levels, kCompactionStyleLevel, |
||||||
|
nullptr), |
||||||
|
file_num_(1) { |
||||||
|
mutable_cf_options_.RefreshDerivedOptions(ioptions_); |
||||||
|
size_being_compacted_.resize(options_.num_levels); |
||||||
|
} |
||||||
|
|
||||||
|
~VersionBuilderTest() { |
||||||
|
for (int i = 0; i < vstorage_.num_levels(); i++) { |
||||||
|
for (auto* f : vstorage_.LevelFiles(i)) { |
||||||
|
if (--f->refs == 0) { |
||||||
|
delete f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
InternalKey GetInternalKey(const char* ukey, |
||||||
|
SequenceNumber smallest_seq = 100) { |
||||||
|
return InternalKey(ukey, smallest_seq, kTypeValue); |
||||||
|
} |
||||||
|
|
||||||
|
void Add(int level, uint32_t file_number, const char* smallest, |
||||||
|
const char* largest, uint64_t file_size = 0, uint32_t path_id = 0, |
||||||
|
SequenceNumber smallest_seq = 100, |
||||||
|
SequenceNumber largest_seq = 100, |
||||||
|
uint64_t num_entries = 0, uint64_t num_deletions = 0, |
||||||
|
bool sampled = false) { |
||||||
|
assert(level < vstorage_.num_levels()); |
||||||
|
FileMetaData* f = new FileMetaData; |
||||||
|
f->fd = FileDescriptor(file_number, path_id, file_size); |
||||||
|
f->smallest = GetInternalKey(smallest, smallest_seq); |
||||||
|
f->largest = GetInternalKey(largest, largest_seq); |
||||||
|
f->compensated_file_size = file_size; |
||||||
|
f->refs = 0; |
||||||
|
f->num_entries = num_entries; |
||||||
|
f->num_deletions = num_deletions; |
||||||
|
vstorage_.AddFile(level, f); |
||||||
|
if (sampled) { |
||||||
|
f->init_stats_from_file = true; |
||||||
|
vstorage_.UpdateAccumulatedStats(f); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void UpdateVersionStorageInfo() { |
||||||
|
vstorage_.UpdateFilesBySize(); |
||||||
|
vstorage_.UpdateNumNonEmptyLevels(); |
||||||
|
vstorage_.GenerateFileIndexer(); |
||||||
|
vstorage_.GenerateLevelFilesBrief(); |
||||||
|
vstorage_.SetFinalized(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST(VersionBuilderTest, ApplyAndSaveTo) { |
||||||
|
Add(0, 1U, "150", "200", 100U); |
||||||
|
// Level 1 score 1.2
|
||||||
|
Add(1, 66U, "150", "200", 100U); |
||||||
|
Add(1, 88U, "201", "300", 100U); |
||||||
|
// Level 2 score 1.8. File 7 is the largest. Should be picked
|
||||||
|
Add(2, 6U, "150", "179", 100U); |
||||||
|
Add(2, 7U, "180", "220", 100U); |
||||||
|
Add(2, 8U, "221", "300", 100U); |
||||||
|
// Level 3 score slightly larger than 1
|
||||||
|
Add(3, 26U, "150", "170", 100U); |
||||||
|
Add(3, 27U, "171", "179", 100U); |
||||||
|
Add(3, 28U, "191", "220", 100U); |
||||||
|
Add(3, 29U, "221", "300", 100U); |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
VersionEdit version_edit; |
||||||
|
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"), |
||||||
|
GetInternalKey("350"), 200, 200); |
||||||
|
version_edit.DeleteFile(3, 27U); |
||||||
|
|
||||||
|
EnvOptions env_options; |
||||||
|
|
||||||
|
VersionBuilder version_builder(env_options, nullptr, &vstorage_); |
||||||
|
|
||||||
|
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, |
||||||
|
kCompactionStyleLevel, nullptr); |
||||||
|
version_builder.Apply(&version_edit); |
||||||
|
version_builder.SaveTo(&new_vstorage); |
||||||
|
|
||||||
|
ASSERT_EQ(400U, new_vstorage.NumLevelBytes(2)); |
||||||
|
ASSERT_EQ(300U, new_vstorage.NumLevelBytes(3)); |
||||||
|
|
||||||
|
for (int i = 0; i < new_vstorage.num_levels(); i++) { |
||||||
|
for (auto* f : new_vstorage.LevelFiles(i)) { |
||||||
|
if (--f->refs == 0) { |
||||||
|
delete f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(VersionBuilderTest, ApplyMultipleAndSaveTo) { |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
VersionEdit version_edit; |
||||||
|
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"), |
||||||
|
GetInternalKey("350"), 200, 200); |
||||||
|
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"), |
||||||
|
GetInternalKey("450"), 200, 200); |
||||||
|
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"), |
||||||
|
GetInternalKey("650"), 200, 200); |
||||||
|
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"), |
||||||
|
GetInternalKey("550"), 200, 200); |
||||||
|
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"), |
||||||
|
GetInternalKey("750"), 200, 200); |
||||||
|
|
||||||
|
EnvOptions env_options; |
||||||
|
|
||||||
|
VersionBuilder version_builder(env_options, nullptr, &vstorage_); |
||||||
|
|
||||||
|
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, |
||||||
|
kCompactionStyleLevel, nullptr); |
||||||
|
version_builder.Apply(&version_edit); |
||||||
|
version_builder.SaveTo(&new_vstorage); |
||||||
|
|
||||||
|
ASSERT_EQ(500U, new_vstorage.NumLevelBytes(2)); |
||||||
|
|
||||||
|
for (int i = 0; i < new_vstorage.num_levels(); i++) { |
||||||
|
for (auto* f : new_vstorage.LevelFiles(i)) { |
||||||
|
if (--f->refs == 0) { |
||||||
|
delete f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(VersionBuilderTest, ApplyDeleteAndSaveTo) { |
||||||
|
UpdateVersionStorageInfo(); |
||||||
|
|
||||||
|
EnvOptions env_options; |
||||||
|
VersionBuilder version_builder(env_options, nullptr, &vstorage_); |
||||||
|
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels, |
||||||
|
kCompactionStyleLevel, nullptr); |
||||||
|
|
||||||
|
VersionEdit version_edit; |
||||||
|
version_edit.AddFile(2, 666, 0, 100U, GetInternalKey("301"), |
||||||
|
GetInternalKey("350"), 200, 200); |
||||||
|
version_edit.AddFile(2, 676, 0, 100U, GetInternalKey("401"), |
||||||
|
GetInternalKey("450"), 200, 200); |
||||||
|
version_edit.AddFile(2, 636, 0, 100U, GetInternalKey("601"), |
||||||
|
GetInternalKey("650"), 200, 200); |
||||||
|
version_edit.AddFile(2, 616, 0, 100U, GetInternalKey("501"), |
||||||
|
GetInternalKey("550"), 200, 200); |
||||||
|
version_edit.AddFile(2, 606, 0, 100U, GetInternalKey("701"), |
||||||
|
GetInternalKey("750"), 200, 200); |
||||||
|
version_builder.Apply(&version_edit); |
||||||
|
|
||||||
|
VersionEdit version_edit2; |
||||||
|
version_edit.AddFile(2, 808, 0, 100U, GetInternalKey("901"), |
||||||
|
GetInternalKey("950"), 200, 200); |
||||||
|
version_edit2.DeleteFile(2, 616); |
||||||
|
version_edit2.DeleteFile(2, 636); |
||||||
|
version_edit.AddFile(2, 806, 0, 100U, GetInternalKey("801"), |
||||||
|
GetInternalKey("850"), 200, 200); |
||||||
|
version_builder.Apply(&version_edit2); |
||||||
|
|
||||||
|
version_builder.SaveTo(&new_vstorage); |
||||||
|
|
||||||
|
ASSERT_EQ(300U, new_vstorage.NumLevelBytes(2)); |
||||||
|
|
||||||
|
for (int i = 0; i < new_vstorage.num_levels(); i++) { |
||||||
|
for (auto* f : new_vstorage.LevelFiles(i)) { |
||||||
|
if (--f->refs == 0) { |
||||||
|
delete f; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
TEST(VersionBuilderTest, EstimatedActiveKeys) { |
||||||
|
const uint32_t kTotalSamples = 20; |
||||||
|
const uint32_t kNumLevels = 5; |
||||||
|
const uint32_t kFilesPerLevel = 8; |
||||||
|
const uint32_t kNumFiles = kNumLevels * kFilesPerLevel; |
||||||
|
const uint32_t kEntriesPerFile = 1000; |
||||||
|
const uint32_t kDeletionsPerFile = 100; |
||||||
|
for (uint32_t i = 0; i < kNumFiles; ++i) { |
||||||
|
Add(static_cast<int>(i / kFilesPerLevel), i + 1, |
||||||
|
ToString((i + 100) * 1000).c_str(), |
||||||
|
ToString((i + 100) * 1000 + 999).c_str(), |
||||||
|
100U, 0, 100, 100, |
||||||
|
kEntriesPerFile, kDeletionsPerFile, |
||||||
|
(i < kTotalSamples)); |
||||||
|
} |
||||||
|
// minus 2X for the number of deletion entries because:
|
||||||
|
// 1x for deletion entry does not count as a data entry.
|
||||||
|
// 1x for each deletion entry will actually remove one data entry.
|
||||||
|
ASSERT_EQ(vstorage_.GetEstimatedActiveKeys(), |
||||||
|
(kEntriesPerFile - 2 * kDeletionsPerFile) * kNumFiles); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
int main(int argc, char** argv) { return rocksdb::test::RunAllTests(); } |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue