diff --git a/.github/workflows/reusable_build_packages.yaml b/.github/workflows/reusable_build_packages.yaml index 650e4317bae..ac648412fb7 100644 --- a/.github/workflows/reusable_build_packages.yaml +++ b/.github/workflows/reusable_build_packages.yaml @@ -75,6 +75,7 @@ jobs: uses: falcosecurity/libs/.github/actions/install-zig@master - name: Prepare project + # Jemalloc and ASAN don't play very well together. run: | cmake -B build -S . \ -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} \ @@ -84,6 +85,7 @@ jobs: -DBUILD_DRIVER=Off \ -DBUILD_BPF=Off \ -DUSE_ASAN=${{ (inputs.sanitizers == true && inputs.arch == 'x86_64' && 'ON') || 'OFF' }} \ + -DUSE_JEMALLOC=${{ (inputs.sanitizers == true && inputs.arch == 'x86_64' && 'OFF') || 'ON' }} \ -DFALCO_VERSION=${{ inputs.version }} - name: Build project diff --git a/CMakeLists.txt b/CMakeLists.txt index 51f9e71ff51..a28d0b62a3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ option(BUILD_FALCO_UNIT_TESTS "Build falco unit tests" OFF) option(USE_ASAN "Build with AddressSanitizer" OFF) option(USE_UBSAN "Build with UndefinedBehaviorSanitizer" OFF) option(UBSAN_HALT_ON_ERROR "Halt on error when building with UBSan" ON) +option(USE_JEMALLOC "Use jemalloc allocator" OFF) if(WIN32) if(POLICY CMP0091) @@ -141,6 +142,13 @@ set(CMD_MAKE make) include(ExternalProject) +if(USE_JEMALLOC) + if(USE_ASAN) + message(WARNING "Jemalloc and ASAN are known to have issues when combined") + endif() + include(jemalloc) +endif() + # libs include(falcosecurity-libs) diff --git a/cmake/modules/jemalloc.cmake b/cmake/modules/jemalloc.cmake new file mode 100644 index 00000000000..bd9d1b11bf7 --- /dev/null +++ b/cmake/modules/jemalloc.cmake @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (C) 2024 The Falco Authors. +# +# Licensed 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. +# + +option(USE_BUNDLED_JEMALLOC "Use bundled jemalloc allocator" ${USE_BUNDLED_DEPS}) + +if(JEMALLOC_INCLUDE) + # we already have JEMALLOC +elseif(NOT USE_BUNDLED_JEMALLOC) + find_path(JEMALLOC_INCLUDE jemalloc/jemalloc.h) + set(JEMALLOC_INCLUDE ${JEMALLOC_INCLUDE}/jemalloc) + if(BUILD_SHARED_LIBS) + set(JEMALLOC_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(JEMALLOC_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + find_library(JEMALLOC_LIB NAMES libjemalloc${JEMALLOC_LIB_SUFFIX}) + if(JEMALLOC_LIB) + message(STATUS "Found JEMALLOC: include: ${JEMALLOC_INCLUDE}, lib: ${JEMALLOC_LIB}") + else() + message(FATAL_ERROR "Couldn't find system jemalloc") + endif() +else() + if(BUILD_SHARED_LIBS) + set(JEMALLOC_LIB_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}) + else() + set(JEMALLOC_LIB_SUFFIX ${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + set(JEMALLOC_SRC "${PROJECT_BINARY_DIR}/jemalloc-prefix/src") + set(JEMALLOC_LIB "${JEMALLOC_SRC}/jemalloc/lib/libjemalloc${JEMALLOC_LIB_SUFFIX}") + set(JEMALLOC_INCLUDE "${JEMALLOC_SRC}/jemalloc/include/jemalloc") + ExternalProject_Add( + jemalloc + PREFIX "${PROJECT_BINARY_DIR}/jemalloc-prefix" + URL "https://github.com/jemalloc/jemalloc/archive/refs/tags/5.3.0.tar.gz" + URL_HASH "SHA256=ef6f74fd45e95ee4ef7f9e19ebe5b075ca6b7fbe0140612b2a161abafb7ee179" + CONFIGURE_COMMAND ./autogen.sh --enable-prof --disable-libdl + BUILD_IN_SOURCE 1 + BUILD_COMMAND make build_lib_static + INSTALL_COMMAND "" + UPDATE_COMMAND "" + BUILD_BYPRODUCTS ${JEMALLOC_LIB} + ) + message(STATUS "Using bundled jemalloc: include: ${JEMALLOC_INCLUDE}, lib: ${JEMALLOC_LIB}") + install( + FILES "${JEMALLOC_LIB}" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${LIBS_PACKAGE_NAME}" + COMPONENT "libs-deps" + ) +endif() + +# We add a custom target, in this way we can always depend on `jemalloc` without distinguishing +# between "bundled" and "not-bundled" case +if(NOT TARGET jemalloc) + add_custom_target(jemalloc) +endif() + +include_directories(${JEMALLOC_INCLUDE}) +add_compile_definitions(HAS_JEMALLOC) diff --git a/falco.yaml b/falco.yaml index 80d7926b22c..4f3ab655360 100644 --- a/falco.yaml +++ b/falco.yaml @@ -1103,6 +1103,8 @@ syscall_event_drops: # there will be no metrics available. In other words, there are no default or # generic plugin metrics at this time. This may be subject to change. # +# `jemalloc_stats_enabled`: Falco can now expose jemalloc related stats. +# # If metrics are enabled, the web server can be configured to activate the # corresponding Prometheus endpoint using `webserver.prometheus_metrics_enabled`. # Prometheus output can be used in combination with the other output options. @@ -1124,6 +1126,7 @@ metrics: kernel_event_counters_per_cpu_enabled: false libbpf_stats_enabled: true plugins_metrics_enabled: true + jemalloc_stats_enabled: false convert_memory_to_mb: true include_empty_values: false diff --git a/userspace/falco/CMakeLists.txt b/userspace/falco/CMakeLists.txt index 13849e6fe9a..d6c399059e1 100644 --- a/userspace/falco/CMakeLists.txt +++ b/userspace/falco/CMakeLists.txt @@ -68,9 +68,13 @@ set(FALCO_INCLUDE_DIRECTORIES ) set(FALCO_DEPENDENCIES cxxopts) - set(FALCO_LIBRARIES falco_engine sinsp yaml-cpp) +if(USE_JEMALLOC) + list(APPEND FALCO_DEPENDENCIES jemalloc) + list(APPEND FALCO_LIBRARIES ${JEMALLOC_LIB}) +endif() + if(NOT WIN32) target_sources(falco_application PRIVATE outputs_program.cpp outputs_syslog.cpp) endif() @@ -96,6 +100,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "Linux" AND NOT MINIMAL_BUILD) list( APPEND FALCO_INCLUDE_DIRECTORIES + FALCO_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}" "${GRPC_INCLUDE}" "${GRPCPP_INCLUDE}" diff --git a/userspace/falco/config_json_schema.h b/userspace/falco/config_json_schema.h index 92ae04fe109..02006f6e37d 100644 --- a/userspace/falco/config_json_schema.h +++ b/userspace/falco/config_json_schema.h @@ -569,6 +569,9 @@ const char config_schema_string[] = LONG_STRING_CONST( }, "include_empty_values": { "type": "boolean" + }, + "jemalloc_stats_enabled": { + "type": "boolean" } }, "minProperties": 1, diff --git a/userspace/falco/configuration.cpp b/userspace/falco/configuration.cpp index b2359ed1da4..574fcd79535 100644 --- a/userspace/falco/configuration.cpp +++ b/userspace/falco/configuration.cpp @@ -610,6 +610,9 @@ void falco_configuration::load_yaml(const std::string &config_name) { if(m_config.get_scalar("metrics.plugins_metrics_enabled", true)) { m_metrics_flags |= METRICS_V2_PLUGINS; } + if(m_config.get_scalar("metrics.jemalloc_stats_enabled", true)) { + m_metrics_flags |= METRICS_V2_JEMALLOC_STATS; + } m_metrics_convert_memory_to_mb = m_config.get_scalar("metrics.convert_memory_to_mb", true); diff --git a/userspace/falco/configuration.h b/userspace/falco/configuration.h index ba6eb201e01..3a5f3a5ac9d 100644 --- a/userspace/falco/configuration.h +++ b/userspace/falco/configuration.h @@ -37,6 +37,9 @@ limitations under the License. #include "event_drops.h" #include "falco_outputs.h" +// Falco only metric +#define METRICS_V2_JEMALLOC_STATS 1 << 31 + enum class engine_kind_t : uint8_t { KMOD, EBPF, MODERN_EBPF, REPLAY, GVISOR, NODRIVER }; // Map that holds { config filename | validation status } for each loaded config file. diff --git a/userspace/falco/falco_metrics.cpp b/userspace/falco/falco_metrics.cpp index 8d58338de44..aca5e8fd24c 100644 --- a/userspace/falco/falco_metrics.cpp +++ b/userspace/falco/falco_metrics.cpp @@ -23,6 +23,10 @@ limitations under the License. #include +#ifdef HAS_JEMALLOC +#include +#endif + namespace fs = std::filesystem; /*! @@ -249,6 +253,38 @@ std::string falco_metrics::to_text(const falco::app::state& state) { } } } +#ifdef HAS_JEMALLOC + if(state.config->m_metrics_flags & METRICS_V2_JEMALLOC_STATS) { + nlohmann::json j; + malloc_stats_print( + [](void* to, const char* from) { + nlohmann::json* j = (nlohmann::json*)to; + *j = nlohmann::json::parse(from); + }, + &j, + "Jmdablxeg"); + const auto& j_stats = j["jemalloc"]["stats"]; + for(auto it = j_stats.begin(); it != j_stats.end(); ++it) { + if(it.value().is_number_unsigned()) { + std::uint64_t val = it.value().template get(); + std::string key = "jemalloc." + it.key(); + auto metric = libs::metrics::libsinsp_metrics::new_metric( + key.c_str(), + METRICS_V2_JEMALLOC_STATS, + METRIC_VALUE_TYPE_U64, + METRIC_VALUE_UNIT_MEMORY_BYTES, + METRIC_VALUE_METRIC_TYPE_MONOTONIC, + val); + prometheus_metrics_converter.convert_metric_to_unit_convention(metric); + prometheus_text += + prometheus_metrics_converter.convert_metric_to_text_prometheus( + metric, + "falcosecurity", + "falco"); + } + } + } +#endif } // Libs metrics categories diff --git a/userspace/falco/stats_writer.cpp b/userspace/falco/stats_writer.cpp index c5af9698e74..eac9e784be0 100644 --- a/userspace/falco/stats_writer.cpp +++ b/userspace/falco/stats_writer.cpp @@ -32,6 +32,10 @@ limitations under the License. #include #include +#ifdef HAS_JEMALLOC +#include +#endif + namespace fs = std::filesystem; // note: ticker_t is an uint16_t, which is enough because we don't care about @@ -434,6 +438,43 @@ void stats_writer::collector::get_metrics_output_fields_additional( } } +#ifdef HAS_JEMALLOC + if(m_writer->m_config->m_metrics_flags & METRICS_V2_JEMALLOC_STATS) { + nlohmann::json j; + malloc_stats_print( + [](void* to, const char* from) { + nlohmann::json* j = (nlohmann::json*)to; + *j = nlohmann::json::parse(from); + }, + &j, + "Jmdablxeg"); + const auto& j_stats = j["jemalloc"]["stats"]; + for(auto it = j_stats.begin(); it != j_stats.end(); ++it) { + if(it.value().is_number_unsigned()) { + std::uint64_t val = it.value().template get(); + if(m_writer->m_config->m_metrics_include_empty_values || val != 0) { + std::string key = "falco.jemalloc." + it.key() + "_bytes"; + auto metric = libs::metrics::libsinsp_metrics::new_metric( + key.c_str(), + METRICS_V2_JEMALLOC_STATS, + METRIC_VALUE_TYPE_U64, + METRIC_VALUE_UNIT_MEMORY_BYTES, + METRIC_VALUE_METRIC_TYPE_MONOTONIC, + val); + if(m_writer->m_config->m_metrics_convert_memory_to_mb && + m_writer->m_output_rule_metrics_converter) { + m_writer->m_output_rule_metrics_converter + ->convert_metric_to_unit_convention(metric); + output_fields[metric.name] = metric.value.d; + } else { + output_fields[metric.name] = metric.value.u64; + } + } + } + } + } +#endif + #if defined(__linux__) and !defined(MINIMAL_BUILD) and !defined(__EMSCRIPTEN__) if(m_writer->m_libs_metrics_collector && m_writer->m_output_rule_metrics_converter) { // Libs metrics categories