* ucache is licensed under the Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
#include "resource_collector.h"
#include <dirent.h>
#include <cstring>
#include "ucache_file_util.h"
#include "ucache_string_util.h"
#include "ucache_turbo_config.h"
#include "ucache_turbo_error.h"
namespace turbo::ucache {
using namespace turbo::log;
namespace fs = std::filesystem;
const int KB_TO_BYTE = 1024;
uint32_t GetCgroupsIoBytesInfo(const std::string &dockerId, CgroupInfos &infos)
{
std::string path = "/sys/fs/cgroup/blkio/docker/" + dockerId + "/blkio.throttle.io_service_bytes_recursive";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE)
<< "Read cgroup io bytes failed, dockerId: " << dockerId;
return UCACHE_ERR;
}
if (lines.empty()) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Cgroup info is empty, dockerId: " << dockerId;
return UCACHE_ERR;
}
infos.ioInfo.ioReadBytes = 0;
infos.ioInfo.ioWriteBytes = 0;
static const char *kRead = "Read ";
static const char *kWrite = "Write ";
for (const auto &line : lines) {
uint64_t val = 0;
if (TryExtractValue(line, kRead, val)) {
infos.ioInfo.ioReadBytes += val;
} else if (TryExtractValue(line, kWrite, val)) {
infos.ioInfo.ioWriteBytes += val;
}
}
return UCACHE_OK;
}
uint32_t GetCgroupsIoTimesInfo(const std::string &dockerId, CgroupInfos &infos)
{
std::string path = "/sys/fs/cgroup/blkio/docker/" + dockerId + "/blkio.throttle.io_serviced_recursive";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE)
<< "Read cgroup io times failed, dockerId: " << dockerId;
return UCACHE_ERR;
}
if (lines.empty()) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Cgroup info is empty, dockerId: " << dockerId;
return UCACHE_ERR;
}
infos.ioInfo.ioReadTimes = 0;
infos.ioInfo.ioWriteTimes = 0;
static const char *kRead = "Read ";
static const char *kWrite = "Write ";
for (const auto &line : lines) {
uint64_t val = 0;
if (TryExtractValue(line, kRead, val)) {
infos.ioInfo.ioReadTimes += val;
} else if (TryExtractValue(line, kWrite, val)) {
infos.ioInfo.ioWriteTimes += val;
}
}
return UCACHE_OK;
}
uint32_t GetCgroupsPageCacheInfo(const std::string &dockerId, CgroupInfos &infos)
{
std::string path = "/sys/fs/cgroup/memory/docker/" + dockerId + "/memory.stat";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE)
<< "Read cgroup pagecache info failed, dockerId: " << dockerId;
return UCACHE_ERR;
}
if (lines.empty()) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Cgroup info is empty, dockerId: " << dockerId;
return UCACHE_ERR;
}
infos.pageCacheInfo = {0, 0, 0, 0};
static const char *kPgin = "total_pgpgin ";
static const char *kPgout = "total_pgpgout ";
static const char *kInact = "total_inactive_file ";
static const char *kAct = "total_active_file ";
for (const auto &line : lines) {
uint64_t val = 0;
if (TryExtractValue(line, kPgin, val)) {
infos.pageCacheInfo.pageCacheIn = val;
} else if (TryExtractValue(line, kPgout, val)) {
infos.pageCacheInfo.pageCacheOut = val;
} else if (TryExtractValue(line, kInact, val)) {
infos.pageCacheInfo.totalInactiveFileBytes = val;
} else if (TryExtractValue(line, kAct, val)) {
infos.pageCacheInfo.totalActiveFileBytes = val;
}
}
return UCACHE_OK;
}
uint32_t GetNodeMemInfo(const std::string &nodeId, NodeInfo &infos)
{
std::string path = "/sys/devices/system/node/" + nodeId + "/meminfo";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Read node meminfo failed, nodeId: " << nodeId;
return UCACHE_ERR;
}
if (lines.empty()) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Node meminfo is empty, nodeId: " << nodeId;
return UCACHE_ERR;
}
infos.memTotalBytes = 0;
infos.memFreeBytes = 0;
infos.memUsedBytes = 0;
infos.activeFileBytes = 0;
infos.inactiveFileBytes = 0;
static const char *kTotal = "MemTotal:";
static const char *kFree = "MemFree:";
static const char *kUsed = "MemUsed:";
static const char *kActive = "Active(file):";
static const char *kInactive = "Inactive(file):";
for (const auto &line : lines) {
uint64_t val = 0;
if (TryExtractValue(line, kTotal, val)) {
infos.memTotalBytes = val * KB_TO_BYTE;
} else if (TryExtractValue(line, kFree, val)) {
infos.memFreeBytes = val * KB_TO_BYTE;
} else if (TryExtractValue(line, kUsed, val)) {
infos.memUsedBytes = val * KB_TO_BYTE;
} else if (TryExtractValue(line, kActive, val)) {
infos.activeFileBytes = val * KB_TO_BYTE;
} else if (TryExtractValue(line, kInactive, val)) {
infos.inactiveFileBytes = val * KB_TO_BYTE;
}
}
return UCACHE_OK;
}
void GetRemoteInfo(const std::string &nodeId, NodeInfo &infos)
{
infos.isRemote = false;
std::string path = "/sys/devices/system/node/" + nodeId + "/remote";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK || lines.empty()) {
return;
}
std::string cleanStr = TrimString(lines[0]);
uint32_t val = 0;
if (SafeStoInt(cleanStr, val) == UCACHE_OK) {
infos.isRemote = (val != 0);
}
}
static bool IsDockerIdFormat(const std::string &s)
{
const size_t dockerIdLength = 64;
if (s.length() != dockerIdLength) {
return false;
}
for (char c : s) {
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
return false;
}
}
return true;
}
uint32_t GetDockerIDs(std::vector<std::string> &dockerIDs)
{
static const std::string kPath = "/sys/fs/cgroup/cpuset/docker/";
namespace fs = std::filesystem;
std::vector<std::string> allFiles;
if (UCacheFileUtil::ListDirectory(kPath, allFiles, true) != UCACHE_OK) {
UBTURBO_LOG_WARN(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Failed to open or list dockerSysPath.";
return UCACHE_OK;
}
for (const auto &name : allFiles) {
if (!IsDockerIdFormat(name)) {
continue;
}
std::string fullPath = kPath + name;
std::error_code ec;
if (!fs::is_directory(fullPath, ec) && !fs::is_symlink(fullPath, ec)) {
continue;
}
dockerIDs.emplace_back(name);
}
if (dockerIDs.empty()) {
UBTURBO_LOG_WARN(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "DockerIDs is empty.";
} else {
UBTURBO_LOG_DEBUG(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE)
<< dockerIDs.size() << " running docker containers found.";
}
return UCACHE_OK;
}
uint32_t CollectCgroupsInfo(std::unordered_map<std::string, CgroupInfos> &cgroupInfos)
{
std::vector<std::string> dockerIDs{};
uint32_t ret = GetDockerIDs(dockerIDs);
if (ret != UCACHE_OK) {
return ret;
}
for (const auto &id : dockerIDs) {
CgroupInfos infos{};
uint32_t ret = GetCgroupsIoBytesInfo(id, infos);
if (ret == UCACHE_ERR) {
return ret;
}
ret = GetCgroupsIoTimesInfo(id, infos);
if (ret == UCACHE_ERR) {
return ret;
}
ret = GetCgroupsPageCacheInfo(id, infos);
if (ret == UCACHE_ERR) {
return ret;
}
cgroupInfos.emplace(id, infos);
}
return UCACHE_OK;
}
uint32_t GetNodeIDs(std::vector<std::string> &nodeIDs)
{
static const std::string kPath = "/sys/devices/system/node";
namespace fs = std::filesystem;
std::vector<std::string> allFiles{};
if (UCacheFileUtil::ListDirectory(kPath, allFiles, true) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Sys_node_path is not exist or invalid.";
return UCACHE_ERR;
}
for (const auto &name : allFiles) {
if (name.find("node") != 0) {
continue;
}
std::string fullPath = kPath + "/" + name;
std::error_code ec;
if (!fs::is_directory(fullPath, ec)) {
continue;
}
nodeIDs.emplace_back(name);
}
return nodeIDs.empty() ? UCACHE_ERR : UCACHE_OK;
}
uint32_t CollectNodeInfo(std::unordered_map<std::string, NodeInfo> &numaInfos)
{
std::vector<std::string> nodeIDs{};
uint32_t ret = GetNodeIDs(nodeIDs);
if (ret == UCACHE_ERR) {
return ret;
}
for (const auto &id : nodeIDs) {
NodeInfo info{};
ret = GetNodeMemInfo(id, info);
if (ret == UCACHE_ERR) {
return ret;
}
GetRemoteInfo(id, info);
numaInfos.emplace(id, info);
}
return UCACHE_OK;
}
uint32_t GetMemMinFreeKbytes(MemWatermarkInfo &memWatermark)
{
std::string path = "/proc/sys/vm/min_free_kbytes";
std::vector<std::string> lines;
if (UCacheFileUtil::GetFileInfo(path, lines) != UCACHE_OK) {
return UCACHE_ERR;
}
if (lines.empty()) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << path << " has no content.";
return UCACHE_ERR;
}
std::string cleanVal = TrimString(lines[0]);
uint64_t val = 0;
if (SafeStoInt(cleanVal, val) != UCACHE_OK) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Invalid min_free_kbytes content: " << lines[0];
return UCACHE_ERR;
}
memWatermark.minFreeKBytes = val;
return UCACHE_OK;
}
uint32_t CollectMemWatermarkInfo(MemWatermarkInfo &memWatermark)
{
uint32_t ret = GetMemMinFreeKbytes(memWatermark);
if (ret == UCACHE_ERR) {
return ret;
}
return UCACHE_OK;
}
uint32_t GetMemWatermarkInfo(MemWatermarkInfo &memWatermark)
{
uint32_t ret = CollectMemWatermarkInfo(memWatermark);
if (ret == UCACHE_ERR) {
return ret;
}
return UCACHE_OK;
}
uint32_t GetCgroupsInfo(std::unordered_map<std::string, CgroupInfos> &cgroupInfos)
{
uint32_t ret = CollectCgroupsInfo(cgroupInfos);
if (ret == UCACHE_ERR) {
return ret;
}
return UCACHE_OK;
}
uint32_t GetNumaInfo(std::unordered_map<std::string, NodeInfo> &numaInfos)
{
uint32_t ret = CollectNodeInfo(numaInfos);
if (ret == UCACHE_ERR) {
UBTURBO_LOG_ERROR(UCACHE_MODULE_NAME, UCACHE_MODULE_CODE) << "Collect node info failed!";
return ret;
}
return UCACHE_OK;
}
}