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(MValue(activePercValue, 100));
107     }
108     return true;
109 }
110 
111 auto HealthMetricCollection::readMemory() -> bool
112 {
113     constexpr auto procMeminfo = "/proc/meminfo";
114     std::ifstream memInfo(procMeminfo);
115     if (!memInfo.is_open())
116     {
117         error("Unable to open {PATH} for reading Memory stats", "PATH",
118               procMeminfo);
119         return false;
120     }
121     std::string line;
122     std::unordered_map<MetricIntf::SubType, double> memoryValues;
123 
124     while (std::getline(memInfo, line))
125     {
126         std::string name;
127         double value;
128         std::istringstream iss(line);
129 
130         if (!(iss >> name >> value))
131         {
132             continue;
133         }
134         if (name.starts_with("MemAvailable"))
135         {
136             memoryValues[MetricIntf::SubType::memoryAvailable] = value;
137         }
138         else if (name.starts_with("MemFree"))
139         {
140             memoryValues[MetricIntf::SubType::memoryFree] = value;
141         }
142         else if (name.starts_with("Buffers") || name.starts_with("Cached"))
143         {
144             memoryValues[MetricIntf::SubType::memoryBufferedAndCached] += value;
145         }
146         else if (name.starts_with("MemTotal"))
147         {
148             memoryValues[MetricIntf::SubType::memoryTotal] = value;
149         }
150         else if (name.starts_with("Shmem"))
151         {
152             memoryValues[MetricIntf::SubType::memoryShared] += value;
153         }
154     }
155 
156     for (auto& config : configs)
157     {
158         // Convert kB to Bytes
159         auto value = memoryValues.at(config.subType) * 1024;
160         auto total = memoryValues.at(MetricIntf::SubType::memoryTotal) * 1024;
161         debug("Memory Metric {SUBTYPE}: {VALUE}, {TOTAL}", "SUBTYPE",
162               std::to_underlying(config.subType), "VALUE", value, "TOTAL",
163               total);
164         metrics[config.subType]->update(MValue(value, total));
165     }
166     return true;
167 }
168 
169 auto HealthMetricCollection::readStorage() -> bool
170 {
171     for (auto& config : configs)
172     {
173         struct statvfs buffer;
174         if (statvfs(config.path.c_str(), &buffer) != 0)
175         {
176             auto e = errno;
177             error("Error from statvfs: {ERROR}, path: {PATH}", "ERROR",
178                   strerror(e), "PATH", config.path);
179             continue;
180         }
181         double value = buffer.f_bfree * buffer.f_frsize;
182         double total = buffer.f_blocks * buffer.f_frsize;
183         debug("Storage Metric {SUBTYPE}: {VALUE}, {TOTAL}", "SUBTYPE",
184               std::to_underlying(config.subType), "VALUE", value, "TOTAL",
185               total);
186         metrics[config.subType]->update(MValue(value, total));
187     }
188     return true;
189 }
190 
191 void HealthMetricCollection::read()
192 {
193     switch (type)
194     {
195         case MetricIntf::Type::cpu:
196         {
197             if (!readCPU())
198             {
199                 error("Failed to read CPU health metric");
200             }
201             break;
202         }
203         case MetricIntf::Type::memory:
204         {
205             if (!readMemory())
206             {
207                 error("Failed to read memory health metric");
208             }
209             break;
210         }
211         case MetricIntf::Type::storage:
212         {
213             if (!readStorage())
214             {
215                 error("Failed to read storage health metric");
216             }
217             break;
218         }
219         default:
220         {
221             error("Unknown health metric type {TYPE}", "TYPE",
222                   std::to_underlying(type));
223             break;
224         }
225     }
226 }
227 
228 void HealthMetricCollection::create(const MetricIntf::paths_t& bmcPaths)
229 {
230     metrics.clear();
231 
232     for (auto& config : configs)
233     {
234         /* TODO: Remove this after adding iNode support */
235         if (config.subType == MetricIntf::SubType::NA)
236         {
237             continue;
238         }
239         metrics[config.subType] = std::make_unique<MetricIntf::HealthMetric>(
240             bus, type, config, bmcPaths);
241     }
242 }
243 
244 } // namespace phosphor::health::metric::collection
245