From db3fb7e9fe3320cd32c6ca98045e92251c39f0c1 Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 13:26:28 -0600 Subject: [PATCH 1/6] migrate keydb_modstatsd to keydb-internal --- src/modules/keydb_modstatsd/Makefile | 13 + .../cpp-statsd-client/.clang-format | 105 ++ .../.github/workflows/coverage.yml | 30 + .../.github/workflows/lint.yml | 13 + .../.github/workflows/linux.yml | 23 + .../.github/workflows/windows.yml | 18 + .../cpp-statsd-client/.gitignore | 1 + .../cpp-statsd-client/CMakeLists.txt | 85 ++ .../cpp-statsd-client/LICENSE.md | 9 + .../cpp-statsd-client/Makefile | 22 + .../cpp-statsd-client/README.md | 150 +++ .../cmake/CodeCoverage.cmake | 708 ++++++++++ .../cpp-statsd-client/cmake/Config.cmake.in | 18 + .../cmake/cpp-statsd-clientConfig.cmake.in | 4 + .../cpp-statsd-client/images/logo.svg | 16 + .../cpp-statsd-client/StatsdClient.hpp | 318 +++++ .../include/cpp-statsd-client/UDPSender.hpp | 345 +++++ .../cpp-statsd-client/tests/StatsdServer.hpp | 80 ++ .../tests/testStatsdClient.cpp | 184 +++ src/modules/keydb_modstatsd/modmain.cpp | 666 ++++++++++ src/modules/keydb_modstatsd/redismodule.h | 1146 +++++++++++++++++ 21 files changed, 3954 insertions(+) create mode 100644 src/modules/keydb_modstatsd/Makefile create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/Makefile create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/README.md create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp create mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp create mode 100644 src/modules/keydb_modstatsd/modmain.cpp create mode 100644 src/modules/keydb_modstatsd/redismodule.h diff --git a/src/modules/keydb_modstatsd/Makefile b/src/modules/keydb_modstatsd/Makefile new file mode 100644 index 000000000..aec4744f2 --- /dev/null +++ b/src/modules/keydb_modstatsd/Makefile @@ -0,0 +1,13 @@ +MODULE_FLAGS := -fPIC -O2 -Wall -Werror + +OBJECT_FILES := modmain.o +MODSNAP_CXX_FLAGS := -std=gnu++14 + +%.o: %.cpp + $(CXX) -o $@ -c $< $(MODULE_FLAGS) -Icpp-statsd-client/include $(MODSNAP_CXX_FLAGS) + +modstatsd.so: $(OBJECT_FILES) + $(CXX) -shared $(OBJECT_FILES) -o modstatsd.so + +clean: + rm -f $(OBJECT_FILES) modstatsd.so diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format b/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format new file mode 100644 index 000000000..2d8fd6e65 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format @@ -0,0 +1,105 @@ +AccessModifierOffset: -4 +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: Empty +BinPackArguments: false +BinPackParameters: false +ColumnLimit: 120 +IndentCaseLabels: false +IndentWidth: 4 + +--- +Language: Cpp +# BasedOnStyle: Google +#AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +#AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +#AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +#BinPackArguments: true +#BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +#ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +#IndentCaseLabels: true +#IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... + diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml new file mode 100644 index 000000000..4642670ee --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml @@ -0,0 +1,30 @@ +name: Coverage + +on: [push, pull_request] +jobs: + coverage: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y -qq make cmake gcc g++ lcov bc + - name: build + shell: bash + run: | + export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g") + cmake . -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=On + make all -j$(nproc) + - name: coverage + shell: bash + run: | + make coverage + lines=$(lcov --summary coverage.info | grep -F lines | awk '{print $2}' | sed -e "s/%//g") + if (( $(echo "${lines} < ${COVERAGE_THRESHOLD}" | bc -l) )); then + echo "Line coverage dropped below ${COVERAGE_THRESHOLD}% to ${lines}%" + exit 1 + fi + env: + COVERAGE_THRESHOLD: 85.0 diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml new file mode 100644 index 000000000..1ed921645 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml @@ -0,0 +1,13 @@ +name: Lint + +on: [push, pull_request] +jobs: + lint: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: lint + uses: DoozyX/clang-format-lint-action@v0.12 + with: + clangFormatVersion: 12 + source: './include/cpp-statsd-client ./tests' diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml new file mode 100644 index 000000000..a431e1408 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml @@ -0,0 +1,23 @@ +name: Linux + +on: [push, pull_request] +jobs: + linux: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y -qq make cmake gcc g++ + - name: build + shell: bash + run: | + export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g") + cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SANITIZERS=On + make all -j$(nproc) + - name: test + shell: bash + run: | + make test diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml new file mode 100644 index 000000000..3b864009c --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml @@ -0,0 +1,18 @@ +name: Windows + +on: [push, pull_request] +jobs: + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: dependencies + run: | + choco install cmake + - name: build + run: | + cmake -S . -B build -G "Visual Studio 16 2019" -A x64 + cmake --build build --target ALL_BUILD --config Release + - name: test + run: | + cmake --build build --target RUN_TESTS --config Release diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore b/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore new file mode 100644 index 000000000..c5e82d745 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt b/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt new file mode 100644 index 000000000..9b8d64cfa --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt @@ -0,0 +1,85 @@ +# Basic project setup +cmake_minimum_required(VERSION 3.5) +project(cpp-statsd-client + VERSION 1.0.2 + LANGUAGES CXX + DESCRIPTION "A header-only StatsD client implemented in C++" + HOMEPAGE_URL "https://github.com/vthiery/cpp-statsd-client") + +option(CPP_STATSD_STANDALONE "Allows configuration of targets for verifying library functionality" ON) +option(ENABLE_TESTS "Build tests" ON) +option(ENABLE_COVERAGE "Build with coverage instrumentalisation" OFF) + +if(NOT CPP_STATSD_STANDALONE) + set(ENABLE_TESTS OFF) + set(ENABLE_COVERAGE OFF) +endif() + +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +find_package(Threads) + +# Optional code coverage targets +if(ENABLE_COVERAGE) + set(COVERAGE_EXCLUDES /usr/*) + include(${PROJECT_SOURCE_DIR}/cmake/CodeCoverage.cmake) + APPEND_COVERAGE_COMPILER_FLAGS() + SETUP_TARGET_FOR_COVERAGE_LCOV(NAME coverage + EXECUTABLE testStatsdClient + DEPENDENCIES ${PROJECT_NAME} + ) +endif() + +# The library target +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories( + ${PROJECT_NAME} + INTERFACE $ + $) +target_link_libraries(${PROJECT_NAME} INTERFACE Threads::Threads) +if(WIN32) + target_link_libraries(${PROJECT_NAME} INTERFACE ws2_32) +endif() + +# The installation and pkg-config-like cmake config +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}_Targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION + ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) +install(EXPORT ${PROJECT_NAME}_Targets + FILE ${PROJECT_NAME}Targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) +install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" + "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION include) + +if(ENABLE_TESTS) + # The test targets + add_executable(testStatsdClient ${CMAKE_CURRENT_SOURCE_DIR}/tests/testStatsdClient.cpp) + if(WIN32) + target_compile_options(testStatsdClient PRIVATE -W4 -WX /external:W0) + else() + target_compile_options(testStatsdClient PRIVATE -Wall -Wextra -pedantic -Werror) + endif() + target_include_directories(testStatsdClient PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) + target_link_libraries(testStatsdClient ${PROJECT_NAME}) + + set_property(TARGET testStatsdClient PROPERTY CXX_STANDARD 11) + set_property(TARGET testStatsdClient PROPERTY CXX_EXTENSIONS OFF) + + # The test suite + enable_testing() + add_test(ctestTestStatsdClient testStatsdClient) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS testStatsdClient) +endif() diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md b/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md new file mode 100644 index 000000000..632d564b7 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2017 Vincent Thiery + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile b/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile new file mode 100644 index 000000000..071e8bd91 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile @@ -0,0 +1,22 @@ +# simple makefile to build, test and clean + +BUILD_MODE ?= Release +ENABLE_COVERAGE ?= On + +build: clean + @echo "Build in ${BUILD_MODE} mode" + mkdir -p bin/${BUILD_MODE} + @cd bin/${BUILD_MODE}; cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_MODE} -DENABLE_COVERAGE=${ENABLE_COVERAGE} + @cd bin/${BUILD_MODE}; make + +test: build + @cd bin/${BUILD_MODE}; make test + +coverage: build + @cd bin/${BUILD_MODE}; make coverage + +install: build + @cd bin/${BUILD_MODE}; make install + +clean: + @rm -rf bin diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/README.md b/src/modules/keydb_modstatsd/cpp-statsd-client/README.md new file mode 100644 index 000000000..9e8d39b55 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/README.md @@ -0,0 +1,150 @@ +# C++ StatsD Client + +![logo](https://raw.githubusercontent.com/vthiery/cpp-statsd-client/master/images/logo.svg?sanitize=true) + +[![Release](https://img.shields.io/github/release/vthiery/cpp-statsd-client.svg?style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/releases/latest) +![License](https://img.shields.io/github/license/vthiery/cpp-statsd-client?style=for-the-badge) +[![Linux status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Linux?label=Linux&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/linux.yml?query=branch%3Amaster++) +[![Windows status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Windows?label=Windows&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/windows.yml?query=branch%3Amaster++) + +A header-only StatsD client implemented in C++. +The client allows: + +- batching, +- change of configuration at runtime, +- user-defined frequency rate. + +## Install and Test + +### Makefile + +In order to install the header files and/or run the tests, simply use the Makefile and execute + +```sh +make install +``` + +and + +```sh +make test +``` + +### Conan + +If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add statsdclient/x.y.z@vthiery/stable to your conanfile.py's requires, where x.y.z is the release version you want to use. Please file issues here if you experience problems with the packages. You can also directly download the latest version [here](https://bintray.com/vthiery/conan-packages/statsdclient%3Avthiery/_latestVersion). + +## Usage + +### Example + +A simple example of how to use the client: + +```cpp +#include "StatsdClient.hpp" +using namespace Statsd; + +int main() { + // Define the client on localhost, with port 8080, + // using a prefix, + // a batching size of 20 bytes, + // and three points of precision for floating point gauge values + StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 }; + + // Increment the metric "coco" + client.increment("coco"); + + // Decrement the metric "kiki" + client.decrement("kiki"); + + // Adjusts "toto" by +3 + client.count("toto", 2, 0.1f); + + // Record a gauge "titi" to 3 + client.gauge("titi", 3); + + // Record a timing of 2ms for "myTiming" + client.timing("myTiming", 2, 0.1f); + + // Send a metric explicitly + client.send("tutu", 4, "c", 2.0f); + exit(0); +} +``` + +### Advanced Testing + +A simple mock StatsD server can be found at `tests/StatsdServer.hpp`. This can be used to do simple validation of your application's metrics, typically in the form of unit tests. In fact this is the primary means by which this library is tested. The mock server itself is not distributed with the library so to use it you'd need to vendor this project into your project. Once you have though, you can test your application's use of the client like so: + +```cpp +#include "StatsdClient.hpp" +#include "StatsdServer.hpp" + +#include + +using namespace Statsd; + +struct MyApp { + void doWork() const { + m_client.count("bar", 3); + } +private: + StatsdClient m_client{"localhost", 8125, "foo"}; +}; + +int main() { + StatsdServer mockServer; + + MyApp app; + app.doWork(); + + assert(mockServer.receive() == "foo.bar:3|c"); + exit(0); +} +``` + +### Configuration + +The configuration of the client must be input when one instantiates it. Nevertheless, the API allows the configuration ot change afterwards. For example, one can do the following: + +```cpp +#include "StatsdClient.hpp" +using namespace Statsd; + +int main() +{ + // Define the client on localhost, with port 8080, + // using a prefix, + // a batching size of 20 bytes, + // and three points of precision for floating point gauge values + StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 }; + + client.increment("coco"); + + // Set a new configuration, using a different port, a different prefix, and more gauge precision + client.setConfig("127.0.0.1", 8000, "anotherPrefix", 6); + + client.decrement("kiki"); +} +``` + +The batchsize is the only parameter that cannot be changed for the time being. + +### Batching + +The client supports batching of the metrics. The batch size parameter is the number of bytes to allow in each batch (UDP datagram payload) to be sent to the statsd process. This number is not a hard limit. If appending the current stat to the current batch (separated by the `'\n'` character) pushes the current batch over the batch size, that batch is enqueued (not sent) and a new batch is started. If batch size is 0, the default, then each stat is sent individually to the statsd process and no batches are enqueued. + +### Sending + +As previously mentioned, if batching is disabled (by setting the batch size to 0) then every stat is sent immediately in a blocking fashion. If batching is enabled (ie non-zero) then you may also set the send interval. The send interval controls the time, in milliseconds, to wait before flushing/sending the queued stats batches to the statsd process. When the send interval is non-zero a background thread is spawned which will do the flushing/sending at the configured send interval, in other words asynchronously. The queuing mechanism in this case is *not* lock-free. If batching is enabled but the send interval is set to zero then the queued batchs of stats will not be sent automatically by a background thread but must be sent manually via the `flush` method. The `flush` method is a blocking call. + + +### Frequency rate + +When sending a metric, a frequency rate can be set in order to limit the metrics' sampling. By default, the frequency rate is set to one and won't affect the sampling. If set to a value `epsilon` (0.0001 for the time being) close to one, the sampling is not affected either. + +If the frequency rate is set and `epsilon` different from one, the sending will be rejected randomly (the higher the frequency rate, the lower the probability of rejection). + +## License + +This library is under MIT license. diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake new file mode 100644 index 000000000..39da5786b --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake @@ -0,0 +1,708 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags: +# append_coverage_compiler_flags() +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +list(GET LANGUAGES 0 LANG) + +if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() +elseif(NOT CMAKE_COMPILER_IS_GNUCXX) + if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") + # Do nothing; exit conditional without error if true + elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") + # Do nothing; exit conditional without error if true + else() + message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + endif() +endif() + +set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" + CACHE INTERNAL "") +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) + if(HAVE_fprofile_abs_path) + set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS} + --object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}.xml + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + -o ${Coverage_NAME}/index.html + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in new file mode 100644 index 000000000..25a29025f --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in @@ -0,0 +1,18 @@ +# Copyright (c) 2016, Ruslan Baratov +# +# Licensed under the MIT License (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://opensource.org/licenses/MIT +# +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. + +@PACKAGE_INIT@ + +find_package(Threads REQUIRED) + +include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in new file mode 100644 index 000000000..9c15f36a2 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg b/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg new file mode 100644 index 000000000..5f53c40da --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg @@ -0,0 +1,16 @@ + + + + Artboard Copy + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp new file mode 100644 index 000000000..ae08cb190 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp @@ -0,0 +1,318 @@ +#ifndef STATSD_CLIENT_HPP +#define STATSD_CLIENT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Statsd { + +/*! + * + * Statsd client + * + * This is the Statsd client, exposing the classic methods + * and relying on a UDP sender for the actual sending. + * + * The prefix for a stat is provided once at construction or + * on reconfiguring the client. The separator character '.' + * is automatically inserted between the prefix and the stats + * key, therefore you should neither append one to the prefix + * nor prepend one to the key + * + * The sampling frequency is specified per call and uses a + * random number generator to determine whether or not the stat + * will be recorded this time or not. + * + * The top level configuration includes 2 optional parameters + * that determine how the stats are delivered to statsd. These + * parameters are the batching size and the send interval. + * + * The batching size controls the number of bytes to send + * in each UDP datagram to statsd. This is not a hard limit as + * we continue appending to a batch of stats until the limit + * has been reached or surpassed. When this occurs we add the + * batch to a queue and create a new batch to appended to. A + * value of 0 for the batching size will disable batching such + * that each stat will be sent to the daemon individually. + * + * The send interval controls the rate at which queued batches + * of stats will be sent to statsd. If batching is disabled, + * this value is ignored and each individual stat is sent to + * statsd immediately in a blocking fashion. If batching is + * enabled (ie. non-zero) then the send interval is the number + * of milliseconds to wait before flushing the queue of + * batched stats messages to the daemon. This is done in a non- + * blocking fashion via a background thread. If the send + * interval is 0 then the stats messages are appended to a + * queue until the caller manually flushes the queue via the + * flush method. + * + */ +class StatsdClient { +public: + //!@name Constructor and destructor, non-copyable + //!@{ + + //! Constructor + StatsdClient(const std::string& host, + const uint16_t port, + const std::string& prefix, + const uint64_t batchsize = 0, + const uint64_t sendInterval = 1000, + const unsigned int gaugePrecision = 4) noexcept; + + StatsdClient(const StatsdClient&) = delete; + StatsdClient& operator=(const StatsdClient&) = delete; + + //!@} + + //!@name Methods + //!@{ + + //! Sets a configuration { host, port, prefix, batchsize } + void setConfig(const std::string& host, + const uint16_t port, + const std::string& prefix, + const uint64_t batchsize = 0, + const uint64_t sendInterval = 1000, + const unsigned int gaugePrecision = 4) noexcept; + + //! Returns the error message as an std::string + const std::string& errorMessage() const noexcept; + + //! Increments the key, at a given frequency rate + void increment(const std::string& key, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Increments the key, at a given frequency rate + void decrement(const std::string& key, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Adjusts the specified key by a given delta, at a given frequency rate + void count(const std::string& key, + const int delta, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Records a gauge for the key, with a given value, at a given frequency rate + template + void gauge(const std::string& key, + const T value, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Records a timing for a key, at a given frequency + void timing(const std::string& key, + const unsigned int ms, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Records a count of unique occurrences for a key, at a given frequency + void set(const std::string& key, + const unsigned int sum, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; + + //! Seed the RNG that controls sampling + void seed(unsigned int seed = std::random_device()()) noexcept; + + //! Flush any queued stats to the daemon + void flush() noexcept; + + //!@} + +private: + // @name Private methods + // @{ + + //! Send a value for a key, according to its type, at a given frequency + template + void send(const std::string& key, + const T value, + const char* type, + float frequency, + const std::vector& tags) const noexcept; + + //!@} + +private: + //! The prefix to be used for metrics + std::string m_prefix; + + //! The UDP sender to be used for actual sending + std::unique_ptr m_sender; + + //! The random number generator for handling sampling + mutable std::mt19937 m_randomEngine; + + //! The buffer string format our stats before sending them + mutable std::string m_buffer; + + //! Fixed floating point precision of gauges + unsigned int m_gaugePrecision; +}; + +namespace detail { +inline std::string sanitizePrefix(std::string prefix) { + // For convenience we provide the dot when generating the stat message + if (!prefix.empty() && prefix.back() == '.') { + prefix.pop_back(); + } + return prefix; +} + +// All supported metric types +constexpr char METRIC_TYPE_COUNT[] = "c"; +constexpr char METRIC_TYPE_GAUGE[] = "g"; +constexpr char METRIC_TYPE_TIMING[] = "ms"; +constexpr char METRIC_TYPE_SET[] = "s"; +} // namespace detail + +inline StatsdClient::StatsdClient(const std::string& host, + const uint16_t port, + const std::string& prefix, + const uint64_t batchsize, + const uint64_t sendInterval, + const unsigned int gaugePrecision) noexcept + : m_prefix(detail::sanitizePrefix(prefix)), + m_sender(new UDPSender{host, port, batchsize, sendInterval}), + m_gaugePrecision(gaugePrecision) { + // Initialize the random generator to be used for sampling + seed(); + // Avoid re-allocations by reserving a generous buffer + m_buffer.reserve(256); +} + +inline void StatsdClient::setConfig(const std::string& host, + const uint16_t port, + const std::string& prefix, + const uint64_t batchsize, + const uint64_t sendInterval, + const unsigned int gaugePrecision) noexcept { + m_prefix = detail::sanitizePrefix(prefix); + m_sender.reset(new UDPSender(host, port, batchsize, sendInterval)); + m_gaugePrecision = gaugePrecision; +} + +inline const std::string& StatsdClient::errorMessage() const noexcept { + return m_sender->errorMessage(); +} + +inline void StatsdClient::decrement(const std::string& key, + float frequency, + const std::vector& tags) const noexcept { + count(key, -1, frequency, tags); +} + +inline void StatsdClient::increment(const std::string& key, + float frequency, + const std::vector& tags) const noexcept { + count(key, 1, frequency, tags); +} + +inline void StatsdClient::count(const std::string& key, + const int delta, + float frequency, + const std::vector& tags) const noexcept { + send(key, delta, detail::METRIC_TYPE_COUNT, frequency, tags); +} + +template +inline void StatsdClient::gauge(const std::string& key, + const T value, + const float frequency, + const std::vector& tags) const noexcept { + send(key, value, detail::METRIC_TYPE_GAUGE, frequency, tags); +} + +inline void StatsdClient::timing(const std::string& key, + const unsigned int ms, + float frequency, + const std::vector& tags) const noexcept { + send(key, ms, detail::METRIC_TYPE_TIMING, frequency, tags); +} + +inline void StatsdClient::set(const std::string& key, + const unsigned int sum, + float frequency, + const std::vector& tags) const noexcept { + send(key, sum, detail::METRIC_TYPE_SET, frequency, tags); +} + +template +inline void StatsdClient::send(const std::string& key, + const T value, + const char* type, + float frequency, + const std::vector& tags) const noexcept { + // Bail if we can't send anything anyway + if (!m_sender->initialized()) { + return; + } + + // A valid frequency is: 0 <= f <= 1 + // At 0 you never emit the stat, at 1 you always emit the stat and with anything else you roll the dice + frequency = std::max(std::min(frequency, 1.f), 0.f); + constexpr float epsilon{0.0001f}; + const bool isFrequencyOne = std::fabs(frequency - 1.0f) < epsilon; + const bool isFrequencyZero = std::fabs(frequency) < epsilon; + if (isFrequencyZero || + (!isFrequencyOne && (frequency < std::uniform_real_distribution(0.f, 1.f)(m_randomEngine)))) { + return; + } + + // Format the stat message + std::stringstream valueStream; + valueStream << std::fixed << std::setprecision(m_gaugePrecision) << value; + + m_buffer.clear(); + + m_buffer.append(m_prefix); + if (!m_prefix.empty() && !key.empty()) { + m_buffer.push_back('.'); + } + + m_buffer.append(key); + m_buffer.push_back(':'); + m_buffer.append(valueStream.str()); + m_buffer.push_back('|'); + m_buffer.append(type); + + if (frequency < 1.f) { + m_buffer.append("|@0."); + m_buffer.append(std::to_string(static_cast(frequency * 100))); + } + + if (!tags.empty()) { + m_buffer.append("|#"); + for (const auto& tag : tags) { + m_buffer.append(tag); + m_buffer.push_back(','); + } + m_buffer.pop_back(); + } + + // Send the message via the UDP sender + m_sender->send(m_buffer); +} + +inline void StatsdClient::seed(unsigned int seed) noexcept { + m_randomEngine.seed(seed); +} + +inline void StatsdClient::flush() noexcept { + m_sender->flush(); +} + +} // namespace Statsd + +#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp new file mode 100644 index 000000000..c7d667a5f --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp @@ -0,0 +1,345 @@ +#ifndef UDP_SENDER_HPP +#define UDP_SENDER_HPP + +#ifdef _WIN32 +#define NOMINMAX +#include +#include +#include +#else +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Statsd { + +#ifdef _WIN32 +using SOCKET_TYPE = SOCKET; +constexpr SOCKET_TYPE k_invalidSocket{INVALID_SOCKET}; +#define SOCKET_ERRNO WSAGetLastError() +#define SOCKET_CLOSE closesocket +#else +using SOCKET_TYPE = int; +constexpr SOCKET_TYPE k_invalidSocket{-1}; +#define SOCKET_ERRNO errno +#define SOCKET_CLOSE close +#endif + +/*! + * + * UDP sender + * + * A simple UDP sender handling batching. + * + */ +class UDPSender final { +public: + //!@name Constructor and destructor, non-copyable + //!@{ + + //! Constructor + UDPSender(const std::string& host, + const uint16_t port, + const uint64_t batchsize, + const uint64_t sendInterval) noexcept; + + //! Destructor + ~UDPSender(); + + UDPSender(const UDPSender&) = delete; + UDPSender& operator=(const UDPSender&) = delete; + UDPSender(UDPSender&&) = delete; + + //!@} + + //!@name Methods + //!@{ + + //! Send or enqueue a message + void send(const std::string& message) noexcept; + + //! Returns the error message as a string + const std::string& errorMessage() const noexcept; + + //! Returns true if the sender is initialized + bool initialized() const noexcept; + + //! Flushes any queued messages + void flush() noexcept; + + //!@} + +private: + // @name Private methods + // @{ + + //! Initialize the sender and returns true when it is initialized + bool initialize() noexcept; + + //! Queue a message to be sent to the daemon later + inline void queueMessage(const std::string& message) noexcept; + + //! Send a message to the daemon + void sendToDaemon(const std::string& message) noexcept; + + //!@} + +private: + // @name State variables + // @{ + + //! Shall we exit? + std::atomic m_mustExit{false}; + + //!@} + + // @name Network info + // @{ + + //! The hostname + std::string m_host; + + //! The port + uint16_t m_port; + + //! The structure holding the server + struct sockaddr_in m_server; + + //! The socket to be used + SOCKET_TYPE m_socket = k_invalidSocket; + + //!@} + + // @name Batching info + // @{ + + //! The batching size + uint64_t m_batchsize; + + //! The sending frequency in milliseconds + uint64_t m_sendInterval; + + //! The queue batching the messages + std::deque m_batchingMessageQueue; + + //! The mutex used for batching + std::mutex m_batchingMutex; + + //! The thread dedicated to the batching + std::thread m_batchingThread; + + //!@} + + //! Error message (optional string) + std::string m_errorMessage; +}; + +namespace detail { + +inline bool isValidSocket(const SOCKET_TYPE socket) { + return socket != k_invalidSocket; +} + +#ifdef _WIN32 +struct WinSockSingleton { + inline static const WinSockSingleton& getInstance() { + static const WinSockSingleton instance; + return instance; + } + inline bool ok() const { + return m_ok; + } + ~WinSockSingleton() { + WSACleanup(); + } + +private: + WinSockSingleton() { + WSADATA wsa; + m_ok = WSAStartup(MAKEWORD(2, 2), &wsa) == 0; + } + bool m_ok; +}; +#endif + +} // namespace detail + +inline UDPSender::UDPSender(const std::string& host, + const uint16_t port, + const uint64_t batchsize, + const uint64_t sendInterval) noexcept + : m_host(host), m_port(port), m_batchsize(batchsize), m_sendInterval(sendInterval) { + // Initialize the socket + if (!initialize()) { + return; + } + + // If batching is on, use a dedicated thread to send after the wait time is reached + if (m_batchsize != 0 && m_sendInterval > 0) { + // Define the batching thread + m_batchingThread = std::thread([this] { + // TODO: this will drop unsent stats, should we send all the unsent stats before we exit? + while (!m_mustExit.load(std::memory_order_acquire)) { + std::deque stagedMessageQueue; + + std::unique_lock batchingLock(m_batchingMutex); + m_batchingMessageQueue.swap(stagedMessageQueue); + batchingLock.unlock(); + + // Flush the queue + while (!stagedMessageQueue.empty()) { + sendToDaemon(stagedMessageQueue.front()); + stagedMessageQueue.pop_front(); + } + + // Wait before sending the next batch + std::this_thread::sleep_for(std::chrono::milliseconds(m_sendInterval)); + } + }); + } +} + +inline UDPSender::~UDPSender() { + if (!initialized()) { + return; + } + + // If we're running a background thread tell it to stop + if (m_batchingThread.joinable()) { + m_mustExit.store(true, std::memory_order_release); + m_batchingThread.join(); + } + + // Cleanup the socket + SOCKET_CLOSE(m_socket); +} + +inline void UDPSender::send(const std::string& message) noexcept { + m_errorMessage.clear(); + + // If batching is on, accumulate messages in the queue + if (m_batchsize > 0) { + queueMessage(message); + return; + } + + // Or send it right now + sendToDaemon(message); +} + +inline void UDPSender::queueMessage(const std::string& message) noexcept { + // We aquire a lock but only if we actually need to (i.e. there is a thread also accessing the queue) + auto batchingLock = + m_batchingThread.joinable() ? std::unique_lock(m_batchingMutex) : std::unique_lock(); + // Either we don't have a place to batch our message or we exceeded the batch size, so make a new batch + if (m_batchingMessageQueue.empty() || m_batchingMessageQueue.back().length() > m_batchsize) { + m_batchingMessageQueue.emplace_back(); + m_batchingMessageQueue.back().reserve(m_batchsize + 256); + } // When there is already a batch open we need a separator when its not empty + else if (!m_batchingMessageQueue.back().empty()) { + m_batchingMessageQueue.back().push_back('\n'); + } + // Add the new message to the batch + m_batchingMessageQueue.back().append(message); +} + +inline const std::string& UDPSender::errorMessage() const noexcept { + return m_errorMessage; +} + +inline bool UDPSender::initialize() noexcept { +#ifdef _WIN32 + if (!detail::WinSockSingleton::getInstance().ok()) { + m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO); + } +#endif + + // Connect the socket + m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!detail::isValidSocket(m_socket)) { + m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO); + return false; + } + + std::memset(&m_server, 0, sizeof(m_server)); + m_server.sin_family = AF_INET; + m_server.sin_port = htons(m_port); + + if (inet_pton(AF_INET, m_host.c_str(), &m_server.sin_addr) == 0) { + // An error code has been returned by inet_aton + + // Specify the criteria for selecting the socket address structure + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + + // Get the address info using the hints + struct addrinfo* results = nullptr; + const int ret{getaddrinfo(m_host.c_str(), nullptr, &hints, &results)}; + if (ret != 0) { + // An error code has been returned by getaddrinfo + SOCKET_CLOSE(m_socket); + m_socket = k_invalidSocket; + m_errorMessage = "getaddrinfo failed: err=" + std::to_string(ret) + ", msg=" + gai_strerror(ret); + return false; + } + + // Copy the results in m_server + struct sockaddr_in* host_addr = (struct sockaddr_in*)results->ai_addr; + std::memcpy(&m_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr)); + + // Free the memory allocated + freeaddrinfo(results); + } + + return true; +} + +inline void UDPSender::sendToDaemon(const std::string& message) noexcept { + // Try sending the message + const auto ret = sendto(m_socket, + message.data(), +#ifdef _WIN32 + static_cast(message.size()), +#else + message.size(), +#endif + 0, + (struct sockaddr*)&m_server, + sizeof(m_server)); + if (ret == -1) { + m_errorMessage = "sendto server failed: host=" + m_host + ":" + std::to_string(m_port) + + ", err=" + std::to_string(SOCKET_ERRNO); + } +} + +inline bool UDPSender::initialized() const noexcept { + return m_socket != k_invalidSocket; +} + +inline void UDPSender::flush() noexcept { + // We aquire a lock but only if we actually need to (ie there is a thread also accessing the queue) + auto batchingLock = + m_batchingThread.joinable() ? std::unique_lock(m_batchingMutex) : std::unique_lock(); + // Flush the queue + while (!m_batchingMessageQueue.empty()) { + sendToDaemon(m_batchingMessageQueue.front()); + m_batchingMessageQueue.pop_front(); + } +} + +} // namespace Statsd + +#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp new file mode 100644 index 000000000..e87fe5933 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp @@ -0,0 +1,80 @@ +#ifndef STATSD_SERVER_HPP +#define STATSD_SERVER_HPP + +// It might make sense to include this test class in the UDPSender header +// it includes most of the cross platform defines etc that we need for socket io +#include "cpp-statsd-client/UDPSender.hpp" + +#include +#include + +namespace Statsd { + +class StatsdServer { +public: + StatsdServer(unsigned short port = 8125) noexcept { +#ifdef _WIN32 + if (!detail::WinSockSingleton::getInstance().ok()) { + m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO); + } +#endif + + // Create the socket + m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (!detail::isValidSocket(m_socket)) { + m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO); + return; + } + + // Binding should be with ipv4 to all interfaces + struct sockaddr_in address {}; + address.sin_family = AF_INET; + address.sin_port = htons(port); + address.sin_addr.s_addr = INADDR_ANY; + + // Try to bind + if (bind(m_socket, reinterpret_cast(&address), sizeof(address)) != 0) { + SOCKET_CLOSE(m_socket); + m_socket = k_invalidSocket; + m_errorMessage = "bind failed: errno=" + std::to_string(SOCKET_ERRNO); + } + } + + ~StatsdServer() { + if (detail::isValidSocket(m_socket)) { + SOCKET_CLOSE(m_socket); + } + } + + const std::string& errorMessage() const noexcept { + return m_errorMessage; + } + + std::string receive() noexcept { + // If uninitialized then bail + if (!detail::isValidSocket(m_socket)) { + return ""; + } + + // Try to receive (this is blocking) + std::string buffer(256, '\0'); + int string_len; + if ((string_len = recv(m_socket, &buffer[0], static_cast(buffer.size()), 0)) < 1) { + m_errorMessage = "Could not recv on the socket file descriptor"; + return ""; + } + + // No error return the trimmed result + m_errorMessage.clear(); + buffer.resize(std::min(static_cast(string_len), buffer.size())); + return buffer; + } + +private: + SOCKET_TYPE m_socket; + std::string m_errorMessage; +}; + +} // namespace Statsd + +#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp new file mode 100644 index 000000000..74db8468e --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp @@ -0,0 +1,184 @@ +#include + +#include "StatsdServer.hpp" +#include "cpp-statsd-client/StatsdClient.hpp" + +using namespace Statsd; + +// Each test suite below spawns a thread to recv the client messages over UDP as if it were a real statsd server +// Note that we could just synchronously recv metrics and not use a thread but doing the test async has the +// advantage that we can test the threaded batching mode in a straightforward way. The server thread basically +// just keeps storing metrics in an vector until it hears a special one signaling the test is over and bails +void mock(StatsdServer& server, std::vector& messages) { + do { + // Grab the messages that are waiting + auto recvd = server.receive(); + + // Split the messages on '\n' + auto start = std::string::npos; + do { + // Keep this message + auto end = recvd.find('\n', ++start); + messages.emplace_back(recvd.substr(start, end)); + start = end; + + // Bail if we found the special quit message + if (messages.back().find("DONE") != std::string::npos) { + messages.pop_back(); + return; + } + } while (start != std::string::npos); + } while (server.errorMessage().empty() && !messages.back().empty()); +} + +template +void throwOnError(const SocketWrapper& wrapped, bool expectEmpty = true, const std::string& extraMessage = "") { + if (wrapped.errorMessage().empty() != expectEmpty) { + std::cerr << (expectEmpty ? wrapped.errorMessage() : extraMessage) << std::endl; + throw std::runtime_error(expectEmpty ? wrapped.errorMessage() : extraMessage); + } +} + +void throwOnWrongMessage(StatsdServer& server, const std::string& expected) { + auto actual = server.receive(); + if (actual != expected) { + std::cerr << "Expected: " << expected << " but got: " << actual << std::endl; + throw std::runtime_error("Incorrect stat received"); + } +} + +void testErrorConditions() { + // Resolve a rubbish ip and make sure initialization failed + StatsdClient client{"256.256.256.256", 8125, "myPrefix", 20}; + throwOnError(client, false, "Should not be able to resolve a ridiculous ip"); +} + +void testReconfigure() { + StatsdServer server; + throwOnError(server); + + StatsdClient client("localhost", 8125, "first."); + client.increment("foo"); + throwOnWrongMessage(server, "first.foo:1|c"); + + client.setConfig("localhost", 8125, "second"); + client.increment("bar"); + throwOnWrongMessage(server, "second.bar:1|c"); + + client.setConfig("localhost", 8125, ""); + client.increment("third.baz"); + throwOnWrongMessage(server, "third.baz:1|c"); + + client.increment(""); + throwOnWrongMessage(server, ":1|c"); + + // TODO: test what happens with the batching after resolving the question about incomplete + // batches being dropped vs sent on reconfiguring +} + +void testSendRecv(uint64_t batchSize, uint64_t sendInterval) { + StatsdServer mock_server; + std::vector messages, expected; + std::thread server(mock, std::ref(mock_server), std::ref(messages)); + + // Set a new config that has the client send messages to a proper address that can be resolved + StatsdClient client("localhost", 8125, "sendRecv.", batchSize, sendInterval, 3); + throwOnError(client); + + // TODO: I forget if we need to wait for the server to be ready here before sending the first stats + // is there a race condition where the client sending before the server binds would drop that clients message + + for (int i = 0; i < 3; ++i) { + // Increment "coco" + client.increment("coco"); + throwOnError(client); + expected.emplace_back("sendRecv.coco:1|c"); + + // Decrement "kiki" + client.decrement("kiki"); + throwOnError(client); + expected.emplace_back("sendRecv.kiki:-1|c"); + + // Adjusts "toto" by +2 + client.seed(19); // this seed gets a hit on the first call + client.count("toto", 2, 0.1f); + throwOnError(client); + expected.emplace_back("sendRecv.toto:2|c|@0.10"); + + // Gets "sampled out" by the random number generator + client.count("popo", 9, 0.1f); + throwOnError(client); + + // Record a gauge "titi" to 3 + client.gauge("titi", 3); + throwOnError(client); + expected.emplace_back("sendRecv.titi:3|g"); + + // Record a gauge "titifloat" to -123.456789 with precision 3 + client.gauge("titifloat", -123.456789); + throwOnError(client); + expected.emplace_back("sendRecv.titifloat:-123.457|g"); + + // Record a timing of 2ms for "myTiming" + client.seed(19); + client.timing("myTiming", 2, 0.1f); + throwOnError(client); + expected.emplace_back("sendRecv.myTiming:2|ms|@0.10"); + + // Send a set with 1227 total uniques + client.set("tutu", 1227, 2.0f); + throwOnError(client); + expected.emplace_back("sendRecv.tutu:1227|s"); + + // Gauge but with tags + client.gauge("dr.röstigrabe", 333, 1.f, {"liegt", "im", "weste"}); + throwOnError(client); + expected.emplace_back("sendRecv.dr.röstigrabe:333|g|#liegt,im,weste"); + + // All the things + client.count("foo", -42, .9f, {"bar", "baz"}); + throwOnError(client); + expected.emplace_back("sendRecv.foo:-42|c|@0.90|#bar,baz"); + } + + // Signal the mock server we are done + client.timing("DONE", 0); + + // If manual flushing do it now + if (sendInterval == 0) { + client.flush(); + } + + // Wait for the server to stop + server.join(); + + // Make sure we get the exactly correct output + if (messages != expected) { + std::cerr << "Unexpected stats received by server, got:" << std::endl; + for (const auto& message : messages) { + std::cerr << message << std::endl; + } + std::cerr << std::endl << "But we expected:" << std::endl; + for (const auto& message : expected) { + std::cerr << message << std::endl; + } + throw std::runtime_error("Unexpected stats"); + } +} + +int main() { + // If any of these tests fail they throw an exception, not catching makes for a nonzero return code + + // general things that should be errors + testErrorConditions(); + // reconfiguring how you are sending + testReconfigure(); + // no batching + testSendRecv(0, 0); + // background batching + testSendRecv(32, 1000); + // manual flushing of batches + testSendRecv(16, 0); + + return EXIT_SUCCESS; +} diff --git a/src/modules/keydb_modstatsd/modmain.cpp b/src/modules/keydb_modstatsd/modmain.cpp new file mode 100644 index 000000000..ab5d12b05 --- /dev/null +++ b/src/modules/keydb_modstatsd/modmain.cpp @@ -0,0 +1,666 @@ +#include "redismodule.h" +#include +#include +#include +#include +#include +#ifdef __linux__ +#include +#include +#endif +#include +#include +#include +#include + +using namespace Statsd; + +class StatsdClientWrapper +{ +private: + StatsdClient *m_stats; + StatsdClient *m_stats_noprefix; + +public: + StatsdClientWrapper(const std::string& host, + const uint16_t port, + const std::string& prefix, + const uint64_t batchsize, + const uint64_t sendInterval) { + m_stats = new StatsdClient(host, port, prefix, batchsize, sendInterval); + m_stats_noprefix = new StatsdClient(host, port, "keydb", batchsize, sendInterval); + } + + ~StatsdClientWrapper() { + delete m_stats; + delete m_stats_noprefix; + } + + void increment(const std::string& key, const bool prefixOnly = true) { + m_stats->increment(key); + if (!prefixOnly) m_stats_noprefix->increment(key); + } + + void decrement(const std::string& key, const bool prefixOnly = true) { + m_stats->decrement(key); + if (!prefixOnly) m_stats_noprefix->decrement(key); + } + + void count(const std::string& key, const int delta, const bool prefixOnly = true) { + m_stats->count(key, delta); + if (!prefixOnly) m_stats_noprefix->count(key, delta); + } + + template + void gauge(const std::string& key, const T value, const bool prefixOnly = true) { + m_stats->gauge(key, value); + if (!prefixOnly) m_stats_noprefix->gauge(key, value); + } + + void timing(const std::string& key, const unsigned int ms, const bool prefixOnly = true) { + m_stats->timing(key, ms); + if (!prefixOnly) m_stats_noprefix->timing(key, ms); + } +}; + +/* constants */ +static time_t c_infoUpdateSeconds = 10; + +StatsdClientWrapper *g_stats = nullptr; +std::string m_strPrefix { "keydb" }; + +const std::regex g_replica_or_db_info_regex { "^(slave|db)(\\d+)" }; +const char *g_string_counter_separator = "__"; +const uint64_t g_stats_buffer_size_bytes = 1600; +utsname sysName; +int unameResult; + +enum class StatsD_Type { + STATSD_GAUGE_LONGLONG, + STATSD_GAUGE_FLOAT, + STATSD_GAUGE_BYTES, + STATSD_DELTA, + STATSD_COUNTER_STRING +}; + +struct StatsRecord { + StatsD_Type type; + bool prefixOnly = true; + const char *szAlternate = nullptr; + + /* Dynamic Values */ + long long prevVal = 0; +}; + +std::unordered_map g_mapInfoFields = { + // info + { "used_memory", { StatsD_Type::STATSD_GAUGE_BYTES, false /* prefixOnly */}}, + { "used_memory_rss", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "maxmemory", { StatsD_Type::STATSD_GAUGE_BYTES, false /* prefixOnly */}}, + { "used_memory_dataset_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "avg_lock_contention", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "repl_backlog_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "connected_slaves", { StatsD_Type::STATSD_GAUGE_LONGLONG, true, "connected_replicas" }}, + { "errorstat_ERR", { StatsD_Type::STATSD_DELTA }}, + { "connected_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_connections", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "instantaneous_ops_per_sec", { StatsD_Type::STATSD_GAUGE_LONGLONG, false /* prefixOnly */}}, + { "instantaneous_input_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT, false /* prefixOnly */}}, + { "instantaneous_output_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT, false /* prefixOnly */}}, + { "server_threads", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "mvcc_depth", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "sync_full", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "sync_partial_ok", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "sync_partial_err", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "rdb_bgsave_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "rdb_last_bgsave_time_sec", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "used_memory_overhead", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "master_sync_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "uptime_in_seconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "hz", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "configured_hz", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "maxclients", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "client_recent_max_input_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "client_recent_max_output_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "blocked_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "tracking_clients", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "clients_in_timeout_table", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "used_memory_peak", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "used_memory_startup", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "used_memory_dataset", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "allocator_allocated", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "allocator_active", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "allocator_resident", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "total_system_memory", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "used_memory_lua", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "used_memory_scripts", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "number_of_cached_scripts", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "allocator_frag_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "allocator_frag_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "allocator_rss_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "allocator_rss_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "rss_overhead_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "rss_overhead_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_fragmentation_ratio", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "mem_fragmentation_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_not_counted_for_evict", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_replication_backlog", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_clients_slaves", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_clients_normal", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "mem_aof_buffer", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "active_defrag_running", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "lazyfree_pending_objects", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "lazyfreed_objects", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "loading", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "current_cow_peak", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "current_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "current_cow_size_age", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "current_fork_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "current_save_keys_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "current_save_keys_total", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "rdb_changes_since_last_save", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "rdb_last_save_time", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "rdb_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_enabled", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "aof_rewrite_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "aof_rewrite_scheduled", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "aof_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "module_fork_in_progress", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "module_fork_last_cow_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_current_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_base_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_pending_rewrite", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "aof_buffer_length", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_rewrite_buffer_length", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "aof_pending_bio_fsync", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "aof_delayed_fsync", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_connections_received", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_commands_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_net_input_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "total_net_output_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "rejected_connections", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "expired_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "expired_stale_perc", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "expired_time_cap_reached_count", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "expire_cycle_cpu_milliseconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "evicted_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "keyspace_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "keyspace_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "pubsub_channels", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "pubsub_patterns", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "latest_fork_usec", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_forks", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "migrate_cached_sockets", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "slave_expires_tracked_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "active_defrag_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "active_defrag_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "active_defrag_key_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "active_defrag_key_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "tracking_total_keys", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "tracking_total_items", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "tracking_total_prefixes", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "unexpected_error_replies", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_error_replies", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "dump_payload_sanitizations", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_reads_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "total_writes_processed", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "instantaneous_lock_contention", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "avg_lock_contention", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "storage_provider_read_hits", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "storage_provider_read_misses", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "repl_backlog_active", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "repl_backlog_size", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "repl_backlog_first_byte_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "repl_backlog_histlen", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "used_cpu_sys", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "used_cpu_user", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "used_cpu_sys_children", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "used_cpu_user_children", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "used_cpu_user_children", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "long_lock_waits", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "used_cpu_sys_main_thread", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "used_cpu_user_main_thread", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "master_sync_total_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "master_sync_read_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "master_sync_last_io_seconds_ago", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "master_link_down_since_seconds", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "maxmemory_policy", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "role", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "master_global_link_status", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "master_link_status", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "master_failover_state", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "rdb_last_bgsave_status", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "rdb_saves", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "instantaneous_input_repl_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "instantaneous_output_repl_kbps", { StatsD_Type::STATSD_GAUGE_FLOAT }}, + { "master_host", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "master_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "second_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "slave_repl_offset", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "redis_version", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "redis_git_sha1", { StatsD_Type::STATSD_COUNTER_STRING }}, + // cluster info + { "cluster_state", { StatsD_Type::STATSD_COUNTER_STRING }}, + { "cluster_slots_assigned", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_slots_ok", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_slots_pfail", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_slots_fail", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_known_nodes", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "cluster_size", { StatsD_Type::STATSD_GAUGE_LONGLONG }}, + { "storage_flash_available_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, + { "storage_flash_total_bytes", { StatsD_Type::STATSD_GAUGE_BYTES }}, +}; + +/* Globals */ +static uint64_t g_cclients = 0; + +long long ustime(void) { + struct timeval tv; + long long ust; + + gettimeofday(&tv, NULL); + ust = ((long long)tv.tv_sec)*1000000; + ust += tv.tv_usec; + return ust; +} + +void event_client_change_handler(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) { + if (eid.id != REDISMODULE_EVENT_CLIENT_CHANGE) + return; + + uint64_t clientsStart = g_cclients; + switch (subevent) { + case REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED: + ++g_cclients; + g_stats->increment("clients_added"); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric increment for \"clients_added\""); + break; + + case REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED: + --g_cclients; + g_stats->increment("clients_disconnected"); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric increment for \"clients_disconnected\""); + break; + } + + if (g_cclients != clientsStart) { + g_stats->gauge("clients", g_cclients); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"clients\": %" PRIu64, g_cclients); + } +} + +void handleStatItem(struct RedisModuleCtx *ctx, std::string name, StatsRecord &record, const char *pchValue) { + switch (record.type) { + case StatsD_Type::STATSD_GAUGE_LONGLONG: { + long long val = strtoll(pchValue, nullptr, 10); + g_stats->gauge(name, val, record.prefixOnly); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", name.c_str(), val); + break; + } + + case StatsD_Type::STATSD_GAUGE_FLOAT: { + double val = strtod(pchValue, nullptr); + g_stats->gauge(name, val, record.prefixOnly); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %f", name.c_str(), val); + break; + } + + case StatsD_Type::STATSD_GAUGE_BYTES: { + long long val = strtoll(pchValue, nullptr, 10); + g_stats->gauge(name + "_MB", val / 1024/1024, record.prefixOnly); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %llu", (name + "_MB").c_str(), val / 1024/1024); + break; + } + + case StatsD_Type::STATSD_DELTA: { + long long val = strtoll(pchValue, nullptr, 10); + g_stats->count(name, val - record.prevVal, record.prefixOnly); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric count for \"%s\": %lld", name.c_str() , val - record.prevVal); + record.prevVal = val; + break; + } + + case StatsD_Type::STATSD_COUNTER_STRING: { + // parse val string + const char *pNewLine = strchr(pchValue, '\r'); + if (pNewLine == nullptr) { + pNewLine = strchr(pchValue, '\n'); + } + if (pNewLine == nullptr) { + g_stats->increment("STATSD_COUNTER_STRING_failed", 1); + return; + } + std::string val(pchValue, pNewLine - pchValue); + std::replace(val.begin(), val.end(), '.', '-'); + // metrics emit + std::string metricsName = name + g_string_counter_separator + val; + g_stats->increment(metricsName, 1); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\"", metricsName.c_str()); + break; + } + + default: + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unknown stats record type for the key \"%s\": %u", name.c_str(), (unsigned)record.type); + break; + } +} + +void handleErrorStatItem(struct RedisModuleCtx *ctx, std::string name, std::string rest) { + size_t idx = rest.find('='); + if (idx != std::string::npos) { + std::string statValue = rest.substr(idx + 1); + long long val = strtoll(statValue.c_str(), nullptr, 10); + g_stats->gauge(name, val); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", name.c_str(), val); + } else { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected errorstat line format returned by \"INFO\" command: \"%s\"", (name + rest).c_str()); + } +} + +void handleReplicaOrDbInfoItem(struct RedisModuleCtx *ctx, std::string name, std::string rest) { + //use a stringstream to extract each metric of the form = + std::stringstream metrics(rest); + while (metrics.good()) { + std::string metric; + std::getline(metrics, metric, ','); + size_t idx = metric.find('='); + if (idx != std::string::npos) { + std::string stat = metric.substr(0, idx); + std::string statName = name + '-' + stat; + //idx + 1 to ignore the = sign + std::string statValue = metric.substr(idx + 1); + // string values + if (stat == "ip" || stat == "state") { + std::replace(statValue.begin(), statValue.end(), '.', '-'); + statName += g_string_counter_separator + statValue; + g_stats->increment(statName, 1); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\"", statName.c_str()); + } else { + long long val = strtoll(statValue.c_str(), nullptr, 10); + g_stats->gauge(statName, val); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"%s\": %lld", statName.c_str(), val); + } + } + } +} + +void handle_info_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len, const char *command) { + + #define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len)) + + // Parse the INFO reply string line by line + const char *pchLineStart = szReply; + + while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') { + // Loop Each Line + const char *pchColon = pchLineStart; + while (SAFETY_CHECK_POINTER(pchColon) && *pchColon != ':' && *pchColon != '\r') { + ++pchColon; + } + if (!SAFETY_CHECK_POINTER(pchColon)) { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected line termination when parsing response from %s command: \"%s\"", command, pchLineStart); + break; // BUG + } + const char *pchLineEnd = pchColon; + while (SAFETY_CHECK_POINTER(pchLineEnd) && *pchLineEnd != '\n') + ++pchLineEnd; + + std::string strCheck(pchLineStart, pchColon - pchLineStart); + if (strCheck.find("errorstat_") != std::string::npos) { + std::string remainder(pchColon + 1, pchLineEnd - (pchColon + 1)); + handleErrorStatItem(ctx, strCheck, remainder); + } else if (std::regex_match(strCheck, g_replica_or_db_info_regex)) { + std::string remainder(pchColon + 1, pchLineEnd - (pchColon + 1)); + handleReplicaOrDbInfoItem(ctx, strCheck, remainder); + } else { + auto itr = g_mapInfoFields.find(strCheck); + if (itr != g_mapInfoFields.end()) { + // This is an info field we care about + if (itr->second.szAlternate != nullptr) + strCheck = itr->second.szAlternate; + handleStatItem(ctx, strCheck, itr->second, pchColon+1); + } + } + + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "INFO response line: \"%s\"", std::string(pchLineStart, pchLineEnd - pchLineStart).c_str()); + pchLineStart = pchLineEnd + 1; // start of next line, if we're over the loop will catch it + } + + #undef SAFETY_CHECK_POINTER +} + +void handle_cluster_nodes_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len) { + #define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len)) + const char *pchLineStart = szReply; + long long primaries = 0; + long long replicas = 0; + while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') { + // Loop Each Line + const char *pchLineEnd = pchLineStart; + while (SAFETY_CHECK_POINTER(pchLineEnd) && (*pchLineEnd != '\r') && (*pchLineEnd != '\n')) { + ++pchLineEnd; + } + std::string line(pchLineStart, pchLineEnd - pchLineStart); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Cluster Nodes Line: \"%s\"", line.c_str()); + if (std::string::npos != line.find("master")) { + ++primaries; + } else if (std::string::npos != line.find("slave")) { + ++replicas; + } else { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected NODE format returned by \"CLUSTER NODES\" command: \"%s\"", line.c_str()); + } + // emit myself stat + if (line.find("myself") != std::string::npos) { + size_t firstSpaceIdx = line.find(' '); + // emit cluster node id + if (firstSpaceIdx != std::string::npos) { + std::string nodeIdStat = "cluster_node_id"; + nodeIdStat += g_string_counter_separator + line.substr(0, firstSpaceIdx); + g_stats->increment(nodeIdStat); + } + // emit node ip + size_t firstColonIdx = line.find(':'); + if (firstColonIdx != std::string::npos) { + std::string nodeIpStat = "cluster_node_ip"; + std::string nodeIP = line.substr(firstSpaceIdx+1, firstColonIdx-firstSpaceIdx-1); + std::replace(nodeIP.begin(), nodeIP.end(), '.', '-'); + nodeIpStat += g_string_counter_separator + nodeIP; + g_stats->increment(nodeIpStat); + } + } + pchLineStart = pchLineEnd; + while (SAFETY_CHECK_POINTER(pchLineStart) && ((*pchLineStart == '\r') || (*pchLineStart == '\n'))) { + ++pchLineStart; + } + } + g_stats->gauge("primaries", primaries); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"primaries\": %llu", primaries); + g_stats->gauge("replicas", replicas); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"replicas\": %llu", replicas); + #undef SAFETY_CHECK_POINTER +} + +void handle_client_list_response(struct RedisModuleCtx *ctx, const char *szReply, size_t len) { + size_t totalClientOutputBuffer = 0; + size_t totalReplicaClientOutputBuffer = 0; + #define SAFETY_CHECK_POINTER(_p) ((_p) < (szReply + len)) + const char *pchLineStart = szReply; + while (SAFETY_CHECK_POINTER(pchLineStart) && *pchLineStart != '\0') { + // Loop Each Line + const char *pchLineEnd = pchLineStart; + while (SAFETY_CHECK_POINTER(pchLineEnd) && (*pchLineEnd != '\r') && (*pchLineEnd != '\n')) { + ++pchLineEnd; + } + std::string line(pchLineStart, pchLineEnd - pchLineStart); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Client List Line: \"%s\"", line.c_str()); + + // recover output buffer size for client + bool lineFailed = false; + bool replica = line.find("flags=S") != std::string::npos; + size_t idx = line.find("omem"); + if (!(lineFailed = (idx == std::string::npos))) { + std::string rest = line.substr(idx); + size_t startIdx = rest.find("="); + if (!(lineFailed = (startIdx == std::string::npos))) { + size_t endIdx = rest.find(" "); + if (!(lineFailed = (endIdx == std::string::npos))) { + // use startIdx + 1 and endIdx - 1 to exclude the '=' and ' ' characters + std::string valueString = rest.substr(startIdx + 1, (endIdx - 1) - (startIdx + 1)); + size_t value = strtoll(valueString.c_str(), nullptr, 10); + totalClientOutputBuffer += value; + if (replica) { + totalReplicaClientOutputBuffer += value; + } + } + } + } + + if (lineFailed) { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unexpected CLIENT format returned by \"CLIENT LIST\" command: \"%s\"", line.c_str()); + } + + pchLineStart = pchLineEnd; + while (SAFETY_CHECK_POINTER(pchLineStart) && ((*pchLineStart == '\r') || (*pchLineStart == '\n'))) { + ++pchLineStart; + } + } + #undef SAFETY_CHECK_POINTER + g_stats->gauge("total_client_output_buffer", totalClientOutputBuffer); + g_stats->gauge("total_replica_client_output_buffer", totalReplicaClientOutputBuffer); +} + + +void event_cron_handler(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data) { + static time_t lastTime = 0; + time_t curTime = time(nullptr); + + if ((curTime - lastTime) > c_infoUpdateSeconds) { + size_t startTime = ustime(); + +#ifdef __linux__ + /* Log CPU Usage */ + static long long s_mscpuLast = 0; + struct rusage self_ru; + getrusage(RUSAGE_SELF, &self_ru); + + long long mscpuCur = (self_ru.ru_utime.tv_sec * 1000) + (self_ru.ru_utime.tv_usec / 1000) + + (self_ru.ru_stime.tv_sec * 1000) + (self_ru.ru_stime.tv_usec / 1000); + + + g_stats->gauge("cpu_load_perc", (double)(mscpuCur - s_mscpuLast) / ((curTime - lastTime)*1000), true /* prefixOnly */); + s_mscpuLast = mscpuCur; +#endif + + /* Log clients */ + g_stats->gauge("clients", g_cclients); + + /* node name */ + if (unameResult == 0) { + g_stats->increment("node_name" + std::string(g_string_counter_separator) + sysName.nodename); + } + + /* Log INFO Fields */ + size_t commandStartTime = ustime(); + RedisModuleCallReply *reply = RedisModule_Call(ctx, "INFO", ""); + size_t len = 0; + const char *szReply = RedisModule_CallReplyStringPtr(reply, &len); + g_stats->timing("info_time_taken_us", ustime() - commandStartTime); + commandStartTime = ustime(); + handle_info_response(ctx, szReply, len, "INFO"); + g_stats->timing("handle_info_time_taken_us", ustime() - commandStartTime); + RedisModule_FreeCallReply(reply); + + /* Log CLUSTER INFO Fields */ + commandStartTime = ustime(); + reply = RedisModule_Call(ctx, "CLUSTER", "c", "INFO"); + szReply = RedisModule_CallReplyStringPtr(reply, &len); + g_stats->timing("cluster_info_time_taken_us", ustime() - commandStartTime); + commandStartTime = ustime(); + handle_info_response(ctx, szReply, len, "CLUSTER INFO"); + g_stats->timing("handle_cluster_info_time_taken_us", ustime() - commandStartTime); + RedisModule_FreeCallReply(reply); + + /* Log Cluster Topology */ + commandStartTime = ustime(); + reply = RedisModule_Call(ctx, "CLUSTER", "c", "NODES"); + szReply = RedisModule_CallReplyStringPtr(reply, &len); + g_stats->timing("cluster_nodes_time_taken_us", ustime() - commandStartTime); + commandStartTime = ustime(); + handle_cluster_nodes_response(ctx, szReply, len); + g_stats->timing("handle_cluster_nodes_time_taken_us", ustime() - commandStartTime); + RedisModule_FreeCallReply(reply); + + /* Log Client Info */ + // commandStartTime = ustime(); + // reply = RedisModule_Call(ctx, "CLIENT", "c", "LIST"); + // szReply = RedisModule_CallReplyStringPtr(reply, &len); + // g_stats->timing("client_info_time_taken_us", ustime() - commandStartTime); + // commandStartTime = ustime(); + // handle_client_list_response(ctx, szReply, len); + // g_stats->timing("handle_client_info_time_taken_us", ustime() - commandStartTime); + // RedisModule_FreeCallReply(reply); + + /* Log Keys */ + reply = RedisModule_Call(ctx, "dbsize", ""); + long long keys = RedisModule_CallReplyInteger(reply); + RedisModule_FreeCallReply(reply); + g_stats->gauge("keys", keys); + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_DEBUG, "Emitting metric \"keys\": %llu", keys); + g_stats->timing("metrics_time_taken_us", ustime() - startTime); + + lastTime = curTime; + } +} + +extern "C" int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) { + if (RedisModule_Init(ctx,"statsd",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + RedisModule_AutoMemory(ctx); + /* Use pod name if available*/ + const char *podName = getenv("POD_NAME"); + unameResult = uname(&sysName); + if (podName != nullptr) { + m_strPrefix = podName; + } + else { + if (unameResult == 0) { + m_strPrefix = std::string(sysName.nodename); + unameResult = 1; + } + } + std::replace(m_strPrefix.begin(), m_strPrefix.end(), '.', '-'); + + for (int iarg = 0; iarg < argc; ++iarg) { + size_t len = 0; + const char *rgchArg = RedisModule_StringPtrLen(argv[iarg], &len); + if (len == 6 && memcmp(rgchArg, "prefix", 6) == 0) { + if ((iarg+1) >= argc) { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Expected a value after 'prefix'"); + return REDISMODULE_ERR; + } + ++iarg; + size_t lenPrefix = 0; + const char *rgchPrefix = RedisModule_StringPtrLen(argv[iarg], &lenPrefix); + m_strPrefix = std::string(rgchPrefix, lenPrefix); + } else { + RedisModule_Log(ctx, REDISMODULE_LOGLEVEL_WARNING, "Unrecognized configuration flag"); + return REDISMODULE_ERR; + } + } + + g_stats = new StatsdClientWrapper("localhost", 8125, m_strPrefix, g_stats_buffer_size_bytes, c_infoUpdateSeconds * 1000); + + if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_ClientChange, event_client_change_handler) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + if (RedisModule_SubscribeToServerEvent(ctx, RedisModuleEvent_CronLoop, event_cron_handler) == REDISMODULE_ERR) + return REDISMODULE_ERR; + + return REDISMODULE_OK; +} + +extern "C" int RedisModule_OnUnload(RedisModuleCtx *ctx) { + delete g_stats; + return REDISMODULE_OK; +} diff --git a/src/modules/keydb_modstatsd/redismodule.h b/src/modules/keydb_modstatsd/redismodule.h new file mode 100644 index 000000000..4313aee01 --- /dev/null +++ b/src/modules/keydb_modstatsd/redismodule.h @@ -0,0 +1,1146 @@ +#ifndef REDISMODULE_H +#define REDISMODULE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---------------- Defines common between core and modules --------------- */ + +/* Error status return values. */ +#define REDISMODULE_OK 0 +#define REDISMODULE_ERR 1 + +/* API versions. */ +#define REDISMODULE_APIVER_1 1 + +/* Version of the RedisModuleTypeMethods structure. Once the RedisModuleTypeMethods + * structure is changed, this version number needs to be changed synchronistically. */ +#define REDISMODULE_TYPE_METHOD_VERSION 3 + +/* API flags and constants */ +#define REDISMODULE_READ (1<<0) +#define REDISMODULE_WRITE (1<<1) + +/* RedisModule_OpenKey extra flags for the 'mode' argument. + * Avoid touching the LRU/LFU of the key when opened. */ +#define REDISMODULE_OPEN_KEY_NOTOUCH (1<<16) + +#define REDISMODULE_LIST_HEAD 0 +#define REDISMODULE_LIST_TAIL 1 + +/* Key types. */ +#define REDISMODULE_KEYTYPE_EMPTY 0 +#define REDISMODULE_KEYTYPE_STRING 1 +#define REDISMODULE_KEYTYPE_LIST 2 +#define REDISMODULE_KEYTYPE_HASH 3 +#define REDISMODULE_KEYTYPE_SET 4 +#define REDISMODULE_KEYTYPE_ZSET 5 +#define REDISMODULE_KEYTYPE_MODULE 6 +#define REDISMODULE_KEYTYPE_STREAM 7 + +/* Reply types. */ +#define REDISMODULE_REPLY_UNKNOWN -1 +#define REDISMODULE_REPLY_STRING 0 +#define REDISMODULE_REPLY_ERROR 1 +#define REDISMODULE_REPLY_INTEGER 2 +#define REDISMODULE_REPLY_ARRAY 3 +#define REDISMODULE_REPLY_NULL 4 + +/* Postponed array length. */ +#define REDISMODULE_POSTPONED_ARRAY_LEN -1 + +/* Expire */ +#define REDISMODULE_NO_EXPIRE -1 + +/* Sorted set API flags. */ +#define REDISMODULE_ZADD_XX (1<<0) +#define REDISMODULE_ZADD_NX (1<<1) +#define REDISMODULE_ZADD_ADDED (1<<2) +#define REDISMODULE_ZADD_UPDATED (1<<3) +#define REDISMODULE_ZADD_NOP (1<<4) +#define REDISMODULE_ZADD_GT (1<<5) +#define REDISMODULE_ZADD_LT (1<<6) + +/* Hash API flags. */ +#define REDISMODULE_HASH_NONE 0 +#define REDISMODULE_HASH_NX (1<<0) +#define REDISMODULE_HASH_XX (1<<1) +#define REDISMODULE_HASH_CFIELDS (1<<2) +#define REDISMODULE_HASH_EXISTS (1<<3) +#define REDISMODULE_HASH_COUNT_ALL (1<<4) + +/* StreamID type. */ +typedef struct RedisModuleStreamID { + uint64_t ms; + uint64_t seq; +} RedisModuleStreamID; + +/* StreamAdd() flags. */ +#define REDISMODULE_STREAM_ADD_AUTOID (1<<0) +/* StreamIteratorStart() flags. */ +#define REDISMODULE_STREAM_ITERATOR_EXCLUSIVE (1<<0) +#define REDISMODULE_STREAM_ITERATOR_REVERSE (1<<1) +/* StreamIteratorTrim*() flags. */ +#define REDISMODULE_STREAM_TRIM_APPROX (1<<0) + +/* Context Flags: Info about the current context returned by + * RM_GetContextFlags(). */ + +/* The command is running in the context of a Lua script */ +#define REDISMODULE_CTX_FLAGS_LUA (1<<0) +/* The command is running inside a Redis transaction */ +#define REDISMODULE_CTX_FLAGS_MULTI (1<<1) +/* The instance is a master */ +#define REDISMODULE_CTX_FLAGS_MASTER (1<<2) +/* The instance is a replica */ +#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3) +/* The instance is read-only (usually meaning it's a replica as well) */ +#define REDISMODULE_CTX_FLAGS_READONLY (1<<4) +/* The instance is running in cluster mode */ +#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5) +/* The instance has AOF enabled */ +#define REDISMODULE_CTX_FLAGS_AOF (1<<6) +/* The instance has RDB enabled */ +#define REDISMODULE_CTX_FLAGS_RDB (1<<7) +/* The instance has Maxmemory set */ +#define REDISMODULE_CTX_FLAGS_MAXMEMORY (1<<8) +/* Maxmemory is set and has an eviction policy that may delete keys */ +#define REDISMODULE_CTX_FLAGS_EVICT (1<<9) +/* Redis is out of memory according to the maxmemory flag. */ +#define REDISMODULE_CTX_FLAGS_OOM (1<<10) +/* Less than 25% of memory available according to maxmemory. */ +#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11) +/* The command was sent over the replication link. */ +#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12) +/* Redis is currently loading either from AOF or RDB. */ +#define REDISMODULE_CTX_FLAGS_LOADING (1<<13) +/* The replica has no link with its master, note that + * there is the inverse flag as well: + * + * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE + * + * The two flags are exclusive, one or the other can be set. */ +#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14) +/* The replica is trying to connect with the master. + * (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */ +#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15) +/* THe replica is receiving an RDB file from its master. */ +#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16) +/* The replica is online, receiving updates from its master. */ +#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17) +/* There is currently some background process active. */ +#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18) +/* The next EXEC will fail due to dirty CAS (touched keys). */ +#define REDISMODULE_CTX_FLAGS_MULTI_DIRTY (1<<19) +/* Redis is currently running inside background child process. */ +#define REDISMODULE_CTX_FLAGS_IS_CHILD (1<<20) +/* The current client does not allow blocking, either called from + * within multi, lua, or from another module using RM_Call */ +#define REDISMODULE_CTX_FLAGS_DENY_BLOCKING (1<<21) + +/* Next context flag, must be updated when adding new flags above! +This flag should not be used directly by the module. + * Use RedisModule_GetContextFlagsAll instead. */ +#define _REDISMODULE_CTX_FLAGS_NEXT (1<<22) + +/* Keyspace changes notification classes. Every class is associated with a + * character for configuration purposes. + * NOTE: These have to be in sync with NOTIFY_* in server.h */ +#define REDISMODULE_NOTIFY_KEYSPACE (1<<0) /* K */ +#define REDISMODULE_NOTIFY_KEYEVENT (1<<1) /* E */ +#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */ +#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */ +#define REDISMODULE_NOTIFY_LIST (1<<4) /* l */ +#define REDISMODULE_NOTIFY_SET (1<<5) /* s */ +#define REDISMODULE_NOTIFY_HASH (1<<6) /* h */ +#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */ +#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */ +#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */ +#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */ +#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m (Note: This one is excluded from REDISMODULE_NOTIFY_ALL on purpose) */ +#define REDISMODULE_NOTIFY_LOADED (1<<12) /* module only key space notification, indicate a key loaded from rdb */ +#define REDISMODULE_NOTIFY_MODULE (1<<13) /* d, module key space notification */ + +/* Next notification flag, must be updated when adding new flags above! +This flag should not be used directly by the module. + * Use RedisModule_GetKeyspaceNotificationFlagsAll instead. */ +#define _REDISMODULE_NOTIFY_NEXT (1<<14) + +#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_MODULE) /* A */ + +/* A special pointer that we can use between the core and the module to signal + * field deletion, and that is impossible to be a valid pointer. */ +#define REDISMODULE_HASH_DELETE ((RedisModuleString*)(long)1) + +/* Error messages. */ +#define REDISMODULE_ERRORMSG_WRONGTYPE "WRONGTYPE Operation against a key holding the wrong kind of value" + +#define REDISMODULE_POSITIVE_INFINITE (1.0/0.0) +#define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0) + +/* Cluster API defines. */ +#define REDISMODULE_NODE_ID_LEN 40 +#define REDISMODULE_NODE_MYSELF (1<<0) +#define REDISMODULE_NODE_MASTER (1<<1) +#define REDISMODULE_NODE_SLAVE (1<<2) +#define REDISMODULE_NODE_PFAIL (1<<3) +#define REDISMODULE_NODE_FAIL (1<<4) +#define REDISMODULE_NODE_NOFAILOVER (1<<5) + +#define REDISMODULE_CLUSTER_FLAG_NONE 0 +#define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1) +#define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2) + +#define REDISMODULE_NOT_USED(V) ((void) V) + +/* Logging level strings */ +#define REDISMODULE_LOGLEVEL_DEBUG "debug" +#define REDISMODULE_LOGLEVEL_VERBOSE "verbose" +#define REDISMODULE_LOGLEVEL_NOTICE "notice" +#define REDISMODULE_LOGLEVEL_WARNING "warning" + +/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */ +#define REDISMODULE_AUX_BEFORE_RDB (1<<0) +#define REDISMODULE_AUX_AFTER_RDB (1<<1) + +/* This type represents a timer handle, and is returned when a timer is + * registered and used in order to invalidate a timer. It's just a 64 bit + * number, because this is how each timer is represented inside the radix tree + * of timers that are going to expire, sorted by expire time. */ +typedef uint64_t RedisModuleTimerID; + +/* CommandFilter Flags */ + +/* Do filter RedisModule_Call() commands initiated by module itself. */ +#define REDISMODULE_CMDFILTER_NOSELF (1<<0) + +/* Declare that the module can handle errors with RedisModule_SetModuleOptions. */ +#define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0) +/* When set, Redis will not call RedisModule_SignalModifiedKey(), implicitly in + * RedisModule_CloseKey, and the module needs to do that when manually when keys + * are modified from the user's sperspective, to invalidate WATCH. */ +#define REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED (1<<1) + +/* Server events definitions. + * Those flags should not be used directly by the module, instead + * the module should use RedisModuleEvent_* variables */ +#define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0 +#define REDISMODULE_EVENT_PERSISTENCE 1 +#define REDISMODULE_EVENT_FLUSHDB 2 +#define REDISMODULE_EVENT_LOADING 3 +#define REDISMODULE_EVENT_CLIENT_CHANGE 4 +#define REDISMODULE_EVENT_SHUTDOWN 5 +#define REDISMODULE_EVENT_REPLICA_CHANGE 6 +#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7 +#define REDISMODULE_EVENT_CRON_LOOP 8 +#define REDISMODULE_EVENT_MODULE_CHANGE 9 +#define REDISMODULE_EVENT_LOADING_PROGRESS 10 +#define REDISMODULE_EVENT_SWAPDB 11 +#define REDISMODULE_EVENT_REPL_BACKUP 12 +#define REDISMODULE_EVENT_FORK_CHILD 13 +#define _REDISMODULE_EVENT_NEXT 14 /* Next event flag, should be updated if a new event added. */ + +typedef struct RedisModuleEvent { + uint64_t id; /* REDISMODULE_EVENT_... defines. */ + uint64_t dataver; /* Version of the structure we pass as 'data'. */ +} RedisModuleEvent; + +struct RedisModuleCtx; +struct RedisModuleDefragCtx; +typedef void (*RedisModuleEventCallback)(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data); + +static const RedisModuleEvent + RedisModuleEvent_ReplicationRoleChanged = { + REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED, + 1 + }, + RedisModuleEvent_Persistence = { + REDISMODULE_EVENT_PERSISTENCE, + 1 + }, + RedisModuleEvent_FlushDB = { + REDISMODULE_EVENT_FLUSHDB, + 1 + }, + RedisModuleEvent_Loading = { + REDISMODULE_EVENT_LOADING, + 1 + }, + RedisModuleEvent_ClientChange = { + REDISMODULE_EVENT_CLIENT_CHANGE, + 1 + }, + RedisModuleEvent_Shutdown = { + REDISMODULE_EVENT_SHUTDOWN, + 1 + }, + RedisModuleEvent_ReplicaChange = { + REDISMODULE_EVENT_REPLICA_CHANGE, + 1 + }, + RedisModuleEvent_CronLoop = { + REDISMODULE_EVENT_CRON_LOOP, + 1 + }, + RedisModuleEvent_MasterLinkChange = { + REDISMODULE_EVENT_MASTER_LINK_CHANGE, + 1 + }, + RedisModuleEvent_ModuleChange = { + REDISMODULE_EVENT_MODULE_CHANGE, + 1 + }, + RedisModuleEvent_LoadingProgress = { + REDISMODULE_EVENT_LOADING_PROGRESS, + 1 + }, + RedisModuleEvent_SwapDB = { + REDISMODULE_EVENT_SWAPDB, + 1 + }, + RedisModuleEvent_ReplBackup = { + REDISMODULE_EVENT_REPL_BACKUP, + 1 + }, + RedisModuleEvent_ForkChild = { + REDISMODULE_EVENT_FORK_CHILD, + 1 + }; + +/* Those are values that are used for the 'subevent' callback argument. */ +#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0 +#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 1 +#define REDISMODULE_SUBEVENT_PERSISTENCE_SYNC_RDB_START 2 +#define REDISMODULE_SUBEVENT_PERSISTENCE_ENDED 3 +#define REDISMODULE_SUBEVENT_PERSISTENCE_FAILED 4 +#define _REDISMODULE_SUBEVENT_PERSISTENCE_NEXT 5 + +#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0 +#define REDISMODULE_SUBEVENT_LOADING_AOF_START 1 +#define REDISMODULE_SUBEVENT_LOADING_REPL_START 2 +#define REDISMODULE_SUBEVENT_LOADING_ENDED 3 +#define REDISMODULE_SUBEVENT_LOADING_FAILED 4 +#define _REDISMODULE_SUBEVENT_LOADING_NEXT 5 + +#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0 +#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1 +#define _REDISMODULE_SUBEVENT_CLIENT_CHANGE_NEXT 2 + +#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0 +#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1 +#define _REDISMODULE_SUBEVENT_MASTER_NEXT 2 + +#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_ONLINE 0 +#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_OFFLINE 1 +#define _REDISMODULE_SUBEVENT_REPLICA_CHANGE_NEXT 2 + +#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER 0 +#define REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA 1 +#define _REDISMODULE_EVENT_REPLROLECHANGED_NEXT 2 + +#define REDISMODULE_SUBEVENT_FLUSHDB_START 0 +#define REDISMODULE_SUBEVENT_FLUSHDB_END 1 +#define _REDISMODULE_SUBEVENT_FLUSHDB_NEXT 2 + +#define REDISMODULE_SUBEVENT_MODULE_LOADED 0 +#define REDISMODULE_SUBEVENT_MODULE_UNLOADED 1 +#define _REDISMODULE_SUBEVENT_MODULE_NEXT 2 + +#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_RDB 0 +#define REDISMODULE_SUBEVENT_LOADING_PROGRESS_AOF 1 +#define _REDISMODULE_SUBEVENT_LOADING_PROGRESS_NEXT 2 + +#define REDISMODULE_SUBEVENT_REPL_BACKUP_CREATE 0 +#define REDISMODULE_SUBEVENT_REPL_BACKUP_RESTORE 1 +#define REDISMODULE_SUBEVENT_REPL_BACKUP_DISCARD 2 +#define _REDISMODULE_SUBEVENT_REPL_BACKUP_NEXT 3 + +#define REDISMODULE_SUBEVENT_FORK_CHILD_BORN 0 +#define REDISMODULE_SUBEVENT_FORK_CHILD_DIED 1 +#define _REDISMODULE_SUBEVENT_FORK_CHILD_NEXT 2 + +#define _REDISMODULE_SUBEVENT_SHUTDOWN_NEXT 0 +#define _REDISMODULE_SUBEVENT_CRON_LOOP_NEXT 0 +#define _REDISMODULE_SUBEVENT_SWAPDB_NEXT 0 + +/* RedisModuleClientInfo flags. */ +#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0) +#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1) +#define REDISMODULE_CLIENTINFO_FLAG_BLOCKED (1<<2) +#define REDISMODULE_CLIENTINFO_FLAG_TRACKING (1<<3) +#define REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET (1<<4) +#define REDISMODULE_CLIENTINFO_FLAG_MULTI (1<<5) + +/* Here we take all the structures that the module pass to the core + * and the other way around. Notably the list here contains the structures + * used by the hooks API RedisModule_RegisterToServerEvent(). + * + * The structures always start with a 'version' field. This is useful + * when we want to pass a reference to the structure to the core APIs, + * for the APIs to fill the structure. In that case, the structure 'version' + * field is initialized before passing it to the core, so that the core is + * able to cast the pointer to the appropriate structure version. In this + * way we obtain ABI compatibility. + * + * Here we'll list all the structure versions in case they evolve over time, + * however using a define, we'll make sure to use the last version as the + * public name for the module to use. */ + +#define REDISMODULE_CLIENTINFO_VERSION 1 +typedef struct RedisModuleClientInfo { + uint64_t version; /* Version of this structure for ABI compat. */ + uint64_t flags; /* REDISMODULE_CLIENTINFO_FLAG_* */ + uint64_t id; /* Client ID. */ + char addr[46]; /* IPv4 or IPv6 address. */ + uint16_t port; /* TCP port. */ + uint16_t db; /* Selected DB. */ +} RedisModuleClientInfoV1; + +#define RedisModuleClientInfo RedisModuleClientInfoV1 + +#define REDISMODULE_REPLICATIONINFO_VERSION 1 +typedef struct RedisModuleReplicationInfo { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + int master; /* true if master, false if replica */ + const char *masterhost; /* master instance hostname for NOW_REPLICA */ + int masterport; /* master instance port for NOW_REPLICA */ + char *replid1; /* Main replication ID */ + char *replid2; /* Secondary replication ID */ + uint64_t repl1_offset; /* Main replication offset */ + uint64_t repl2_offset; /* Offset of replid2 validity */ +} RedisModuleReplicationInfoV1; + +#define RedisModuleReplicationInfo RedisModuleReplicationInfoV1 + +#define REDISMODULE_FLUSHINFO_VERSION 1 +typedef struct RedisModuleFlushInfo { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + int32_t sync; /* Synchronous or threaded flush?. */ + int32_t dbnum; /* Flushed database number, -1 for ALL. */ +} RedisModuleFlushInfoV1; + +#define RedisModuleFlushInfo RedisModuleFlushInfoV1 + +#define REDISMODULE_MODULE_CHANGE_VERSION 1 +typedef struct RedisModuleModuleChange { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + const char* module_name;/* Name of module loaded or unloaded. */ + int32_t module_version; /* Module version. */ +} RedisModuleModuleChangeV1; + +#define RedisModuleModuleChange RedisModuleModuleChangeV1 + +#define REDISMODULE_CRON_LOOP_VERSION 1 +typedef struct RedisModuleCronLoopInfo { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + int32_t hz; /* Approximate number of events per second. */ +} RedisModuleCronLoopV1; + +#define RedisModuleCronLoop RedisModuleCronLoopV1 + +#define REDISMODULE_LOADING_PROGRESS_VERSION 1 +typedef struct RedisModuleLoadingProgressInfo { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + int32_t hz; /* Approximate number of events per second. */ + int32_t progress; /* Approximate progress between 0 and 1024, or -1 + * if unknown. */ +} RedisModuleLoadingProgressV1; + +#define RedisModuleLoadingProgress RedisModuleLoadingProgressV1 + +#define REDISMODULE_SWAPDBINFO_VERSION 1 +typedef struct RedisModuleSwapDbInfo { + uint64_t version; /* Not used since this structure is never passed + from the module to the core right now. Here + for future compatibility. */ + int32_t dbnum_first; /* Swap Db first dbnum */ + int32_t dbnum_second; /* Swap Db second dbnum */ +} RedisModuleSwapDbInfoV1; + +#define RedisModuleSwapDbInfo RedisModuleSwapDbInfoV1 + +/* ------------------------- End of common defines ------------------------ */ + +#ifndef REDISMODULE_CORE + +typedef long long mstime_t; + +/* Macro definitions specific to individual compilers */ +#ifndef REDISMODULE_ATTR_UNUSED +# ifdef __GNUC__ +# define REDISMODULE_ATTR_UNUSED __attribute__((unused)) +# else +# define REDISMODULE_ATTR_UNUSED +# endif +#endif + +#ifndef REDISMODULE_ATTR_PRINTF +# ifdef __GNUC__ +# define REDISMODULE_ATTR_PRINTF(idx,cnt) __attribute__((format(printf,idx,cnt))) +# else +# define REDISMODULE_ATTR_PRINTF(idx,cnt) +# endif +#endif + +#ifndef REDISMODULE_ATTR_COMMON +# if defined(__GNUC__) && !defined(__clang__) +# define REDISMODULE_ATTR_COMMON __attribute__((__common__)) +# else +# define REDISMODULE_ATTR_COMMON +# endif +#endif + +/* Incomplete structures for compiler checks but opaque access. */ +typedef struct RedisModuleCtx RedisModuleCtx; +typedef struct RedisModuleKey RedisModuleKey; +typedef struct RedisModuleString RedisModuleString; +typedef struct RedisModuleCallReply RedisModuleCallReply; +typedef struct RedisModuleIO RedisModuleIO; +typedef struct RedisModuleType RedisModuleType; +typedef struct RedisModuleDigest RedisModuleDigest; +typedef struct RedisModuleBlockedClient RedisModuleBlockedClient; +typedef struct RedisModuleClusterInfo RedisModuleClusterInfo; +typedef struct RedisModuleDict RedisModuleDict; +typedef struct RedisModuleDictIter RedisModuleDictIter; +typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx; +typedef struct RedisModuleCommandFilter RedisModuleCommandFilter; +typedef struct RedisModuleInfoCtx RedisModuleInfoCtx; +typedef struct RedisModuleServerInfoData RedisModuleServerInfoData; +typedef struct RedisModuleScanCursor RedisModuleScanCursor; +typedef struct RedisModuleDefragCtx RedisModuleDefragCtx; +typedef struct RedisModuleUser RedisModuleUser; + +typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc); +typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc); +typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key); +typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver); +typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value); +typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when); +typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when); +typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value); +typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value); +typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value); +typedef void (*RedisModuleTypeFreeFunc)(void *value); +typedef size_t (*RedisModuleTypeFreeEffortFunc)(RedisModuleString *key, const void *value); +typedef void (*RedisModuleTypeUnlinkFunc)(RedisModuleString *key, const void *value); +typedef void *(*RedisModuleTypeCopyFunc)(RedisModuleString *fromkey, RedisModuleString *tokey, const void *value); +typedef int (*RedisModuleTypeDefragFunc)(RedisModuleDefragCtx *ctx, RedisModuleString *key, void **value); +typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len); +typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data); +typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter); +typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data); +typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report); +typedef void (*RedisModuleScanCB)(RedisModuleCtx *ctx, RedisModuleString *keyname, RedisModuleKey *key, void *privdata); +typedef void (*RedisModuleScanKeyCB)(RedisModuleKey *key, RedisModuleString *field, RedisModuleString *value, void *privdata); +typedef void (*RedisModuleUserChangedFunc) (uint64_t client_id, void *privdata); +typedef int (*RedisModuleDefragFunc)(RedisModuleDefragCtx *ctx); + +typedef struct RedisModuleTypeMethods { + uint64_t version; + RedisModuleTypeLoadFunc rdb_load; + RedisModuleTypeSaveFunc rdb_save; + RedisModuleTypeRewriteFunc aof_rewrite; + RedisModuleTypeMemUsageFunc mem_usage; + RedisModuleTypeDigestFunc digest; + RedisModuleTypeFreeFunc free; + RedisModuleTypeAuxLoadFunc aux_load; + RedisModuleTypeAuxSaveFunc aux_save; + int aux_save_triggers; + RedisModuleTypeFreeEffortFunc free_effort; + RedisModuleTypeUnlinkFunc unlink; + RedisModuleTypeCopyFunc copy; + RedisModuleTypeDefragFunc defrag; +} RedisModuleTypeMethods; + +#define REDISMODULE_GET_API(name) \ + RedisModule_GetApi("RedisModule_" #name, ((void **)&RedisModule_ ## name)) + +/* Default API declaration prefix (not 'extern' for backwards compatibility) */ +#ifndef REDISMODULE_API +#define REDISMODULE_API +#endif + +/* Default API declaration suffix (compiler attributes) */ +#ifndef REDISMODULE_ATTR +#define REDISMODULE_ATTR REDISMODULE_ATTR_COMMON +#endif + +REDISMODULE_API void * (*RedisModule_Alloc)(size_t bytes) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_Realloc)(void *ptr, size_t bytes) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_Free)(void *ptr) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_Calloc)(size_t nmemb, size_t size) REDISMODULE_ATTR; +REDISMODULE_API char * (*RedisModule_Strdup)(const char *str) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetApi)(const char *, void *) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CreateCommand)(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc cmdfunc, const char *strflags, int firstkey, int lastkey, int keystep) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SetModuleAttribs)(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsModuleNameBusy)(const char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_WrongArity)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetSelectedDb)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SelectDb)(RedisModuleCtx *ctx, int newid) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_OpenKey)(RedisModuleCtx *ctx, RedisModuleString *keyname, int mode) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_CloseKey)(RedisModuleKey *kp) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_KeyType)(RedisModuleKey *kp) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_ValueLength)(RedisModuleKey *kp) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ListPush)(RedisModuleKey *kp, int where, RedisModuleString *ele) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_ListPop)(RedisModuleKey *key, int where) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCallReply * (*RedisModule_Call)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR; +REDISMODULE_API const char * (*RedisModule_CallReplyProto)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CallReplyType)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API long long (*RedisModule_CallReplyInteger)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_CallReplyLength)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCallReply * (*RedisModule_CallReplyArrayElement)(RedisModuleCallReply *reply, size_t idx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateString)(RedisModuleCtx *ctx, const char *ptr, size_t len) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongLong)(RedisModuleCtx *ctx, long long ll) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromLongDouble)(RedisModuleCtx *ctx, long double ld, int humanfriendly) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromString)(RedisModuleCtx *ctx, const RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromStreamID)(RedisModuleCtx *ctx, const RedisModuleStreamID *id) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringPrintf)(RedisModuleCtx *ctx, const char *fmt, ...) REDISMODULE_ATTR_PRINTF(2,3) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API const char * (*RedisModule_StringPtrLen)(const RedisModuleString *str, size_t *len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithError)(RedisModuleCtx *ctx, const char *err) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx, const char *msg) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithNullArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithEmptyArray)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithEmptyString)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithVerbatimString)(RedisModuleCtx *ctx, const char *buf, size_t len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithNull)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithLongDouble)(RedisModuleCtx *ctx, long double d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplyWithCallReply)(RedisModuleCtx *ctx, RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringToLongLong)(const RedisModuleString *str, long long *ll) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringToDouble)(const RedisModuleString *str, double *d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringToLongDouble)(const RedisModuleString *str, long double *d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringToStreamID)(const RedisModuleString *str, RedisModuleStreamID *id) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_AutoMemory)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_Replicate)(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ReplicateVerbatim)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API const char * (*RedisModule_CallReplyStringPtr)(RedisModuleCallReply *reply, size_t *len) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_CreateStringFromCallReply)(RedisModuleCallReply *reply) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DeleteKey)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_UnlinkKey)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringSet)(RedisModuleKey *key, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API char * (*RedisModule_StringDMA)(RedisModuleKey *key, size_t *len, int mode) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringTruncate)(RedisModuleKey *key, size_t newlen) REDISMODULE_ATTR; +REDISMODULE_API mstime_t (*RedisModule_GetExpire)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR; +REDISMODULE_API mstime_t (*RedisModule_GetAbsExpire)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetAbsExpire)(RedisModuleKey *key, mstime_t expire) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ResetDataset)(int restart_aof, int async) REDISMODULE_ATTR; +REDISMODULE_API unsigned long long (*RedisModule_DbSize)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_RandomKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetAdd)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetIncrby)(RedisModuleKey *key, double score, RedisModuleString *ele, int *flagsptr, double *newscore) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetScore)(RedisModuleKey *key, RedisModuleString *ele, double *score) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetRem)(RedisModuleKey *key, RedisModuleString *ele, int *deleted) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ZsetRangeStop)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetFirstInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetLastInScoreRange)(RedisModuleKey *key, double min, double max, int minex, int maxex) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetFirstInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetLastInLexRange)(RedisModuleKey *key, RedisModuleString *min, RedisModuleString *max) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_ZsetRangeCurrentElement)(RedisModuleKey *key, double *score) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetRangeNext)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetRangePrev)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ZsetRangeEndReached)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_HashSet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_HashGet)(RedisModuleKey *key, int flags, ...) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamAdd)(RedisModuleKey *key, int flags, RedisModuleStreamID *id, RedisModuleString **argv, int64_t numfields) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamDelete)(RedisModuleKey *key, RedisModuleStreamID *id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamIteratorStart)(RedisModuleKey *key, int flags, RedisModuleStreamID *startid, RedisModuleStreamID *endid) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamIteratorStop)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamIteratorNextID)(RedisModuleKey *key, RedisModuleStreamID *id, long *numfields) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamIteratorNextField)(RedisModuleKey *key, RedisModuleString **field_ptr, RedisModuleString **value_ptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StreamIteratorDelete)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API long long (*RedisModule_StreamTrimByLength)(RedisModuleKey *key, int flags, long long length) REDISMODULE_ATTR; +REDISMODULE_API long long (*RedisModule_StreamTrimByID)(RedisModuleKey *key, int flags, RedisModuleStreamID *id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos) REDISMODULE_ATTR; +REDISMODULE_API unsigned long long (*RedisModule_GetClientId)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetClientUserNameById)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetClientInfoById)(void *ci, uint64_t id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_PublishMessage)(RedisModuleCtx *ctx, RedisModuleString *channel, RedisModuleString *message) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetContextFlags)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_AvoidReplicaTraffic)() REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleType * (*RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ModuleTypeReplaceValue)(RedisModuleKey *key, RedisModuleType *mt, void *new_value, void **old_value) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleType * (*RedisModule_ModuleTypeGetType)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_ModuleTypeGetValue)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsIOError)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SignalModifiedKey)(RedisModuleCtx *ctx, RedisModuleString *keyname) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value) REDISMODULE_ATTR; +REDISMODULE_API uint64_t (*RedisModule_LoadUnsigned)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value) REDISMODULE_ATTR; +REDISMODULE_API int64_t (*RedisModule_LoadSigned)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_EmitAOF)(RedisModuleIO *io, const char *cmdname, const char *fmt, ...) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveString)(RedisModuleIO *io, RedisModuleString *s) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveStringBuffer)(RedisModuleIO *io, const char *str, size_t len) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_LoadString)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API char * (*RedisModule_LoadStringBuffer)(RedisModuleIO *io, size_t *lenptr) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveDouble)(RedisModuleIO *io, double value) REDISMODULE_ATTR; +REDISMODULE_API double (*RedisModule_LoadDouble)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveFloat)(RedisModuleIO *io, float value) REDISMODULE_ATTR; +REDISMODULE_API float (*RedisModule_LoadFloat)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SaveLongDouble)(RedisModuleIO *io, long double value) REDISMODULE_ATTR; +REDISMODULE_API long double (*RedisModule_LoadLongDouble)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_LoadDataTypeFromString)(const RedisModuleString *str, const RedisModuleType *mt) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_SaveDataTypeToString)(RedisModuleCtx *ctx, void *data, const RedisModuleType *mt) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4); +REDISMODULE_API void (*RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...) REDISMODULE_ATTR REDISMODULE_ATTR_PRINTF(3,4); +REDISMODULE_API void (*RedisModule__Assert)(const char *estr, const char *file, int line) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_LatencyAddSample)(const char *event, mstime_t latency) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_HoldString)(RedisModuleCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCtx * (*RedisModule_GetContextFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromIO)(RedisModuleIO *io) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_GetKeyNameFromModuleKey)(RedisModuleKey *key) REDISMODULE_ATTR; +REDISMODULE_API long long (*RedisModule_Milliseconds)(void) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_DigestEndSequence)(RedisModuleDigest *md) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleDict * (*RedisModule_CreateDict)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d) REDISMODULE_ATTR; +REDISMODULE_API uint64_t (*RedisModule_DictSize)(RedisModuleDict *d) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleDictIter * (*RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_DictIteratorStop)(RedisModuleDictIter *di) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleServerInfoData * (*RedisModule_GetServerInfo)(RedisModuleCtx *ctx, const char *section) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeServerInfo)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_ServerInfoGetField)(RedisModuleCtx *ctx, RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR; +REDISMODULE_API const char * (*RedisModule_ServerInfoGetFieldC)(RedisModuleServerInfoData *data, const char* field) REDISMODULE_ATTR; +REDISMODULE_API long long (*RedisModule_ServerInfoGetFieldSigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR; +REDISMODULE_API unsigned long long (*RedisModule_ServerInfoGetFieldUnsigned)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR; +REDISMODULE_API double (*RedisModule_ServerInfoGetFieldDouble)(RedisModuleServerInfoData *data, const char* field, int *out_err) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetLRU)(RedisModuleKey *key, mstime_t lru_idle) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetLRU)(RedisModuleKey *key, mstime_t *lru_idle) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetLFU)(RedisModuleKey *key, long long lfu_freq) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetLFU)(RedisModuleKey *key, long long *lfu_freq) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClientOnKeys)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms, RedisModuleString **keys, int numkeys, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SignalKeyAsReady)(RedisModuleCtx *ctx, RedisModuleString *key) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetBlockedClientReadyKey)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleScanCursor * (*RedisModule_ScanCursorCreate)() REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ScanCursorRestart)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ScanCursorDestroy)(RedisModuleScanCursor *cursor) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_Scan)(RedisModuleCtx *ctx, RedisModuleScanCursor *cursor, RedisModuleScanCB fn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ScanKey)(RedisModuleKey *key, RedisModuleScanCursor *cursor, RedisModuleScanKeyCB fn, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetContextFlagsAll)() REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetKeyspaceNotificationFlagsAll)() REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsSubEventSupported)(RedisModuleEvent event, uint64_t subevent) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetServerVersion)() REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetTypeMethodVersion)() REDISMODULE_ATTR; + +/* Experimental APIs */ +#ifdef REDISMODULE_EXPERIMENTAL_API +#define REDISMODULE_EXPERIMENTAL_API_VERSION 3 +REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleBlockedClient * (*RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_AbortBlock)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_BlockedClientMeasureTimeStart)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_BlockedClientMeasureTimeEnd)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCtx * (*RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCtx * (*RedisModule_GetDetachedThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ThreadSafeContextTryLock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_NotifyKeyspaceEvent)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetNotifyKeyspaceEvents)() REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) REDISMODULE_ATTR; +REDISMODULE_API char ** (*RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeClusterNodesList)(char **ids) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleTimerID (*RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) REDISMODULE_ATTR; +REDISMODULE_API const char * (*RedisModule_GetMyClusterID)(void) REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_GetClusterSize)(void) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_GetRandomBytes)(unsigned char *dst, size_t len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_GetRandomHexChars)(char *dst, size_t len) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func) REDISMODULE_ATTR; +REDISMODULE_API void * (*RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleCommandFilter * (*RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx) REDISMODULE_ATTR; +REDISMODULE_API const RedisModuleString * (*RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_SendChildHeartbeat)(double progress) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_ExitFromChild)(int retcode) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_KillForkChild)(int child_pid) REDISMODULE_ATTR; +REDISMODULE_API float (*RedisModule_GetUsedMemoryRatio)() REDISMODULE_ATTR; +REDISMODULE_API size_t (*RedisModule_MallocSize)(void* ptr) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleUser * (*RedisModule_CreateModuleUser)(const char *name) REDISMODULE_ATTR; +REDISMODULE_API void (*RedisModule_FreeModuleUser)(RedisModuleUser *user) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_SetModuleUserACL)(RedisModuleUser *user, const char* acl) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_AuthenticateClientWithACLUser)(RedisModuleCtx *ctx, const char *name, size_t len, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_AuthenticateClientWithUser)(RedisModuleCtx *ctx, RedisModuleUser *user, RedisModuleUserChangedFunc callback, void *privdata, uint64_t *client_id) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DeauthenticateAndCloseClient)(RedisModuleCtx *ctx, uint64_t client_id) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString * (*RedisModule_GetClientCertificate)(RedisModuleCtx *ctx, uint64_t id) REDISMODULE_ATTR; +REDISMODULE_API int *(*RedisModule_GetCommandKeys)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc, int *num_keys) REDISMODULE_ATTR; +REDISMODULE_API const char *(*RedisModule_GetCurrentCommandName)(RedisModuleCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_RegisterDefragFunc)(RedisModuleCtx *ctx, RedisModuleDefragFunc func) REDISMODULE_ATTR; +REDISMODULE_API void *(*RedisModule_DefragAlloc)(RedisModuleDefragCtx *ctx, void *ptr) REDISMODULE_ATTR; +REDISMODULE_API RedisModuleString *(*RedisModule_DefragRedisModuleString)(RedisModuleDefragCtx *ctx, RedisModuleString *str) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DefragShouldStop)(RedisModuleDefragCtx *ctx) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DefragCursorSet)(RedisModuleDefragCtx *ctx, unsigned long cursor) REDISMODULE_ATTR; +REDISMODULE_API int (*RedisModule_DefragCursorGet)(RedisModuleDefragCtx *ctx, unsigned long *cursor) REDISMODULE_ATTR; +#endif + +#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX) + +/* This is included inline inside each Redis module. */ +static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) REDISMODULE_ATTR_UNUSED; +static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) { + void *getapifuncptr = ((void**)ctx)[0]; + RedisModule_GetApi = (int (*)(const char *, void *)) (unsigned long)getapifuncptr; + REDISMODULE_GET_API(Alloc); + REDISMODULE_GET_API(Calloc); + REDISMODULE_GET_API(Free); + REDISMODULE_GET_API(Realloc); + REDISMODULE_GET_API(Strdup); + REDISMODULE_GET_API(CreateCommand); + REDISMODULE_GET_API(SetModuleAttribs); + REDISMODULE_GET_API(IsModuleNameBusy); + REDISMODULE_GET_API(WrongArity); + REDISMODULE_GET_API(ReplyWithLongLong); + REDISMODULE_GET_API(ReplyWithError); + REDISMODULE_GET_API(ReplyWithSimpleString); + REDISMODULE_GET_API(ReplyWithArray); + REDISMODULE_GET_API(ReplyWithNullArray); + REDISMODULE_GET_API(ReplyWithEmptyArray); + REDISMODULE_GET_API(ReplySetArrayLength); + REDISMODULE_GET_API(ReplyWithStringBuffer); + REDISMODULE_GET_API(ReplyWithCString); + REDISMODULE_GET_API(ReplyWithString); + REDISMODULE_GET_API(ReplyWithEmptyString); + REDISMODULE_GET_API(ReplyWithVerbatimString); + REDISMODULE_GET_API(ReplyWithNull); + REDISMODULE_GET_API(ReplyWithCallReply); + REDISMODULE_GET_API(ReplyWithDouble); + REDISMODULE_GET_API(ReplyWithLongDouble); + REDISMODULE_GET_API(GetSelectedDb); + REDISMODULE_GET_API(SelectDb); + REDISMODULE_GET_API(OpenKey); + REDISMODULE_GET_API(CloseKey); + REDISMODULE_GET_API(KeyType); + REDISMODULE_GET_API(ValueLength); + REDISMODULE_GET_API(ListPush); + REDISMODULE_GET_API(ListPop); + REDISMODULE_GET_API(StringToLongLong); + REDISMODULE_GET_API(StringToDouble); + REDISMODULE_GET_API(StringToLongDouble); + REDISMODULE_GET_API(StringToStreamID); + REDISMODULE_GET_API(Call); + REDISMODULE_GET_API(CallReplyProto); + REDISMODULE_GET_API(FreeCallReply); + REDISMODULE_GET_API(CallReplyInteger); + REDISMODULE_GET_API(CallReplyType); + REDISMODULE_GET_API(CallReplyLength); + REDISMODULE_GET_API(CallReplyArrayElement); + REDISMODULE_GET_API(CallReplyStringPtr); + REDISMODULE_GET_API(CreateStringFromCallReply); + REDISMODULE_GET_API(CreateString); + REDISMODULE_GET_API(CreateStringFromLongLong); + REDISMODULE_GET_API(CreateStringFromDouble); + REDISMODULE_GET_API(CreateStringFromLongDouble); + REDISMODULE_GET_API(CreateStringFromString); + REDISMODULE_GET_API(CreateStringFromStreamID); + REDISMODULE_GET_API(CreateStringPrintf); + REDISMODULE_GET_API(FreeString); + REDISMODULE_GET_API(StringPtrLen); + REDISMODULE_GET_API(AutoMemory); + REDISMODULE_GET_API(Replicate); + REDISMODULE_GET_API(ReplicateVerbatim); + REDISMODULE_GET_API(DeleteKey); + REDISMODULE_GET_API(UnlinkKey); + REDISMODULE_GET_API(StringSet); + REDISMODULE_GET_API(StringDMA); + REDISMODULE_GET_API(StringTruncate); + REDISMODULE_GET_API(GetExpire); + REDISMODULE_GET_API(SetExpire); + REDISMODULE_GET_API(GetAbsExpire); + REDISMODULE_GET_API(SetAbsExpire); + REDISMODULE_GET_API(ResetDataset); + REDISMODULE_GET_API(DbSize); + REDISMODULE_GET_API(RandomKey); + REDISMODULE_GET_API(ZsetAdd); + REDISMODULE_GET_API(ZsetIncrby); + REDISMODULE_GET_API(ZsetScore); + REDISMODULE_GET_API(ZsetRem); + REDISMODULE_GET_API(ZsetRangeStop); + REDISMODULE_GET_API(ZsetFirstInScoreRange); + REDISMODULE_GET_API(ZsetLastInScoreRange); + REDISMODULE_GET_API(ZsetFirstInLexRange); + REDISMODULE_GET_API(ZsetLastInLexRange); + REDISMODULE_GET_API(ZsetRangeCurrentElement); + REDISMODULE_GET_API(ZsetRangeNext); + REDISMODULE_GET_API(ZsetRangePrev); + REDISMODULE_GET_API(ZsetRangeEndReached); + REDISMODULE_GET_API(HashSet); + REDISMODULE_GET_API(HashGet); + REDISMODULE_GET_API(StreamAdd); + REDISMODULE_GET_API(StreamDelete); + REDISMODULE_GET_API(StreamIteratorStart); + REDISMODULE_GET_API(StreamIteratorStop); + REDISMODULE_GET_API(StreamIteratorNextID); + REDISMODULE_GET_API(StreamIteratorNextField); + REDISMODULE_GET_API(StreamIteratorDelete); + REDISMODULE_GET_API(StreamTrimByLength); + REDISMODULE_GET_API(StreamTrimByID); + REDISMODULE_GET_API(IsKeysPositionRequest); + REDISMODULE_GET_API(KeyAtPos); + REDISMODULE_GET_API(GetClientId); + REDISMODULE_GET_API(GetClientUserNameById); + REDISMODULE_GET_API(GetContextFlags); + REDISMODULE_GET_API(AvoidReplicaTraffic); + REDISMODULE_GET_API(PoolAlloc); + REDISMODULE_GET_API(CreateDataType); + REDISMODULE_GET_API(ModuleTypeSetValue); + REDISMODULE_GET_API(ModuleTypeReplaceValue); + REDISMODULE_GET_API(ModuleTypeGetType); + REDISMODULE_GET_API(ModuleTypeGetValue); + REDISMODULE_GET_API(IsIOError); + REDISMODULE_GET_API(SetModuleOptions); + REDISMODULE_GET_API(SignalModifiedKey); + REDISMODULE_GET_API(SaveUnsigned); + REDISMODULE_GET_API(LoadUnsigned); + REDISMODULE_GET_API(SaveSigned); + REDISMODULE_GET_API(LoadSigned); + REDISMODULE_GET_API(SaveString); + REDISMODULE_GET_API(SaveStringBuffer); + REDISMODULE_GET_API(LoadString); + REDISMODULE_GET_API(LoadStringBuffer); + REDISMODULE_GET_API(SaveDouble); + REDISMODULE_GET_API(LoadDouble); + REDISMODULE_GET_API(SaveFloat); + REDISMODULE_GET_API(LoadFloat); + REDISMODULE_GET_API(SaveLongDouble); + REDISMODULE_GET_API(LoadLongDouble); + REDISMODULE_GET_API(SaveDataTypeToString); + REDISMODULE_GET_API(LoadDataTypeFromString); + REDISMODULE_GET_API(EmitAOF); + REDISMODULE_GET_API(Log); + REDISMODULE_GET_API(LogIOError); + REDISMODULE_GET_API(_Assert); + REDISMODULE_GET_API(LatencyAddSample); + REDISMODULE_GET_API(StringAppendBuffer); + REDISMODULE_GET_API(RetainString); + REDISMODULE_GET_API(HoldString); + REDISMODULE_GET_API(StringCompare); + REDISMODULE_GET_API(GetContextFromIO); + REDISMODULE_GET_API(GetKeyNameFromIO); + REDISMODULE_GET_API(GetKeyNameFromModuleKey); + REDISMODULE_GET_API(Milliseconds); + REDISMODULE_GET_API(DigestAddStringBuffer); + REDISMODULE_GET_API(DigestAddLongLong); + REDISMODULE_GET_API(DigestEndSequence); + REDISMODULE_GET_API(CreateDict); + REDISMODULE_GET_API(FreeDict); + REDISMODULE_GET_API(DictSize); + REDISMODULE_GET_API(DictSetC); + REDISMODULE_GET_API(DictReplaceC); + REDISMODULE_GET_API(DictSet); + REDISMODULE_GET_API(DictReplace); + REDISMODULE_GET_API(DictGetC); + REDISMODULE_GET_API(DictGet); + REDISMODULE_GET_API(DictDelC); + REDISMODULE_GET_API(DictDel); + REDISMODULE_GET_API(DictIteratorStartC); + REDISMODULE_GET_API(DictIteratorStart); + REDISMODULE_GET_API(DictIteratorStop); + REDISMODULE_GET_API(DictIteratorReseekC); + REDISMODULE_GET_API(DictIteratorReseek); + REDISMODULE_GET_API(DictNextC); + REDISMODULE_GET_API(DictPrevC); + REDISMODULE_GET_API(DictNext); + REDISMODULE_GET_API(DictPrev); + REDISMODULE_GET_API(DictCompare); + REDISMODULE_GET_API(DictCompareC); + REDISMODULE_GET_API(RegisterInfoFunc); + REDISMODULE_GET_API(InfoAddSection); + REDISMODULE_GET_API(InfoBeginDictField); + REDISMODULE_GET_API(InfoEndDictField); + REDISMODULE_GET_API(InfoAddFieldString); + REDISMODULE_GET_API(InfoAddFieldCString); + REDISMODULE_GET_API(InfoAddFieldDouble); + REDISMODULE_GET_API(InfoAddFieldLongLong); + REDISMODULE_GET_API(InfoAddFieldULongLong); + REDISMODULE_GET_API(GetServerInfo); + REDISMODULE_GET_API(FreeServerInfo); + REDISMODULE_GET_API(ServerInfoGetField); + REDISMODULE_GET_API(ServerInfoGetFieldC); + REDISMODULE_GET_API(ServerInfoGetFieldSigned); + REDISMODULE_GET_API(ServerInfoGetFieldUnsigned); + REDISMODULE_GET_API(ServerInfoGetFieldDouble); + REDISMODULE_GET_API(GetClientInfoById); + REDISMODULE_GET_API(PublishMessage); + REDISMODULE_GET_API(SubscribeToServerEvent); + REDISMODULE_GET_API(SetLRU); + REDISMODULE_GET_API(GetLRU); + REDISMODULE_GET_API(SetLFU); + REDISMODULE_GET_API(GetLFU); + REDISMODULE_GET_API(BlockClientOnKeys); + REDISMODULE_GET_API(SignalKeyAsReady); + REDISMODULE_GET_API(GetBlockedClientReadyKey); + REDISMODULE_GET_API(ScanCursorCreate); + REDISMODULE_GET_API(ScanCursorRestart); + REDISMODULE_GET_API(ScanCursorDestroy); + REDISMODULE_GET_API(Scan); + REDISMODULE_GET_API(ScanKey); + REDISMODULE_GET_API(GetContextFlagsAll); + REDISMODULE_GET_API(GetKeyspaceNotificationFlagsAll); + REDISMODULE_GET_API(IsSubEventSupported); + REDISMODULE_GET_API(GetServerVersion); + REDISMODULE_GET_API(GetTypeMethodVersion); + +#ifdef REDISMODULE_EXPERIMENTAL_API + REDISMODULE_GET_API(GetThreadSafeContext); + REDISMODULE_GET_API(GetDetachedThreadSafeContext); + REDISMODULE_GET_API(FreeThreadSafeContext); + REDISMODULE_GET_API(ThreadSafeContextLock); + REDISMODULE_GET_API(ThreadSafeContextTryLock); + REDISMODULE_GET_API(ThreadSafeContextUnlock); + REDISMODULE_GET_API(BlockClient); + REDISMODULE_GET_API(UnblockClient); + REDISMODULE_GET_API(IsBlockedReplyRequest); + REDISMODULE_GET_API(IsBlockedTimeoutRequest); + REDISMODULE_GET_API(GetBlockedClientPrivateData); + REDISMODULE_GET_API(GetBlockedClientHandle); + REDISMODULE_GET_API(AbortBlock); + REDISMODULE_GET_API(BlockedClientMeasureTimeStart); + REDISMODULE_GET_API(BlockedClientMeasureTimeEnd); + REDISMODULE_GET_API(SetDisconnectCallback); + REDISMODULE_GET_API(SubscribeToKeyspaceEvents); + REDISMODULE_GET_API(NotifyKeyspaceEvent); + REDISMODULE_GET_API(GetNotifyKeyspaceEvents); + REDISMODULE_GET_API(BlockedClientDisconnected); + REDISMODULE_GET_API(RegisterClusterMessageReceiver); + REDISMODULE_GET_API(SendClusterMessage); + REDISMODULE_GET_API(GetClusterNodeInfo); + REDISMODULE_GET_API(GetClusterNodesList); + REDISMODULE_GET_API(FreeClusterNodesList); + REDISMODULE_GET_API(CreateTimer); + REDISMODULE_GET_API(StopTimer); + REDISMODULE_GET_API(GetTimerInfo); + REDISMODULE_GET_API(GetMyClusterID); + REDISMODULE_GET_API(GetClusterSize); + REDISMODULE_GET_API(GetRandomBytes); + REDISMODULE_GET_API(GetRandomHexChars); + REDISMODULE_GET_API(SetClusterFlags); + REDISMODULE_GET_API(ExportSharedAPI); + REDISMODULE_GET_API(GetSharedAPI); + REDISMODULE_GET_API(RegisterCommandFilter); + REDISMODULE_GET_API(UnregisterCommandFilter); + REDISMODULE_GET_API(CommandFilterArgsCount); + REDISMODULE_GET_API(CommandFilterArgGet); + REDISMODULE_GET_API(CommandFilterArgInsert); + REDISMODULE_GET_API(CommandFilterArgReplace); + REDISMODULE_GET_API(CommandFilterArgDelete); + REDISMODULE_GET_API(Fork); + REDISMODULE_GET_API(SendChildHeartbeat); + REDISMODULE_GET_API(ExitFromChild); + REDISMODULE_GET_API(KillForkChild); + REDISMODULE_GET_API(GetUsedMemoryRatio); + REDISMODULE_GET_API(MallocSize); + REDISMODULE_GET_API(CreateModuleUser); + REDISMODULE_GET_API(FreeModuleUser); + REDISMODULE_GET_API(SetModuleUserACL); + REDISMODULE_GET_API(DeauthenticateAndCloseClient); + REDISMODULE_GET_API(AuthenticateClientWithACLUser); + REDISMODULE_GET_API(AuthenticateClientWithUser); + REDISMODULE_GET_API(GetClientCertificate); + REDISMODULE_GET_API(GetCommandKeys); + REDISMODULE_GET_API(GetCurrentCommandName); + REDISMODULE_GET_API(RegisterDefragFunc); + REDISMODULE_GET_API(DefragAlloc); + REDISMODULE_GET_API(DefragRedisModuleString); + REDISMODULE_GET_API(DefragShouldStop); + REDISMODULE_GET_API(DefragCursorSet); + REDISMODULE_GET_API(DefragCursorGet); +#endif + + if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR; + RedisModule_SetModuleAttribs(ctx,name,ver,apiver); + return REDISMODULE_OK; +} + +#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1))) + +#define RMAPI_FUNC_SUPPORTED(func) (func != NULL) + +#else + +/* Things only defined for the modules core, not exported to modules + * including this file. */ +#define RedisModuleString robj + +#endif /* REDISMODULE_CORE */ + +#ifdef __cplusplus +} +#endif + +#endif /* REDISMODULE_H */ From c94560f0bad462727981463d97255a8182556d79 Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 13:32:20 -0600 Subject: [PATCH 2/6] rm --- .../cpp-statsd-client/.clang-format | 105 --- .../.github/workflows/coverage.yml | 30 - .../.github/workflows/lint.yml | 13 - .../.github/workflows/linux.yml | 23 - .../.github/workflows/windows.yml | 18 - .../cpp-statsd-client/.gitignore | 1 - .../cpp-statsd-client/CMakeLists.txt | 85 --- .../cpp-statsd-client/LICENSE.md | 9 - .../cpp-statsd-client/Makefile | 22 - .../cpp-statsd-client/README.md | 150 ---- .../cmake/CodeCoverage.cmake | 708 ------------------ .../cpp-statsd-client/cmake/Config.cmake.in | 18 - .../cmake/cpp-statsd-clientConfig.cmake.in | 4 - .../cpp-statsd-client/images/logo.svg | 16 - .../cpp-statsd-client/StatsdClient.hpp | 318 -------- .../include/cpp-statsd-client/UDPSender.hpp | 345 --------- .../cpp-statsd-client/tests/StatsdServer.hpp | 80 -- .../tests/testStatsdClient.cpp | 184 ----- 18 files changed, 2129 deletions(-) delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/Makefile delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/README.md delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp delete mode 100644 src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format b/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format deleted file mode 100644 index 2d8fd6e65..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.clang-format +++ /dev/null @@ -1,105 +0,0 @@ -AccessModifierOffset: -4 -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortFunctionsOnASingleLine: Empty -BinPackArguments: false -BinPackParameters: false -ColumnLimit: 120 -IndentCaseLabels: false -IndentWidth: 4 - ---- -Language: Cpp -# BasedOnStyle: Google -#AccessModifierOffset: -1 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlinesLeft: true -AlignOperands: true -AlignTrailingComments: true -#AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -#AllowShortFunctionsOnASingleLine: All -AllowShortIfStatementsOnASingleLine: true -AllowShortLoopsOnASingleLine: true -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: true -#BinPackArguments: true -#BinPackParameters: true -BraceWrapping: - AfterClass: false - AfterControlStatement: false - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false -BreakBeforeBinaryOperators: None -BreakBeforeBraces: Attach -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: true -#ColumnLimit: 80 -CommentPragmas: '^ IWYU pragma:' -ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: true -DerivePointerAlignment: true -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] -IncludeCategories: - - Regex: '^<.*\.h>' - Priority: 1 - - Regex: '^<.*' - Priority: 2 - - Regex: '.*' - Priority: 3 -IncludeIsMainRegex: '([-_](test|unittest))?$' -#IndentCaseLabels: true -#IndentWidth: 2 -IndentWrappedFunctionNames: false -JavaScriptQuotes: Leave -JavaScriptWrapImports: true -KeepEmptyLinesAtTheStartOfBlocks: false -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 -NamespaceIndentation: None -ObjCBlockIndentWidth: 2 -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: false -PenaltyBreakBeforeFirstCallParameter: 1 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakString: 1000 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 200 -PointerAlignment: Left -ReflowComments: true -SortIncludes: true -SpaceAfterCStyleCast: false -SpaceAfterTemplateKeyword: true -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Auto -TabWidth: 8 -UseTab: Never -... - diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml deleted file mode 100644 index 4642670ee..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/coverage.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Coverage - -on: [push, pull_request] -jobs: - coverage: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: dependencies - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y -qq make cmake gcc g++ lcov bc - - name: build - shell: bash - run: | - export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g") - cmake . -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=On - make all -j$(nproc) - - name: coverage - shell: bash - run: | - make coverage - lines=$(lcov --summary coverage.info | grep -F lines | awk '{print $2}' | sed -e "s/%//g") - if (( $(echo "${lines} < ${COVERAGE_THRESHOLD}" | bc -l) )); then - echo "Line coverage dropped below ${COVERAGE_THRESHOLD}% to ${lines}%" - exit 1 - fi - env: - COVERAGE_THRESHOLD: 85.0 diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml deleted file mode 100644 index 1ed921645..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/lint.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Lint - -on: [push, pull_request] -jobs: - lint: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: lint - uses: DoozyX/clang-format-lint-action@v0.12 - with: - clangFormatVersion: 12 - source: './include/cpp-statsd-client ./tests' diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml deleted file mode 100644 index a431e1408..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/linux.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Linux - -on: [push, pull_request] -jobs: - linux: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - name: dependencies - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y -qq make cmake gcc g++ - - name: build - shell: bash - run: | - export LD_LIBRARY_PATH=.:$(cat /etc/ld.so.conf.d/* | grep -vF "#" | tr "\\n" ":" | sed -e "s/:$//g") - cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SANITIZERS=On - make all -j$(nproc) - - name: test - shell: bash - run: | - make test diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml b/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml deleted file mode 100644 index 3b864009c..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.github/workflows/windows.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Windows - -on: [push, pull_request] -jobs: - windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - name: dependencies - run: | - choco install cmake - - name: build - run: | - cmake -S . -B build -G "Visual Studio 16 2019" -A x64 - cmake --build build --target ALL_BUILD --config Release - - name: test - run: | - cmake --build build --target RUN_TESTS --config Release diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore b/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore deleted file mode 100644 index c5e82d745..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/.gitignore +++ /dev/null @@ -1 +0,0 @@ -bin \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt b/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt deleted file mode 100644 index 9b8d64cfa..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/CMakeLists.txt +++ /dev/null @@ -1,85 +0,0 @@ -# Basic project setup -cmake_minimum_required(VERSION 3.5) -project(cpp-statsd-client - VERSION 1.0.2 - LANGUAGES CXX - DESCRIPTION "A header-only StatsD client implemented in C++" - HOMEPAGE_URL "https://github.com/vthiery/cpp-statsd-client") - -option(CPP_STATSD_STANDALONE "Allows configuration of targets for verifying library functionality" ON) -option(ENABLE_TESTS "Build tests" ON) -option(ENABLE_COVERAGE "Build with coverage instrumentalisation" OFF) - -if(NOT CPP_STATSD_STANDALONE) - set(ENABLE_TESTS OFF) - set(ENABLE_COVERAGE OFF) -endif() - -include(GNUInstallDirs) -include(CMakePackageConfigHelpers) -find_package(Threads) - -# Optional code coverage targets -if(ENABLE_COVERAGE) - set(COVERAGE_EXCLUDES /usr/*) - include(${PROJECT_SOURCE_DIR}/cmake/CodeCoverage.cmake) - APPEND_COVERAGE_COMPILER_FLAGS() - SETUP_TARGET_FOR_COVERAGE_LCOV(NAME coverage - EXECUTABLE testStatsdClient - DEPENDENCIES ${PROJECT_NAME} - ) -endif() - -# The library target -add_library(${PROJECT_NAME} INTERFACE) -target_include_directories( - ${PROJECT_NAME} - INTERFACE $ - $) -target_link_libraries(${PROJECT_NAME} INTERFACE Threads::Threads) -if(WIN32) - target_link_libraries(${PROJECT_NAME} INTERFACE ws2_32) -endif() - -# The installation and pkg-config-like cmake config -install(TARGETS ${PROJECT_NAME} - EXPORT ${PROJECT_NAME}_Targets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) -configure_package_config_file( - "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake.in" - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - INSTALL_DESTINATION - ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) -install(EXPORT ${PROJECT_NAME}_Targets - FILE ${PROJECT_NAME}Targets.cmake - NAMESPACE ${PROJECT_NAME}:: - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) -install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" - "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION include) - -if(ENABLE_TESTS) - # The test targets - add_executable(testStatsdClient ${CMAKE_CURRENT_SOURCE_DIR}/tests/testStatsdClient.cpp) - if(WIN32) - target_compile_options(testStatsdClient PRIVATE -W4 -WX /external:W0) - else() - target_compile_options(testStatsdClient PRIVATE -Wall -Wextra -pedantic -Werror) - endif() - target_include_directories(testStatsdClient PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) - target_link_libraries(testStatsdClient ${PROJECT_NAME}) - - set_property(TARGET testStatsdClient PROPERTY CXX_STANDARD 11) - set_property(TARGET testStatsdClient PROPERTY CXX_EXTENSIONS OFF) - - # The test suite - enable_testing() - add_test(ctestTestStatsdClient testStatsdClient) - add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS testStatsdClient) -endif() diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md b/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md deleted file mode 100644 index 632d564b7..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2017 Vincent Thiery - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile b/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile deleted file mode 100644 index 071e8bd91..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# simple makefile to build, test and clean - -BUILD_MODE ?= Release -ENABLE_COVERAGE ?= On - -build: clean - @echo "Build in ${BUILD_MODE} mode" - mkdir -p bin/${BUILD_MODE} - @cd bin/${BUILD_MODE}; cmake ../../ -DCMAKE_BUILD_TYPE=${BUILD_MODE} -DENABLE_COVERAGE=${ENABLE_COVERAGE} - @cd bin/${BUILD_MODE}; make - -test: build - @cd bin/${BUILD_MODE}; make test - -coverage: build - @cd bin/${BUILD_MODE}; make coverage - -install: build - @cd bin/${BUILD_MODE}; make install - -clean: - @rm -rf bin diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/README.md b/src/modules/keydb_modstatsd/cpp-statsd-client/README.md deleted file mode 100644 index 9e8d39b55..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# C++ StatsD Client - -![logo](https://raw.githubusercontent.com/vthiery/cpp-statsd-client/master/images/logo.svg?sanitize=true) - -[![Release](https://img.shields.io/github/release/vthiery/cpp-statsd-client.svg?style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/releases/latest) -![License](https://img.shields.io/github/license/vthiery/cpp-statsd-client?style=for-the-badge) -[![Linux status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Linux?label=Linux&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/linux.yml?query=branch%3Amaster++) -[![Windows status](https://img.shields.io/github/workflow/status/vthiery/cpp-statsd-client/Windows?label=Windows&style=for-the-badge)](https://github.com/vthiery/cpp-statsd-client/actions/workflows/windows.yml?query=branch%3Amaster++) - -A header-only StatsD client implemented in C++. -The client allows: - -- batching, -- change of configuration at runtime, -- user-defined frequency rate. - -## Install and Test - -### Makefile - -In order to install the header files and/or run the tests, simply use the Makefile and execute - -```sh -make install -``` - -and - -```sh -make test -``` - -### Conan - -If you are using [Conan](https://www.conan.io/) to manage your dependencies, merely add statsdclient/x.y.z@vthiery/stable to your conanfile.py's requires, where x.y.z is the release version you want to use. Please file issues here if you experience problems with the packages. You can also directly download the latest version [here](https://bintray.com/vthiery/conan-packages/statsdclient%3Avthiery/_latestVersion). - -## Usage - -### Example - -A simple example of how to use the client: - -```cpp -#include "StatsdClient.hpp" -using namespace Statsd; - -int main() { - // Define the client on localhost, with port 8080, - // using a prefix, - // a batching size of 20 bytes, - // and three points of precision for floating point gauge values - StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 }; - - // Increment the metric "coco" - client.increment("coco"); - - // Decrement the metric "kiki" - client.decrement("kiki"); - - // Adjusts "toto" by +3 - client.count("toto", 2, 0.1f); - - // Record a gauge "titi" to 3 - client.gauge("titi", 3); - - // Record a timing of 2ms for "myTiming" - client.timing("myTiming", 2, 0.1f); - - // Send a metric explicitly - client.send("tutu", 4, "c", 2.0f); - exit(0); -} -``` - -### Advanced Testing - -A simple mock StatsD server can be found at `tests/StatsdServer.hpp`. This can be used to do simple validation of your application's metrics, typically in the form of unit tests. In fact this is the primary means by which this library is tested. The mock server itself is not distributed with the library so to use it you'd need to vendor this project into your project. Once you have though, you can test your application's use of the client like so: - -```cpp -#include "StatsdClient.hpp" -#include "StatsdServer.hpp" - -#include - -using namespace Statsd; - -struct MyApp { - void doWork() const { - m_client.count("bar", 3); - } -private: - StatsdClient m_client{"localhost", 8125, "foo"}; -}; - -int main() { - StatsdServer mockServer; - - MyApp app; - app.doWork(); - - assert(mockServer.receive() == "foo.bar:3|c"); - exit(0); -} -``` - -### Configuration - -The configuration of the client must be input when one instantiates it. Nevertheless, the API allows the configuration ot change afterwards. For example, one can do the following: - -```cpp -#include "StatsdClient.hpp" -using namespace Statsd; - -int main() -{ - // Define the client on localhost, with port 8080, - // using a prefix, - // a batching size of 20 bytes, - // and three points of precision for floating point gauge values - StatsdClient client{ "127.0.0.1", 8080, "myPrefix", 20, 3 }; - - client.increment("coco"); - - // Set a new configuration, using a different port, a different prefix, and more gauge precision - client.setConfig("127.0.0.1", 8000, "anotherPrefix", 6); - - client.decrement("kiki"); -} -``` - -The batchsize is the only parameter that cannot be changed for the time being. - -### Batching - -The client supports batching of the metrics. The batch size parameter is the number of bytes to allow in each batch (UDP datagram payload) to be sent to the statsd process. This number is not a hard limit. If appending the current stat to the current batch (separated by the `'\n'` character) pushes the current batch over the batch size, that batch is enqueued (not sent) and a new batch is started. If batch size is 0, the default, then each stat is sent individually to the statsd process and no batches are enqueued. - -### Sending - -As previously mentioned, if batching is disabled (by setting the batch size to 0) then every stat is sent immediately in a blocking fashion. If batching is enabled (ie non-zero) then you may also set the send interval. The send interval controls the time, in milliseconds, to wait before flushing/sending the queued stats batches to the statsd process. When the send interval is non-zero a background thread is spawned which will do the flushing/sending at the configured send interval, in other words asynchronously. The queuing mechanism in this case is *not* lock-free. If batching is enabled but the send interval is set to zero then the queued batchs of stats will not be sent automatically by a background thread but must be sent manually via the `flush` method. The `flush` method is a blocking call. - - -### Frequency rate - -When sending a metric, a frequency rate can be set in order to limit the metrics' sampling. By default, the frequency rate is set to one and won't affect the sampling. If set to a value `epsilon` (0.0001 for the time being) close to one, the sampling is not affected either. - -If the frequency rate is set and `epsilon` different from one, the sending will be rejected randomly (the higher the frequency rate, the lower the probability of rejection). - -## License - -This library is under MIT license. diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake deleted file mode 100644 index 39da5786b..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/CodeCoverage.cmake +++ /dev/null @@ -1,708 +0,0 @@ -# Copyright (c) 2012 - 2017, Lars Bilke -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# CHANGES: -# -# 2012-01-31, Lars Bilke -# - Enable Code Coverage -# -# 2013-09-17, Joakim Söderberg -# - Added support for Clang. -# - Some additional usage instructions. -# -# 2016-02-03, Lars Bilke -# - Refactored functions to use named parameters -# -# 2017-06-02, Lars Bilke -# - Merged with modified version from github.com/ufz/ogs -# -# 2019-05-06, Anatolii Kurotych -# - Remove unnecessary --coverage flag -# -# 2019-12-13, FeRD (Frank Dana) -# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor -# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. -# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY -# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list -# - Set lcov basedir with -b argument -# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be -# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) -# - Delete output dir, .info file on 'make clean' -# - Remove Python detection, since version mismatches will break gcovr -# - Minor cleanup (lowercase function names, update examples...) -# -# 2019-12-19, FeRD (Frank Dana) -# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets -# -# 2020-01-19, Bob Apthorpe -# - Added gfortran support -# -# 2020-02-17, FeRD (Frank Dana) -# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters -# in EXCLUDEs, and remove manual escaping from gcovr targets -# -# 2021-01-19, Robin Mueller -# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run -# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional -# flags to the gcovr command -# -# 2020-05-04, Mihchael Davis -# - Add -fprofile-abs-path to make gcno files contain absolute paths -# - Fix BASE_DIRECTORY not working when defined -# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines -# -# 2021-05-10, Martin Stump -# - Check if the generator is multi-config before warning about non-Debug builds -# -# USAGE: -# -# 1. Copy this file into your cmake modules path. -# -# 2. Add the following line to your CMakeLists.txt (best inside an if-condition -# using a CMake option() to enable it just optionally): -# include(CodeCoverage) -# -# 3. Append necessary compiler flags: -# append_coverage_compiler_flags() -# -# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og -# -# 4. If you need to exclude additional directories from the report, specify them -# using full paths in the COVERAGE_EXCLUDES variable before calling -# setup_target_for_coverage_*(). -# Example: -# set(COVERAGE_EXCLUDES -# '${PROJECT_SOURCE_DIR}/src/dir1/*' -# '/path/to/my/src/dir2/*') -# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). -# Example: -# setup_target_for_coverage_lcov( -# NAME coverage -# EXECUTABLE testrunner -# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") -# -# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set -# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) -# Example: -# set(COVERAGE_EXCLUDES "dir1/*") -# setup_target_for_coverage_gcovr_html( -# NAME coverage -# EXECUTABLE testrunner -# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" -# EXCLUDE "dir2/*") -# -# 5. Use the functions described below to create a custom make target which -# runs your test executable and produces a code coverage report. -# -# 6. Build a Debug build: -# cmake -DCMAKE_BUILD_TYPE=Debug .. -# make -# make my_coverage_target -# - -include(CMakeParseArguments) - -option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) - -# Check prereqs -find_program( GCOV_PATH gcov ) -find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) -find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) -find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) -find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) -find_program( CPPFILT_PATH NAMES c++filt ) - -if(NOT GCOV_PATH) - message(FATAL_ERROR "gcov not found! Aborting...") -endif() # NOT GCOV_PATH - -get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) -list(GET LANGUAGES 0 LANG) - -if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) - message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - endif() -elseif(NOT CMAKE_COMPILER_IS_GNUCXX) - if("${CMAKE_Fortran_COMPILER_ID}" MATCHES "[Ff]lang") - # Do nothing; exit conditional without error if true - elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU") - # Do nothing; exit conditional without error if true - else() - message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") - endif() -endif() - -set(COVERAGE_COMPILER_FLAGS "-g -fprofile-arcs -ftest-coverage" - CACHE INTERNAL "") -if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") - include(CheckCXXCompilerFlag) - check_cxx_compiler_flag(-fprofile-abs-path HAVE_fprofile_abs_path) - if(HAVE_fprofile_abs_path) - set(COVERAGE_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") - endif() -endif() - -set(CMAKE_Fortran_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the Fortran compiler during coverage builds." - FORCE ) -set(CMAKE_CXX_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C++ compiler during coverage builds." - FORCE ) -set(CMAKE_C_FLAGS_COVERAGE - ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C compiler during coverage builds." - FORCE ) -set(CMAKE_EXE_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used for linking binaries during coverage builds." - FORCE ) -set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE - "" - CACHE STRING "Flags used by the shared libraries linker during coverage builds." - FORCE ) -mark_as_advanced( - CMAKE_Fortran_FLAGS_COVERAGE - CMAKE_CXX_FLAGS_COVERAGE - CMAKE_C_FLAGS_COVERAGE - CMAKE_EXE_LINKER_FLAGS_COVERAGE - CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) - -get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) - message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") -endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") - link_libraries(gcov) -endif() - -# Defines a target for running and collection code coverage information -# Builds dependencies, runs the given executable and outputs reports. -# NOTE! The executable should always have a ZERO as exit code otherwise -# the coverage generation will not complete. -# -# setup_target_for_coverage_lcov( -# NAME testrunner_coverage # New target name -# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR -# DEPENDENCIES testrunner # Dependencies to build first -# BASE_DIRECTORY "../" # Base directory for report -# # (defaults to PROJECT_SOURCE_DIR) -# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative -# # to BASE_DIRECTORY, with CMake 3.4+) -# NO_DEMANGLE # Don't demangle C++ symbols -# # even if c++filt is found -# ) -function(setup_target_for_coverage_lcov) - - set(options NO_DEMANGLE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() # NOT LCOV_PATH - - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() # NOT GENHTML_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(LCOV_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND LCOV_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES LCOV_EXCLUDES) - - # Conditional arguments - if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) - set(GENHTML_EXTRA_ARGS "--demangle-cpp") - endif() - - # Setting up commands which will be run to generate coverage data. - # Cleanup lcov - set(LCOV_CLEAN_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . - -b ${BASEDIR} --zerocounters - ) - # Create baseline to make sure untouched files show up in the report - set(LCOV_BASELINE_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b - ${BASEDIR} -o ${Coverage_NAME}.base - ) - # Run tests - set(LCOV_EXEC_TESTS_CMD - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) - # Capturing lcov counters and generating report - set(LCOV_CAPTURE_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b - ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture - ) - # add baseline counters - set(LCOV_BASELINE_COUNT_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base - -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total - ) - # filter collected data to final coverage report - set(LCOV_FILTER_CMD - ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove - ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info - ) - # Generate HTML output - set(LCOV_GEN_HTML_CMD - ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o - ${Coverage_NAME} ${Coverage_NAME}.info - ) - - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - message(STATUS "Command to clean up lcov: ") - string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") - message(STATUS "${LCOV_CLEAN_CMD_SPACED}") - - message(STATUS "Command to create baseline: ") - string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") - message(STATUS "${LCOV_BASELINE_CMD_SPACED}") - - message(STATUS "Command to run the tests: ") - string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") - message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to capture counters and generate report: ") - string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") - message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") - - message(STATUS "Command to add baseline counters: ") - string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") - message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") - - message(STATUS "Command to filter collected data: ") - string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") - message(STATUS "${LCOV_FILTER_CMD_SPACED}") - - message(STATUS "Command to generate lcov HTML output: ") - string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") - message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") - endif() - - # Setup target - add_custom_target(${Coverage_NAME} - COMMAND ${LCOV_CLEAN_CMD} - COMMAND ${LCOV_BASELINE_CMD} - COMMAND ${LCOV_EXEC_TESTS_CMD} - COMMAND ${LCOV_CAPTURE_CMD} - COMMAND ${LCOV_BASELINE_COUNT_CMD} - COMMAND ${LCOV_FILTER_CMD} - COMMAND ${LCOV_GEN_HTML_CMD} - - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS - ${Coverage_NAME}.base - ${Coverage_NAME}.capture - ${Coverage_NAME}.total - ${Coverage_NAME}.info - ${Coverage_NAME}/index.html - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) - - # Show where to find the lcov info report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." - ) - - # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." - ) - -endfunction() # setup_target_for_coverage_lcov - -# Defines a target for running and collection code coverage information -# Builds dependencies, runs the given executable and outputs reports. -# NOTE! The executable should always have a ZERO as exit code otherwise -# the coverage generation will not complete. -# -# setup_target_for_coverage_gcovr_xml( -# NAME ctest_coverage # New target name -# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR -# DEPENDENCIES executable_target # Dependencies to build first -# BASE_DIRECTORY "../" # Base directory for report -# # (defaults to PROJECT_SOURCE_DIR) -# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative -# # to BASE_DIRECTORY, with CMake 3.4+) -# ) -# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the -# GCVOR command. -function(setup_target_for_coverage_gcovr_xml) - - set(options NONE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT GCOVR_PATH) - message(FATAL_ERROR "gcovr not found! Aborting...") - endif() # NOT GCOVR_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(GCOVR_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES GCOVR_EXCLUDES) - - # Combine excludes to several -e arguments - set(GCOVR_EXCLUDE_ARGS "") - foreach(EXCLUDE ${GCOVR_EXCLUDES}) - list(APPEND GCOVR_EXCLUDE_ARGS "-e") - list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") - endforeach() - - # Set up commands which will be run to generate coverage data - # Run tests - set(GCOVR_XML_EXEC_TESTS_CMD - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) - # Running gcovr - set(GCOVR_XML_CMD - ${GCOVR_PATH} --xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} ${GCOVR_EXCLUDE_ARGS} - --object-directory=${PROJECT_BINARY_DIR} -o ${Coverage_NAME}.xml - ) - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - - message(STATUS "Command to run tests: ") - string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") - message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to generate gcovr XML coverage data: ") - string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") - message(STATUS "${GCOVR_XML_CMD_SPACED}") - endif() - - add_custom_target(${Coverage_NAME} - COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_XML_CMD} - - BYPRODUCTS ${Coverage_NAME}.xml - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) - - # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." - ) -endfunction() # setup_target_for_coverage_gcovr_xml - -# Defines a target for running and collection code coverage information -# Builds dependencies, runs the given executable and outputs reports. -# NOTE! The executable should always have a ZERO as exit code otherwise -# the coverage generation will not complete. -# -# setup_target_for_coverage_gcovr_html( -# NAME ctest_coverage # New target name -# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR -# DEPENDENCIES executable_target # Dependencies to build first -# BASE_DIRECTORY "../" # Base directory for report -# # (defaults to PROJECT_SOURCE_DIR) -# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative -# # to BASE_DIRECTORY, with CMake 3.4+) -# ) -# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the -# GCVOR command. -function(setup_target_for_coverage_gcovr_html) - - set(options NONE) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT GCOVR_PATH) - message(FATAL_ERROR "gcovr not found! Aborting...") - endif() # NOT GCOVR_PATH - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(DEFINED Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (CMake 3.4+: Also compute absolute paths) - set(GCOVR_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) - if(CMAKE_VERSION VERSION_GREATER 3.4) - get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) - endif() - list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES GCOVR_EXCLUDES) - - # Combine excludes to several -e arguments - set(GCOVR_EXCLUDE_ARGS "") - foreach(EXCLUDE ${GCOVR_EXCLUDES}) - list(APPEND GCOVR_EXCLUDE_ARGS "-e") - list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") - endforeach() - - # Set up commands which will be run to generate coverage data - # Run tests - set(GCOVR_HTML_EXEC_TESTS_CMD - ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} - ) - # Create folder - set(GCOVR_HTML_FOLDER_CMD - ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} - ) - # Running gcovr - set(GCOVR_HTML_CMD - ${GCOVR_PATH} --html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} - ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} - -o ${Coverage_NAME}/index.html - ) - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Executed command report") - - message(STATUS "Command to run tests: ") - string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") - message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") - - message(STATUS "Command to create a folder: ") - string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") - message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") - - message(STATUS "Command to generate gcovr HTML coverage data: ") - string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") - message(STATUS "${GCOVR_HTML_CMD_SPACED}") - endif() - - add_custom_target(${Coverage_NAME} - COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} - COMMAND ${GCOVR_HTML_FOLDER_CMD} - COMMAND ${GCOVR_HTML_CMD} - - BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Running gcovr to produce HTML code coverage report." - ) - - # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." - ) - -endfunction() # setup_target_for_coverage_gcovr_html - -# Defines a target for running and collection code coverage information -# Builds dependencies, runs the given executable and outputs reports. -# NOTE! The executable should always have a ZERO as exit code otherwise -# the coverage generation will not complete. -# -# setup_target_for_coverage_fastcov( -# NAME testrunner_coverage # New target name -# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR -# DEPENDENCIES testrunner # Dependencies to build first -# BASE_DIRECTORY "../" # Base directory for report -# # (defaults to PROJECT_SOURCE_DIR) -# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. -# NO_DEMANGLE # Don't demangle C++ symbols -# # even if c++filt is found -# SKIP_HTML # Don't create html report -# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths -# ) -function(setup_target_for_coverage_fastcov) - - set(options NO_DEMANGLE SKIP_HTML) - set(oneValueArgs BASE_DIRECTORY NAME) - set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT FASTCOV_PATH) - message(FATAL_ERROR "fastcov not found! Aborting...") - endif() - - if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() - - # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR - if(Coverage_BASE_DIRECTORY) - get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) - else() - set(BASEDIR ${PROJECT_SOURCE_DIR}) - endif() - - # Collect excludes (Patterns, not paths, for fastcov) - set(FASTCOV_EXCLUDES "") - foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) - list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") - endforeach() - list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) - - # Conditional arguments - if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) - set(GENHTML_EXTRA_ARGS "--demangle-cpp") - endif() - - # Set up commands which will be run to generate coverage data - set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) - - set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} - --search-directory ${BASEDIR} - --process-gcno - --output ${Coverage_NAME}.json - --exclude ${FASTCOV_EXCLUDES} - --exclude ${FASTCOV_EXCLUDES} - ) - - set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} - -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info - ) - - if(Coverage_SKIP_HTML) - set(FASTCOV_HTML_CMD ";") - else() - set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} - -o ${Coverage_NAME} ${Coverage_NAME}.info - ) - endif() - - set(FASTCOV_POST_CMD ";") - if(Coverage_POST_CMD) - set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) - endif() - - if(CODE_COVERAGE_VERBOSE) - message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") - - message(" Running tests:") - string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") - message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") - - message(" Capturing fastcov counters and generating report:") - string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") - message(" ${FASTCOV_CAPTURE_CMD_SPACED}") - - message(" Converting fastcov .json to lcov .info:") - string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") - message(" ${FASTCOV_CONVERT_CMD_SPACED}") - - if(NOT Coverage_SKIP_HTML) - message(" Generating HTML report: ") - string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") - message(" ${FASTCOV_HTML_CMD_SPACED}") - endif() - if(Coverage_POST_CMD) - message(" Running post command: ") - string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") - message(" ${FASTCOV_POST_CMD_SPACED}") - endif() - endif() - - # Setup target - add_custom_target(${Coverage_NAME} - - # Cleanup fastcov - COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} - --search-directory ${BASEDIR} - --zerocounters - - COMMAND ${FASTCOV_EXEC_TESTS_CMD} - COMMAND ${FASTCOV_CAPTURE_CMD} - COMMAND ${FASTCOV_CONVERT_CMD} - COMMAND ${FASTCOV_HTML_CMD} - COMMAND ${FASTCOV_POST_CMD} - - # Set output files as GENERATED (will be removed on 'make clean') - BYPRODUCTS - ${Coverage_NAME}.info - ${Coverage_NAME}.json - ${Coverage_NAME}/index.html # report directory - - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - VERBATIM # Protect arguments to commands - COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." - ) - - set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") - if(NOT Coverage_SKIP_HTML) - string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") - endif() - # Show where to find the fastcov info report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} - ) - -endfunction() # setup_target_for_coverage_fastcov - -function(append_coverage_compiler_flags) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") -endfunction() # append_coverage_compiler_flags diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in deleted file mode 100644 index 25a29025f..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/Config.cmake.in +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2016, Ruslan Baratov -# -# Licensed under the MIT License (the "License"); you may not use this file except -# in compliance with the License. You may obtain a copy of the License at -# -# http://opensource.org/licenses/MIT -# -# Unless required by applicable law or agreed to in writing, software distributed -# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -# CONDITIONS OF ANY KIND, either express or implied. See the License for the -# specific language governing permissions and limitations under the License. - -@PACKAGE_INIT@ - -find_package(Threads REQUIRED) - -include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake") -check_required_components("@PROJECT_NAME@") diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in b/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in deleted file mode 100644 index 9c15f36a2..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/cmake/cpp-statsd-clientConfig.cmake.in +++ /dev/null @@ -1,4 +0,0 @@ -@PACKAGE_INIT@ - -include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") -check_required_components("@PROJECT_NAME@") diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg b/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg deleted file mode 100644 index 5f53c40da..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/images/logo.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - Artboard Copy - Created with Sketch. - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp deleted file mode 100644 index ae08cb190..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/StatsdClient.hpp +++ /dev/null @@ -1,318 +0,0 @@ -#ifndef STATSD_CLIENT_HPP -#define STATSD_CLIENT_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Statsd { - -/*! - * - * Statsd client - * - * This is the Statsd client, exposing the classic methods - * and relying on a UDP sender for the actual sending. - * - * The prefix for a stat is provided once at construction or - * on reconfiguring the client. The separator character '.' - * is automatically inserted between the prefix and the stats - * key, therefore you should neither append one to the prefix - * nor prepend one to the key - * - * The sampling frequency is specified per call and uses a - * random number generator to determine whether or not the stat - * will be recorded this time or not. - * - * The top level configuration includes 2 optional parameters - * that determine how the stats are delivered to statsd. These - * parameters are the batching size and the send interval. - * - * The batching size controls the number of bytes to send - * in each UDP datagram to statsd. This is not a hard limit as - * we continue appending to a batch of stats until the limit - * has been reached or surpassed. When this occurs we add the - * batch to a queue and create a new batch to appended to. A - * value of 0 for the batching size will disable batching such - * that each stat will be sent to the daemon individually. - * - * The send interval controls the rate at which queued batches - * of stats will be sent to statsd. If batching is disabled, - * this value is ignored and each individual stat is sent to - * statsd immediately in a blocking fashion. If batching is - * enabled (ie. non-zero) then the send interval is the number - * of milliseconds to wait before flushing the queue of - * batched stats messages to the daemon. This is done in a non- - * blocking fashion via a background thread. If the send - * interval is 0 then the stats messages are appended to a - * queue until the caller manually flushes the queue via the - * flush method. - * - */ -class StatsdClient { -public: - //!@name Constructor and destructor, non-copyable - //!@{ - - //! Constructor - StatsdClient(const std::string& host, - const uint16_t port, - const std::string& prefix, - const uint64_t batchsize = 0, - const uint64_t sendInterval = 1000, - const unsigned int gaugePrecision = 4) noexcept; - - StatsdClient(const StatsdClient&) = delete; - StatsdClient& operator=(const StatsdClient&) = delete; - - //!@} - - //!@name Methods - //!@{ - - //! Sets a configuration { host, port, prefix, batchsize } - void setConfig(const std::string& host, - const uint16_t port, - const std::string& prefix, - const uint64_t batchsize = 0, - const uint64_t sendInterval = 1000, - const unsigned int gaugePrecision = 4) noexcept; - - //! Returns the error message as an std::string - const std::string& errorMessage() const noexcept; - - //! Increments the key, at a given frequency rate - void increment(const std::string& key, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Increments the key, at a given frequency rate - void decrement(const std::string& key, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Adjusts the specified key by a given delta, at a given frequency rate - void count(const std::string& key, - const int delta, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Records a gauge for the key, with a given value, at a given frequency rate - template - void gauge(const std::string& key, - const T value, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Records a timing for a key, at a given frequency - void timing(const std::string& key, - const unsigned int ms, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Records a count of unique occurrences for a key, at a given frequency - void set(const std::string& key, - const unsigned int sum, - float frequency = 1.0f, - const std::vector& tags = {}) const noexcept; - - //! Seed the RNG that controls sampling - void seed(unsigned int seed = std::random_device()()) noexcept; - - //! Flush any queued stats to the daemon - void flush() noexcept; - - //!@} - -private: - // @name Private methods - // @{ - - //! Send a value for a key, according to its type, at a given frequency - template - void send(const std::string& key, - const T value, - const char* type, - float frequency, - const std::vector& tags) const noexcept; - - //!@} - -private: - //! The prefix to be used for metrics - std::string m_prefix; - - //! The UDP sender to be used for actual sending - std::unique_ptr m_sender; - - //! The random number generator for handling sampling - mutable std::mt19937 m_randomEngine; - - //! The buffer string format our stats before sending them - mutable std::string m_buffer; - - //! Fixed floating point precision of gauges - unsigned int m_gaugePrecision; -}; - -namespace detail { -inline std::string sanitizePrefix(std::string prefix) { - // For convenience we provide the dot when generating the stat message - if (!prefix.empty() && prefix.back() == '.') { - prefix.pop_back(); - } - return prefix; -} - -// All supported metric types -constexpr char METRIC_TYPE_COUNT[] = "c"; -constexpr char METRIC_TYPE_GAUGE[] = "g"; -constexpr char METRIC_TYPE_TIMING[] = "ms"; -constexpr char METRIC_TYPE_SET[] = "s"; -} // namespace detail - -inline StatsdClient::StatsdClient(const std::string& host, - const uint16_t port, - const std::string& prefix, - const uint64_t batchsize, - const uint64_t sendInterval, - const unsigned int gaugePrecision) noexcept - : m_prefix(detail::sanitizePrefix(prefix)), - m_sender(new UDPSender{host, port, batchsize, sendInterval}), - m_gaugePrecision(gaugePrecision) { - // Initialize the random generator to be used for sampling - seed(); - // Avoid re-allocations by reserving a generous buffer - m_buffer.reserve(256); -} - -inline void StatsdClient::setConfig(const std::string& host, - const uint16_t port, - const std::string& prefix, - const uint64_t batchsize, - const uint64_t sendInterval, - const unsigned int gaugePrecision) noexcept { - m_prefix = detail::sanitizePrefix(prefix); - m_sender.reset(new UDPSender(host, port, batchsize, sendInterval)); - m_gaugePrecision = gaugePrecision; -} - -inline const std::string& StatsdClient::errorMessage() const noexcept { - return m_sender->errorMessage(); -} - -inline void StatsdClient::decrement(const std::string& key, - float frequency, - const std::vector& tags) const noexcept { - count(key, -1, frequency, tags); -} - -inline void StatsdClient::increment(const std::string& key, - float frequency, - const std::vector& tags) const noexcept { - count(key, 1, frequency, tags); -} - -inline void StatsdClient::count(const std::string& key, - const int delta, - float frequency, - const std::vector& tags) const noexcept { - send(key, delta, detail::METRIC_TYPE_COUNT, frequency, tags); -} - -template -inline void StatsdClient::gauge(const std::string& key, - const T value, - const float frequency, - const std::vector& tags) const noexcept { - send(key, value, detail::METRIC_TYPE_GAUGE, frequency, tags); -} - -inline void StatsdClient::timing(const std::string& key, - const unsigned int ms, - float frequency, - const std::vector& tags) const noexcept { - send(key, ms, detail::METRIC_TYPE_TIMING, frequency, tags); -} - -inline void StatsdClient::set(const std::string& key, - const unsigned int sum, - float frequency, - const std::vector& tags) const noexcept { - send(key, sum, detail::METRIC_TYPE_SET, frequency, tags); -} - -template -inline void StatsdClient::send(const std::string& key, - const T value, - const char* type, - float frequency, - const std::vector& tags) const noexcept { - // Bail if we can't send anything anyway - if (!m_sender->initialized()) { - return; - } - - // A valid frequency is: 0 <= f <= 1 - // At 0 you never emit the stat, at 1 you always emit the stat and with anything else you roll the dice - frequency = std::max(std::min(frequency, 1.f), 0.f); - constexpr float epsilon{0.0001f}; - const bool isFrequencyOne = std::fabs(frequency - 1.0f) < epsilon; - const bool isFrequencyZero = std::fabs(frequency) < epsilon; - if (isFrequencyZero || - (!isFrequencyOne && (frequency < std::uniform_real_distribution(0.f, 1.f)(m_randomEngine)))) { - return; - } - - // Format the stat message - std::stringstream valueStream; - valueStream << std::fixed << std::setprecision(m_gaugePrecision) << value; - - m_buffer.clear(); - - m_buffer.append(m_prefix); - if (!m_prefix.empty() && !key.empty()) { - m_buffer.push_back('.'); - } - - m_buffer.append(key); - m_buffer.push_back(':'); - m_buffer.append(valueStream.str()); - m_buffer.push_back('|'); - m_buffer.append(type); - - if (frequency < 1.f) { - m_buffer.append("|@0."); - m_buffer.append(std::to_string(static_cast(frequency * 100))); - } - - if (!tags.empty()) { - m_buffer.append("|#"); - for (const auto& tag : tags) { - m_buffer.append(tag); - m_buffer.push_back(','); - } - m_buffer.pop_back(); - } - - // Send the message via the UDP sender - m_sender->send(m_buffer); -} - -inline void StatsdClient::seed(unsigned int seed) noexcept { - m_randomEngine.seed(seed); -} - -inline void StatsdClient::flush() noexcept { - m_sender->flush(); -} - -} // namespace Statsd - -#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp deleted file mode 100644 index c7d667a5f..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/include/cpp-statsd-client/UDPSender.hpp +++ /dev/null @@ -1,345 +0,0 @@ -#ifndef UDP_SENDER_HPP -#define UDP_SENDER_HPP - -#ifdef _WIN32 -#define NOMINMAX -#include -#include -#include -#else -#include -#include -#include -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Statsd { - -#ifdef _WIN32 -using SOCKET_TYPE = SOCKET; -constexpr SOCKET_TYPE k_invalidSocket{INVALID_SOCKET}; -#define SOCKET_ERRNO WSAGetLastError() -#define SOCKET_CLOSE closesocket -#else -using SOCKET_TYPE = int; -constexpr SOCKET_TYPE k_invalidSocket{-1}; -#define SOCKET_ERRNO errno -#define SOCKET_CLOSE close -#endif - -/*! - * - * UDP sender - * - * A simple UDP sender handling batching. - * - */ -class UDPSender final { -public: - //!@name Constructor and destructor, non-copyable - //!@{ - - //! Constructor - UDPSender(const std::string& host, - const uint16_t port, - const uint64_t batchsize, - const uint64_t sendInterval) noexcept; - - //! Destructor - ~UDPSender(); - - UDPSender(const UDPSender&) = delete; - UDPSender& operator=(const UDPSender&) = delete; - UDPSender(UDPSender&&) = delete; - - //!@} - - //!@name Methods - //!@{ - - //! Send or enqueue a message - void send(const std::string& message) noexcept; - - //! Returns the error message as a string - const std::string& errorMessage() const noexcept; - - //! Returns true if the sender is initialized - bool initialized() const noexcept; - - //! Flushes any queued messages - void flush() noexcept; - - //!@} - -private: - // @name Private methods - // @{ - - //! Initialize the sender and returns true when it is initialized - bool initialize() noexcept; - - //! Queue a message to be sent to the daemon later - inline void queueMessage(const std::string& message) noexcept; - - //! Send a message to the daemon - void sendToDaemon(const std::string& message) noexcept; - - //!@} - -private: - // @name State variables - // @{ - - //! Shall we exit? - std::atomic m_mustExit{false}; - - //!@} - - // @name Network info - // @{ - - //! The hostname - std::string m_host; - - //! The port - uint16_t m_port; - - //! The structure holding the server - struct sockaddr_in m_server; - - //! The socket to be used - SOCKET_TYPE m_socket = k_invalidSocket; - - //!@} - - // @name Batching info - // @{ - - //! The batching size - uint64_t m_batchsize; - - //! The sending frequency in milliseconds - uint64_t m_sendInterval; - - //! The queue batching the messages - std::deque m_batchingMessageQueue; - - //! The mutex used for batching - std::mutex m_batchingMutex; - - //! The thread dedicated to the batching - std::thread m_batchingThread; - - //!@} - - //! Error message (optional string) - std::string m_errorMessage; -}; - -namespace detail { - -inline bool isValidSocket(const SOCKET_TYPE socket) { - return socket != k_invalidSocket; -} - -#ifdef _WIN32 -struct WinSockSingleton { - inline static const WinSockSingleton& getInstance() { - static const WinSockSingleton instance; - return instance; - } - inline bool ok() const { - return m_ok; - } - ~WinSockSingleton() { - WSACleanup(); - } - -private: - WinSockSingleton() { - WSADATA wsa; - m_ok = WSAStartup(MAKEWORD(2, 2), &wsa) == 0; - } - bool m_ok; -}; -#endif - -} // namespace detail - -inline UDPSender::UDPSender(const std::string& host, - const uint16_t port, - const uint64_t batchsize, - const uint64_t sendInterval) noexcept - : m_host(host), m_port(port), m_batchsize(batchsize), m_sendInterval(sendInterval) { - // Initialize the socket - if (!initialize()) { - return; - } - - // If batching is on, use a dedicated thread to send after the wait time is reached - if (m_batchsize != 0 && m_sendInterval > 0) { - // Define the batching thread - m_batchingThread = std::thread([this] { - // TODO: this will drop unsent stats, should we send all the unsent stats before we exit? - while (!m_mustExit.load(std::memory_order_acquire)) { - std::deque stagedMessageQueue; - - std::unique_lock batchingLock(m_batchingMutex); - m_batchingMessageQueue.swap(stagedMessageQueue); - batchingLock.unlock(); - - // Flush the queue - while (!stagedMessageQueue.empty()) { - sendToDaemon(stagedMessageQueue.front()); - stagedMessageQueue.pop_front(); - } - - // Wait before sending the next batch - std::this_thread::sleep_for(std::chrono::milliseconds(m_sendInterval)); - } - }); - } -} - -inline UDPSender::~UDPSender() { - if (!initialized()) { - return; - } - - // If we're running a background thread tell it to stop - if (m_batchingThread.joinable()) { - m_mustExit.store(true, std::memory_order_release); - m_batchingThread.join(); - } - - // Cleanup the socket - SOCKET_CLOSE(m_socket); -} - -inline void UDPSender::send(const std::string& message) noexcept { - m_errorMessage.clear(); - - // If batching is on, accumulate messages in the queue - if (m_batchsize > 0) { - queueMessage(message); - return; - } - - // Or send it right now - sendToDaemon(message); -} - -inline void UDPSender::queueMessage(const std::string& message) noexcept { - // We aquire a lock but only if we actually need to (i.e. there is a thread also accessing the queue) - auto batchingLock = - m_batchingThread.joinable() ? std::unique_lock(m_batchingMutex) : std::unique_lock(); - // Either we don't have a place to batch our message or we exceeded the batch size, so make a new batch - if (m_batchingMessageQueue.empty() || m_batchingMessageQueue.back().length() > m_batchsize) { - m_batchingMessageQueue.emplace_back(); - m_batchingMessageQueue.back().reserve(m_batchsize + 256); - } // When there is already a batch open we need a separator when its not empty - else if (!m_batchingMessageQueue.back().empty()) { - m_batchingMessageQueue.back().push_back('\n'); - } - // Add the new message to the batch - m_batchingMessageQueue.back().append(message); -} - -inline const std::string& UDPSender::errorMessage() const noexcept { - return m_errorMessage; -} - -inline bool UDPSender::initialize() noexcept { -#ifdef _WIN32 - if (!detail::WinSockSingleton::getInstance().ok()) { - m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO); - } -#endif - - // Connect the socket - m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (!detail::isValidSocket(m_socket)) { - m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO); - return false; - } - - std::memset(&m_server, 0, sizeof(m_server)); - m_server.sin_family = AF_INET; - m_server.sin_port = htons(m_port); - - if (inet_pton(AF_INET, m_host.c_str(), &m_server.sin_addr) == 0) { - // An error code has been returned by inet_aton - - // Specify the criteria for selecting the socket address structure - struct addrinfo hints; - std::memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - - // Get the address info using the hints - struct addrinfo* results = nullptr; - const int ret{getaddrinfo(m_host.c_str(), nullptr, &hints, &results)}; - if (ret != 0) { - // An error code has been returned by getaddrinfo - SOCKET_CLOSE(m_socket); - m_socket = k_invalidSocket; - m_errorMessage = "getaddrinfo failed: err=" + std::to_string(ret) + ", msg=" + gai_strerror(ret); - return false; - } - - // Copy the results in m_server - struct sockaddr_in* host_addr = (struct sockaddr_in*)results->ai_addr; - std::memcpy(&m_server.sin_addr, &host_addr->sin_addr, sizeof(struct in_addr)); - - // Free the memory allocated - freeaddrinfo(results); - } - - return true; -} - -inline void UDPSender::sendToDaemon(const std::string& message) noexcept { - // Try sending the message - const auto ret = sendto(m_socket, - message.data(), -#ifdef _WIN32 - static_cast(message.size()), -#else - message.size(), -#endif - 0, - (struct sockaddr*)&m_server, - sizeof(m_server)); - if (ret == -1) { - m_errorMessage = "sendto server failed: host=" + m_host + ":" + std::to_string(m_port) + - ", err=" + std::to_string(SOCKET_ERRNO); - } -} - -inline bool UDPSender::initialized() const noexcept { - return m_socket != k_invalidSocket; -} - -inline void UDPSender::flush() noexcept { - // We aquire a lock but only if we actually need to (ie there is a thread also accessing the queue) - auto batchingLock = - m_batchingThread.joinable() ? std::unique_lock(m_batchingMutex) : std::unique_lock(); - // Flush the queue - while (!m_batchingMessageQueue.empty()) { - sendToDaemon(m_batchingMessageQueue.front()); - m_batchingMessageQueue.pop_front(); - } -} - -} // namespace Statsd - -#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp deleted file mode 100644 index e87fe5933..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/StatsdServer.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef STATSD_SERVER_HPP -#define STATSD_SERVER_HPP - -// It might make sense to include this test class in the UDPSender header -// it includes most of the cross platform defines etc that we need for socket io -#include "cpp-statsd-client/UDPSender.hpp" - -#include -#include - -namespace Statsd { - -class StatsdServer { -public: - StatsdServer(unsigned short port = 8125) noexcept { -#ifdef _WIN32 - if (!detail::WinSockSingleton::getInstance().ok()) { - m_errorMessage = "WSAStartup failed: errno=" + std::to_string(SOCKET_ERRNO); - } -#endif - - // Create the socket - m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (!detail::isValidSocket(m_socket)) { - m_errorMessage = "socket creation failed: errno=" + std::to_string(SOCKET_ERRNO); - return; - } - - // Binding should be with ipv4 to all interfaces - struct sockaddr_in address {}; - address.sin_family = AF_INET; - address.sin_port = htons(port); - address.sin_addr.s_addr = INADDR_ANY; - - // Try to bind - if (bind(m_socket, reinterpret_cast(&address), sizeof(address)) != 0) { - SOCKET_CLOSE(m_socket); - m_socket = k_invalidSocket; - m_errorMessage = "bind failed: errno=" + std::to_string(SOCKET_ERRNO); - } - } - - ~StatsdServer() { - if (detail::isValidSocket(m_socket)) { - SOCKET_CLOSE(m_socket); - } - } - - const std::string& errorMessage() const noexcept { - return m_errorMessage; - } - - std::string receive() noexcept { - // If uninitialized then bail - if (!detail::isValidSocket(m_socket)) { - return ""; - } - - // Try to receive (this is blocking) - std::string buffer(256, '\0'); - int string_len; - if ((string_len = recv(m_socket, &buffer[0], static_cast(buffer.size()), 0)) < 1) { - m_errorMessage = "Could not recv on the socket file descriptor"; - return ""; - } - - // No error return the trimmed result - m_errorMessage.clear(); - buffer.resize(std::min(static_cast(string_len), buffer.size())); - return buffer; - } - -private: - SOCKET_TYPE m_socket; - std::string m_errorMessage; -}; - -} // namespace Statsd - -#endif diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp b/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp deleted file mode 100644 index 74db8468e..000000000 --- a/src/modules/keydb_modstatsd/cpp-statsd-client/tests/testStatsdClient.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include - -#include "StatsdServer.hpp" -#include "cpp-statsd-client/StatsdClient.hpp" - -using namespace Statsd; - -// Each test suite below spawns a thread to recv the client messages over UDP as if it were a real statsd server -// Note that we could just synchronously recv metrics and not use a thread but doing the test async has the -// advantage that we can test the threaded batching mode in a straightforward way. The server thread basically -// just keeps storing metrics in an vector until it hears a special one signaling the test is over and bails -void mock(StatsdServer& server, std::vector& messages) { - do { - // Grab the messages that are waiting - auto recvd = server.receive(); - - // Split the messages on '\n' - auto start = std::string::npos; - do { - // Keep this message - auto end = recvd.find('\n', ++start); - messages.emplace_back(recvd.substr(start, end)); - start = end; - - // Bail if we found the special quit message - if (messages.back().find("DONE") != std::string::npos) { - messages.pop_back(); - return; - } - } while (start != std::string::npos); - } while (server.errorMessage().empty() && !messages.back().empty()); -} - -template -void throwOnError(const SocketWrapper& wrapped, bool expectEmpty = true, const std::string& extraMessage = "") { - if (wrapped.errorMessage().empty() != expectEmpty) { - std::cerr << (expectEmpty ? wrapped.errorMessage() : extraMessage) << std::endl; - throw std::runtime_error(expectEmpty ? wrapped.errorMessage() : extraMessage); - } -} - -void throwOnWrongMessage(StatsdServer& server, const std::string& expected) { - auto actual = server.receive(); - if (actual != expected) { - std::cerr << "Expected: " << expected << " but got: " << actual << std::endl; - throw std::runtime_error("Incorrect stat received"); - } -} - -void testErrorConditions() { - // Resolve a rubbish ip and make sure initialization failed - StatsdClient client{"256.256.256.256", 8125, "myPrefix", 20}; - throwOnError(client, false, "Should not be able to resolve a ridiculous ip"); -} - -void testReconfigure() { - StatsdServer server; - throwOnError(server); - - StatsdClient client("localhost", 8125, "first."); - client.increment("foo"); - throwOnWrongMessage(server, "first.foo:1|c"); - - client.setConfig("localhost", 8125, "second"); - client.increment("bar"); - throwOnWrongMessage(server, "second.bar:1|c"); - - client.setConfig("localhost", 8125, ""); - client.increment("third.baz"); - throwOnWrongMessage(server, "third.baz:1|c"); - - client.increment(""); - throwOnWrongMessage(server, ":1|c"); - - // TODO: test what happens with the batching after resolving the question about incomplete - // batches being dropped vs sent on reconfiguring -} - -void testSendRecv(uint64_t batchSize, uint64_t sendInterval) { - StatsdServer mock_server; - std::vector messages, expected; - std::thread server(mock, std::ref(mock_server), std::ref(messages)); - - // Set a new config that has the client send messages to a proper address that can be resolved - StatsdClient client("localhost", 8125, "sendRecv.", batchSize, sendInterval, 3); - throwOnError(client); - - // TODO: I forget if we need to wait for the server to be ready here before sending the first stats - // is there a race condition where the client sending before the server binds would drop that clients message - - for (int i = 0; i < 3; ++i) { - // Increment "coco" - client.increment("coco"); - throwOnError(client); - expected.emplace_back("sendRecv.coco:1|c"); - - // Decrement "kiki" - client.decrement("kiki"); - throwOnError(client); - expected.emplace_back("sendRecv.kiki:-1|c"); - - // Adjusts "toto" by +2 - client.seed(19); // this seed gets a hit on the first call - client.count("toto", 2, 0.1f); - throwOnError(client); - expected.emplace_back("sendRecv.toto:2|c|@0.10"); - - // Gets "sampled out" by the random number generator - client.count("popo", 9, 0.1f); - throwOnError(client); - - // Record a gauge "titi" to 3 - client.gauge("titi", 3); - throwOnError(client); - expected.emplace_back("sendRecv.titi:3|g"); - - // Record a gauge "titifloat" to -123.456789 with precision 3 - client.gauge("titifloat", -123.456789); - throwOnError(client); - expected.emplace_back("sendRecv.titifloat:-123.457|g"); - - // Record a timing of 2ms for "myTiming" - client.seed(19); - client.timing("myTiming", 2, 0.1f); - throwOnError(client); - expected.emplace_back("sendRecv.myTiming:2|ms|@0.10"); - - // Send a set with 1227 total uniques - client.set("tutu", 1227, 2.0f); - throwOnError(client); - expected.emplace_back("sendRecv.tutu:1227|s"); - - // Gauge but with tags - client.gauge("dr.röstigrabe", 333, 1.f, {"liegt", "im", "weste"}); - throwOnError(client); - expected.emplace_back("sendRecv.dr.röstigrabe:333|g|#liegt,im,weste"); - - // All the things - client.count("foo", -42, .9f, {"bar", "baz"}); - throwOnError(client); - expected.emplace_back("sendRecv.foo:-42|c|@0.90|#bar,baz"); - } - - // Signal the mock server we are done - client.timing("DONE", 0); - - // If manual flushing do it now - if (sendInterval == 0) { - client.flush(); - } - - // Wait for the server to stop - server.join(); - - // Make sure we get the exactly correct output - if (messages != expected) { - std::cerr << "Unexpected stats received by server, got:" << std::endl; - for (const auto& message : messages) { - std::cerr << message << std::endl; - } - std::cerr << std::endl << "But we expected:" << std::endl; - for (const auto& message : expected) { - std::cerr << message << std::endl; - } - throw std::runtime_error("Unexpected stats"); - } -} - -int main() { - // If any of these tests fail they throw an exception, not catching makes for a nonzero return code - - // general things that should be errors - testErrorConditions(); - // reconfiguring how you are sending - testReconfigure(); - // no batching - testSendRecv(0, 0); - // background batching - testSendRecv(32, 1000); - // manual flushing of batches - testSendRecv(16, 0); - - return EXIT_SUCCESS; -} From 6e1fbb2d143cb2a51240007d38dd7c91d801dff7 Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 13:34:23 -0600 Subject: [PATCH 3/6] add submodule cpp-statsd-client --- .gitmodules | 3 +++ src/modules/keydb_modstatsd/cpp-statsd-client | 1 + 2 files changed, 4 insertions(+) create mode 160000 src/modules/keydb_modstatsd/cpp-statsd-client diff --git a/.gitmodules b/.gitmodules index 21accf31b..dfd9ee451 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "deps/depot_tools"] path = deps/depot_tools url = https://chromium.googlesource.com/chromium/tools/depot_tools.git +[submodule "src/modules/keydb_modstatsd/cpp-statsd-client"] + path = src/modules/keydb_modstatsd/cpp-statsd-client + url = https://github.com/vthiery/cpp-statsd-client.git diff --git a/src/modules/keydb_modstatsd/cpp-statsd-client b/src/modules/keydb_modstatsd/cpp-statsd-client new file mode 160000 index 000000000..a06a5b935 --- /dev/null +++ b/src/modules/keydb_modstatsd/cpp-statsd-client @@ -0,0 +1 @@ +Subproject commit a06a5b9359f31d946fe163b9038586982971ae49 From 6e0305828e9f08e96019e67cbc80e7754459d79c Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 15:14:35 -0600 Subject: [PATCH 4/6] include trigger keydb_modstatsd Makefile in modules Makefile --- src/modules/Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/Makefile b/src/modules/Makefile index 3db19e79a..56a8e0677 100644 --- a/src/modules/Makefile +++ b/src/modules/Makefile @@ -13,7 +13,10 @@ endif .SUFFIXES: .c .so .xo .o -all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so +all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so build-keydb_modstatsd + +build-keydb_modstatsd: + -(cd keydb_modstatsd && $(MAKE)) .c.xo: $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ From 289df10eb1792bd8e1ecfec80091caf7455771cf Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 15:16:10 -0600 Subject: [PATCH 5/6] update --- src/modules/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/Makefile b/src/modules/Makefile index 56a8e0677..d485aac1a 100644 --- a/src/modules/Makefile +++ b/src/modules/Makefile @@ -16,7 +16,7 @@ endif all: helloworld.so hellotype.so helloblock.so hellocluster.so hellotimer.so hellodict.so hellohook.so helloacl.so build-keydb_modstatsd build-keydb_modstatsd: - -(cd keydb_modstatsd && $(MAKE)) + $(MAKE) -C keydb_modstatsd .c.xo: $(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@ From 4d56a1c5ed76b234a2d6f7d1e2ce46a0bf1ec1d4 Mon Sep 17 00:00:00 2001 From: zliang Date: Fri, 11 Aug 2023 15:19:53 -0600 Subject: [PATCH 6/6] have clean also trigger keydb_modstatsd clean --- src/modules/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/Makefile b/src/modules/Makefile index d485aac1a..64014afc8 100644 --- a/src/modules/Makefile +++ b/src/modules/Makefile @@ -63,3 +63,4 @@ helloacl.so: helloacl.xo clean: rm -rf *.xo *.so + $(MAKE) -C keydb_modstatsd clean