diff --git a/.github/workflows/lint_and_test_cpp.yaml b/.github/workflows/lint_and_test_cpp.yaml index a19b6568fc..65d59e47e7 100644 --- a/.github/workflows/lint_and_test_cpp.yaml +++ b/.github/workflows/lint_and_test_cpp.yaml @@ -373,6 +373,90 @@ jobs: ./scripts/config_hdfs.sh ./run.sh test --on_travis -m ${{ matrix.test_module }} + build_with_jemalloc: + name: Build with jemalloc + needs: cpp_clang_format_linter + runs-on: ubuntu-latest + container: + image: apache/pegasus:thirdparties-bin-test-jemallc-ubuntu1804-${{ github.base_ref }} + steps: + - uses: actions/checkout@v2 + - name: Setup cache + uses: actions/cache@v3 + with: + path: | + /github/home/.ccache + key: jemalloc_ccache + - uses: dorny/paths-filter@v2 + id: changes + with: + filters: | + thirdparty: + - '.github/workflows/thirdparty-regular-push.yml' + - 'docker/thirdparties-src/**' + - 'docker/thirdparties-bin/**' + - 'thirdparty/**' + - name: Unpack prebuilt third-parties + if: steps.changes.outputs.thirdparty == 'false' + run: unzip /root/thirdparties-bin.zip -d ./thirdparty + - name: Rebuild third-parties + if: steps.changes.outputs.thirdparty == 'true' + working-directory: thirdparty + run: | + mkdir build + cmake -DCMAKE_BUILD_TYPE=Release -DROCKSDB_PORTABLE=ON -DUSE_JEMALLOC=ON -B build/ + cmake --build build/ -j $(nproc) + - name: Compilation + run: | + ccache -p + ccache -z + ./run.sh build --test --skip_thirdparty -j $(nproc) -t release --use_jemalloc + ccache -s + - name: Pack Server + run: ./run.sh pack_server -j + - name: Pack Tools + run: ./run.sh pack_tools -j + - name: Tar files + run: | + rm -rf thirdparty + tar -zcvhf release_jemalloc_builder.tar DSN_ROOT/ src/builder/bin src/builder/src/server/test/config.ini --exclude='*CMakeFiles*' + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: release_jemalloc_artifact_${{ github.sha }} + path: release_jemalloc_builder.tar + + test_with_jemalloc: + name: Test with jemallc + strategy: + fail-fast: false + matrix: + test_module: + - dsn_utils_tests + needs: build_with_jemalloc + runs-on: ubuntu-latest + container: + image: apache/pegasus:thirdparties-bin-test-jemallc-ubuntu1804-${{ github.base_ref }} + options: --cap-add=SYS_PTRACE + steps: + - uses: actions/checkout@v2 + - name: Unpack prebuilt third-parties + run: unzip /root/thirdparties-bin.zip -d ./thirdparty + - name: Download Artifact + uses: actions/download-artifact@v3 + with: + name: release_jemalloc_artifact_${{ github.sha }} + path: . + - name: Tar files + run: | + tar -zxvf release_jemalloc_builder.tar + - name: Unit Testing + run: | + export LD_LIBRARY_PATH=`pwd`/thirdparty/output/lib:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server + ulimit -s unlimited + ./scripts/config_hdfs.sh + ./run.sh test --on_travis -m ${{ matrix.test_module }} + build_pegasus_on_macos: name: macOS needs: cpp_clang_format_linter diff --git a/src/rdsn/include/dsn/utility/enum_helper.h b/src/rdsn/include/dsn/utility/enum_helper.h index 64d72638a2..e5d83e0d01 100644 --- a/src/rdsn/include/dsn/utility/enum_helper.h +++ b/src/rdsn/include/dsn/utility/enum_helper.h @@ -48,6 +48,7 @@ #define ENUM_BEGIN(type, invalid_value) ENUM_BEGIN2(type, type, invalid_value) +#define ENUM_REG2(type, name) helper->register_enum(#name, type::name); #define ENUM_REG(e) helper->register_enum(#e, e); #define ENUM_END2(type, name) \ diff --git a/src/rdsn/src/replica/replica_stub.cpp b/src/rdsn/src/replica/replica_stub.cpp index 9e7625e648..b85722c56a 100644 --- a/src/rdsn/src/replica/replica_stub.cpp +++ b/src/rdsn/src/replica/replica_stub.cpp @@ -57,6 +57,8 @@ #include #ifdef DSN_ENABLE_GPERF #include +#elif defined(DSN_USE_JEMALLOC) +#include "utils/je_ctl.h" #endif #include #include @@ -106,6 +108,8 @@ replica_stub::replica_stub(replica_state_subscriber subscriber /*= nullptr*/, _get_tcmalloc_status_command = nullptr; _max_reserved_memory_percentage_command = nullptr; _release_all_reserved_memory_command = nullptr; +#elif defined(DSN_USE_JEMALLOC) + _dump_jemalloc_stats_command = nullptr; #endif _replica_state_subscriber = subscriber; _is_long_subscriber = is_long_subscriber; @@ -2261,6 +2265,41 @@ void replica_stub::open_service() register_ctrl_command(); } +#if !defined(DSN_ENABLE_GPERF) && defined(DSN_USE_JEMALLOC) +void replica_stub::register_jemalloc_ctrl_command() +{ + _dump_jemalloc_stats_command = ::dsn::command_manager::instance().register_command( + {"replica.dump-jemalloc-stats"}, + fmt::format("replica.dump-jemalloc-stats <{}> [buffer size]", kAllJeStatsTypesStr), + "dump stats of jemalloc", + [](const std::vector &args) { + if (args.empty()) { + return std::string("invalid arguments"); + } + + auto type = enum_from_string(args[0].c_str(), je_stats_type::INVALID); + if (type == je_stats_type::INVALID) { + return std::string("invalid stats type"); + } + + std::string stats("\n"); + + if (args.size() == 1) { + dsn::je_dump_stats(type, stats); + return stats; + } + + uint64_t buf_sz; + if (!dsn::buf2uint64(args[1], buf_sz)) { + return std::string("invalid buffer size"); + } + + dsn::je_dump_stats(type, static_cast(buf_sz), stats); + return stats; + }); +} +#endif + void replica_stub::register_ctrl_command() { /// In simple_kv test, three replica apps are created, which means that three replica_stubs are @@ -2411,6 +2450,8 @@ void replica_stub::register_ctrl_command() auto release_bytes = gc_tcmalloc_memory(true); return "OK, release_bytes=" + std::to_string(release_bytes); }); +#elif defined(DSN_USE_JEMALLOC) + register_jemalloc_ctrl_command(); #endif _max_concurrent_bulk_load_downloading_count_command = dsn::command_manager::instance().register_command( @@ -2573,6 +2614,8 @@ void replica_stub::close() UNREGISTER_VALID_HANDLER(_get_tcmalloc_status_command); UNREGISTER_VALID_HANDLER(_max_reserved_memory_percentage_command); UNREGISTER_VALID_HANDLER(_release_all_reserved_memory_command); +#elif defined(DSN_USE_JEMALLOC) + UNREGISTER_VALID_HANDLER(_dump_jemalloc_stats_command); #endif UNREGISTER_VALID_HANDLER(_max_concurrent_bulk_load_downloading_count_command); @@ -2588,6 +2631,8 @@ void replica_stub::close() _get_tcmalloc_status_command = nullptr; _max_reserved_memory_percentage_command = nullptr; _release_all_reserved_memory_command = nullptr; +#elif defined(DSN_USE_JEMALLOC) + _dump_jemalloc_stats_command = nullptr; #endif _max_concurrent_bulk_load_downloading_count_command = nullptr; diff --git a/src/rdsn/src/replica/replica_stub.h b/src/rdsn/src/replica/replica_stub.h index 32a47584b4..0db8bc10ad 100644 --- a/src/rdsn/src/replica/replica_stub.h +++ b/src/rdsn/src/replica/replica_stub.h @@ -307,6 +307,8 @@ class replica_stub : public serverlet, public ref_counter // Try to release tcmalloc memory back to operating system // If release_all = true, it will release all reserved-not-used memory uint64_t gc_tcmalloc_memory(bool release_all); +#elif defined(DSN_USE_JEMALLOC) + void register_jemalloc_ctrl_command(); #endif private: @@ -382,6 +384,8 @@ class replica_stub : public serverlet, public ref_counter dsn_handle_t _get_tcmalloc_status_command; dsn_handle_t _max_reserved_memory_percentage_command; dsn_handle_t _release_all_reserved_memory_command; +#elif defined(DSN_USE_JEMALLOC) + dsn_handle_t _dump_jemalloc_stats_command; #endif dsn_handle_t _max_concurrent_bulk_load_downloading_count_command; diff --git a/src/rdsn/src/utils/je_ctl.cpp b/src/rdsn/src/utils/je_ctl.cpp new file mode 100644 index 0000000000..92761d3c2f --- /dev/null +++ b/src/rdsn/src/utils/je_ctl.cpp @@ -0,0 +1,107 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#ifdef DSN_USE_JEMALLOC + +#include "je_ctl.h" + +#include +#include + +#include +#include + +#include +#include + +#define RETURN_ARRAY_ELEM_BY_ENUM_TYPE(type, array) \ + do { \ + const auto index = static_cast(type); \ + dcheck_lt(index, sizeof(array) / sizeof(array[0])); \ + return array[index]; \ + } while (0); + +namespace dsn { + +namespace { + +void je_stats_cb(void *opaque, const char *str) +{ + if (str == nullptr) { + return; + } + + auto stats = reinterpret_cast(opaque); + auto avail_capacity = stats->capacity() - stats->size(); + auto len = strlen(str); + if (len > avail_capacity) { + len = avail_capacity; + } + + stats->append(str, len); +} + +void je_dump_malloc_stats(const char *opts, size_t buf_sz, std::string &stats) +{ + // Avoid malloc in callback. + stats.reserve(buf_sz); + + malloc_stats_print(je_stats_cb, &stats, opts); +} + +const char *je_stats_type_to_opts(je_stats_type type) +{ + static const char *opts_map[] = { + "gmdablxe", "mdablxe", "gblxe", "", + }; + + RETURN_ARRAY_ELEM_BY_ENUM_TYPE(type, opts_map); +} + +size_t je_stats_type_to_default_buf_sz(je_stats_type type) +{ + static const size_t buf_sz_map[] = { + 2 * 1024, 4 * 1024, 1024 * 1024, 2 * 1024 * 1024, + }; + + RETURN_ARRAY_ELEM_BY_ENUM_TYPE(type, buf_sz_map); +} + +} // anonymous namespace + +std::string get_all_je_stats_types_str() +{ + std::vector names; + for (size_t i = 0; i < static_cast(je_stats_type::COUNT); ++i) { + names.emplace_back(enum_to_string(static_cast(i))); + } + return boost::join(names, " | "); +} + +void je_dump_stats(je_stats_type type, size_t buf_sz, std::string &stats) +{ + je_dump_malloc_stats(je_stats_type_to_opts(type), buf_sz, stats); +} + +void je_dump_stats(je_stats_type type, std::string &stats) +{ + je_dump_stats(type, je_stats_type_to_default_buf_sz(type), stats); +} + +} // namespace dsn + +#endif // DSN_USE_JEMALLOC diff --git a/src/rdsn/src/utils/je_ctl.h b/src/rdsn/src/utils/je_ctl.h new file mode 100644 index 0000000000..b26acfea9d --- /dev/null +++ b/src/rdsn/src/utils/je_ctl.h @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#pragma once + +#ifdef DSN_USE_JEMALLOC + +#include + +#include + +namespace dsn { + +// `je_stats_type` defines the types of stats that are dumped for jemalloc allocator: +// * By SUMMARY_STATS, it will dump the briefest message of stats, which only referred the +// summary information; +// * By CONFIGS, it will dump the configurations of jemalloc; +// * By BRIEF_ARENA_STATS, it will dump necessary information about overall stats for all +// arenas and individual stats for each arena; +// * By DETAILED_STATS, it will dump the detailed stats for all arenas, bins, extents, etc. +enum class je_stats_type : size_t +{ + SUMMARY_STATS, + CONFIGS, + BRIEF_ARENA_STATS, + DETAILED_STATS, + COUNT, + INVALID, +}; + +ENUM_BEGIN(je_stats_type, je_stats_type::INVALID) +ENUM_REG2(je_stats_type, SUMMARY_STATS) +ENUM_REG2(je_stats_type, CONFIGS) +ENUM_REG2(je_stats_type, BRIEF_ARENA_STATS) +ENUM_REG2(je_stats_type, DETAILED_STATS) +ENUM_END(je_stats_type) + +std::string get_all_je_stats_types_str(); +const std::string kAllJeStatsTypesStr = get_all_je_stats_types_str(); + +// Dump the stats of specified type to a string, with specified buffer size. +// +// The buffer is used to read stats from jemalloc allocator. The reason why an extra buffer is +// involved is that the stats can only be accessed in the callback function for jemalloc where +// memory allocation should be avoided. +void je_dump_stats(je_stats_type type, size_t buf_sz, std::string &stats); + +// Dump the stats of specified type to a string, with default buffer size. +void je_dump_stats(je_stats_type type, std::string &stats); + +} // namespace dsn + +#endif // DSN_USE_JEMALLOC diff --git a/src/rdsn/src/utils/test/je_ctl_test.cpp b/src/rdsn/src/utils/test/je_ctl_test.cpp new file mode 100644 index 0000000000..06e64be17c --- /dev/null +++ b/src/rdsn/src/utils/test/je_ctl_test.cpp @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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. + +#ifdef DSN_USE_JEMALLOC + +#include "utils/je_ctl.h" + +#include + +namespace dsn { + +namespace { + +// This function does not check "End" mark, since sometimes returned message is very long while +// the buffer may not have enough space: the message will be truncated. If it is known that the +// message can be held by the buffer completely, `check_base_stats_marks_with_end` should be used +// instead. +void check_base_stats_marks(const std::string &stats) +{ + ASSERT_NE(stats.find("Begin jemalloc statistics"), std::string::npos); + ASSERT_NE(stats.find("Allocated:"), std::string::npos); + ASSERT_NE(stats.find("Background threads:"), std::string::npos); +} + +void check_base_stats_marks_with_end(const std::string &stats) +{ + check_base_stats_marks(stats); + ASSERT_NE(stats.find("End jemalloc statistics"), std::string::npos); +} + +void check_configs_marks(const std::string &stats) +{ + ASSERT_NE(stats.find("Version:"), std::string::npos); + ASSERT_NE(stats.find("Build-time option settings"), std::string::npos); + ASSERT_NE(stats.find("Run-time option settings"), std::string::npos); + ASSERT_NE(stats.find("Profiling settings"), std::string::npos); +} + +void check_arena_marks(const std::string &stats) +{ + // Marks for merged arenas. + ASSERT_NE(stats.find("Merged arenas stats:"), std::string::npos); + + // Marks for each arena. + ASSERT_NE(stats.find("arenas[0]:"), std::string::npos); +} + +} // anonymous namespace + +TEST(je_ctl_test, dump_summary_stats) +{ + std::string stats; + je_dump_stats(je_stats_type::SUMMARY_STATS, stats); + + check_base_stats_marks_with_end(stats); +} + +TEST(je_ctl_test, dump_configs) +{ + std::string stats; + je_dump_stats(je_stats_type::CONFIGS, stats); + + check_base_stats_marks_with_end(stats); + check_configs_marks(stats); +} + +TEST(je_ctl_test, dump_brief_arena_stats) +{ + std::string stats; + je_dump_stats(je_stats_type::BRIEF_ARENA_STATS, stats); + + // Since there may be many arenas, "End" mark is not required to be checked here. + check_base_stats_marks(stats); + check_arena_marks(stats); +} + +TEST(je_ctl_test, dump_detailed_stats) +{ + std::string stats; + je_dump_stats(je_stats_type::DETAILED_STATS, stats); + + // Since there may be many arenas, "End" mark is not required to be checked here. + check_base_stats_marks(stats); + + // Detailed stats will contain all information, therefore everything should be checked. + check_configs_marks(stats); + check_arena_marks(stats); + ASSERT_NE(stats.find("bins:"), std::string::npos); + ASSERT_NE(stats.find("extents:"), std::string::npos); +} + +} // namespace dsn + +#endif // DSN_USE_JEMALLOC