mirror of
https://github.com/codership/wsrep-lib.git
synced 2025-07-30 07:23:07 +03:00
Initial implementation of the status interface reporter object.
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (C) 2018 Codership Oy <info@codership.com>
|
||||
# Copyright (C) 2021 Codership Oy <info@codership.com>
|
||||
#
|
||||
|
||||
cmake_minimum_required (VERSION 2.8)
|
||||
@ -25,7 +25,15 @@ else()
|
||||
project(wsrep-lib)
|
||||
endif()
|
||||
|
||||
include(CheckIncludeFile)
|
||||
if (POLICY CMP0057)
|
||||
cmake_policy(SET CMP0057 NEW)
|
||||
endif()
|
||||
|
||||
if (POLICY CMP0077)
|
||||
cmake_policy(SET CMP0077 NEW)
|
||||
endif()
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
include(CheckLibraryExists)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
@ -36,6 +44,7 @@ option(WSREP_LIB_WITH_UNIT_TESTS "Compile unit tests" ON)
|
||||
if (WSREP_LIB_WITH_UNIT_TESTS)
|
||||
# Run tests automatically by default if compiled
|
||||
option(WSREP_LIB_WITH_AUTO_TEST "Run unit tests automatically after build" OFF)
|
||||
option(WSREP_LIB_WITH_UNIT_TESTS_EXTRA "Compile unit tests that may require additional software" OFF)
|
||||
endif()
|
||||
|
||||
# Build a sample program
|
||||
@ -107,14 +116,47 @@ else()
|
||||
set(WSREP_LIB_LIBDL "")
|
||||
endif()
|
||||
|
||||
set(MIN_BOOST_VERSION "1.54.0")
|
||||
if (WSREP_LIB_WITH_UNIT_TESTS)
|
||||
find_package(Boost 1.54.0 REQUIRED
|
||||
if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA)
|
||||
set(json_HEADER "boost/json/src.hpp")
|
||||
# Extra tests may require packages from very recent boost which may be
|
||||
# unavailable on the system. In such case download private boost distro.
|
||||
check_include_file_cxx(${json_HEADER} system_json_FOUND)
|
||||
if (NOT system_json_FOUND)
|
||||
if (NOT WITH_BOOST)
|
||||
set(WITH_BOOST "${CMAKE_SOURCE_DIR}/../boost")
|
||||
endif()
|
||||
set(DOWNLOAD_BOOST ON)
|
||||
include (cmake/boost.cmake)
|
||||
set(MIN_BOOST_VERSION "${BOOST_MAJOR}.${BOOST_MINOR}.${BOOST_PATCH}")
|
||||
message(STATUS "Boost includes: ${BOOST_INCLUDE_DIR}, ver: ${MIN_BOOST_VERSION}")
|
||||
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
|
||||
COMPONENTS json headers
|
||||
PATHS ${WITH_BOOST}/lib/cmake
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
# Boost 1.76.0 comes very sloppy (and not only in JSON department)
|
||||
# - need to disable some checks.
|
||||
set(ADDITIONAL_CXX_FLAGS "-Wno-effc++ -Wno-conversion -Wno-suggest-override -Wno-overloaded-virtual")
|
||||
set(ADDITIONAL_CXX_INCLUDES ${BOOST_INCLUDE_DIR})
|
||||
check_include_file_cxx(${json_HEADER} json_FOUND
|
||||
"-I${ADDITIONAL_CXX_INCLUDES} ${ADDITIONAL_CXX_FLAGS}"
|
||||
)
|
||||
if (NOT json_FOUND)
|
||||
message(FATAL_ERROR "Required header 'boost/json.hpp' not found: ${json_FOUND}")
|
||||
else()
|
||||
include_directories(SYSTEM ${ADDITIONAL_CXX_INCLUDES})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
|
||||
unit_test_framework
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WSREP_LIB_WITH_DBSIM)
|
||||
find_package(Boost 1.54.0 REQUIRED
|
||||
find_package(Boost ${MIN_BOOST_VERSION} REQUIRED
|
||||
program_options
|
||||
filesystem
|
||||
thread
|
||||
|
335
cmake/boost.cmake
Normal file
335
cmake/boost.cmake
Normal file
@ -0,0 +1,335 @@
|
||||
# Copyright (c) 2014, 2021, Oracle and/or its affiliates.
|
||||
# Copyright (c) 2021, Codership OY.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License, version 2.0,
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# This program is also distributed with certain software (including
|
||||
# but not limited to OpenSSL) that is licensed under separate terms,
|
||||
# as designated in a particular file or component or in included license
|
||||
# documentation. The authors of MySQL hereby grant you an additional
|
||||
# permission to link the program and your derivative works with the
|
||||
# separately licensed software that they have included with MySQL.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License, version 2.0, for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
# We need boost verision >= 1.75 to test JSON-related code.
|
||||
|
||||
# Invoke with -DWITH_BOOST=<directory> or set WITH_BOOST in environment.
|
||||
# If WITH_BOOST is *not* set, or is set to the special value "system",
|
||||
# we assume that the correct version (see below)
|
||||
# is installed on the compile host in the standard location.
|
||||
|
||||
FUNCTION(GET_FILE_SIZE FILE_NAME OUTPUT_SIZE)
|
||||
IF(WIN32)
|
||||
FILE(TO_NATIVE_PATH "${CMAKE_SOURCE_DIR}/cmake/filesize.bat" FILESIZE_BAT)
|
||||
FILE(TO_NATIVE_PATH "${FILE_NAME}" NATIVE_FILE_NAME)
|
||||
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND "${FILESIZE_BAT}" "${NATIVE_FILE_NAME}"
|
||||
RESULT_VARIABLE COMMAND_RESULT
|
||||
OUTPUT_VARIABLE RESULT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
ELSEIF(APPLE OR FREEBSD)
|
||||
EXEC_PROGRAM(stat ARGS -f '%z' ${FILE_NAME} OUTPUT_VARIABLE RESULT)
|
||||
ELSE()
|
||||
EXEC_PROGRAM(stat ARGS -c '%s' ${FILE_NAME} OUTPUT_VARIABLE RESULT)
|
||||
ENDIF()
|
||||
SET(${OUTPUT_SIZE} ${RESULT} PARENT_SCOPE)
|
||||
ENDFUNCTION()
|
||||
|
||||
SET(BOOST_MAJOR "1")
|
||||
SET(BOOST_MINOR "76")
|
||||
SET(BOOST_PATCH "0")
|
||||
SET(BOOST_PACKAGE_NAME "boost_${BOOST_MAJOR}_${BOOST_MINOR}_${BOOST_PATCH}")
|
||||
SET(BOOST_TARBALL "${BOOST_PACKAGE_NAME}.tar.gz")
|
||||
SET(BOOST_DOWNLOAD_URL
|
||||
"https://boostorg.jfrog.io/artifactory/main/release/${BOOST_MAJOR}.${BOOST_MINOR}.${BOOST_PATCH}/source/${BOOST_TARBALL}"
|
||||
)
|
||||
|
||||
MACRO(RESET_BOOST_VARIABLES)
|
||||
UNSET(BOOST_INCLUDE_DIR)
|
||||
UNSET(BOOST_INCLUDE_DIR CACHE)
|
||||
UNSET(LOCAL_BOOST_DIR)
|
||||
UNSET(LOCAL_BOOST_DIR CACHE)
|
||||
UNSET(LOCAL_BOOST_ZIP)
|
||||
UNSET(LOCAL_BOOST_ZIP CACHE)
|
||||
ENDMACRO()
|
||||
|
||||
MACRO(ECHO_BOOST_VARIABLES)
|
||||
MESSAGE(STATUS "BOOST_INCLUDE_DIR ${BOOST_INCLUDE_DIR}")
|
||||
MESSAGE(STATUS "LOCAL_BOOST_DIR ${LOCAL_BOOST_DIR}")
|
||||
MESSAGE(STATUS "LOCAL_BOOST_ZIP ${LOCAL_BOOST_ZIP}")
|
||||
ENDMACRO()
|
||||
|
||||
MACRO(COULD_NOT_FIND_BOOST)
|
||||
ECHO_BOOST_VARIABLES()
|
||||
RESET_BOOST_VARIABLES()
|
||||
MESSAGE(STATUS "Could not find (the correct version of) boost.")
|
||||
MESSAGE(STATUS "wsrep-lib currently requires ${BOOST_PACKAGE_NAME}\n")
|
||||
MESSAGE(FATAL_ERROR
|
||||
"You can download it with -DDOWNLOAD_BOOST=1 -DWITH_BOOST=<directory>\n"
|
||||
"This CMake script will look for boost in <directory>. "
|
||||
"If it is not there, it will download and unpack it "
|
||||
"(in that directory) for you.\n"
|
||||
"You can also download boost manually, from ${BOOST_DOWNLOAD_URL}\n"
|
||||
"If you are inside a firewall, you may need to use an https proxy:\n"
|
||||
"export https_proxy=http://example.com:80\n"
|
||||
)
|
||||
ENDMACRO()
|
||||
|
||||
# Pick value from environment if not set on command line.
|
||||
IF(DEFINED ENV{WITH_BOOST} AND NOT DEFINED WITH_BOOST)
|
||||
SET(WITH_BOOST "$ENV{WITH_BOOST}")
|
||||
ENDIF()
|
||||
|
||||
# Pick value from environment if not set on command line.
|
||||
IF(DEFINED ENV{BOOST_ROOT} AND NOT DEFINED WITH_BOOST)
|
||||
SET(WITH_BOOST "$ENV{BOOST_ROOT}")
|
||||
ENDIF()
|
||||
|
||||
IF(WITH_BOOST AND WITH_BOOST STREQUAL "system")
|
||||
UNSET(WITH_BOOST)
|
||||
UNSET(WITH_BOOST CACHE)
|
||||
ENDIF()
|
||||
|
||||
# Update the cache, to make it visible in cmake-gui.
|
||||
SET(WITH_BOOST ${WITH_BOOST} CACHE PATH
|
||||
"Path to boost sources: a directory, or a tarball to be unzipped.")
|
||||
|
||||
# If the value of WITH_BOOST changes, we must unset all dependent variables:
|
||||
IF(OLD_WITH_BOOST)
|
||||
IF(NOT "${OLD_WITH_BOOST}" STREQUAL "${WITH_BOOST}")
|
||||
RESET_BOOST_VARIABLES()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
SET(OLD_WITH_BOOST ${WITH_BOOST} CACHE INTERNAL
|
||||
"Previous version of WITH_BOOST" FORCE)
|
||||
|
||||
IF (WITH_BOOST)
|
||||
## Did we get a full path name, including file name?
|
||||
IF (${WITH_BOOST} MATCHES ".*\\.tar.gz" OR ${WITH_BOOST} MATCHES ".*\\.zip")
|
||||
GET_FILENAME_COMPONENT(BOOST_DIR ${WITH_BOOST} PATH)
|
||||
GET_FILENAME_COMPONENT(BOOST_ZIP ${WITH_BOOST} NAME)
|
||||
FIND_FILE(LOCAL_BOOST_ZIP
|
||||
NAMES ${BOOST_ZIP}
|
||||
PATHS ${BOOST_DIR}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
ENDIF()
|
||||
## Did we get a path name to the directory of the .tar.gz or .zip file?
|
||||
FIND_FILE(LOCAL_BOOST_ZIP
|
||||
NAMES "${BOOST_PACKAGE_NAME}.tar.gz" "${BOOST_PACKAGE_NAME}.zip"
|
||||
PATHS "${WITH_BOOST}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
IF(LOCAL_BOOST_ZIP)
|
||||
MESSAGE(STATUS "Local boost zip: ${LOCAL_BOOST_ZIP}")
|
||||
GET_FILE_SIZE(${LOCAL_BOOST_ZIP} LOCAL_BOOST_ZIP_SIZE)
|
||||
IF(LOCAL_BOOST_ZIP_SIZE EQUAL 0)
|
||||
# A previous failed download has left an empty file, most likely the
|
||||
# user pressed Ctrl-C to kill a hanging connection due to missing vpn
|
||||
# proxy. Remove it!
|
||||
MESSAGE("${LOCAL_BOOST_ZIP} is zero length. Deleting it.")
|
||||
FILE(REMOVE ${WITH_BOOST}/${BOOST_TARBALL})
|
||||
UNSET(LOCAL_BOOST_ZIP)
|
||||
UNSET(LOCAL_BOOST_ZIP CACHE)
|
||||
ENDIF()
|
||||
UNSET(LOCAL_BOOST_ZIP_ZERO_LENGTH)
|
||||
ENDIF()
|
||||
|
||||
## Did we get a path name to the directory of an unzipped version?
|
||||
FIND_FILE(LOCAL_BOOST_SOURCE
|
||||
NAMES "${BOOST_PACKAGE_NAME}"
|
||||
PATHS "${WITH_BOOST}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
## Did we get a path name to an unzippped version?
|
||||
FIND_PATH(LOCAL_BOOST_VER
|
||||
NAMES "boost/version.hpp"
|
||||
PATHS "${WITH_BOOST}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
IF(LOCAL_BOOST_VER AND NOT LOCAL_BOOST_SOURCE)
|
||||
SET(LOCAL_BOOST_SOURCE ${WITH_BOOST})
|
||||
UNSET(LOCAL_BOOST_VER)
|
||||
UNSET(LOCAL_BOOST_VER CACHE)
|
||||
ENDIF()
|
||||
IF(LOCAL_BOOST_SOURCE)
|
||||
MESSAGE(STATUS "Local boost source: ${LOCAL_BOOST_SOURCE}")
|
||||
ENDIF()
|
||||
|
||||
## Did we get a path to compiled boost install?
|
||||
FIND_PATH(LOCAL_BOOST_LIB
|
||||
NAMES "lib"
|
||||
PATHS "${WITH_BOOST}"
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
IF(LOCAL_BOOST_LIB)
|
||||
SET(LOCAL_BOOST_BUILD ${WITH_BOOST})
|
||||
UNSET(LOCAL_BOOST_LIB)
|
||||
UNSET(LOCAL_BOOST_LIB CACHE)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
# There is a similar option in unittest/gunit.
|
||||
# But the boost tarball is much bigger, so we have a separate option.
|
||||
OPTION(DOWNLOAD_BOOST "Download boost from sourceforge." OFF)
|
||||
SET(DOWNLOAD_BOOST_TIMEOUT 600 CACHE STRING
|
||||
"Timeout in seconds when downloading boost.")
|
||||
|
||||
# If we could not find it, then maybe download it.
|
||||
IF(WITH_BOOST AND NOT LOCAL_BOOST_ZIP AND NOT LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
|
||||
IF(NOT DOWNLOAD_BOOST)
|
||||
MESSAGE(STATUS "WITH_BOOST=${WITH_BOOST}")
|
||||
COULD_NOT_FIND_BOOST()
|
||||
ENDIF()
|
||||
# Download the tarball
|
||||
MESSAGE(STATUS "Downloading ${BOOST_TARBALL} to ${WITH_BOOST}")
|
||||
FILE(DOWNLOAD ${BOOST_DOWNLOAD_URL}
|
||||
${WITH_BOOST}/${BOOST_TARBALL}
|
||||
TIMEOUT ${DOWNLOAD_BOOST_TIMEOUT}
|
||||
STATUS ERR
|
||||
SHOW_PROGRESS
|
||||
)
|
||||
IF(ERR EQUAL 0)
|
||||
SET(LOCAL_BOOST_ZIP "${WITH_BOOST}/${BOOST_TARBALL}")
|
||||
SET(LOCAL_BOOST_ZIP "${WITH_BOOST}/${BOOST_TARBALL}" CACHE INTERNAL "")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Download failed, error: ${ERR}")
|
||||
# A failed DOWNLOAD leaves an empty file, remove it
|
||||
FILE(REMOVE ${WITH_BOOST}/${BOOST_TARBALL})
|
||||
# STATUS returns a list of length 2
|
||||
LIST(GET ERR 0 NUMERIC_RETURN)
|
||||
IF(NUMERIC_RETURN EQUAL 28)
|
||||
MESSAGE(FATAL_ERROR
|
||||
"You can try downloading ${BOOST_DOWNLOAD_URL} manually"
|
||||
" using curl/wget or a similar tool,"
|
||||
" or increase the value of DOWNLOAD_BOOST_TIMEOUT"
|
||||
" (which is now ${DOWNLOAD_BOOST_TIMEOUT} seconds)"
|
||||
)
|
||||
ENDIF()
|
||||
MESSAGE(FATAL_ERROR
|
||||
"You can try downloading ${BOOST_DOWNLOAD_URL} manually"
|
||||
" using curl/wget or a similar tool"
|
||||
)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
IF(LOCAL_BOOST_ZIP AND NOT LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
|
||||
GET_FILENAME_COMPONENT(LOCAL_BOOST_DIR ${LOCAL_BOOST_ZIP} PATH)
|
||||
IF(NOT EXISTS "${LOCAL_BOOST_DIR}/${BOOST_PACKAGE_NAME}")
|
||||
GET_FILENAME_COMPONENT(BOOST_TARBALL ${LOCAL_BOOST_ZIP} NAME)
|
||||
MESSAGE(STATUS "cd ${LOCAL_BOOST_DIR}; tar xfz ${BOOST_TARBALL}")
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND ${CMAKE_COMMAND} -E tar xfz "${BOOST_TARBALL}"
|
||||
WORKING_DIRECTORY "${LOCAL_BOOST_DIR}"
|
||||
RESULT_VARIABLE tar_result
|
||||
)
|
||||
IF (tar_result MATCHES 0)
|
||||
SET(BOOST_FOUND 1 CACHE INTERNAL "")
|
||||
SET(LOCAL_BOOST_SOURCE "${LOCAL_BOOST_DIR}/${BOOST_PACKAGE_NAME}")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "WITH_BOOST ${WITH_BOOST}.")
|
||||
MESSAGE(STATUS "Failed to extract files.\n"
|
||||
" Please try downloading and extracting yourself.\n"
|
||||
" The url is: ${BOOST_DOWNLOAD_URL}")
|
||||
MESSAGE(FATAL_ERROR "Giving up.")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
IF (LOCAL_BOOST_SOURCE AND NOT LOCAL_BOOST_BUILD)
|
||||
GET_FILENAME_COMPONENT(LOCAL_BOOST_BUILD ${LOCAL_BOOST_SOURCE} PATH)
|
||||
SET(BOOST_TO_BUILD "thread,filesystem,program_options,test,json")
|
||||
MESSAGE(STATUS "Executing Boost configure")
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND ./bootstrap.sh --prefix=${LOCAL_BOOST_BUILD} --with-libraries=${BOOST_TO_BUILD}
|
||||
WORKING_DIRECTORY "${LOCAL_BOOST_SOURCE}"
|
||||
RESULT_VARIABLE RES
|
||||
ERROR_VARIABLE ERR
|
||||
OUTPUT_FILE "bootstrap.log"
|
||||
ERROR_FILE "bootstrap.err"
|
||||
COMMAND_ECHO STDOUT
|
||||
)
|
||||
IF (NOT RES MATCHES 0)
|
||||
MESSAGE(FATAL_ERROR "Boost configure failed: ${ERR}. Logs at ${LOCAL_BOOST_SOURCE}/bootstrap.log, ${LOCAL_BOOST_SOURCE}/bootstrap.err")
|
||||
ENDIF()
|
||||
MESSAGE(STATUS "Executing Boost build")
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND ./b2 install -q
|
||||
WORKING_DIRECTORY "${LOCAL_BOOST_SOURCE}"
|
||||
RESULT_VARIABLE RES
|
||||
ERROR_VARIABLE ERR
|
||||
OUTPUT_FILE "b2.log"
|
||||
ERROR_FILE "b2.err"
|
||||
COMMAND_ECHO STDOUT
|
||||
)
|
||||
IF (NOT RES MATCHES 0)
|
||||
MESSAGE(FATAL_ERROR "Boost build failed: ${ERR}. Logs at ${LOCAL_BOOST_SOURCE}/b2.log, ${LOCAL_BOOST_SOURCE}/b2.err")
|
||||
ENDIF()
|
||||
# remove the source directory so save space
|
||||
FILE(REMOVE_RECURSE ${LOCAL_BOOST_SOURCE})
|
||||
ENDIF()
|
||||
|
||||
# Search for the version file, first in LOCAL_BOOST_DIR or WITH_BOOST
|
||||
MESSAGE(STATUS
|
||||
"Looking for boost/version.hpp in ${LOCAL_BOOST_BUILD}/include")
|
||||
FIND_PATH(BOOST_INCLUDE_DIR
|
||||
NAMES "boost/version.hpp"
|
||||
NO_DEFAULT_PATH
|
||||
PATHS ${LOCAL_BOOST_BUILD}/include
|
||||
)
|
||||
# Then search in standard places (if not found above).
|
||||
FIND_PATH(BOOST_INCLUDE_DIR
|
||||
NAMES "boost/version.hpp"
|
||||
)
|
||||
|
||||
IF(NOT BOOST_INCLUDE_DIR)
|
||||
MESSAGE(STATUS
|
||||
"Looked for boost/version.hpp in ${LOCAL_BOOST_BUILD}/include and default locations")
|
||||
COULD_NOT_FIND_BOOST()
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Found ${BOOST_INCLUDE_DIR}/boost/version.hpp ")
|
||||
ENDIF()
|
||||
|
||||
# Verify version number. Version information looks like:
|
||||
# // BOOST_VERSION % 100 is the patch level
|
||||
# // BOOST_VERSION / 100 % 1000 is the minor version
|
||||
# // BOOST_VERSION / 100000 is the major version
|
||||
# #define BOOST_VERSION 107300
|
||||
FILE(STRINGS "${BOOST_INCLUDE_DIR}/boost/version.hpp"
|
||||
BOOST_VERSION_NUMBER
|
||||
REGEX "^#define[\t ]+BOOST_VERSION[\t ][0-9]+.*"
|
||||
)
|
||||
STRING(REGEX REPLACE
|
||||
"^.*BOOST_VERSION[\t ]([0-9][0-9])([0-9][0-9])([0-9][0-9]).*$" "\\1"
|
||||
BOOST_MAJOR_VERSION "${BOOST_VERSION_NUMBER}")
|
||||
STRING(REGEX REPLACE
|
||||
"^.*BOOST_VERSION[\t ]([0-9][0-9])([0-9][0-9])([0-9][0-9]).*$" "\\2"
|
||||
BOOST_MINOR_VERSION "${BOOST_VERSION_NUMBER}")
|
||||
|
||||
MESSAGE(STATUS "BOOST_VERSION_NUMBER is ${BOOST_VERSION_NUMBER}")
|
||||
|
||||
MESSAGE(STATUS "BOOST_INCLUDE_DIR ${BOOST_INCLUDE_DIR}")
|
||||
|
||||
# Boost gets confused about language support with Clang 7 + MSVC 15.9
|
||||
IF(WIN32_CLANG)
|
||||
ADD_DEFINITIONS(-DBOOST_NO_CXX17_HDR_STRING_VIEW)
|
||||
ENDIF()
|
||||
|
||||
IF(LOCAL_BOOST_BUILD OR LOCAL_BOOST_ZIP)
|
||||
SET(USING_LOCAL_BOOST 1)
|
||||
ELSE()
|
||||
SET(USING_SYSTEM_BOOST 1)
|
||||
ENDIF()
|
@ -57,6 +57,9 @@ db::params db::parse_args(int argc, char** argv)
|
||||
("wsrep-provider-options",
|
||||
po::value<std::string>(¶ms.wsrep_provider_options),
|
||||
"wsrep provider options")
|
||||
("status-file",
|
||||
po::value<std::string>(¶ms.status_file),
|
||||
"status output file")
|
||||
("servers", po::value<size_t>(¶ms.n_servers)->required(),
|
||||
"number of servers to start")
|
||||
("topology", po::value<std::string>(¶ms.topology),
|
||||
|
@ -38,6 +38,7 @@ namespace db
|
||||
std::string topology;
|
||||
std::string wsrep_provider;
|
||||
std::string wsrep_provider_options;
|
||||
std::string status_file;
|
||||
int debug_log_level;
|
||||
int fast_exit;
|
||||
int thread_instrumentation;
|
||||
@ -55,6 +56,7 @@ namespace db
|
||||
, topology()
|
||||
, wsrep_provider()
|
||||
, wsrep_provider_options()
|
||||
, status_file("status.json")
|
||||
, debug_log_level(0)
|
||||
, fast_exit(0)
|
||||
, thread_instrumentation()
|
||||
|
@ -24,10 +24,52 @@
|
||||
#include "db_simulator.hpp"
|
||||
|
||||
#include "wsrep/logger.hpp"
|
||||
#include "wsrep/reporter.hpp"
|
||||
|
||||
#include <ostream>
|
||||
#include <cstdio>
|
||||
|
||||
static wsrep::default_mutex logger_mtx;
|
||||
static wsrep::reporter* reporter = nullptr;
|
||||
|
||||
static void
|
||||
logger_fn(wsrep::log::level l, const char* pfx, const char* msg)
|
||||
{
|
||||
wsrep::unique_lock<wsrep::mutex> lock(logger_mtx);
|
||||
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
|
||||
time_t const tt(time.tv_sec);
|
||||
struct tm date;
|
||||
localtime_r(&tt, &date);
|
||||
|
||||
char date_str[85] = { '\0', };
|
||||
snprintf(date_str, sizeof(date_str) - 1,
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
||||
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday,
|
||||
date.tm_hour, date.tm_min, date.tm_sec, (int)time.tv_nsec/1000000);
|
||||
|
||||
#define LOG_STR date_str << pfx << wsrep::log::to_c_string(l) << msg
|
||||
if (l >= wsrep::log::error && reporter)
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << LOG_STR;
|
||||
std::cerr << os.str() << std::endl;
|
||||
auto const tstamp(double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
|
||||
reporter->report_log_msg(wsrep::reporter::error, os.str(), tstamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << LOG_STR << std::endl;
|
||||
}
|
||||
#undef LOG_STR
|
||||
}
|
||||
|
||||
db::server::server(simulator& simulator,
|
||||
const std::string& name,
|
||||
const std::string& address)
|
||||
const std::string& address,
|
||||
const std::string& status_file)
|
||||
: simulator_(simulator)
|
||||
, storage_engine_(simulator_.params())
|
||||
, mutex_()
|
||||
@ -40,7 +82,15 @@ db::server::server(simulator& simulator,
|
||||
, appliers_()
|
||||
, clients_()
|
||||
, client_threads_()
|
||||
{ }
|
||||
{
|
||||
wsrep::log::logger_fn(logger_fn);
|
||||
reporter = new wsrep::reporter(mutex_, status_file, 3);
|
||||
}
|
||||
|
||||
db::server::~server()
|
||||
{
|
||||
delete reporter;
|
||||
}
|
||||
|
||||
void db::server::applier_thread()
|
||||
{
|
||||
@ -129,3 +179,9 @@ wsrep::high_priority_service* db::server::streaming_applier_service()
|
||||
throw wsrep::not_implemented_error();
|
||||
}
|
||||
|
||||
void db::server::log_state_change(enum wsrep::server_state::state from,
|
||||
enum wsrep::server_state::state to)
|
||||
{
|
||||
wsrep::log_info() << "State changed " << from << " -> " << to;
|
||||
if (reporter) reporter->report_state(to, 0);
|
||||
}
|
||||
|
@ -41,7 +41,9 @@ namespace db
|
||||
public:
|
||||
server(simulator& simulator,
|
||||
const std::string& name,
|
||||
const std::string& address);
|
||||
const std::string& address,
|
||||
const std::string& status_file);
|
||||
~server();
|
||||
void applier_thread();
|
||||
void start_applier();
|
||||
void stop_applier();
|
||||
@ -58,6 +60,8 @@ namespace db
|
||||
wsrep::client_state* local_client_state();
|
||||
void release_client_state(wsrep::client_state*);
|
||||
wsrep::high_priority_service* streaming_applier_service();
|
||||
void log_state_change(enum wsrep::server_state::state,
|
||||
enum wsrep::server_state::state);
|
||||
private:
|
||||
void start_client(size_t id);
|
||||
|
||||
|
@ -154,10 +154,9 @@ void db::server_service::log_state_change(
|
||||
enum wsrep::server_state::state prev_state,
|
||||
enum wsrep::server_state::state current_state)
|
||||
{
|
||||
|
||||
wsrep::log_info() << "State changed "
|
||||
<< prev_state << " -> " << current_state;
|
||||
server_.log_state_change(prev_state, current_state);
|
||||
}
|
||||
|
||||
int db::server_service::wait_committing_transactions(int)
|
||||
{
|
||||
throw wsrep::not_implemented_error();
|
||||
|
@ -137,7 +137,8 @@ void db::simulator::start()
|
||||
std::make_unique<db::server>(
|
||||
*this,
|
||||
name_os.str(),
|
||||
address_os.str()))));
|
||||
address_os.str(),
|
||||
name_os.str() + "_" + params_.status_file))));
|
||||
if (it.second == false)
|
||||
{
|
||||
throw wsrep::runtime_error("Failed to add server");
|
||||
|
114
include/wsrep/reporter.hpp
Normal file
114
include/wsrep/reporter.hpp
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Codership Oy <info@codership.com>
|
||||
*
|
||||
* This file is part of wsrep-lib.
|
||||
*
|
||||
* Wsrep-lib is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Wsrep-lib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/** @file info.hpp
|
||||
*
|
||||
* Interface to report application status to external programs
|
||||
* via JSON file.
|
||||
*/
|
||||
|
||||
#ifndef WSREP_REPORTER_HPP
|
||||
#define WSREP_REPORTER_HPP
|
||||
|
||||
#include "mutex.hpp"
|
||||
#include "server_state.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
|
||||
namespace wsrep
|
||||
{
|
||||
class reporter
|
||||
{
|
||||
public:
|
||||
reporter(mutex& mutex,
|
||||
const std::string& file_name,
|
||||
size_t max_msg);
|
||||
|
||||
virtual ~reporter();
|
||||
|
||||
// indefinite progress value
|
||||
static float constexpr indefinite = -1.0f;
|
||||
|
||||
void report_state(enum server_state::state state,
|
||||
float progress = indefinite);
|
||||
|
||||
enum log_level
|
||||
{
|
||||
error,
|
||||
warning
|
||||
};
|
||||
|
||||
// undefined timestamp value
|
||||
static double constexpr undefined = 0.0;
|
||||
|
||||
void report_log_msg(log_level, const std::string& msg,
|
||||
double timestamp = undefined);
|
||||
|
||||
private:
|
||||
enum substates {
|
||||
s_disconnected_disconnected,
|
||||
s_disconnected_initializing,
|
||||
s_disconnected_initialized,
|
||||
s_connected_waiting, // to become joiner
|
||||
s_joining_initialized,
|
||||
s_joining_sst,
|
||||
s_joining_initializing,
|
||||
s_joining_ist,
|
||||
s_joined_syncing,
|
||||
s_synced_running,
|
||||
s_donor_sending,
|
||||
s_disconnecting_disconnecting,
|
||||
substates_max
|
||||
};
|
||||
|
||||
wsrep::mutex& mutex_;
|
||||
std::string const file_name_;
|
||||
char* template_;
|
||||
substates state_;
|
||||
float progress_;
|
||||
bool initialized_;
|
||||
|
||||
typedef struct {
|
||||
double tstamp;
|
||||
std::string msg;
|
||||
} log_msg;
|
||||
|
||||
std::deque<log_msg> err_msg_;
|
||||
std::deque<log_msg> warn_msg_;
|
||||
size_t const max_msg_;
|
||||
|
||||
static void write_log_msgs(std::ostream& os,
|
||||
const std::string& label,
|
||||
const std::deque<log_msg>& msgs);
|
||||
static void write_log_msg(std::ostream& os,
|
||||
const log_msg& msg);
|
||||
|
||||
substates substate_map(enum server_state::state state);
|
||||
float progress_map(float progress) const;
|
||||
void write_file(double timestamp);
|
||||
|
||||
// make uncopyable
|
||||
reporter(const wsrep::reporter&);
|
||||
void operator=(const wsrep::reporter&);
|
||||
}; /* reporter */
|
||||
|
||||
} /* wsrep */
|
||||
|
||||
#endif /* WSREP_REPORTER_HPP */
|
@ -19,5 +19,6 @@ add_library(wsrep-lib
|
||||
tls_service_v1.cpp
|
||||
transaction.cpp
|
||||
uuid.cpp
|
||||
reporter.cpp
|
||||
wsrep_provider_v26.cpp)
|
||||
target_link_libraries(wsrep-lib wsrep_api_v26 pthread ${WSREP_LIB_LIBDL})
|
||||
|
321
src/reporter.cpp
Normal file
321
src/reporter.cpp
Normal file
@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Codership Oy <info@codership.com>
|
||||
*
|
||||
* This file is part of wsrep-lib.
|
||||
*
|
||||
* Wsrep-lib is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Wsrep-lib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "wsrep/reporter.hpp"
|
||||
#include "wsrep/logger.hpp"
|
||||
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <cstring> // strerror()
|
||||
#include <cstdlib> // mkstemp()
|
||||
#include <cerrno> // errno
|
||||
#include <unistd.h> // write()
|
||||
#include <cstdio> // rename(), snprintf()
|
||||
#include <ctime> // clock_gettime()
|
||||
#include <cmath> // floor()
|
||||
|
||||
static std::string const TEMP_EXTENSION(".XXXXXX");
|
||||
|
||||
static inline double
|
||||
timestamp()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
|
||||
}
|
||||
|
||||
wsrep::reporter::reporter(wsrep::mutex& mutex,
|
||||
const std::string& file_name,
|
||||
size_t const max_msg)
|
||||
: mutex_(mutex)
|
||||
, file_name_(file_name)
|
||||
, template_(new char [file_name_.length() + TEMP_EXTENSION.length() + 1])
|
||||
, state_(wsrep::reporter::s_disconnected_disconnected)
|
||||
, progress_(indefinite)
|
||||
, initialized_(false)
|
||||
, err_msg_()
|
||||
, warn_msg_()
|
||||
, max_msg_(max_msg)
|
||||
{
|
||||
template_[file_name_.length() + TEMP_EXTENSION.length()] = '\0';
|
||||
write_file(timestamp());
|
||||
}
|
||||
|
||||
wsrep::reporter::~reporter()
|
||||
{
|
||||
delete [] template_;
|
||||
}
|
||||
|
||||
wsrep::reporter::substates
|
||||
wsrep::reporter::substate_map(enum wsrep::server_state::state const state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case wsrep::server_state::s_disconnected:
|
||||
initialized_ = false;
|
||||
return s_disconnected_disconnected;
|
||||
case wsrep::server_state::s_initializing:
|
||||
if (s_disconnected_disconnected == state_)
|
||||
return s_disconnected_initializing;
|
||||
else if (s_joining_sst == state_)
|
||||
return s_joining_initializing;
|
||||
else
|
||||
{
|
||||
assert(0);
|
||||
return state_;
|
||||
}
|
||||
case wsrep::server_state::s_initialized:
|
||||
initialized_ = true;
|
||||
if (s_disconnected_initializing >= state_)
|
||||
return s_disconnected_initialized;
|
||||
else if (s_joining_initializing == state_)
|
||||
return s_joining_ist;
|
||||
else
|
||||
{
|
||||
assert(0);
|
||||
return state_;
|
||||
}
|
||||
case wsrep::server_state::s_connected:
|
||||
return s_connected_waiting;
|
||||
case wsrep::server_state::s_joiner:
|
||||
if (initialized_)
|
||||
return s_joining_initialized;
|
||||
else
|
||||
return s_joining_sst;
|
||||
case wsrep::server_state::s_joined:
|
||||
return s_joined_syncing;
|
||||
case wsrep::server_state::s_donor:
|
||||
return s_donor_sending;
|
||||
case wsrep::server_state::s_synced:
|
||||
return s_synced_running;
|
||||
case wsrep::server_state::s_disconnecting:
|
||||
return s_disconnecting_disconnecting;
|
||||
default:
|
||||
assert(0);
|
||||
return state_;
|
||||
}
|
||||
}
|
||||
|
||||
static float const SST_SHARE = 0.5f; // SST share of JOINING progress
|
||||
static float const INIT_SHARE = 0.1f; // initialization share of JOINING progress
|
||||
static float const IST_SHARE = (1.0f - SST_SHARE - INIT_SHARE); // IST share
|
||||
|
||||
float
|
||||
wsrep::reporter::progress_map(float const progress) const
|
||||
{
|
||||
assert(progress >= 0.0f);
|
||||
assert(progress <= 1.0f);
|
||||
|
||||
switch (state_)
|
||||
{
|
||||
case s_disconnected_disconnected:
|
||||
return indefinite;
|
||||
case s_disconnected_initializing:
|
||||
return indefinite;
|
||||
case s_disconnected_initialized:
|
||||
return indefinite;
|
||||
case s_connected_waiting:
|
||||
return indefinite;
|
||||
case s_joining_initialized:
|
||||
return progress;
|
||||
case s_joining_sst:
|
||||
return progress * SST_SHARE;
|
||||
case s_joining_initializing:
|
||||
return SST_SHARE + progress * INIT_SHARE;
|
||||
case s_joining_ist:
|
||||
return SST_SHARE + INIT_SHARE + progress * IST_SHARE;
|
||||
case s_joined_syncing:
|
||||
return progress;
|
||||
case s_synced_running:
|
||||
return 1.0;
|
||||
case s_donor_sending:
|
||||
return progress;
|
||||
case s_disconnecting_disconnecting:
|
||||
return indefinite;
|
||||
default:
|
||||
assert(0);
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wsrep::reporter::write_log_msg(std::ostream& os,
|
||||
const log_msg& msg)
|
||||
{
|
||||
os << "\t\t{\n";
|
||||
os << "\t\t\t\"timestamp\": " << std::showpoint << std::setprecision(18)
|
||||
<< msg.tstamp << ",\n";
|
||||
os << "\t\t\t\"msg\": \"" << msg.msg << "\"\n";
|
||||
os << "\t\t}";
|
||||
}
|
||||
|
||||
void
|
||||
wsrep::reporter::write_log_msgs(std::ostream& os,
|
||||
const std::string& label,
|
||||
const std::deque<log_msg>& msgs)
|
||||
{
|
||||
os << "\t\"" << label << "\": [\n";
|
||||
for (size_t i(0); i < msgs.size(); ++i)
|
||||
{
|
||||
write_log_msg(os, msgs[i]);
|
||||
os << (i+1 < msgs.size() ? ",\n" : "\n");
|
||||
}
|
||||
os << "\t],\n";
|
||||
}
|
||||
|
||||
// write data to temporary file and then rename it to target file for atomicity
|
||||
void
|
||||
wsrep::reporter::write_file(double const tstamp)
|
||||
{
|
||||
enum progress_type {
|
||||
t_indefinite = -1, // indefinite wait
|
||||
t_progressive, // measurable progress
|
||||
t_final // final state
|
||||
};
|
||||
|
||||
struct strings {
|
||||
const char* state;
|
||||
const char* comment;
|
||||
progress_type type;
|
||||
};
|
||||
|
||||
static const struct strings strings[substates_max] =
|
||||
{
|
||||
{ "DISCONNECTED", "Disconnected", t_indefinite },
|
||||
{ "DISCONNECTED", "Initializing", t_indefinite },
|
||||
{ "DISCONNECTED", "Connecting", t_indefinite },
|
||||
{ "CONNECTED", "Waiting", t_indefinite },
|
||||
{ "JOINING", "Receiving state", t_progressive },
|
||||
{ "JOINING", "Receiving SST", t_progressive },
|
||||
{ "JOINING", "Initializing", t_progressive },
|
||||
{ "JOINING", "Receiving IST", t_progressive },
|
||||
{ "JOINED", "Syncing", t_progressive },
|
||||
{ "SYNCED", "Operational", t_final },
|
||||
{ "DONOR", "Donating SST", t_progressive },
|
||||
{ "DISCONNECTING", "Disconnecting", t_indefinite }
|
||||
};
|
||||
|
||||
// prepare template for mkstemp()
|
||||
file_name_.copy(template_, file_name_.length());
|
||||
TEMP_EXTENSION.copy(template_ +file_name_.length(),TEMP_EXTENSION.length());
|
||||
|
||||
int const fd(mkstemp(template_));
|
||||
if (fd < 0)
|
||||
{
|
||||
std::cerr << "Reporter could not open temporary file `" << template_
|
||||
<< "': " << strerror(errno) << " (" << errno << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
double const seconds(floor(tstamp));
|
||||
time_t const tt = time_t(seconds);
|
||||
struct tm date;
|
||||
localtime_r(&tt, &date);
|
||||
|
||||
char date_str[85] = { '\0', };
|
||||
snprintf(date_str, sizeof(date_str) - 1,
|
||||
"%04d-%02d-%02d %02d:%02d:%02d.%03d",
|
||||
date.tm_year + 1900, date.tm_mon + 1, date.tm_mday,
|
||||
date.tm_hour, date.tm_min, date.tm_sec,
|
||||
(int)((tstamp-seconds)*1000));
|
||||
|
||||
std::ostringstream os;
|
||||
os << "{\n";
|
||||
os << "\t\"date\": \"" << date_str << "\",\n";
|
||||
os << "\t\"timestamp\": " << std::showpoint << std::setprecision(18)
|
||||
<< tstamp << ",\n";
|
||||
write_log_msgs(os, "errors", err_msg_);
|
||||
write_log_msgs(os, "warnings", warn_msg_);
|
||||
os << "\t\"status\": {\n";
|
||||
os << "\t\t\"state\": \"" << strings[state_].state << "\",\n";
|
||||
os << "\t\t\"comment\": \"" << strings[state_].comment << "\",\n";
|
||||
os << "\t\t\"progress\": " << std::showpoint << std::setprecision(6)
|
||||
<< progress_ << "\n";
|
||||
os << "\t}\n";
|
||||
os << "}\n";
|
||||
|
||||
std::string str(os.str());
|
||||
ssize_t err(write(fd, str.c_str(), str.length()));
|
||||
if (err < 0)
|
||||
{
|
||||
std::cerr << "Could not write " << str.length()
|
||||
<< " bytes to temporary file '"
|
||||
<< template_ << "': " << strerror(errno)
|
||||
<< " (" << errno << ")";
|
||||
return;
|
||||
}
|
||||
|
||||
rename(template_, file_name_.c_str());
|
||||
}
|
||||
|
||||
void
|
||||
wsrep::reporter::report_state(enum server_state::state const s, float const p)
|
||||
{
|
||||
assert(p >= -1);
|
||||
assert(p <= 1);
|
||||
|
||||
bool flush(false);
|
||||
|
||||
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
|
||||
|
||||
substates const state(substate_map(s));
|
||||
|
||||
if (state != state_)
|
||||
{
|
||||
state_ = state;
|
||||
flush = true;
|
||||
}
|
||||
|
||||
float const progress(progress_map(p));
|
||||
assert(progress >= -1);
|
||||
assert(progress <= 1);
|
||||
|
||||
if (progress != progress_)
|
||||
{
|
||||
progress_ = progress;
|
||||
flush = true;
|
||||
}
|
||||
|
||||
if (flush)
|
||||
{
|
||||
write_file(timestamp());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wsrep::reporter::report_log_msg(log_level const lvl,
|
||||
const std::string& msg,
|
||||
double tstamp)
|
||||
{
|
||||
std::deque<log_msg>& deque(lvl == error ? err_msg_ : warn_msg_);
|
||||
|
||||
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
|
||||
|
||||
if (deque.empty() || msg != deque.back().msg)
|
||||
{
|
||||
if (deque.size() == max_msg_) deque.pop_front();
|
||||
|
||||
if (tstamp <= undefined) tstamp = timestamp();
|
||||
|
||||
log_msg entry({tstamp, msg});
|
||||
deque.push_back(entry);
|
||||
write_file(tstamp);
|
||||
}
|
||||
}
|
@ -2,7 +2,8 @@
|
||||
# Copyright (C) 2018 Codership Oy <info@codership.com>
|
||||
#
|
||||
|
||||
add_executable(wsrep-lib_test
|
||||
|
||||
set(TEST_SOURCES
|
||||
mock_client_state.cpp
|
||||
mock_high_priority_service.cpp
|
||||
mock_storage_service.cpp
|
||||
@ -22,6 +23,14 @@ add_executable(wsrep-lib_test
|
||||
wsrep-lib_test.cpp
|
||||
)
|
||||
|
||||
if (WSREP_LIB_WITH_UNIT_TESTS_EXTRA)
|
||||
set(TEST_SOURCES ${TEST_SOURCES}
|
||||
reporter_test.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
add_executable(wsrep-lib_test ${TEST_SOURCES})
|
||||
|
||||
target_link_libraries(wsrep-lib_test wsrep-lib)
|
||||
|
||||
add_test(NAME wsrep-lib_test
|
||||
|
543
test/reporter_test.cpp
Normal file
543
test/reporter_test.cpp
Normal file
@ -0,0 +1,543 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Codership Oy <info@codership.com>
|
||||
*
|
||||
* This file is part of wsrep-lib.
|
||||
*
|
||||
* Wsrep-lib is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Wsrep-lib is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with wsrep-lib. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "wsrep/reporter.hpp"
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <unistd.h> // unlink() for cleanup
|
||||
|
||||
namespace json = boost::json;
|
||||
|
||||
//////// HELPERS ///////
|
||||
|
||||
static inline double
|
||||
timestamp()
|
||||
{
|
||||
struct timespec time;
|
||||
clock_gettime(CLOCK_REALTIME, &time);
|
||||
return (double(time.tv_sec) + double(time.tv_nsec)*1.0e-9);
|
||||
}
|
||||
|
||||
static json::value
|
||||
read_file(const char* const filename)
|
||||
{
|
||||
std::ifstream input(filename, std::ios::binary);
|
||||
std::vector<char> buffer(std::istreambuf_iterator<char>(input), {});
|
||||
json::stream_parser parser;
|
||||
json::error_code err;
|
||||
|
||||
parser.write(buffer.data(), buffer.size(), err);
|
||||
if (err)
|
||||
{
|
||||
assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
parser.finish(err);
|
||||
if (err)
|
||||
{
|
||||
assert(0);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return parser.release();
|
||||
}
|
||||
|
||||
struct logs
|
||||
{
|
||||
std::deque<double> tstamp_;
|
||||
std::deque<std::string> msg_;
|
||||
};
|
||||
|
||||
struct result
|
||||
{
|
||||
logs errors_;
|
||||
logs warnings_;
|
||||
struct
|
||||
{
|
||||
std::string state_;
|
||||
std::string comment_;
|
||||
float progress_;
|
||||
} status_;
|
||||
};
|
||||
|
||||
static void
|
||||
parse_result(const json::value& value, struct result& res,
|
||||
const std::string& path = "")
|
||||
{
|
||||
//std::cout << "Parsing " << path << ": " << value << ": " << value.kind() << std::endl;
|
||||
switch (value.kind())
|
||||
{
|
||||
case json::kind::object:
|
||||
{
|
||||
auto const obj(value.get_object());
|
||||
if (!obj.empty())
|
||||
{
|
||||
for (auto it = obj.begin(); it != obj.end(); ++it)
|
||||
{
|
||||
std::string const key(it->key().data(), it->key().length());
|
||||
parse_result(it->value(), res, path + "." + key);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case json::kind::array:
|
||||
{
|
||||
auto const arr(value.get_array());
|
||||
if (!arr.empty())
|
||||
{
|
||||
for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
{
|
||||
parse_result(*it, res, path + ".[]");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
case json::kind::string:
|
||||
{
|
||||
auto const val(value.get_string().c_str());
|
||||
if (path == ".errors.[].msg")
|
||||
{
|
||||
res.errors_.msg_.push_back(val);
|
||||
}
|
||||
else if (path == ".warnings.[].msg")
|
||||
{
|
||||
res.warnings_.msg_.push_back(val);
|
||||
}
|
||||
else if (path == ".status.state")
|
||||
{
|
||||
res.status_.state_ = val;
|
||||
}
|
||||
else if (path == ".status.comment")
|
||||
{
|
||||
res.status_.comment_ = val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case json::kind::uint64:
|
||||
return;
|
||||
case json::kind::int64:
|
||||
return;
|
||||
case json::kind::double_:
|
||||
if (path == ".status.progress")
|
||||
{
|
||||
res.status_.progress_ = float(value.get_double());
|
||||
}
|
||||
else if (path == ".errors.[].timestamp")
|
||||
{
|
||||
res.errors_.tstamp_.push_back(value.get_double());
|
||||
}
|
||||
else if (path == ".warnings.[].timestamp")
|
||||
{
|
||||
res.warnings_.tstamp_.push_back(value.get_double());
|
||||
}
|
||||
|
||||
return;
|
||||
case json::kind::bool_:
|
||||
return;
|
||||
case json::kind::null:
|
||||
return;
|
||||
}
|
||||
|
||||
assert(0);
|
||||
}
|
||||
|
||||
static bool
|
||||
equal(const std::string& left, const std::string& right)
|
||||
{
|
||||
return left == right;
|
||||
}
|
||||
|
||||
static bool
|
||||
equal(double const left, double const right)
|
||||
{
|
||||
return ::fabs(left - right) < 0.0001; // we are looking for ms precision
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static bool
|
||||
operator!=(const std::deque<T>& left, const std::deque<T>& right)
|
||||
{
|
||||
if (left.size() != right.size()) return true;
|
||||
|
||||
for (size_t i(0); i < left.size(); ++i)
|
||||
if (!equal(left[i], right[i])) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
operator!=(const logs& left, const logs& right)
|
||||
{
|
||||
if (left.tstamp_ != right.tstamp_) return true;
|
||||
return (left.msg_ != right.msg_);
|
||||
}
|
||||
|
||||
static bool
|
||||
operator==(const result& left, const result& right)
|
||||
{
|
||||
if (left.errors_ != right.errors_) return false;
|
||||
if (left.warnings_ != right.warnings_) return false;
|
||||
|
||||
if (left.status_.state_ != right.status_.state_) return false;
|
||||
if (left.status_.comment_ != right.status_.comment_) return false;
|
||||
if (left.status_.progress_ != right.status_.progress_) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void
|
||||
print_deque(std::ostream& os,const std::deque<T> left,const std::deque<T> right)
|
||||
{
|
||||
auto const max(std::max(left.size(), right.size()));
|
||||
for (size_t i(0); i < max; ++i)
|
||||
{
|
||||
os << "|\t'";
|
||||
|
||||
if (i < left.size())
|
||||
os << left[i] << "'";
|
||||
else
|
||||
os << "'\t";
|
||||
|
||||
if (i < right.size())
|
||||
os << "\t'" << right[i] << "'";
|
||||
else
|
||||
os << "\t''";
|
||||
|
||||
if (!equal(left[i], right[i])) os << "\t!!!";
|
||||
|
||||
os << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_logs(std::ostream& os, const logs& left, const logs& right)
|
||||
{
|
||||
os << "|\t" << left.msg_.size() << "\t" << right.msg_.size() << "\n";
|
||||
print_deque(os, left.tstamp_, right.tstamp_);
|
||||
print_deque(os, left.msg_, right.msg_);
|
||||
}
|
||||
|
||||
// print two results against each other
|
||||
static std::string
|
||||
print(const result& left, const result& right, size_t it)
|
||||
{
|
||||
std::ostringstream os;
|
||||
|
||||
os << std::showpoint << std::setprecision(18);
|
||||
|
||||
os << "Iteration " << it << "\nerrors:\n";
|
||||
print_logs(os, left.errors_, right.errors_);
|
||||
os << "warnings:\n";
|
||||
print_logs(os, left.warnings_, right.warnings_);
|
||||
os << "state:\n";
|
||||
os << "\t" << left.status_.state_ << "\t" << right.status_.state_
|
||||
<< "\n";
|
||||
os << "\t" << left.status_.comment_ << "\t" << right.status_.comment_
|
||||
<< "\n";
|
||||
os << "\t" << left.status_.progress_ << "\t" << right.status_.progress_
|
||||
<< "\n";
|
||||
|
||||
return os.str();
|
||||
}
|
||||
|
||||
#define VERIFY_RESULT(left, right, it) \
|
||||
BOOST_CHECK_MESSAGE(left == right, print(left, right, it));
|
||||
|
||||
static const char*
|
||||
const REPORT = "report.json";
|
||||
|
||||
static auto
|
||||
const indefinite(wsrep::reporter::indefinite);
|
||||
|
||||
static struct logs
|
||||
const LOGS_INIT = { std::deque<double>(), std::deque<std::string>() };
|
||||
static struct result
|
||||
const RES_INIT = { LOGS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTED", "Disconnected", indefinite } };
|
||||
|
||||
static void
|
||||
test_log(const char* const fname,
|
||||
wsrep::reporter& rep,
|
||||
result& check,
|
||||
wsrep::reporter::log_level const lvl,
|
||||
double const tstamp,
|
||||
const std::string& msg,
|
||||
size_t const iteration)
|
||||
{
|
||||
// this is implementaiton detail, if it changes in the code, it needs
|
||||
// to be changed here
|
||||
size_t const MAX_ERROR(4);
|
||||
|
||||
logs& log(lvl == wsrep::reporter::error ? check.errors_ : check.warnings_);
|
||||
log.tstamp_.push_back(tstamp);
|
||||
if (log.tstamp_.size() > MAX_ERROR) log.tstamp_.pop_front();
|
||||
log.msg_.push_back(msg);
|
||||
if (log.msg_.size() > MAX_ERROR) log.msg_.pop_front();
|
||||
|
||||
rep.report_log_msg(lvl, msg, tstamp);
|
||||
|
||||
auto value = read_file(fname);
|
||||
auto res = RES_INIT;
|
||||
parse_result(value, res);
|
||||
VERIFY_RESULT(res, check, iteration);
|
||||
}
|
||||
|
||||
static size_t const MAX_MSG = 4;
|
||||
|
||||
BOOST_AUTO_TEST_CASE(log_msg_test)
|
||||
{
|
||||
wsrep::default_mutex m;
|
||||
wsrep::reporter rep(m, REPORT, MAX_MSG);
|
||||
|
||||
auto value = read_file(REPORT);
|
||||
BOOST_REQUIRE(value != nullptr);
|
||||
|
||||
struct result res(RES_INIT), check(RES_INIT);
|
||||
parse_result(value, res);
|
||||
VERIFY_RESULT(res, check, -1);
|
||||
|
||||
struct entry
|
||||
{
|
||||
double tstamp_;
|
||||
std::string msg_;
|
||||
};
|
||||
std::vector<entry> msgs =
|
||||
{
|
||||
{ 0.1, "a" },
|
||||
{ 0.2, "bb" },
|
||||
{ 0.3, "ccc" },
|
||||
{ 0.4, "dddd" },
|
||||
{ 0.5, "eeeee" },
|
||||
{ 0.6, "ffffff" }
|
||||
};
|
||||
for (size_t i(0); i < msgs.size(); ++i)
|
||||
{
|
||||
test_log(REPORT, rep, check,
|
||||
wsrep::reporter::error, msgs[i].tstamp_, msgs[i].msg_, i);
|
||||
test_log(REPORT, rep, check,
|
||||
wsrep::reporter::warning, msgs[i].tstamp_, msgs[i].msg_, i);
|
||||
}
|
||||
|
||||
// test indefinite timestmap
|
||||
std::string const msg("err");
|
||||
rep.report_log_msg(wsrep::reporter::error, msg);
|
||||
value = read_file(REPORT);
|
||||
res = RES_INIT;
|
||||
parse_result(value, res);
|
||||
BOOST_REQUIRE(res.errors_.tstamp_.back() > 0);
|
||||
BOOST_REQUIRE(res.errors_.msg_.back() == msg);
|
||||
|
||||
::unlink(REPORT);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(state_test)
|
||||
{
|
||||
using wsrep::server_state;
|
||||
|
||||
wsrep::default_mutex m;
|
||||
wsrep::reporter rep(m, REPORT, MAX_MSG);
|
||||
double const err_tstamp(timestamp());
|
||||
std::string const err_msg("Error!");
|
||||
|
||||
struct test
|
||||
{
|
||||
struct
|
||||
{
|
||||
enum wsrep::server_state::state state;
|
||||
float progress;
|
||||
} input;
|
||||
struct result output;
|
||||
};
|
||||
|
||||
logs const ERRS_INIT = { {err_tstamp}, {err_msg} };
|
||||
|
||||
std::vector<test> tests =
|
||||
{
|
||||
{{ server_state::s_disconnected, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTED", "Disconnected", indefinite }}},
|
||||
{{ server_state::s_initializing, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTED", "Initializing", indefinite }}},
|
||||
{{ server_state::s_initialized, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTED", "Connecting", indefinite }}},
|
||||
{{ server_state::s_connected, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "CONNECTED", "Waiting", indefinite }}},
|
||||
{{ server_state::s_joiner, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINING", "Receiving state", 0.0f }}},
|
||||
{{ server_state::s_disconnecting, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTING", "Disconnecting", indefinite }}},
|
||||
{{ server_state::s_disconnected, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTED", "Disconnected", indefinite }}},
|
||||
{{ server_state::s_connected, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "CONNECTED", "Waiting", indefinite }}},
|
||||
{{ server_state::s_joiner, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINING", "Receiving SST", 0.0f }}},
|
||||
{{ server_state::s_initializing, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINING", "Initializing", 0.5f }}},
|
||||
{{ server_state::s_initialized, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINING", "Receiving IST", 0.6f }}},
|
||||
{{ server_state::s_joined, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINED", "Syncing", 0.0f }}},
|
||||
{{ server_state::s_synced, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "SYNCED", "Operational", 1.0f }}},
|
||||
{{ server_state::s_donor, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DONOR", "Donating SST", 0.0f }}},
|
||||
{{ server_state::s_joined, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "JOINED", "Syncing", 0.0f }}},
|
||||
{{ server_state::s_synced, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "SYNCED", "Operational", 1.0f }}},
|
||||
{{ server_state::s_disconnecting, 0 },
|
||||
{ ERRS_INIT, LOGS_INIT,
|
||||
{ "DISCONNECTING", "Disconnecting", indefinite }}},
|
||||
};
|
||||
|
||||
rep.report_log_msg(wsrep::reporter::error, err_msg, err_tstamp);
|
||||
|
||||
for (auto i(tests.begin()); i != tests.end(); ++i)
|
||||
{
|
||||
rep.report_state(i->input.state, i->input.progress);
|
||||
auto value = read_file(REPORT);
|
||||
result res(RES_INIT);
|
||||
parse_result(value, res);
|
||||
auto check(i->output);
|
||||
VERIFY_RESULT(res, check, i - tests.begin());
|
||||
}
|
||||
|
||||
::unlink(REPORT);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(progress_test)
|
||||
{
|
||||
using wsrep::server_state;
|
||||
|
||||
wsrep::default_mutex m;
|
||||
wsrep::reporter rep(m, REPORT, MAX_MSG);
|
||||
double const warn_tstamp(timestamp());
|
||||
std::string const warn_msg("Warn!");
|
||||
|
||||
struct test
|
||||
{
|
||||
struct
|
||||
{
|
||||
enum wsrep::server_state::state state;
|
||||
float progress;
|
||||
} input;
|
||||
struct result output;
|
||||
};
|
||||
|
||||
logs const WARN_INIT = { {warn_tstamp}, {warn_msg} };
|
||||
|
||||
std::vector<test> tests =
|
||||
{
|
||||
{{ server_state::s_initialized, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "DISCONNECTED", "Connecting", indefinite }}},
|
||||
{{ server_state::s_connected, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "CONNECTED", "Waiting", indefinite }}},
|
||||
{{ server_state::s_joiner, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving state", 0.0f }}},
|
||||
{{ server_state::s_joiner, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving state", 0.5f }}},
|
||||
{{ server_state::s_disconnected, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "DISCONNECTED", "Disconnected", indefinite }}},
|
||||
{{ server_state::s_connected, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "CONNECTED", "Waiting", indefinite }}},
|
||||
{{ server_state::s_joiner, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving SST", 0.0f }}},
|
||||
{{ server_state::s_joiner, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving SST", 0.25f }}},
|
||||
{{ server_state::s_initializing, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Initializing", 0.5f }}},
|
||||
{{ server_state::s_initializing, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Initializing", 0.55f }}},
|
||||
{{ server_state::s_initialized, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving IST", 0.6f }}},
|
||||
{{ server_state::s_initialized, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINING", "Receiving IST", 0.8f }}},
|
||||
{{ server_state::s_joined, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINED", "Syncing", 0.0f }}},
|
||||
{{ server_state::s_joined, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINED", "Syncing", 0.5f }}},
|
||||
{{ server_state::s_synced, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "SYNCED", "Operational", 1.0f }}},
|
||||
{{ server_state::s_donor, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "DONOR", "Donating SST", 0.0f }}},
|
||||
{{ server_state::s_donor, 0.5 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "DONOR", "Donating SST", 0.5f }}},
|
||||
{{ server_state::s_joined, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "JOINED", "Syncing", 0.0f }}},
|
||||
{{ server_state::s_synced, 0 },
|
||||
{ LOGS_INIT, WARN_INIT,
|
||||
{ "SYNCED", "Operational", 1.0f }}},
|
||||
};
|
||||
|
||||
rep.report_log_msg(wsrep::reporter::warning, warn_msg, warn_tstamp);
|
||||
|
||||
for (auto i(tests.begin()); i != tests.end(); ++i)
|
||||
{
|
||||
rep.report_state(i->input.state, i->input.progress);
|
||||
auto value = read_file(REPORT);
|
||||
result res(RES_INIT);
|
||||
parse_result(value, res);
|
||||
auto check(i->output);
|
||||
VERIFY_RESULT(res, check, i - tests.begin());
|
||||
}
|
||||
|
||||
::unlink(REPORT);
|
||||
}
|
Reference in New Issue
Block a user