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