blob: 0e146982e8921250fef252ed845c908e0e683aad [file] [log] [blame]
Brandon Kimdab96f12021-02-18 11:21:37 -08001// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Sui Chen03eba282021-02-11 11:35:56 -080015#include "util.hpp"
16
Michael Shenb63d6312021-04-26 13:30:57 +080017#include <fcntl.h>
18#include <sys/mman.h>
19#include <sys/stat.h>
Sui Chen03eba282021-02-11 11:35:56 -080020#include <unistd.h>
21
22#include <phosphor-logging/log.hpp>
Michael Shenb63d6312021-04-26 13:30:57 +080023#include <sdbusplus/bus.hpp>
24#include <sdbusplus/message.hpp>
Sui Chen03eba282021-02-11 11:35:56 -080025
26#include <cmath>
27#include <cstdlib>
28#include <fstream>
29#include <sstream>
30#include <string>
31#include <string_view>
Michael Shenb63d6312021-04-26 13:30:57 +080032#include <unordered_map>
33#include <utility>
34#include <variant>
35#include <vector>
Sui Chen03eba282021-02-11 11:35:56 -080036
37namespace metric_blob
38{
39
40using phosphor::logging::log;
41using level = phosphor::logging::level;
42
43char controlCharsToSpace(char c)
44{
45 if (c < 32)
46 {
47 c = ' ';
48 }
49 return c;
50}
51
52long getTicksPerSec()
53{
54 return sysconf(_SC_CLK_TCK);
55}
56
Michael Shenb63d6312021-04-26 13:30:57 +080057std::string readFileThenGrepIntoString(const std::string_view fileName,
58 const std::string_view grepStr)
Sui Chen03eba282021-02-11 11:35:56 -080059{
60 std::stringstream ss;
61 std::ifstream ifs(fileName.data());
62 while (ifs.good())
63 {
64 std::string line;
65 std::getline(ifs, line);
Michael Shenb63d6312021-04-26 13:30:57 +080066 if (line.find(grepStr) != std::string::npos)
67 {
68 ss << line;
69 }
Sui Chen03eba282021-02-11 11:35:56 -080070 if (ifs.good())
71 ss << std::endl;
72 }
73 return ss.str();
74}
75
76bool isNumericPath(const std::string_view path, int& value)
77{
78 size_t p = path.rfind('/');
79 if (p == std::string::npos)
80 {
81 return false;
82 }
83 int id = 0;
84 for (size_t i = p + 1; i < path.size(); ++i)
85 {
86 const char ch = path[i];
87 if (ch < '0' || ch > '9')
88 return false;
89 else
90 {
91 id = id * 10 + (ch - '0');
92 }
93 }
94 value = id;
95 return true;
96}
97
98// Trims all control characters at the end of a string.
99std::string trimStringRight(std::string_view s)
100{
101 std::string ret(s.data());
102 while (!ret.empty())
103 {
104 if (ret.back() <= 32)
105 ret.pop_back();
106 else
107 break;
108 }
109 return ret;
110}
111
112std::string getCmdLine(const int pid)
113{
Patrick Williams2be45232023-05-10 07:51:22 -0500114 const std::string& cmdlinePath = "/proc/" + std::to_string(pid) +
115 "/cmdline";
Sui Chen03eba282021-02-11 11:35:56 -0800116
Michael Shenb63d6312021-04-26 13:30:57 +0800117 std::string cmdline = readFileThenGrepIntoString(cmdlinePath);
Sui Chen03eba282021-02-11 11:35:56 -0800118 for (size_t i = 0; i < cmdline.size(); ++i)
119 {
120 cmdline[i] = controlCharsToSpace(cmdline[i]);
121 }
122
123 // Trim empty strings
124 cmdline = trimStringRight(cmdline);
125
126 return cmdline;
127}
128
129// strtok is used in this function in order to avoid usage of <sstream>.
130// However, that would require us to create a temporary std::string.
131TcommUtimeStime parseTcommUtimeStimeString(std::string_view content,
132 const long ticksPerSec)
133{
134 TcommUtimeStime ret;
135 ret.tcomm = "";
136 ret.utime = ret.stime = 0;
137
138 const float invTicksPerSec = 1.0f / static_cast<float>(ticksPerSec);
139
140 // pCol now points to the first part in content after content is split by
141 // space.
142 // This is not ideal,
143 std::string temp(content);
144 char* pCol = strtok(temp.data(), " ");
145
146 if (pCol != nullptr)
147 {
148 const int fields[] = {1, 13, 14}; // tcomm, utime, stime
149 int fieldIdx = 0;
150 for (int colIdx = 0; colIdx < 15; ++colIdx)
151 {
152 if (fieldIdx < 3 && colIdx == fields[fieldIdx])
153 {
154 switch (fieldIdx)
155 {
156 case 0:
157 {
158 ret.tcomm = std::string(pCol);
159 break;
160 }
161 case 1:
162 [[fallthrough]];
163 case 2:
164 {
165 int ticks = std::atoi(pCol);
166 float t = static_cast<float>(ticks) * invTicksPerSec;
167
168 if (fieldIdx == 1)
169 {
170 ret.utime = t;
171 }
172 else if (fieldIdx == 2)
173 {
174 ret.stime = t;
175 }
176 break;
177 }
178 }
179 ++fieldIdx;
180 }
181 pCol = strtok(nullptr, " ");
182 }
183 }
184
185 if (ticksPerSec <= 0)
186 {
187 log<level::ERR>("ticksPerSec is equal or less than zero");
188 }
189
190 return ret;
191}
192
193TcommUtimeStime getTcommUtimeStime(const int pid, const long ticksPerSec)
194{
195 const std::string& statPath = "/proc/" + std::to_string(pid) + "/stat";
Michael Shenb63d6312021-04-26 13:30:57 +0800196 return parseTcommUtimeStimeString(readFileThenGrepIntoString(statPath),
Sui Chen03eba282021-02-11 11:35:56 -0800197 ticksPerSec);
198}
199
200// Returns true if successfully parsed and false otherwise. If parsing was
201// successful, value is set accordingly.
202// Input: "MemAvailable: 1234 kB"
203// Returns true, value set to 1234
204bool parseMeminfoValue(const std::string_view content,
205 const std::string_view keyword, int& value)
206{
207 size_t p = content.find(keyword);
208 if (p != std::string::npos)
209 {
210 std::string_view v = content.substr(p + keyword.size());
211 p = v.find("kB");
212 if (p != std::string::npos)
213 {
214 v = v.substr(0, p);
215 value = std::atoi(v.data());
216 return true;
217 }
218 }
219 return false;
220}
221
222bool parseProcUptime(const std::string_view content, double& uptime,
223 double& idleProcessTime)
224{
225 double t0, t1; // Attempts to parse uptime and idleProcessTime
226 int ret = sscanf(content.data(), "%lf %lf", &t0, &t1);
227 if (ret == 2 && std::isfinite(t0) && std::isfinite(t1))
228 {
229 uptime = t0;
230 idleProcessTime = t1;
231 return true;
232 }
233 return false;
234}
235
Michael Shenb63d6312021-04-26 13:30:57 +0800236bool readMem(const uint32_t target, uint32_t& memResult)
237{
238 int fd = open("/dev/mem", O_RDONLY | O_SYNC);
239 if (fd < 0)
240 {
241 return false;
242 }
243
244 int pageSize = getpagesize();
245 uint32_t pageOffset = target & ~static_cast<uint32_t>(pageSize - 1);
246 uint32_t offsetInPage = target & static_cast<uint32_t>(pageSize - 1);
247
Patrick Williams2be45232023-05-10 07:51:22 -0500248 void* mapBase = mmap(NULL, pageSize * 2, PROT_READ, MAP_SHARED, fd,
249 pageOffset);
Michael Shenb63d6312021-04-26 13:30:57 +0800250 if (mapBase == MAP_FAILED)
251 {
252 close(fd);
253 return false;
254 }
255
256 char* virtAddr = reinterpret_cast<char*>(mapBase) + offsetInPage;
257 memResult = *(reinterpret_cast<uint32_t*>(virtAddr));
258 close(fd);
Michael Shen3d7cd152022-04-26 04:34:47 +0000259 munmap(mapBase, pageSize * 2);
Michael Shenb63d6312021-04-26 13:30:57 +0800260 return true;
261}
262
263// clang-format off
264/*
265 * power-on
266 * counter(start) uptime(start)
267 * firmware(Neg) loader(Neg) kernel(always 0) initrd userspace finish
268 * |----------------|-------------|-------------------|----------------------|----------------------|
269 * |----------------| <--- firmwareTime=firmware-loader
270 * |-------------| <--- loaderTime=loader
271 * |------------------------------| <--- firmwareTime(Actually is firmware+loader)=counter-uptime \
272 * (in this case we can treat this as firmware time \
273 * since firmware consumes most of the time)
274 * |-------------------| <--- kernelTime=initrd (if initrd present)
275 * |------------------------------------------| <--- kernelTime=userspace (if no initrd)
276 * |----------------------| <--- initrdTime=userspace-initrd (if initrd present)
277 * |----------------------| <--- userspaceTime=finish-userspace
278 */
279// clang-format on
280bool getBootTimesMonotonic(BootTimesMonotonic& btm)
281{
282 // Timestamp name and its offset in the struct.
283 std::vector<std::pair<std::string_view, size_t>> timeMap = {
284 {"FirmwareTimestampMonotonic",
285 offsetof(BootTimesMonotonic, firmwareTime)}, // negative value
286 {"LoaderTimestampMonotonic",
Patrick Williams2be45232023-05-10 07:51:22 -0500287 offsetof(BootTimesMonotonic, loaderTime)}, // negative value
Michael Shenb63d6312021-04-26 13:30:57 +0800288 {"InitRDTimestampMonotonic", offsetof(BootTimesMonotonic, initrdTime)},
289 {"UserspaceTimestampMonotonic",
290 offsetof(BootTimesMonotonic, userspaceTime)},
291 {"FinishTimestampMonotonic", offsetof(BootTimesMonotonic, finishTime)}};
292
293 auto b = sdbusplus::bus::new_default_system();
294 auto m = b.new_method_call("org.freedesktop.systemd1",
295 "/org/freedesktop/systemd1",
296 "org.freedesktop.DBus.Properties", "GetAll");
297 m.append("");
298 auto reply = b.call(m);
299 std::vector<std::pair<std::string, std::variant<uint64_t>>> timestamps;
300 reply.read(timestamps);
301
302 // Parse timestamps from dbus result.
303 auto btmPtr = reinterpret_cast<char*>(&btm);
304 unsigned int recordCnt = 0;
305 for (auto& t : timestamps)
306 {
307 for (auto& tm : timeMap)
308 {
309 if (tm.first.compare(t.first) == 0)
310 {
311 auto temp = std::get<uint64_t>(t.second);
312 memcpy(btmPtr + tm.second, reinterpret_cast<char*>(&temp),
313 sizeof(temp));
314 recordCnt++;
315 break;
316 }
317 }
318 if (recordCnt == timeMap.size())
319 {
320 break;
321 }
322 }
323 if (recordCnt != timeMap.size())
324 {
325 log<level::ERR>("Didn't get desired timestamps");
326 return false;
327 }
328
Patrick Williams2be45232023-05-10 07:51:22 -0500329 std::string cpuinfo = readFileThenGrepIntoString("/proc/cpuinfo",
330 "Hardware");
Michael Shenb63d6312021-04-26 13:30:57 +0800331 // Nuvoton NPCM7XX chip has a counter which starts from power-on.
332 if (cpuinfo.find("NPCM7XX") != std::string::npos)
333 {
334 // Get elapsed seconds from SEC_CNT register
335 const uint32_t SEC_CNT_ADDR = 0xf0801068;
336 uint32_t memResult = 0;
337 if (readMem(SEC_CNT_ADDR, memResult))
338 {
339 btm.powerOnSecCounterTime = static_cast<uint64_t>(memResult);
340 }
341 else
342 {
343 log<level::ERR>("Read memory SEC_CNT_ADDR(0xf0801068) failed");
344 return false;
345 }
346 }
347
348 return true;
349}
350
Willy Tu4dba2202024-04-08 21:27:20 +0000351bool getECCErrorCounts(EccCounts& eccCounts)
352{
353 std::vector<
354 std::pair<std::string, std::variant<uint64_t, uint8_t, std::string>>>
355 values;
356 try
357 {
358 auto bus = sdbusplus::bus::new_default_system();
359 auto m =
360 bus.new_method_call("xyz.openbmc_project.memory.ECC",
361 "/xyz/openbmc_project/metrics/memory/BmcECC",
362 "org.freedesktop.DBus.Properties", "GetAll");
363 m.append("xyz.openbmc_project.Memory.MemoryECC");
364 auto reply = bus.call(m);
365 reply.read(values);
366 }
367 catch (const sdbusplus::exception::SdBusError& ex)
368 {
369 return false;
370 }
371 bool hasCorrectable = false;
372 bool hasUncorrectable = false;
373 for (const auto& [key, value] : values)
374 {
375 if (key == "ceCount")
376 {
377 eccCounts.correctableErrCount =
378 static_cast<int32_t>(std::get<uint64_t>(value));
379 hasCorrectable = true;
380 if (hasUncorrectable)
381 {
382 return true;
383 }
384 }
385 if (key == "ueCount")
386 {
387 eccCounts.uncorrectableErrCount =
388 static_cast<int32_t>(std::get<uint64_t>(value));
389 hasUncorrectable = true;
390 if (hasCorrectable)
391 {
392 return true;
393 }
394 }
395 }
396 return false;
397}
398
Patrick Williams2be45232023-05-10 07:51:22 -0500399} // namespace metric_blob