1 #include "health_metric_collection.hpp" 2 3 #include <phosphor-logging/lg2.hpp> 4 5 #include <fstream> 6 #include <numeric> 7 #include <unordered_map> 8 9 extern "C" 10 { 11 #include <sys/statvfs.h> 12 } 13 14 PHOSPHOR_LOG2_USING; 15 16 namespace phosphor::health::metric::collection 17 { 18 19 auto HealthMetricCollection::readCPU() -> bool 20 { 21 enum CPUStatsIndex 22 { 23 userIndex = 0, 24 niceIndex, 25 systemIndex, 26 idleIndex, 27 iowaitIndex, 28 irqIndex, 29 softirqIndex, 30 stealIndex, 31 guestUserIndex, 32 guestNiceIndex, 33 maxIndex 34 }; 35 constexpr auto procStat = "/proc/stat"; 36 std::ifstream fileStat(procStat); 37 if (!fileStat.is_open()) 38 { 39 error("Unable to open {PATH} for reading CPU stats", "PATH", procStat); 40 return false; 41 } 42 43 std::string firstLine, labelName; 44 std::size_t timeData[CPUStatsIndex::maxIndex] = {0}; 45 46 std::getline(fileStat, firstLine); 47 std::stringstream ss(firstLine); 48 ss >> labelName; 49 50 if (labelName.compare("cpu")) 51 { 52 error("CPU data not available"); 53 return false; 54 } 55 56 for (auto idx = 0; idx < CPUStatsIndex::maxIndex; idx++) 57 { 58 if (!(ss >> timeData[idx])) 59 { 60 error("CPU data not correct"); 61 return false; 62 } 63 } 64 65 for (auto& config : configs) 66 { 67 uint64_t activeTime = 0, activeTimeDiff = 0, totalTime = 0, 68 totalTimeDiff = 0; 69 double activePercValue = 0; 70 71 if (config.subType == MetricIntf::SubType::cpuTotal) 72 { 73 activeTime = timeData[CPUStatsIndex::userIndex] + 74 timeData[CPUStatsIndex::niceIndex] + 75 timeData[CPUStatsIndex::systemIndex] + 76 timeData[CPUStatsIndex::irqIndex] + 77 timeData[CPUStatsIndex::softirqIndex] + 78 timeData[CPUStatsIndex::stealIndex] + 79 timeData[CPUStatsIndex::guestUserIndex] + 80 timeData[CPUStatsIndex::guestNiceIndex]; 81 } 82 else if (config.subType == MetricIntf::SubType::cpuKernel) 83 { 84 activeTime = timeData[CPUStatsIndex::systemIndex]; 85 } 86 else if (config.subType == MetricIntf::SubType::cpuUser) 87 { 88 activeTime = timeData[CPUStatsIndex::userIndex]; 89 } 90 91 totalTime = std::accumulate(std::begin(timeData), std::end(timeData), 92 decltype(totalTime){0}); 93 94 activeTimeDiff = activeTime - preActiveTime[config.subType]; 95 totalTimeDiff = totalTime - preTotalTime[config.subType]; 96 97 /* Store current active and total time for next calculation */ 98 preActiveTime[config.subType] = activeTime; 99 preTotalTime[config.subType] = totalTime; 100 101 activePercValue = (100.0 * activeTimeDiff) / totalTimeDiff; 102 debug("CPU Metric {SUBTYPE}: {VALUE}", "SUBTYPE", 103 std::to_underlying(config.subType), "VALUE", 104 (double)activePercValue); 105 /* For CPU, both user and monitor uses percentage values */ 106 metrics[config.subType]->update( 107 MValue(activePercValue, activePercValue)); 108 } 109 return true; 110 } 111 112 auto HealthMetricCollection::readMemory() -> bool 113 { 114 constexpr auto procMeminfo = "/proc/meminfo"; 115 std::ifstream memInfo(procMeminfo); 116 if (!memInfo.is_open()) 117 { 118 error("Unable to open {PATH} for reading Memory stats", "PATH", 119 procMeminfo); 120 return false; 121 } 122 std::string line; 123 std::unordered_map<MetricIntf::SubType, double> memoryValues; 124 125 while (std::getline(memInfo, line)) 126 { 127 std::string name; 128 double value; 129 std::istringstream iss(line); 130 131 if (!(iss >> name >> value)) 132 { 133 continue; 134 } 135 if (name.starts_with("MemAvailable")) 136 { 137 memoryValues[MetricIntf::SubType::memoryAvailable] = value; 138 } 139 else if (name.starts_with("MemFree")) 140 { 141 memoryValues[MetricIntf::SubType::memoryFree] = value; 142 } 143 else if (name.starts_with("Buffers") || name.starts_with("Cached")) 144 { 145 memoryValues[MetricIntf::SubType::memoryBufferedAndCached] += value; 146 } 147 else if (name.starts_with("MemTotal")) 148 { 149 memoryValues[MetricIntf::SubType::memoryTotal] = value; 150 } 151 else if (name.starts_with("Shmem")) 152 { 153 memoryValues[MetricIntf::SubType::memoryShared] = value; 154 } 155 } 156 157 for (auto& config : configs) 158 { 159 auto absoluteValue = memoryValues.at(config.subType); 160 auto memoryTotal = memoryValues.at(MetricIntf::SubType::memoryTotal); 161 double percentValue = (memoryTotal - absoluteValue) / memoryTotal * 100; 162 // Convert kB to Bytes 163 absoluteValue = absoluteValue * 1024; 164 debug("Memory Metric {SUBTYPE}: {VALUE}, {PERCENT}", "SUBTYPE", 165 std::to_underlying(config.subType), "VALUE", absoluteValue, 166 "PERCENT", percentValue); 167 metrics[config.subType]->update(MValue(absoluteValue, percentValue)); 168 } 169 return true; 170 } 171 172 auto HealthMetricCollection::readStorage() -> bool 173 { 174 for (auto& config : configs) 175 { 176 struct statvfs buffer; 177 if (statvfs(config.path.c_str(), &buffer) != 0) 178 { 179 auto e = errno; 180 error("Error from statvfs: {ERROR}, path: {PATH}", "ERROR", 181 strerror(e), "PATH", config.path); 182 continue; 183 } 184 double total = buffer.f_blocks * buffer.f_frsize; 185 double available = buffer.f_bfree * buffer.f_frsize; 186 double availablePercent = ((available / total) * 100); 187 188 debug("Storage Metric {SUBTYPE}: {TOTAL} {AVAIL} {AVAIL_PERCENT}", 189 "SUBTYPE", std::to_underlying(config.subType), "TOTAL", total, 190 "AVAIL", available, "AVAIL_PERCENT", availablePercent); 191 metrics[config.subType]->update(MValue(available, availablePercent)); 192 } 193 return true; 194 } 195 196 void HealthMetricCollection::read() 197 { 198 switch (type) 199 { 200 case MetricIntf::Type::cpu: 201 { 202 if (!readCPU()) 203 { 204 error("Failed to read CPU health metric"); 205 } 206 break; 207 } 208 case MetricIntf::Type::memory: 209 { 210 if (!readMemory()) 211 { 212 error("Failed to read memory health metric"); 213 } 214 break; 215 } 216 case MetricIntf::Type::storage: 217 { 218 if (!readStorage()) 219 { 220 error("Failed to read storage health metric"); 221 } 222 break; 223 } 224 default: 225 { 226 error("Unknown health metric type {TYPE}", "TYPE", 227 std::to_underlying(type)); 228 break; 229 } 230 } 231 } 232 233 void HealthMetricCollection::create(const MetricIntf::paths_t& bmcPaths) 234 { 235 metrics.clear(); 236 237 for (auto& config : configs) 238 { 239 /* TODO: Remove this after adding iNode support */ 240 if (config.subType == MetricIntf::SubType::NA) 241 { 242 continue; 243 } 244 metrics[config.subType] = std::make_unique<MetricIntf::HealthMetric>( 245 bus, type, config, bmcPaths); 246 } 247 } 248 249 } // namespace phosphor::health::metric::collection 250