1 // 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 
15 #include "util.hpp"
16 
17 #include <fcntl.h>
18 #include <sys/mman.h>
19 #include <sys/stat.h>
20 #include <unistd.h>
21 
22 #include <phosphor-logging/log.hpp>
23 #include <sdbusplus/bus.hpp>
24 #include <sdbusplus/message.hpp>
25 
26 #include <cmath>
27 #include <cstdlib>
28 #include <fstream>
29 #include <sstream>
30 #include <string>
31 #include <string_view>
32 #include <unordered_map>
33 #include <utility>
34 #include <variant>
35 #include <vector>
36 
37 namespace metric_blob
38 {
39 
40 using phosphor::logging::log;
41 using level = phosphor::logging::level;
42 
43 char controlCharsToSpace(char c)
44 {
45     if (c < 32)
46     {
47         c = ' ';
48     }
49     return c;
50 }
51 
52 long getTicksPerSec()
53 {
54     return sysconf(_SC_CLK_TCK);
55 }
56 
57 std::string readFileThenGrepIntoString(const std::string_view fileName,
58                                        const std::string_view grepStr)
59 {
60     std::stringstream ss;
61     std::ifstream ifs(fileName.data());
62     while (ifs.good())
63     {
64         std::string line;
65         std::getline(ifs, line);
66         if (line.find(grepStr) != std::string::npos)
67         {
68             ss << line;
69         }
70         if (ifs.good())
71             ss << std::endl;
72     }
73     return ss.str();
74 }
75 
76 bool 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.
99 std::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 
112 std::string getCmdLine(const int pid)
113 {
114     const std::string& cmdlinePath =
115         "/proc/" + std::to_string(pid) + "/cmdline";
116 
117     std::string cmdline = readFileThenGrepIntoString(cmdlinePath);
118     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.
131 TcommUtimeStime 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 
193 TcommUtimeStime getTcommUtimeStime(const int pid, const long ticksPerSec)
194 {
195     const std::string& statPath = "/proc/" + std::to_string(pid) + "/stat";
196     return parseTcommUtimeStimeString(readFileThenGrepIntoString(statPath),
197                                       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
204 bool 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 
222 bool 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 
236 bool 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 
248     void* mapBase =
249         mmap(NULL, pageSize * 2, PROT_READ, MAP_SHARED, fd, pageOffset);
250     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);
259     munmap(mapBase, pageSize * 2);
260     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
280 bool 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",
287          offsetof(BootTimesMonotonic, loaderTime)}, // negative value
288         {"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 
329     std::string cpuinfo =
330         readFileThenGrepIntoString("/proc/cpuinfo", "Hardware");
331     // 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 
351 } // namespace metric_blob