// 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. // This file is copied from // https://github.com/ClickHouse/ClickHouse/blob/master/src/Common/CgroupsMemoryUsageObserver.cpp // and modified by Doris #include "common/cgroup_memory_ctl.h" #include #include #include #include #include "common/status.h" #include "util/cgroup_util.h" namespace doris { // Is the memory controller of cgroups v2 enabled on the system? // Assumes that cgroupsv2_enable() is enabled. Status cgroupsv2_memory_controller_enabled(bool* ret) { #if defined(OS_LINUX) if (!CGroupUtil::cgroupsv2_enable()) { return Status::CgroupError("cgroupsv2_enable is false"); } // According to https://docs.kernel.org/admin-guide/cgroup-v2.html, file "cgroup.controllers" defines which controllers are available // for the current + child cgroups. The set of available controllers can be restricted from level to level using file // "cgroups.subtree_control". It is therefore sufficient to check the bottom-most nested "cgroup.controllers" file. std::string cgroup = CGroupUtil::cgroupv2_of_process(); auto cgroup_dir = cgroup.empty() ? default_cgroups_mount : (default_cgroups_mount / cgroup); std::ifstream controllers_file(cgroup_dir / "cgroup.controllers"); if (!controllers_file.is_open()) { *ret = false; return Status::CgroupError("open cgroup.controllers failed"); } std::string controllers; std::getline(controllers_file, controllers); *ret = controllers.find("memory") != std::string::npos; return Status::OK(); #else *ret = false; return Status::CgroupError("cgroupsv2 only support Linux"); #endif } struct CgroupsV1Reader : CGroupMemoryCtl::ICgroupsReader { explicit CgroupsV1Reader(std::filesystem::path mount_file_dir) : _mount_file_dir(std::move(mount_file_dir)) {} Status read_memory_limit(int64_t* value) override { RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file( (_mount_file_dir / "memory.limit_in_bytes"), value)); return Status::OK(); } Status read_memory_usage(int64_t* value) override { std::unordered_map metrics_map; CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"), metrics_map); *value = metrics_map["rss"]; return Status::OK(); } private: std::filesystem::path _mount_file_dir; }; struct CgroupsV2Reader : CGroupMemoryCtl::ICgroupsReader { explicit CgroupsV2Reader(std::filesystem::path mount_file_dir) : _mount_file_dir(std::move(mount_file_dir)) {} Status read_memory_limit(int64_t* value) override { RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file((_mount_file_dir / "memory.max"), value)); return Status::OK(); } Status read_memory_usage(int64_t* value) override { // memory.current contains a single number // the reason why we subtract it described here: https://github.com/ClickHouse/ClickHouse/issues/64652#issuecomment-2149630667 RETURN_IF_ERROR(CGroupUtil::read_int_line_from_cgroup_file( (_mount_file_dir / "memory.current"), value)); std::unordered_map metrics_map; CGroupUtil::read_int_metric_from_cgroup_file((_mount_file_dir / "memory.stat"), metrics_map); if (*value < metrics_map["inactive_file"]) { return Status::CgroupError("CgroupsV2Reader read_memory_usage negative memory usage"); } *value -= metrics_map["inactive_file"]; return Status::OK(); } private: std::filesystem::path _mount_file_dir; }; std::pair get_cgroups_path() { bool enable_controller; auto cgroupsv2_memory_controller_st = cgroupsv2_memory_controller_enabled(&enable_controller); if (CGroupUtil::cgroupsv2_enable() && cgroupsv2_memory_controller_st.ok() && enable_controller) { auto v2_memory_stat_path = CGroupUtil::get_cgroupsv2_path("memory.stat"); auto v2_memory_current_path = CGroupUtil::get_cgroupsv2_path("memory.current"); auto v2_memory_max_path = CGroupUtil::get_cgroupsv2_path("memory.max"); if (v2_memory_stat_path.has_value() && v2_memory_current_path.has_value() && v2_memory_max_path.has_value() && v2_memory_stat_path == v2_memory_current_path && v2_memory_current_path == v2_memory_max_path) { return {*v2_memory_stat_path, CGroupUtil::CgroupsVersion::V2}; } } std::string cgroup_path; auto st = CGroupUtil::find_abs_cgroupv1_path("memory", &cgroup_path); if (st.ok()) { return {cgroup_path, CGroupUtil::CgroupsVersion::V1}; } return {"", CGroupUtil::CgroupsVersion::V1}; } Status get_cgroups_reader(std::shared_ptr& reader) { const auto [cgroup_path, version] = get_cgroups_path(); if (cgroup_path.empty()) { bool enable_controller; auto st = cgroupsv2_memory_controller_enabled(&enable_controller); return Status::CgroupError( "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, " "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}", CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(), CGroupUtil::cgroupsv1_enable()); } if (version == CGroupUtil::CgroupsVersion::V2) { reader = std::make_shared(cgroup_path); } else { reader = std::make_shared(cgroup_path); } return Status::OK(); } Status CGroupMemoryCtl::find_cgroup_mem_limit(int64_t* bytes) { std::shared_ptr reader; RETURN_IF_ERROR(get_cgroups_reader(reader)); RETURN_IF_ERROR(reader->read_memory_limit(bytes)); return Status::OK(); } Status CGroupMemoryCtl::find_cgroup_mem_usage(int64_t* bytes) { std::shared_ptr reader; RETURN_IF_ERROR(get_cgroups_reader(reader)); RETURN_IF_ERROR(reader->read_memory_usage(bytes)); return Status::OK(); } std::string CGroupMemoryCtl::debug_string() { const auto [cgroup_path, version] = get_cgroups_path(); if (cgroup_path.empty()) { bool enable_controller; auto st = cgroupsv2_memory_controller_enabled(&enable_controller); return fmt::format( "Cannot find cgroups v1 or v2 current memory file, cgroupsv2_enable: {},{}, " "cgroupsv2_memory_controller_enabled: {}, cgroupsv1_enable: {}", CGroupUtil::cgroupsv2_enable(), enable_controller, st.to_string(), CGroupUtil::cgroupsv1_enable()); } int64_t mem_limit; auto mem_limit_st = find_cgroup_mem_limit(&mem_limit); int64_t mem_usage; auto mem_usage_st = find_cgroup_mem_usage(&mem_usage); return fmt::format( "Process CGroup Memory Info (cgroups path: {}, cgroup version: {}): memory limit: " "{}, " "memory usage: {}", cgroup_path, (version == CGroupUtil::CgroupsVersion::V1) ? "v1" : "v2", mem_limit_st.ok() ? std::to_string(mem_limit) : mem_limit_st.to_string(), mem_usage_st.ok() ? std::to_string(mem_usage) : mem_usage_st.to_string()); } } // namespace doris