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