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         absoluteValue = absoluteValue * 1000;
163         debug("Memory Metric {SUBTYPE}: {VALUE}, {PERCENT}", "SUBTYPE",
164               std::to_underlying(config.subType), "VALUE", absoluteValue,
165               "PERCENT", percentValue);
166         metrics[config.subType]->update(MValue(absoluteValue, percentValue));
167     }
168     return true;
169 }
170 
171 auto HealthMetricCollection::readStorage() -> bool
172 {
173     for (auto& config : configs)
174     {
175         struct statvfs buffer;
176         if (statvfs(config.path.c_str(), &buffer) != 0)
177         {
178             auto e = errno;
179             error("Error from statvfs: {ERROR}, path: {PATH}", "ERROR",
180                   strerror(e), "PATH", config.path);
181             continue;
182         }
183         double total = buffer.f_blocks * buffer.f_frsize;
184         double available = buffer.f_bfree * buffer.f_frsize;
185         double availablePercent = ((available / total) * 100);
186 
187         debug("Storage Metric {SUBTYPE}: {TOTAL} {AVAIL} {AVAIL_PERCENT}",
188               "SUBTYPE", std::to_underlying(config.subType), "TOTAL", total,
189               "AVAIL", available, "AVAIL_PERCENT", availablePercent);
190         metrics[config.subType]->update(MValue(available, availablePercent));
191     }
192     return true;
193 }
194 
195 void HealthMetricCollection::read()
196 {
197     switch (type)
198     {
199         case MetricIntf::Type::cpu:
200         {
201             if (!readCPU())
202             {
203                 error("Failed to read CPU health metric");
204             }
205             break;
206         }
207         case MetricIntf::Type::memory:
208         {
209             if (!readMemory())
210             {
211                 error("Failed to read memory health metric");
212             }
213             break;
214         }
215         case MetricIntf::Type::storage:
216         {
217             if (!readStorage())
218             {
219                 error("Failed to read storage health metric");
220             }
221             break;
222         }
223         default:
224         {
225             error("Unknown health metric type {TYPE}", "TYPE",
226                   std::to_underlying(type));
227             break;
228         }
229     }
230 }
231 
232 void HealthMetricCollection::create(const MetricIntf::paths_t& bmcPaths)
233 {
234     metrics.clear();
235 
236     for (auto& config : configs)
237     {
238         /* TODO: Remove this after adding iNode support */
239         if (config.subType == MetricIntf::SubType::NA)
240         {
241             continue;
242         }
243         metrics[config.subType] = std::make_unique<MetricIntf::HealthMetric>(
244             bus, type, config, bmcPaths);
245     }
246 }
247 
248 } // namespace phosphor::health::metric::collection
249