diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e923e3..537ff7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable(${PROJECT_NAME} FileParsers/MemInfo.cpp FileParsers/Smaps.cpp + FileParsers/ZRAM.cpp JsonReportGenerator.cpp diff --git a/FileParsers/ZRAM.cpp b/FileParsers/ZRAM.cpp new file mode 100644 index 0000000..3c5fdaa --- /dev/null +++ b/FileParsers/ZRAM.cpp @@ -0,0 +1,85 @@ +#include "ZRAM.h" +#include "Log.h" + +#include +#include +#include +#include +#include + +ZRAM::ZRAM() { + FindZRAMDevices(); +} + +void ZRAM::FindZRAMDevices() { + m_zramDevices.clear(); + + DIR* dir = opendir(SYS_ZRAM_PATH); + if (!dir) { + LOG_SYS_WARN(errno, "Failed to open directory: %s", SYS_ZRAM_PATH); + return; + } + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr && m_zramDevices.size() < MAX_ZRAM_DEVICES) { + if (strncmp(entry->d_name, ZRAM_DEVICE_PREFIX, strlen(ZRAM_DEVICE_PREFIX)) == 0) { + m_zramDevices.push_back(entry->d_name); + LOG_INFO("Tracking ZRAM device: %s", entry->d_name); + } + } + + closedir(dir); +} + +std::vector ZRAM::GetDeviceStats() const { + std::vector deviceStats; + + for (const auto& device : m_zramDevices) { + ZRAMDeviceStats stats; + stats.device_name = device; + + if (ReadMMStat(device, stats)) { + deviceStats.push_back(stats); + + LOG_DEBUG("ZRAM device: %s, Data: %lu MB, Compressed: %lu MB, Memory Used: %lu MB", + stats.device_name.c_str(), + stats.orig_data_size, + stats.compr_data_size, + stats.mem_used_total); + } + } + + return deviceStats; +} + +bool ZRAM::ReadMMStat(const std::string& device, ZRAMDeviceStats& stats) const { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s%s/mm_stat", SYS_ZRAM_PATH, device.c_str()); + + FILE* file = fopen(path, "r"); + if (!file) { + LOG_SYS_ERROR(errno, "Failed to open file: %s", path); + return false; + } + + unsigned long orig_data_size, compr_data_size, mem_used_total; + + if (fscanf(file, "%lu %lu %lu", &orig_data_size, &compr_data_size, &mem_used_total) != 3) { + LOG_ERROR("Failed to read mm_stat metrics from file: %s", path); + fclose(file); + return false; + } + + fclose(file); + + // Convert to MB + stats.orig_data_size = BytesToMB(orig_data_size); + stats.compr_data_size = BytesToMB(compr_data_size); + stats.mem_used_total = BytesToMB(mem_used_total); + + return true; +} + +unsigned long ZRAM::BytesToMB(unsigned long bytes) const { + return bytes / (1024 * 1024); +} \ No newline at end of file diff --git a/FileParsers/ZRAM.h b/FileParsers/ZRAM.h new file mode 100644 index 0000000..a1ef5de --- /dev/null +++ b/FileParsers/ZRAM.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +// Structure to store ZRAM device statistics +struct ZRAMDeviceStats { + std::string device_name; + unsigned long orig_data_size; // Original data size in MB + unsigned long compr_data_size; // Compressed data size in MB + unsigned long mem_used_total; // Total memory used in MB +}; + +class ZRAM { +public: + ZRAM(); + + bool hasZRAM() { return m_zramDevices.size() > 0; } + + std::vector GetDeviceStats() const; + +private: + static constexpr const char* SYS_ZRAM_PATH = "/sys/block/"; + static constexpr const char* ZRAM_DEVICE_PREFIX = "zram"; + static constexpr int MAX_ZRAM_DEVICES = 4; + + // Collects ZRAM device paths + void FindZRAMDevices(); + + // Read stats from mm_stat file for a specific device + bool ReadMMStat(const std::string& device, ZRAMDeviceStats& stats) const; + + // Convert bytes to megabytes + unsigned long BytesToMB(unsigned long bytes) const; + + std::vector m_zramDevices; +}; \ No newline at end of file diff --git a/MemoryMetric.cpp b/MemoryMetric.cpp index fcead00..10f7670 100644 --- a/MemoryMetric.cpp +++ b/MemoryMetric.cpp @@ -25,6 +25,7 @@ #include #include #include +#include MemoryMetric::MemoryMetric(Platform platform, std::shared_ptr reportGenerator) : mQuit(false), @@ -36,7 +37,8 @@ MemoryMetric::MemoryMetric(Platform platform, std::shared_ptr(end - start).count()); @@ -322,6 +335,7 @@ void MemoryMetric::SaveResults() measurement.second}); } mReportGenerator->addDataset("BMEM", data); + data.clear(); // Add all BMEM memory to accumulated total long double bmemSum = 0; @@ -331,6 +345,27 @@ void MemoryMetric::SaveResults() }); mReportGenerator->addToAccumulatedMemoryUsage(bmemSum); } + + if (mZRAMSupported) { + for (const auto &result : mZramMeasurements) { + data.emplace_back(JsonReportGenerator::dataItems{std::make_pair("Name", result.first), result.second.OrigDataSize, result.second.ComprDataSize, + result.second.MemUsedTotal}); + } + mReportGenerator->addDataset("zram", data); + data.clear(); + } + + // *** Cgroup *** + for (const auto &result: mCgroupMeasurements) { + data.emplace_back(JsonReportGenerator::dataItems{ + std::make_pair("Heap", result.first), + result.second.FailCnt, + result.second.LimitKB, + result.second.UsedKB + }); + } + mReportGenerator->addDataset("Cgroup", data); + data.clear(); } void MemoryMetric::GetLinuxMemoryUsage() @@ -895,3 +930,145 @@ pid_t MemoryMetric::tidToParentPid(pid_t tid) // Failed to find Tgid in file, weird? return -1; } + +void MemoryMetric::GetZramMetrics() +{ + if (!mZRAMSupported) { + return; + } + + std::vector devStats = mZRAM.GetDeviceStats(); + + for (const auto& dev : devStats) { + auto itr = mZramMeasurements.find(dev.device_name); + if (itr == mZramMeasurements.end()) { + Measurement origSize("Data_Used_MB"); + origSize.AddDataPoint(dev.orig_data_size); + + Measurement compSize("Compressed_Data_MB"); + compSize.AddDataPoint(dev.compr_data_size); + + Measurement memUsed("Total_Memory_MB"); + memUsed.AddDataPoint(dev.mem_used_total); + + mZramMeasurements.insert(std::make_pair(dev.device_name, + zramMeasurement(origSize, compSize, memUsed))); + } else { + itr->second.OrigDataSize.AddDataPoint(dev.orig_data_size); + itr->second.ComprDataSize.AddDataPoint(dev.compr_data_size); + itr->second.MemUsedTotal.AddDataPoint(dev.mem_used_total); + } + } +} + +void MemoryMetric::GetCgroupMetrics(const std::string &cgroupType) +{ + static const std::string CGROUP_BASE = "/sys/fs/cgroup/"; + + std::string cgroupKeyStr; + + const std::string cgroupPath = CGROUP_BASE + cgroupType; + if (!std::filesystem::exists(cgroupPath)) { + return; + } + + static std::ifstream fileStream; + fileStream.rdbuf()->pubsetbuf(nullptr, 0); + + static const auto readValue = [](std::ifstream &fs, const std::string &path) -> long { + fs.clear(); + fs.open(path); + if (!fs) { + LOG_WARN("Failed to open file %s", path.c_str()); + fs.close(); + return 0; + } + + long value = 0; + fs >> value; + fs.close(); + + return (value == LONG_MAX) ? 0 : value; + }; + + for (const auto &dirEntry : std::filesystem::directory_iterator(cgroupPath)) { + if (!dirEntry.is_directory()) { + continue; + } + + const auto &dirPath = dirEntry.path(); + const auto &cgroupName = dirPath.filename().string(); + const auto cgroupKey = cgroupType + ":" + cgroupName; + + const auto basePath = dirPath / cgroupType; + try { + long usageVal = readValue(fileStream, basePath.string() + ".usage_in_bytes"); + long limitVal = readValue(fileStream, basePath.string() + ".limit_in_bytes"); + long failcntVal = readValue(fileStream, basePath.string() + ".failcnt"); + + auto itr = mCgroupMeasurements.find(cgroupKey); + if (itr != mCgroupMeasurements.end()) { + itr->second.UsedKB.AddDataPoint(usageVal / 1024.0); + itr->second.LimitKB.AddDataPoint(limitVal / 1024.0); + itr->second.FailCnt.AddDataPoint(failcntVal); + LOG_DEBUG("%s - Used: %ld, limit: %ld, failcnt: %ld", cgroupKey.c_str(), usageVal, limitVal, failcntVal); + } else { + Measurement used("Used_KB"); + used.AddDataPoint(usageVal / 1024.0); + Measurement limit("Limit_KB"); + limit.AddDataPoint(limitVal / 1024.0); + Measurement failcnt("Fail_Count"); + failcnt.AddDataPoint(failcntVal); + + mCgroupMeasurements.insert(std::make_pair(cgroupKey, cgroupMeasurement(failcnt, used, limit))); + } + + if (cgroupType == "memory") { + long kmemUsage = readValue(fileStream, basePath.string() + ".kmem.usage_in_bytes"); + long kmemLimit = readValue(fileStream, basePath.string() + ".kmem.limit_in_bytes"); + long kmemFailcnt = readValue(fileStream, basePath.string() + ".kmem.failcnt"); + + long swapUsage = readValue(fileStream, basePath.string() + ".memsw.usage_in_bytes"); + long swapLimit = readValue(fileStream, basePath.string() + ".memsw.limit_in_bytes"); + long swapFailcnt = readValue(fileStream, basePath.string() + ".memsw.failcnt"); + + cgroupKeyStr = cgroupType + "_KMem:" + cgroupName; + auto memItr = mCgroupMeasurements.find(cgroupKeyStr); + if (memItr != mCgroupMeasurements.end()) { + memItr->second.UsedKB.AddDataPoint(kmemUsage / 1024.0); + memItr->second.LimitKB.AddDataPoint(kmemLimit / 1024.0); + memItr->second.FailCnt.AddDataPoint(kmemFailcnt); + LOG_DEBUG("%s - used: %ld, limit: %ld, failcnt: %ld", cgroupKeyStr.c_str(), kmemUsage, kmemLimit, kmemFailcnt); + } else { + Measurement kmemUsed("Used_KB"); + kmemUsed.AddDataPoint(kmemUsage / 1024.0); + Measurement kmemLim("Limit_KB"); + kmemLim.AddDataPoint(kmemLimit / 1024.0); + Measurement kmemFail("Fail_Count"); + kmemFail.AddDataPoint(kmemFailcnt); + mCgroupMeasurements.insert(std::make_pair(cgroupKeyStr, cgroupMeasurement(kmemFail, kmemUsed, kmemLim))); + } + + cgroupKeyStr = cgroupType + "_MemSw:" + cgroupName; + memItr = mCgroupMeasurements.find(cgroupKeyStr); + if (memItr != mCgroupMeasurements.end()) { + memItr->second.UsedKB.AddDataPoint(swapUsage / 1024.0); + memItr->second.LimitKB.AddDataPoint(swapLimit / 1024.0); + memItr->second.FailCnt.AddDataPoint(swapFailcnt); + LOG_DEBUG("%s - used: %ld, limit: %ld, failcnt: %ld", cgroupKeyStr.c_str(), swapUsage, swapLimit, swapFailcnt); + } else { + Measurement swapUsed("Used_KB"); + swapUsed.AddDataPoint(swapUsage / 1024.0); + Measurement swapLim("Limit_KB"); + swapLim.AddDataPoint(swapLimit / 1024.0); + Measurement swapFail("Fail_Count"); + swapFail.AddDataPoint(swapFailcnt); + mCgroupMeasurements.insert(std::make_pair(cgroupKeyStr, cgroupMeasurement(swapFail, swapUsed, swapLim))); + } + } + } catch (const std::exception &) { + LOG_WARN("Failed to read cgroup metrics for %s", cgroupKey.c_str()); + continue; + } + } +} \ No newline at end of file diff --git a/MemoryMetric.h b/MemoryMetric.h index c02a7cb..725d006 100644 --- a/MemoryMetric.h +++ b/MemoryMetric.h @@ -30,6 +30,7 @@ #include "Procrank.h" #include "JsonReportGenerator.h" +#include "FileParsers/ZRAM.h" class MemoryMetric : public IMetric @@ -69,6 +70,10 @@ class MemoryMetric : public IMetric void GetGpuMemoryUsageRealtek(); + void GetZramMetrics(); + + void GetCgroupMetrics(const std::string &cgroupType); + pid_t tidToParentPid(pid_t tid); private: @@ -114,6 +119,35 @@ class MemoryMetric : public IMetric Measurement Used; }; + struct zramMeasurement + { + zramMeasurement(Measurement &_origDataSize, Measurement &_comprDataSize, Measurement &_memUsedTotal) + : OrigDataSize(std::move(_origDataSize)), + ComprDataSize(std::move(_comprDataSize)), + MemUsedTotal(std::move(_memUsedTotal)) + { + } + + Measurement OrigDataSize; + Measurement ComprDataSize; + Measurement MemUsedTotal; + }; + + struct cgroupMeasurement + { + cgroupMeasurement(Measurement &_failcnt, Measurement &_usedKB, Measurement &_limitKB) + : FailCnt(std::move(_failcnt)), + UsedKB(std::move(_usedKB)), + LimitKB(std::move(_limitKB)) + { + + } + + Measurement FailCnt; + Measurement UsedKB; + Measurement LimitKB; + }; + std::thread mCollectionThread; bool mQuit; std::condition_variable mCv; @@ -143,4 +177,10 @@ class MemoryMetric : public IMetric std::map mCmaNames; std::shared_ptr mReportGenerator; + + ZRAM mZRAM; + bool mZRAMSupported; + std::map mZramMeasurements; + + std::map mCgroupMeasurements; };