diff --git a/.gitignore b/.gitignore index ace9dc100..f366f88b3 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,10 @@ lcov # python compiled files *.pyc + +cdb.rs +*.cdb +compile_commands.json + +*.cmd +core diff --git a/.travis.yml b/.travis.yml index 1615cf255..b864a10fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: required +sudo: false language: c # using anchor to import sources into linux builds @@ -10,6 +10,11 @@ addons: - llvm-toolchain-precise-3.7 - llvm-toolchain-precise +# important for allowed-to-fail matching +# see https://docs.travis-ci.com/user/customizing-the-build#Rows-that-are-Allowed-to-Fail +env: + - ALLOWED_TO_FAIL=0 + # travis currently does not support directly setting gcc/clang with versions # (e.g. gcc-4.8) as value for the compiler key. So we will have to manually # request these packages and use environment varibles to create the matrix. @@ -19,39 +24,55 @@ addons: matrix: include: # gcc 4.8 on linux - - env: C_COMPILER=gcc-4.8 + - env: + - C_COMPILER=gcc-4.8 addons: apt: <<: *apt - packages: gcc-4.8 + packages: + - gcc-4.8 + - libsubunit-dev + # gcc 4.9 on linux - - env: C_COMPILER=gcc-4.9 + - env: + - C_COMPILER=gcc-4.9 addons: apt: <<: *apt - packages: gcc-4.9 + packages: + - gcc-4.9 + - libsubunit-dev # gcc 5 on linux - - env: C_COMPILER=gcc-5 + - env: + - C_COMPILER=gcc-5 addons: apt: <<: *apt - packages: gcc-5 + packages: + - gcc-5 + - libsubunit-dev # clang 3.6 on linux - - env: C_COMPILER=clang-3.6 + - env: + - C_COMPILER=clang-3.6 addons: apt: <<: *apt - packages: clang-3.6 + packages: + - clang-3.6 + - libsubunit-dev # clang 3.7 on linux - - env: C_COMPILER=clang-3.7 + - env: + - C_COMPILER=clang-3.7 addons: apt: <<: *apt - packages: clang-3.7 + packages: + - clang-3.7 + - libsubunit-dev ## gcc 4.8 on osx #- os: osx @@ -65,38 +86,39 @@ matrix: #- os: osx # env: FORMULA=gcc5 COMPILER=gcc C_COMPILER=gcc-5 - # clang 3.6 on osx + # OSX 10.13 + # Apple LLVM version 9.1.0 (clang-902.0.39.2) + # Target: x86_64-apple-darwin17.6.0 - os: osx - osx_image: xcode6.4 - env: C_COMPILER=clang + osx_image: xcode9.4 + env: + - C_COMPILER=clang + - ALLOWED_TO_FAIL=1 - # clang 3.7 on osx + # OSX 10.12 + # Apple LLVM version 9.0.0 (clang-900.0.39.2) + # Target: x86_64-apple-darwin16.7.0 - os: osx - osx_image: xcode7.3 - env: C_COMPILER=clang + osx_image: xcode9.2 + env: + - C_COMPILER=clang + - ALLOWED_TO_FAIL=1 - # # clang 4.2 on osx - # - os: osx - # osx_image: xcode8.2 - # env: C_COMPILER=clang + allow_failures: + - os: osx + osx_image: xcode9.4 + env: + - C_COMPILER=clang + - ALLOWED_TO_FAIL=1 + - os: osx + osx_image: xcode9.2 + env: + - C_COMPILER=clang + - ALLOWED_TO_FAIL=1 before_install: - # for osx: 0. update brew; 1. install cmake if missing; 2. (gcc) unlink pre-installed gcc; 3. (gcc) install desired version of gcc - - 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi' - - 'if [[ "$TRAVIS_OS_NAME" == "osx" && -z "$(which cmake)" ]]; then brew install cmake; fi' # xcode 7.1 is missing cmake - - 'if [[ "$TRAVIS_OS_NAME" == "osx" && "$COMPILER" == "gcc" ]]; then brew unlink gcc || true; fi' # ignore unlink errors - - 'if [[ "$TRAVIS_OS_NAME" == "osx" && "$COMPILER" == "gcc" ]]; then brew unlink $FORMULA || true; fi' # ignore unlink errors - - 'if [[ "$TRAVIS_OS_NAME" == "osx" && "$COMPILER" == "gcc" ]]; then brew install $FORMULA; fi' - - export CC=$C_COMPILER - - wget https://github.com/libcheck/check/releases/download/0.11.0/check-0.11.0.tar.gz - - tar xvfz check-0.11.0.tar.gz - - cd check-0.11.0 && ./configure && make && sudo make install && cd .. + - ./ci/before-install.sh script: - - mkdir _build && cd _build - - cmake .. - - make -j - - make check - - egrep -r ":F:|:E:" . || true - - cd ../test/integration && python test_twemcache.py + - ./ci/run.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 648bfc9b6..38edae4d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,9 +39,12 @@ option(TARGET_REDIS "build redis binary" ON) option(TARGET_SLIMREDIS "build slimredis binary" ON) option(TARGET_SLIMCACHE "build slimcache binary" ON) option(TARGET_TWEMCACHE "build twemcache binary" ON) - +option(TARGET_CDB "build cdb binary" OFF) option(TARGET_RESPCLI "build resp-cli binary" ON) +option(RUST_USE_MUSL "build rust deps against musl" OFF) +option(BUILD_AND_INSTALL_CHECK "build our own version of check and link against it" OFF) + option(COVERAGE "code coverage" OFF) # Note: duplicate custom targets only works with Makefile generators, will break XCode & VS @@ -80,6 +83,28 @@ set(CFLAGS_LIST "-Wunused-function -Wunused-value -Wunused-variable " "-fno-strict-aliasing ") +if(BUILD_AND_INSTALL_CHECK) + # (simms) What follows is a crime against build systems as we run the build/install + # for the check library up front, during the planning phase. + + set(LIBCHECK_PREFIX "${CMAKE_BINARY_DIR}/check") + + # check for a local install of check + if(NOT EXISTS "${LIBCHECK_PREFIX}") + # (simms) This is terrible and I did it this way to ensure this gets built + # before the rest of the 'check' tests run. This should be rewritten so that + # the other dependencies know that there's a target that can build check + execute_process( + COMMAND "bash" "${PROJECT_SOURCE_DIR}/ci/install-check.sh" "${LIBCHECK_PREFIX}" + TIMEOUT 300 # if this doesn't build in 5 minutes something is hosed + ) + endif() + + set(CHECK_ROOT_DIR "${LIBCHECK_PREFIX}") + set(CMAKE_REQUIRED_INCLUDES "${CHECK_ROOT_DIR}/include") # these make check link correctly in ccommon and pelikan + set(CMAKE_REQUIRED_LIBRARIES "${CHECK_ROOT_DIR}/lib") +endif() + if(${OS_PLATFORM} MATCHES "OS_LINUX") set(CFLAGS_LIST "${CFLAGS_LIST} -lrt") endif() @@ -96,9 +121,11 @@ endif(COVERAGE) # dependency: libccommon set(CCOMMON_SOURCE_DIR "${PROJECT_SOURCE_DIR}/deps/ccommon" CACHE PATH "Path to the ccommon") add_subdirectory(${CCOMMON_SOURCE_DIR} ${PROJECT_BINARY_DIR}/ccommon) + # other dependencies include(FindPackageHandleStandardArgs) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") + find_package(Check) if(NOT CHECK_FOUND) message(WARNING "Check is required to build and run tests") @@ -112,6 +139,9 @@ endif(CHECK_FOUND) find_package(Threads) +enable_language(Rust) +include(CMakeCargo) + #set(CMAKE_INCLUDE_CURRENT_DIR) include_directories(${include_directories} "${PROJECT_BINARY_DIR}" @@ -138,5 +168,15 @@ message(STATUS "HAVE_SIGNAME: " ${HAVE_SIGNAME}) message(STATUS "HAVE_BACKTRACE: " ${HAVE_BACKTRACE}) message(STATUS "HAVE_BIG_ENDIAN: " ${HAVE_BIG_ENDIAN}) +if(DUMP_ALL) + message(STATUS "<<++=====------------------\\/------------------=====++>>") + get_cmake_property(_variableNames VARIABLES) + foreach (_variableName ${_variableNames}) + message(STATUS "${_variableName}=${${_variableName}}") + endforeach() + message(STATUS "<<++=====------------------/\\------------------=====++>>") +endif() + # Note: to uninstall targets, run: # xargs rm < install_manifest.txt +# vim:ts=4:sw=4:et diff --git a/ci/.gitignore b/ci/.gitignore new file mode 100644 index 000000000..26e0873a9 --- /dev/null +++ b/ci/.gitignore @@ -0,0 +1 @@ +CMAKE_BINARY_DIR diff --git a/ci/before-install.sh b/ci/before-install.sh new file mode 100755 index 000000000..d948672db --- /dev/null +++ b/ci/before-install.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +die() { echo "fatal: $*" >&2; exit 1; } + +TEMP="$(mktemp -d -t TEMP.XXXXXXX)" || die "failed to make tmpdir" +cleanup() { [[ -n "${TEMP:-}" ]] && rm -rf "${TEMP}"; } +trap cleanup EXIT + +TOPLEVEL="$(git -C "$(cd "$(dirname "$0")" >/dev/null || exit 1; pwd)" rev-parse --show-toplevel)" || die 'failed to find TOPLEVEL' + +# for osx: 0. update brew; 1. install cmake if missing; 2. (gcc) unlink pre-installed gcc; 3. (gcc) install desired version of gcc + +if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + brew update &>/dev/null + brew install cmake || true # xcode 8.1 is missing cmake + + if [[ "$C_COMPILER" =~ ^gcc && -n "${FORMULA:-}" ]]; then + brew unlink gcc || true + brew unlink "$FORMULA" || true + brew install "$FORMULA" + fi +fi + +export CC="$C_COMPILER" + +curl https://sh.rustup.rs -sSf | sh -s -- -y diff --git a/ci/install-check.sh b/ci/install-check.sh new file mode 100755 index 000000000..628d6d229 --- /dev/null +++ b/ci/install-check.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +die() { echo "fatal: $*" >&2; exit 1; } + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 check-install-path" + exit 1 +fi + +CHECK_PREFIX="$1" +shift + +TEMP="$(mktemp -d -t TEMP.XXXXXXX)" || die "failed to make tmpdir" +cleanup() { [[ -n "${TEMP:-}" ]] && rm -rf "${TEMP}"; } +trap cleanup EXIT + +TOPLEVEL="$(git -C "$(cd "$(dirname "$0")" >/dev/null || exit 1; pwd)" rev-parse --show-toplevel)" || die 'failed to find TOPLEVEL' + +CHECK_VERSION=0.12.0 +CHECK_TARBALL="check-${CHECK_VERSION}.tar.gz" +CHECK_DIR="check-${CHECK_VERSION}" + +( + cd "$TEMP" && + wget "https://github.com/libcheck/check/releases/download/${CHECK_VERSION}/${CHECK_TARBALL}" && + tar xvfz "${CHECK_TARBALL}" && + cd "${CHECK_DIR}" && + ./configure --prefix="$CHECK_PREFIX" && + make && + make install +) || die "check build failed" diff --git a/ci/local-testing-with-docker.sh b/ci/local-testing-with-docker.sh new file mode 100755 index 000000000..4d9922168 --- /dev/null +++ b/ci/local-testing-with-docker.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +die() { echo "fatal: $*" >&2; exit 1; } + +TEMP="$(mktemp -d -t TEMP.XXXXXXX)" || die "failed to make tmpdir" +cleanup() { [[ -n "${TEMP:-}" ]] && rm -rf "${TEMP}"; } +trap cleanup EXIT + +# This script installs a travis-ci docker image and attempts to run the build +# script in that container. in order for it to work you have to have the 'travis' +# command-line utility installed and the 'travis-build' plugin installed as well. +# +# instructions here: https://github.com/travis-ci/travis-build#use-as-addon-for-cli + +CONTAINER_NAME='pelikan-travis-debug' + +# this will undoubtedly need to be updated regularly +# see https://stackoverflow.com/a/49019950/965434 for more context +# +INSTANCE='travisci/ci-garnet:packer-1512502276-986baf0' + + +echo "cleanup previous run's container, no problem if failure" >&2 + +docker stop "$CONTAINER_NAME" || true +docker rm "$CONTAINER_NAME" || true + +set -x + +docker run --name "$CONTAINER_NAME" -dit "$INSTANCE" /sbin/init + +CI_SCRIPT="$TEMP/ci.sh" + +"$HOME/.travis/travis-build/bin/travis" compile > "$CI_SCRIPT" +chmod 755 "$CI_SCRIPT" + +docker cp "$CI_SCRIPT" "$CONTAINER_NAME:/home/travis/ci.sh" + +cat <"$TEMP/run.sh" +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +su - travis -- -c 'cd /home/travis && ./ci.sh' + +EO_RUN_SCRIPT + +docker cp "$TEMP/run.sh" "$CONTAINER_NAME:/run.sh" + +docker exec "$CONTAINER_NAME" "/bin/bash" "/run.sh" diff --git a/ci/run.sh b/ci/run.sh new file mode 100755 index 000000000..b1faeea17 --- /dev/null +++ b/ci/run.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' + +die() { echo "fatal: $*" >&2; exit 1; } + +TEMP="$(mktemp -d -t TEMP.XXXXXXX)" || die "failed to make tmpdir" +cleanup() { [[ -n "${TEMP:-}" ]] && rm -rf "${TEMP}"; } +trap cleanup EXIT + +export PATH=$HOME/.cargo/bin:$PATH + +mkdir -p _build && ( cd _build && cmake -D BUILD_AND_INSTALL_CHECK=yes .. && make -j && make check ) || die 'make failed' + +egrep -r ":F:|:E:" . |grep -v 'Binary file' || true + +( cd test/integration && python test_twemcache.py ) || die 'twemcache tests failed' + +set +e + +( cd src/storage/cdb && env RUST_BACKTRACE=full cargo test ) + +RESULT=$? + +if [[ "$(uname -s)" == "Darwin" ]]; then + if [[ $RESULT -ne 0 ]]; then + echo "Rust test failed on OSX, but this does not fail the build" >&2 + fi + + exit 0 +fi + +if [[ $RESULT -ne 0 ]]; then + echo "Build failure" >&2 + exit $RESULT +else + echo "success!" >&2 + exit 0 +fi diff --git a/cmake/CMakeCargo.cmake b/cmake/CMakeCargo.cmake new file mode 100644 index 000000000..975b0ccd2 --- /dev/null +++ b/cmake/CMakeCargo.cmake @@ -0,0 +1,97 @@ +function(cargo_set_lib_target LIB_NAME) + if(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_TARGET "x86_64-pc-windows-msvc" PARENT_SCOPE) + else() + set(LIB_TARGET "i686-pc-windows-msvc" PARENT_SCOPE) + endif() + elseif(ANDROID) + if(ANDROID_SYSROOT_ABI STREQUAL "x86") + set(LIB_TARGET "i686-linux-android" PARENT_SCOPE) + elseif(ANDROID_SYSROOT_ABI STREQUAL "x86_64") + set(LIB_TARGET "x86_64-linux-android" PARENT_SCOPE) + elseif(ANDROID_SYSROOT_ABI STREQUAL "arm") + set(LIB_TARGET "arm-linux-androideabi" PARENT_SCOPE) + elseif(ANDROID_SYSROOT_ABI STREQUAL "arm64") + set(LIB_TARGET "aarch64-linux-android" PARENT_SCOPE) + endif() + elseif(IOS) + set(LIB_TARGET "universal" PARENT_SCOPE) + elseif(CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(LIB_TARGET "x86_64-apple-darwin" PARENT_SCOPE) + else() + if(RUST_USE_MUSL) + set(RUST_LIBC_NAME "musl") + else() + set(RUST_LIBC_NAME "gnu") + endif() + + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(LIB_TARGET "x86_64-unknown-linux-${RUST_LIBC_NAME}" PARENT_SCOPE) + else() + set(LIB_TARGET "i686-unknown-linux-${RUST_LIBC_NAME}" PARENT_SCOPE) + endif() + endif() +endfunction() + +function(cargo_build) + cmake_parse_arguments(CARGO "" "NAME" "" ${ARGN}) + string(REPLACE "-" "_" LIB_NAME ${CARGO_NAME}) + + get_filename_component(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}" ABSOLUTE) + + cargo_set_lib_target(LIB_NAME) + + if(NOT CMAKE_BUILD_TYPE) + set(LIB_BUILD_TYPE "debug") + elseif(${CMAKE_BUILD_TYPE} STREQUAL "Release") + set(LIB_BUILD_TYPE "release") + else() + set(LIB_BUILD_TYPE "debug") + endif() + + set(SHARED_LIB_FNAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIB_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(STATIC_LIB_FNAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIB_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") + + set(LIB_BASE_DIR "${CARGO_TARGET_DIR}/${LIB_TARGET}/${LIB_BUILD_TYPE}") + + get_filename_component(SHARED_LIB_FILE "${LIB_BASE_DIR}/${SHARED_LIB_FNAME}" ABSOLUTE) + get_filename_component(STATIC_LIB_FILE "${LIB_BASE_DIR}/${STATIC_LIB_FNAME}" ABSOLUTE) + + if(IOS) + set(CARGO_ARGS "lipo") + else() + set(CARGO_ARGS "build") + list(APPEND CARGO_ARGS "--target" ${LIB_TARGET}) + endif() + + if(${LIB_BUILD_TYPE} STREQUAL "release") + list(APPEND CARGO_ARGS "--release") + endif() + + file(GLOB_RECURSE LIB_SOURCES "*.rs") + + set(CARGO_ENV_COMMAND ${CMAKE_COMMAND} -E env "CARGO_TARGET_DIR=${CARGO_TARGET_DIR}") + + add_custom_command( + OUTPUT ${STATIC_LIB_FILE} + COMMAND ${CARGO_ENV_COMMAND} ${CARGO_EXECUTABLE} ARGS ${CARGO_ARGS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${LIB_SOURCES} + COMMENT "running cargo") + add_custom_target(${CARGO_NAME}_static_target ALL DEPENDS ${STATIC_LIB_FILE}) + add_library(${CARGO_NAME}_static STATIC IMPORTED GLOBAL) + add_dependencies(${CARGO_NAME}_static ${CARGO_NAME}_static_target) + set_target_properties(${CARGO_NAME}_static PROPERTIES IMPORTED_LOCATION ${STATIC_LIB_FILE}) + + add_custom_command( + OUTPUT ${SHARED_LIB_FILE} + COMMAND ${CARGO_ENV_COMMAND} ${CARGO_EXECUTABLE} ARGS ${CARGO_ARGS} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${LIB_SOURCES} + COMMENT "running cargo") + add_custom_target(${CARGO_NAME}_shared_target ALL DEPENDS ${SHARED_LIB_FILE}) + add_library(${CARGO_NAME}_shared SHARED IMPORTED GLOBAL) + add_dependencies(${CARGO_NAME}_shared ${CARGO_NAME}_shared_target) + set_target_properties(${CARGO_NAME}_shared PROPERTIES IMPORTED_LOCATION ${SHARED_LIB_FILE}) +endfunction() diff --git a/cmake/CMakeDetermineRustCompiler.cmake b/cmake/CMakeDetermineRustCompiler.cmake new file mode 100644 index 000000000..33a459c0a --- /dev/null +++ b/cmake/CMakeDetermineRustCompiler.cmake @@ -0,0 +1,25 @@ + +if(NOT CMAKE_Rust_COMPILER) + find_package(Rust) + if(RUST_FOUND) + set(CMAKE_Rust_COMPILER "${RUSTC_EXECUTABLE}") + set(CMAKE_Rust_COMPILER_ID "Rust") + set(CMAKE_Rust_COMPILER_VERSION "${RUST_VERSION}") + set(CMAKE_Rust_PLATFORM_ID "Rust") + endif() +endif() + +message(STATUS "Cargo Prefix: ${CARGO_PREFIX}") +message(STATUS "Rust Compiler Version: ${RUSTC_VERSION}") + +mark_as_advanced(CMAKE_Rust_COMPILER) + +if(CMAKE_Rust_COMPILER) + set(CMAKE_Rust_COMPILER_LOADED 1) +endif(CMAKE_Rust_COMPILER) + +configure_file(${CMAKE_SOURCE_DIR}/cmake/CMakeRustCompiler.cmake.in + ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${CMAKE_VERSION}/CMakeRustCompiler.cmake IMMEDIATE @ONLY) + +set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC") + diff --git a/cmake/CMakeRustCompiler.cmake.in b/cmake/CMakeRustCompiler.cmake.in new file mode 100644 index 000000000..5916c1c3d --- /dev/null +++ b/cmake/CMakeRustCompiler.cmake.in @@ -0,0 +1,15 @@ + +set(CMAKE_Rust_COMPILER "@CMAKE_Rust_COMPILER@") +set(CMAKE_Rust_COMPILER_ID "@CMAKE_Rust_COMPILER_ID@") +set(CMAKE_Rust_COMPILER_VERSION "@CMAKE_Rust_COMPILER_VERSION@") +set(CMAKE_Rust_COMPILER_LOADED @CMAKE_Rust_COMPILER_LOADED@) +set(CMAKE_Rust_PLATFORM_ID "@CMAKE_Rust_PLATFORM_ID@") + +SET(CMAKE_Rust_SOURCE_FILE_EXTENSIONS rs) +SET(CMAKE_Rust_LINKER_PREFERENCE 40) +#SET(CMAKE_Rust_OUTPUT_EXTENSION_REPLACE 1) +SET(CMAKE_STATIC_LIBRARY_PREFIX_Rust "") +SET(CMAKE_STATIC_LIBRARY_SUFFIX_Rust .a) + +set(CMAKE_Rust_COMPILER_ENV_VAR "RUSTC") + diff --git a/cmake/CMakeRustInformation.cmake b/cmake/CMakeRustInformation.cmake new file mode 100644 index 000000000..05d96c94b --- /dev/null +++ b/cmake/CMakeRustInformation.cmake @@ -0,0 +1,106 @@ + +# +# Usage: rustc [OPTIONS] INPUT +# +# Options: +# -h --help Display this message +# --cfg SPEC Configure the compilation environment +# -L [KIND=]PATH Add a directory to the library search path. The +# optional KIND can be one of dependency, crate, native, +# framework or all (the default). +# -l [KIND=]NAME Link the generated crate(s) to the specified native +# library NAME. The optional KIND can be one of static, +# dylib, or framework. If omitted, dylib is assumed. +# --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|metadata] +# Comma separated list of types of crates for the +# compiler to emit +# --crate-name NAME Specify the name of the crate being built +# --emit [asm|llvm-bc|llvm-ir|obj|link|dep-info] +# Comma separated list of types of output for the +# compiler to emit +# --print [crate-name|file-names|sysroot|cfg|target-list|target-cpus|target-features|relocation-models|code-models] +# Comma separated list of compiler information to print +# on stdout +# -g Equivalent to -C debuginfo=2 +# -O Equivalent to -C opt-level=2 +# -o FILENAME Write output to +# --out-dir DIR Write output to compiler-chosen filename in +# --explain OPT Provide a detailed explanation of an error message +# --test Build a test harness +# --target TARGET Target triple for which the code is compiled +# -W --warn OPT Set lint warnings +# -A --allow OPT Set lint allowed +# -D --deny OPT Set lint denied +# -F --forbid OPT Set lint forbidden +# --cap-lints LEVEL Set the most restrictive lint level. More restrictive +# lints are capped at this level +# -C --codegen OPT[=VALUE] +# Set a codegen option +# -V --version Print version info and exit +# -v --verbose Use verbose output +# +# Additional help: +# -C help Print codegen options +# -W help Print 'lint' options and default settings +# -Z help Print internal options for debugging rustc +# --help -v Print the full set of options rustc accepts +# + +# + +include(CMakeLanguageInformation) + +if(UNIX) + set(CMAKE_Rust_OUTPUT_EXTENSION .o) +else() + set(CMAKE_Rust_OUTPUT_EXTENSION .obj) +endif() + +set(CMAKE_Rust_ECHO_ALL "echo \"TARGET: TARGET_BASE: ") +set(CMAKE_Rust_ECHO_ALL "${CMAKE_Rust_ECHO_ALL} OBJECT: OBJECTS: OBJECT_DIR: SOURCE: SOURCES: ") +set(CMAKE_Rust_ECHO_ALL "${CMAKE_Rust_ECHO_ALL} LINK_LIBRARIES: FLAGS: LINK_FLAGS: \"") + +if(NOT CMAKE_Rust_CREATE_SHARED_LIBRARY) + set(CMAKE_Rust_CREATE_SHARED_LIBRARY + "echo \"CMAKE_Rust_CREATE_SHARED_LIBRARY\"" + "${CMAKE_Rust_ECHO_ALL}" + ) +endif() + +if(NOT CMAKE_Rust_CREATE_SHARED_MODULE) + set(CMAKE_Rust_CREATE_SHARED_MODULE + "echo \"CMAKE_Rust_CREATE_SHARED_MODULE\"" + "${CMAKE_Rust_ECHO_ALL}" + ) +endif() + +if(NOT CMAKE_Rust_CREATE_STATIC_LIBRARY) + set(CMAKE_Rust_CREATE_STATIC_LIBRARY + "echo \"CMAKE_Rust_CREATE_STATIC_LIBRARY\"" + "${CMAKE_Rust_ECHO_ALL}" + ) +endif() + +if(NOT CMAKE_Rust_COMPILE_OBJECT) + set(CMAKE_Rust_COMPILE_OBJECT + "echo \"CMAKE_Rust_COMPILE_OBJECT\"" + "${CMAKE_Rust_ECHO_ALL}" + "${CMAKE_Rust_COMPILER} --emit obj -o ") +endif() + +if(NOT CMAKE_Rust_LINK_EXECUTABLE) + set(CMAKE_Rust_LINK_EXECUTABLE + "echo \"CMAKE_Rust_LINK_EXECUTABLE\"" + "${CMAKE_Rust_ECHO_ALL}" + ) +endif() + +mark_as_advanced( + CMAKE_Rust_FLAGS + CMAKE_Rust_FLAGS_DEBUG + CMAKE_Rust_FLAGS_MINSIZEREL + CMAKE_Rust_FLAGS_RELEASE + CMAKE_Rust_FLAGS_RELWITHDEBINFO) + +set(CMAKE_Rust_INFORMATION_LOADED 1) + diff --git a/cmake/CMakeTestRustCompiler.cmake b/cmake/CMakeTestRustCompiler.cmake new file mode 100644 index 000000000..ff75bb3e0 --- /dev/null +++ b/cmake/CMakeTestRustCompiler.cmake @@ -0,0 +1,3 @@ + +set(CMAKE_Rust_COMPILER_WORKS 1 CACHE INTERNAL "") + diff --git a/cmake/FindRust.cmake b/cmake/FindRust.cmake new file mode 100644 index 000000000..2dbf14ddf --- /dev/null +++ b/cmake/FindRust.cmake @@ -0,0 +1,75 @@ + +set(_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) + +if(WIN32) + set(USER_HOME "$ENV{USERPROFILE}") +else() + set(USER_HOME "$ENV{HOME}") +endif() + +# Find cargo prefix +find_path(CARGO_PREFIX ".cargo" + PATHS "${USER_HOME}") + +if(CARGO_PREFIX MATCHES "NOTFOUND") + message(FATAL_ERROR "Could not find Rust!") +else() + set(CARGO_PREFIX "${CARGO_PREFIX}/.cargo") +endif() + +# Find cargo executable +find_program(CARGO_EXECUTABLE cargo + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(CARGO_EXECUTABLE) + +# Find rustc executable +find_program(RUSTC_EXECUTABLE rustc + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(RUSTC_EXECUTABLE) + +# Find rustdoc executable +find_program(RUSTDOC_EXECUTABLE rustdoc + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(RUSTDOC_EXECUTABLE) + +# Find rust-gdb executable +find_program(RUST_GDB_EXECUTABLE rust-gdb + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(RUST_GDB_EXECUTABLE) + +# Find rust-lldb executable +find_program(RUST_LLDB_EXECUTABLE rust-lldb + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(RUST_LLDB_EXECUTABLE) + +# Find rustup executable +find_program(RUSTUP_EXECUTABLE rustup + HINTS "${CARGO_PREFIX}" + PATH_SUFFIXES "bin") +mark_as_advanced(RUSTUP_EXECUTABLE) + +set(RUST_FOUND FALSE CACHE INTERNAL "") + +if(CARGO_EXECUTABLE AND RUSTC_EXECUTABLE AND RUSTDOC_EXECUTABLE) + set(RUST_FOUND TRUE CACHE INTERNAL "") + + set(CARGO_PREFIX "${CARGO_PREFIX}" CACHE PATH "Rust Cargo prefix") + + execute_process(COMMAND ${RUSTC_EXECUTABLE} --version OUTPUT_VARIABLE RUSTC_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX REPLACE "rustc ([^ ]+) .*" "\\1" RUSTC_VERSION "${RUSTC_VERSION}") +endif() + +if(NOT RUST_FOUND) + message(FATAL_ERROR "Could not find Rust!") +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${_CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${_CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}) diff --git a/config/cdb.conf b/config/cdb.conf new file mode 100644 index 000000000..f0804585a --- /dev/null +++ b/config/cdb.conf @@ -0,0 +1,17 @@ +# if slab profile is specified, then the profile wil be explicitly set +# otherwise, slab profile will be generated by using growth factor, slab size, +# and item min/max size +# For example, you can specify all item sizes allowed with slab_profile: +# slab_profile: 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 + +debug_log_level: 4 +debug_log_file: cdb.log +debug_log_nbuf: 1048576 + +klog_file: cdb.cmd +klog_backup: cdb.cmd.old +klog_sample: 100 +klog_max: 1073741824 + +# by default load file in the cwd named 'db.cdb' +cdb_file_path: db.cdb diff --git a/deps/.gitignore b/deps/.gitignore new file mode 100644 index 000000000..58a3fa1d5 --- /dev/null +++ b/deps/.gitignore @@ -0,0 +1 @@ +check/ diff --git a/deps/ccommon/src/CMakeLists.txt b/deps/ccommon/src/CMakeLists.txt index 43828ce17..99582e640 100644 --- a/deps/ccommon/src/CMakeLists.txt +++ b/deps/ccommon/src/CMakeLists.txt @@ -19,13 +19,13 @@ set(SOURCE cc_ring_array.c cc_signal.c) -# targets to build: here we have both static and dynmaic libs +# targets to build: here we have both static and dynamic libs set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) add_library(${PROJECT_NAME}-static STATIC ${SOURCE}) add_library(${PROJECT_NAME}-shared SHARED ${SOURCE}) if (OS_PLATFORM STREQUAL "OS_LINUX") - target_link_libraries(${PROJECT_NAME}-static rt) - target_link_libraries(${PROJECT_NAME}-shared rt) + target_link_libraries(${PROJECT_NAME}-static rt m pthread) + target_link_libraries(${PROJECT_NAME}-shared rt m pthread) endif(OS_PLATFORM STREQUAL "OS_LINUX") set_target_properties(${PROJECT_NAME}-static PROPERTIES diff --git a/src/protocol/data/memcache/response.h b/src/protocol/data/memcache/response.h index 9e531792c..ab3ad0821 100644 --- a/src/protocol/data/memcache/response.h +++ b/src/protocol/data/memcache/response.h @@ -65,6 +65,7 @@ typedef enum response_state { RSP_DONE } response_state_t; + /* * NOTE(yao): we store fields as location in rbuf, this assumes the data will * not be overwritten prematurely. @@ -80,6 +81,7 @@ struct response { struct bstring key; /* key string */ struct bstring vstr; /* value string */ + uint64_t vint; /* return value for incr/decr, or integer get value */ uint64_t vcas; /* value for cas */ struct metric *met; /* metric, for reporting stats */ diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index fd7805623..032941ea0 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -17,3 +17,7 @@ endif() if(TARGET_TWEMCACHE) add_subdirectory(twemcache) endif() + +if(TARGET_CDB) + add_subdirectory(cdb) +endif() diff --git a/src/server/cdb/CMakeLists.txt b/src/server/cdb/CMakeLists.txt new file mode 100644 index 000000000..6e54e7ca0 --- /dev/null +++ b/src/server/cdb/CMakeLists.txt @@ -0,0 +1,31 @@ +add_subdirectory(admin) +add_subdirectory(data) + +set(SOURCE + ${SOURCE} + main.c + setting.c + stats.c) + +set(MODULES + cdb_ffi_static + core + protocol_admin + protocol_memcache + time + util) + +set(LIBS + ccommon-static + dl + ${CMAKE_THREAD_LIBS_INIT}) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/_bin) +set(TARGET_NAME ${PROJECT_NAME}_cdb) + +add_executable(${TARGET_NAME} ${SOURCE}) +target_link_libraries(${TARGET_NAME} ${MODULES} ${LIBS}) +add_dependencies(${TARGET_NAME} cdb_ffi_static) + +install(TARGETS ${TARGET_NAME} RUNTIME DESTINATION bin) +add_dependencies(service ${TARGET_NAME}) diff --git a/src/server/cdb/admin/CMakeLists.txt b/src/server/cdb/admin/CMakeLists.txt new file mode 100644 index 000000000..31a7e65f3 --- /dev/null +++ b/src/server/cdb/admin/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCE + ${SOURCE} + ${CMAKE_CURRENT_SOURCE_DIR}/process.c + PARENT_SCOPE) diff --git a/src/server/cdb/admin/process.c b/src/server/cdb/admin/process.c new file mode 100644 index 000000000..ec0b6c18f --- /dev/null +++ b/src/server/cdb/admin/process.c @@ -0,0 +1,77 @@ +#include "process.h" + +#include "protocol/admin/admin_include.h" +#include "util/procinfo.h" + +#include +#include + +#define CDB_ADMIN_MODULE_NAME "cdb::admin" + +extern struct stats stats; +extern unsigned int nmetric; + +static bool admin_init = false; +static char *buf = NULL; +static size_t cap; + +void +admin_process_setup(void) +{ + log_info("set up the %s module", CDB_ADMIN_MODULE_NAME); + if (admin_init) { + log_warn("%s has already been setup, overwrite", + CDB_ADMIN_MODULE_NAME); + } + + cap = METRIC_PRINT_LEN * nmetric + METRIC_END_LEN; + + buf = cc_alloc(cap); + if (buf == NULL) { + log_panic("failure to allocate buf in admin_process_setup"); + } + + admin_init = true; +} + +void +admin_process_teardown(void) +{ + log_info("Mr. Gorbechev, tear down this module [%s]", CDB_ADMIN_MODULE_NAME); + if (!admin_init) { + log_warn("%s has never been setup", CDB_ADMIN_MODULE_NAME); + } + + admin_init = false; +} + +static void +_admin_stats(struct response *rsp, struct request *req) +{ + if (bstring_empty(&req->arg)) { + procinfo_update(); + rsp->data.data = buf; + rsp->data.len = (uint32_t)print_stats(buf, cap, (struct metric *)&stats, nmetric); + return; + } else { + rsp->type = RSP_INVALID; + } +} + +void +admin_process_request(struct response *rsp, struct request *req) +{ + rsp->type = RSP_GENERIC; + + switch (req->type) { + case REQ_STATS: + _admin_stats(rsp, req); + break; + case REQ_VERSION: + rsp->data = str2bstr(VERSION_PRINTED); + break; + default: + rsp->type = RSP_INVALID; + break; + } +} diff --git a/src/server/cdb/admin/process.h b/src/server/cdb/admin/process.h new file mode 100644 index 000000000..361230004 --- /dev/null +++ b/src/server/cdb/admin/process.h @@ -0,0 +1,4 @@ +#pragma once + +void admin_process_setup(void); +void admin_process_teardown(void); diff --git a/src/server/cdb/data/CMakeLists.txt b/src/server/cdb/data/CMakeLists.txt new file mode 100644 index 000000000..31a7e65f3 --- /dev/null +++ b/src/server/cdb/data/CMakeLists.txt @@ -0,0 +1,4 @@ +set(SOURCE + ${SOURCE} + ${CMAKE_CURRENT_SOURCE_DIR}/process.c + PARENT_SCOPE) diff --git a/src/server/cdb/data/process.c b/src/server/cdb/data/process.c new file mode 100644 index 000000000..79cd5dc1c --- /dev/null +++ b/src/server/cdb/data/process.c @@ -0,0 +1,341 @@ +#include + +#include "process.h" + +#include "protocol/data/memcache_include.h" +#include "storage/cdb/cdb.h" + +#include +#include +#include +#include + +#define CDB_PROCESS_MODULE_NAME "cdb::process" + +#define OVERSIZE_ERR_MSG "oversized value, cannot be stored" +#define DELTA_ERR_MSG "value is not a number" +#define OOM_ERR_MSG "server is out of memory" +#define CMD_ERR_MSG "command not supported" +#define OTHER_ERR_MSG "unknown server error" + +/* value_buf is a buffer of configurable size that processors can use by + * rsp->vstr.data = value_buf.data. vstr.data is nulled out in response_reset + * so the link is broken after each response */ +static struct bstring value_buf; + +static bool process_init = false; +static process_metrics_st *process_metrics = NULL; + +static struct CDBHandle *cdb_handle = NULL; + +void +process_setup(process_options_st *options, process_metrics_st *metrics, struct CDBHandle *handle) +{ + log_info("set up the %s module", CDB_PROCESS_MODULE_NAME); + + if (process_init) { + log_warn("%s has already been setup, overwrite", + CDB_PROCESS_MODULE_NAME); + } + + if (handle == NULL) { + log_panic("cdb_handle was null, cannot continue"); + } + cdb_handle = handle; + + if (options->vbuf_size.val.vuint > UINT_MAX) { + log_panic("Value for vbuf_size was too large. Must be < %ld", UINT_MAX); + } + + value_buf.len = (uint32_t)options->vbuf_size.val.vuint; + value_buf.data = (char *)cc_alloc(value_buf.len); + + if (value_buf.data == NULL) { + log_panic("failed to allocate value buffer, cannot continue"); + } + + process_metrics = metrics; + + process_init = true; +} + +void +process_teardown(void) +{ + log_info("tear down the %s module", CDB_PROCESS_MODULE_NAME); + if (!process_init) { + log_warn("%s has never been setup", CDB_PROCESS_MODULE_NAME); + } + + if (cdb_handle != NULL) { + struct CDBHandle *p = cdb_handle; + cdb_handle = NULL; + cdb_handle_destroy(p); + } + + if (value_buf.data != NULL) { + char *p = value_buf.data; + value_buf.data = NULL; + value_buf.len = 0; + cc_free(p); + } + + process_metrics = NULL; + process_init = false; +} + +static bool +_get_key(struct response *rsp, struct bstring *key) +{ + /* this is a slight abuse of the bstring API. we're setting + * the data pointer to point to the vbuf that was staticly allocated + * and we're setting the len of the buffer to the allocation size. This is + * so that we can create a rust slice from this information. */ + rsp->vstr.data = value_buf.data; + rsp->vstr.len = value_buf.len; + + struct bstring *vstr = cdb_get(cdb_handle, key, &(rsp->vstr)); + + if (vstr != NULL) { + rsp->type = RSP_VALUE; + rsp->key = *key; + rsp->flag = 0; + rsp->vcas = 0; + rsp->vstr.len = vstr->len; + rsp->vstr.data = vstr->data; + + log_verb("found key at %p, location %p", key, vstr); + return true; + } else { + log_verb("key at %p not found", key); + return false; + } +} + +static void +_process_get(struct response *rsp, struct request *req) +{ + struct bstring *key; + struct response *r = rsp; + uint32_t i; + + INCR(process_metrics, get); + /* use chained responses, move to the next response if key is found. */ + for (i = 0; i < array_nelem(req->keys); ++i) { + INCR(process_metrics, get_key); + key = array_get(req->keys, i); + if (_get_key(r, key)) { + req->nfound++; + r->cas = false; + r = STAILQ_NEXT(r, next); + if (r == NULL) { + INCR(process_metrics, get_ex); + log_warn("get response incomplete due to lack of rsp objects"); + return; + } + INCR(process_metrics, get_key_hit); + } else { + INCR(process_metrics, get_key_miss); + } + } + r->type = RSP_END; + + log_verb("get req %p processed, %d out of %d keys found", req, req->nfound, i); +} + +static void +_process_invalid(struct response *rsp, struct request *req) +{ + INCR(process_metrics, invalid); + rsp->type = RSP_CLIENT_ERROR; + rsp->vstr = str2bstr(CMD_ERR_MSG); + log_verb("req %p processed, rsp type %d", req, rsp->type); +} + +void +process_request(struct response *rsp, struct request *req) +{ + log_verb("processing req %p, write rsp to %p", req, rsp); + INCR(process_metrics, process_req); + + switch (req->type) { + case REQ_GET: + _process_get(rsp, req); + break; + + default: + _process_invalid(rsp, req); + break; + } +} + +static inline void +_cleanup(struct request *req, struct response *rsp) +{ + struct response *nr = STAILQ_NEXT(rsp, next); + + request_reset(req); + /* return all but the first response */ + if (nr != NULL) { + response_return_all(&nr); + } + + response_reset(rsp); + req->rsp = rsp; +} + +int +cdb_process_read(struct buf **rbuf, struct buf **wbuf, void **data) +{ + parse_rstatus_t status; + struct request *req; /* data should be NULL or hold a req pointer */ + struct response *rsp; + + log_verb("post-read processing"); + + /* deal with the stateful part: request and response */ + req = (*data != NULL) ? *data : request_borrow(); + if (req == NULL) { + /* TODO(yao): simply return for now, better to respond with OOM */ + log_error("cannot process request: OOM"); + INCR(process_metrics, process_ex); + + return -1; + } + rsp = (req->rsp != NULL) ? req->rsp : response_borrow(); + if (rsp == NULL) { + request_return(&req); + /* TODO(yao): simply return for now, better to respond with OOM */ + log_error("cannot process request: OOM"); + INCR(process_metrics, process_ex); + + return -1; + } + + /* keep parse-process-compose until running out of data in rbuf */ + while (buf_rsize(*rbuf) > 0) { + struct response *nr; + int i, card; + + /* stage 1: parsing */ + log_verb("%"PRIu32" bytes left", buf_rsize(*rbuf)); + + status = parse_req(req, *rbuf); + if (status == PARSE_EUNFIN) { + buf_lshift(*rbuf); + return 0; + } + if (status != PARSE_OK) { + /* parsing errors are all client errors, since we don't know + * how to recover from client errors in this condition (we do not + * have a valid request so we don't know where the invalid request + * ends), we should close the connection + */ + log_warn("illegal request received, status: %d", status); + return -1; + } + + if (req->swallow) { /* skip to the end of current request */ + continue; + } + + /* stage 2: processing- check for quit, allocate response(s), process */ + + /* quit is special, no response expected */ + if (req->type == REQ_QUIT) { + log_info("peer called quit"); + return -1; + } + + /* find cardinality of the request and get enough response objects */ + card = array_nelem(req->keys) - 1; /* we already have one in rsp */ + if (req->type == REQ_GET || req->type == REQ_GETS) { + /* extra response object for the "END" line after values */ + card++; + } + for (i = 0, nr = rsp; + i < card; + i++, STAILQ_NEXT(nr, next) = response_borrow(), nr = + STAILQ_NEXT(nr, next)) { + if (nr == NULL) { + log_error("cannot acquire response: OOM"); + INCR(process_metrics, process_ex); + _cleanup(req, rsp); + return -1; + } + } + + /* actual processing */ + process_request(rsp, req); + if (req->partial) { /* implies end of rbuf w/o complete processing */ + /* in this case, do not attempt to log or write response */ + buf_lshift(*rbuf); + return 0; + } + + /* stage 3: write response(s) if necessary */ + + /* noreply means no need to write to buffers */ + card++; + if (!req->noreply) { + nr = rsp; + if (req->type == REQ_GET || req->type == REQ_GETS) { + /* for get/gets, card is determined by number of values */ + card = req->nfound + 1; + } + for (i = 0; i < card; nr = STAILQ_NEXT(nr, next), ++i) { + if (compose_rsp(wbuf, nr) < 0) { + log_error("composing rsp erred"); + INCR(process_metrics, process_ex); + _cleanup(req, rsp); + return -1; + } + } + } + + /* logging, clean-up */ + klog_write(req, rsp); + _cleanup(req, rsp); + } + + return 0; +} + + +int +cdb_process_write(struct buf **rbuf, struct buf **wbuf, void **data) +{ + log_verb("post-write processing"); + + buf_lshift(*rbuf); + dbuf_shrink(rbuf); + buf_lshift(*wbuf); + dbuf_shrink(wbuf); + + return 0; +} + + +int +cdb_process_error(struct buf **rbuf, struct buf **wbuf, void **data) +{ + struct request *req = *data; + struct response *rsp; + + log_verb("post-error processing"); + + /* normalize buffer size */ + buf_reset(*rbuf); + dbuf_shrink(rbuf); + buf_reset(*wbuf); + dbuf_shrink(wbuf); + + /* release request data & associated reserved data */ + if (req != NULL) { + rsp = req->rsp; + request_return(&req); + response_return_all(&rsp); + } + + return 0; +} diff --git a/src/server/cdb/data/process.h b/src/server/cdb/data/process.h new file mode 100644 index 000000000..8b7cd8df7 --- /dev/null +++ b/src/server/cdb/data/process.h @@ -0,0 +1,41 @@ +#pragma once + +#include "storage/cdb/cdb.h" + +#include +#include +#include +#include + +#define RSP_VAL_BUF_SIZE 1048576 + +/* name type default description */ +#define PROCESS_OPTION(ACTION) \ +ACTION( vbuf_size, OPTION_TYPE_UINT, RSP_VAL_BUF_SIZE, "size in bytes of the value response buffer" ) + +typedef struct { + PROCESS_OPTION(OPTION_DECLARE) +} process_options_st; + +/* name type description */ +#define PROCESS_METRIC(ACTION) \ + ACTION( process_req, METRIC_COUNTER, "# requests processed" )\ + ACTION( process_ex, METRIC_COUNTER, "# processing error" )\ + ACTION( process_server_ex, METRIC_COUNTER, "# internal error" )\ + ACTION( get, METRIC_COUNTER, "# get requests" )\ + ACTION( get_key, METRIC_COUNTER, "# keys by get" )\ + ACTION( get_key_hit, METRIC_COUNTER, "# key hits by get" )\ + ACTION( get_key_miss, METRIC_COUNTER, "# key misses by get" )\ + ACTION( get_ex, METRIC_COUNTER, "# get errors" )\ + ACTION( invalid, METRIC_COUNTER, "# invalid command" ) + +typedef struct { + PROCESS_METRIC(METRIC_DECLARE) +} process_metrics_st; + +void process_setup(process_options_st *options, process_metrics_st *metrics, struct CDBHandle *cdb_handle); +void process_teardown(void); + +int cdb_process_read(struct buf **rbuf, struct buf **wbuf, void **data); +int cdb_process_write(struct buf **rbuf, struct buf **wbuf, void **data); +int cdb_process_error(struct buf **rbuf, struct buf **wbuf, void **data); diff --git a/src/server/cdb/main.c b/src/server/cdb/main.c new file mode 100644 index 000000000..b54757180 --- /dev/null +++ b/src/server/cdb/main.c @@ -0,0 +1,238 @@ +#include "admin/process.h" +#include "setting.h" +#include "stats.h" +#include "storage/cdb/cdb.h" + +#include "time/time.h" +#include "util/util.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct data_processor worker_processor = { + cdb_process_read, + cdb_process_write, + cdb_process_error, +}; + +static void +show_usage(void) +{ + log_stdout( + "Usage:" CRLF + " pelikan_cdb [option|config]" CRLF + ); + log_stdout( + "Description:" CRLF + " pelikan_cdb is one of the unified cache backends. " CRLF + " It uses djb's CDB format (a read-only data store) to cache key/val pairs. " CRLF + " It speaks the memcached ASCII protocol and supports the get command. " CRLF + ); + log_stdout( + "Command-line options:" CRLF + " -h, --help show this message" CRLF + " -v, --version show version number" CRLF + " -c, --config list & describe all options in config" CRLF + " -s, --stats list & describe all metrics in stats" CRLF + ); + log_stdout( + "Example:" CRLF + " pelikan_cdb cdb.conf" CRLF CRLF + "Sample config files can be found under the config dir." CRLF + ); +} + +static void +teardown(void) +{ + core_worker_teardown(); + core_server_teardown(); + core_admin_teardown(); + admin_process_teardown(); + process_teardown(); + cdb_teardown(); + klog_teardown(); + compose_teardown(); + parse_teardown(); + response_teardown(); + request_teardown(); + procinfo_teardown(); + time_teardown(); + + timing_wheel_teardown(); + tcp_teardown(); + sockio_teardown(); + event_teardown(); + dbuf_teardown(); + buf_teardown(); + + debug_teardown(); + log_teardown(); +} + +static struct CDBHandle* +setup_cdb_handle(cdb_options_st *opt) +{ + cdb_setup(); + + char *cdb_file_path = opt->cdb_file_path.val.vstr; + if (cdb_file_path == NULL) { + log_stderr("cdb_file_path option not set, cannot continue"); + exit(EX_CONFIG); + } + return cdb_handle_create(cdb_file_path); +} + +static void +setup(void) +{ + char *fname = NULL; + uint64_t intvl; + + + if (atexit(teardown) != 0) { + log_stderr("cannot register teardown procedure with atexit()"); + exit(EX_OSERR); /* only failure comes from NOMEM */ + } + + /* Setup logging first */ + log_setup(&stats.log); + if (debug_setup(&setting.debug) != CC_OK) { + log_stderr("debug log setup failed"); + exit(EX_CONFIG); + } + + /* setup top-level application options */ + if (option_bool(&setting.cdb.daemonize)) { + daemonize(); + } + fname = option_str(&setting.cdb.pid_filename); + if (fname != NULL) { + /* to get the correct pid, call create_pidfile after daemonize */ + create_pidfile(fname); + } + + + /* setup library modules */ + buf_setup(&setting.buf, &stats.buf); + dbuf_setup(&setting.dbuf, &stats.dbuf); + event_setup(&stats.event); + sockio_setup(&setting.sockio, &stats.sockio); + tcp_setup(&setting.tcp, &stats.tcp); + timing_wheel_setup(&stats.timing_wheel); + + /* setup pelikan modules */ + time_setup(); + procinfo_setup(&stats.procinfo); + request_setup(&setting.request, &stats.request); + response_setup(&setting.response, &stats.response); + parse_setup(&stats.parse_req, NULL); + compose_setup(NULL, &stats.compose_rsp); + klog_setup(&setting.klog, &stats.klog); + + struct CDBHandle* cdb_handle = setup_cdb_handle(&setting.cdb); + if (cdb_handle == NULL) { + log_stderr("failed to set up cdb"); + goto error; + } + + process_setup(&setting.process, &stats.process, cdb_handle); + admin_process_setup(); + core_admin_setup(&setting.admin); + core_server_setup(&setting.server, &stats.server); + core_worker_setup(&setting.worker, &stats.worker); + + /* adding recurring events to maintenance/admin thread */ + intvl = option_uint(&setting.cdb.dlog_intvl); + if (core_admin_register(intvl, debug_log_flush, NULL) == NULL) { + log_stderr("Could not register timed event to flush debug log"); + goto error; + } + + intvl = option_uint(&setting.cdb.klog_intvl); + if (core_admin_register(intvl, klog_flush, NULL) == NULL) { + log_error("Could not register timed event to flush command log"); + goto error; + } + + return; + +error: + if (fname != NULL) { + remove_pidfile(fname); + } + + /* since we registered teardown with atexit, it'll be called upon exit */ + exit(EX_CONFIG); +} + +int +main(int argc, char **argv) +{ + rstatus_i status = CC_OK;; + FILE *fp = NULL; + + if (argc > 2) { + show_usage(); + exit(EX_USAGE); + } + + if (argc == 1) { + log_stderr("launching server with default values."); + } else { + /* argc == 2 */ + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + show_usage(); + exit(EX_OK); + } + if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0) { + show_version(); + exit(EX_OK); + } + if (strcmp(argv[1], "-c") == 0 || strcmp(argv[1], "--config") == 0) { + option_describe_all((struct option *)&setting, nopt); + exit(EX_OK); + } + if (strcmp(argv[1], "-s") == 0 || strcmp(argv[1], "--stats") == 0) { + metric_describe_all((struct metric *)&stats, nmetric); + exit(EX_OK); + } + fp = fopen(argv[1], "r"); + if (fp == NULL) { + log_stderr("cannot open config: incorrect path or doesn't exist"); + exit(EX_DATAERR); + } + } + + if (option_load_default((struct option *)&setting, nopt) != CC_OK) { + log_stderr("failed to load default option values"); + exit(EX_CONFIG); + } + + if (fp != NULL) { + log_stderr("load config from %s", argv[1]); + status = option_load_file(fp, (struct option *)&setting, nopt); + fclose(fp); + } + if (status != CC_OK) { + log_stderr("failed to load config"); + exit(EX_DATAERR); + } + + setup(); + option_print_all((struct option *)&setting, nopt); + + core_run(&worker_processor); + + exit(EX_OK); +} diff --git a/src/server/cdb/setting.c b/src/server/cdb/setting.c new file mode 100644 index 000000000..07277ea27 --- /dev/null +++ b/src/server/cdb/setting.c @@ -0,0 +1,20 @@ +#include "setting.h" + +struct setting setting = { + { CDB_OPTION(OPTION_INIT) }, + { ADMIN_OPTION(OPTION_INIT) }, + { SERVER_OPTION(OPTION_INIT) }, + { WORKER_OPTION(OPTION_INIT) }, + { PROCESS_OPTION(OPTION_INIT) }, + { KLOG_OPTION(OPTION_INIT) }, + { REQUEST_OPTION(OPTION_INIT) }, + { RESPONSE_OPTION(OPTION_INIT) }, + { ARRAY_OPTION(OPTION_INIT) }, + { BUF_OPTION(OPTION_INIT) }, + { DBUF_OPTION(OPTION_INIT) }, + { DEBUG_OPTION(OPTION_INIT) }, + { SOCKIO_OPTION(OPTION_INIT) }, + { TCP_OPTION(OPTION_INIT) }, +}; + +unsigned int nopt = OPTION_CARDINALITY(struct setting); diff --git a/src/server/cdb/setting.h b/src/server/cdb/setting.h new file mode 100644 index 000000000..1842a85a7 --- /dev/null +++ b/src/server/cdb/setting.h @@ -0,0 +1,52 @@ +#pragma once + +#include "data/process.h" + +#include "core/core.h" +#include "protocol/data/memcache_include.h" +#include "storage/slab/item.h" +#include "storage/slab/slab.h" + +#include +#include +#include +#include +#include +#include +#include + +/* option related */ +/* name type default description */ +#define CDB_OPTION(ACTION) \ + ACTION( daemonize, OPTION_TYPE_BOOL, false, "daemonize the process" )\ + ACTION( pid_filename, OPTION_TYPE_STR, NULL, "file storing the pid" )\ + ACTION( cdb_file_path, OPTION_TYPE_STR, "db.cdb", "location of the .cdb file" )\ + ACTION( dlog_intvl, OPTION_TYPE_UINT, 500, "debug log flush interval(ms)" )\ + ACTION( klog_intvl, OPTION_TYPE_UINT, 100, "cmd log flush interval(ms)" ) + +typedef struct { + CDB_OPTION(OPTION_DECLARE) +} cdb_options_st; + +struct setting { + /* top-level */ + cdb_options_st cdb; + /* application modules */ + admin_options_st admin; + server_options_st server; + worker_options_st worker; + process_options_st process; + klog_options_st klog; + request_options_st request; + response_options_st response; + /* ccommon libraries */ + array_options_st array; + buf_options_st buf; + dbuf_options_st dbuf; + debug_options_st debug; + sockio_options_st sockio; + tcp_options_st tcp; +}; + +extern struct setting setting; +extern unsigned int nopt; diff --git a/src/server/cdb/stats.c b/src/server/cdb/stats.c new file mode 100644 index 000000000..cf66a3a7c --- /dev/null +++ b/src/server/cdb/stats.c @@ -0,0 +1,22 @@ +#include "stats.h" + +struct stats stats = { + { PROCINFO_METRIC(METRIC_INIT) }, + { PROCESS_METRIC(METRIC_INIT) }, + { PARSE_REQ_METRIC(METRIC_INIT) }, + { COMPOSE_RSP_METRIC(METRIC_INIT) }, + { KLOG_METRIC(METRIC_INIT) }, + { REQUEST_METRIC(METRIC_INIT) }, + { RESPONSE_METRIC(METRIC_INIT) }, + { CORE_SERVER_METRIC(METRIC_INIT) }, + { CORE_WORKER_METRIC(METRIC_INIT) }, + { BUF_METRIC(METRIC_INIT) }, + { DBUF_METRIC(METRIC_INIT) }, + { EVENT_METRIC(METRIC_INIT) }, + { LOG_METRIC(METRIC_INIT) }, + { SOCKIO_METRIC(METRIC_INIT) }, + { TCP_METRIC(METRIC_INIT) }, + { TIMING_WHEEL_METRIC(METRIC_INIT) }, +}; + +unsigned int nmetric = METRIC_CARDINALITY(struct stats); diff --git a/src/server/cdb/stats.h b/src/server/cdb/stats.h new file mode 100644 index 000000000..585706615 --- /dev/null +++ b/src/server/cdb/stats.h @@ -0,0 +1,40 @@ +#pragma once + +#include "data/process.h" + +#include "core/core.h" +#include "protocol/data/memcache_include.h" +#include "storage/slab/item.h" +#include "storage/slab/slab.h" +#include "util/procinfo.h" + +#include +#include +#include +#include +#include