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
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