| Brandon Kim | dab96f1 | 2021-02-18 11:21:37 -0800 | [diff] [blame] | 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 |  | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 15 | #include "util.hpp" | 
|  | 16 |  | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 17 | #include <fcntl.h> | 
|  | 18 | #include <sys/mman.h> | 
|  | 19 | #include <sys/stat.h> | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 20 | #include <unistd.h> | 
|  | 21 |  | 
|  | 22 | #include <phosphor-logging/log.hpp> | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 23 | #include <sdbusplus/bus.hpp> | 
|  | 24 | #include <sdbusplus/message.hpp> | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 25 |  | 
|  | 26 | #include <cmath> | 
|  | 27 | #include <cstdlib> | 
|  | 28 | #include <fstream> | 
|  | 29 | #include <sstream> | 
|  | 30 | #include <string> | 
|  | 31 | #include <string_view> | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 32 | #include <unordered_map> | 
|  | 33 | #include <utility> | 
|  | 34 | #include <variant> | 
|  | 35 | #include <vector> | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 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 |  | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 57 | std::string readFileThenGrepIntoString(const std::string_view fileName, | 
|  | 58 | const std::string_view grepStr) | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 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); | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 66 | if (line.find(grepStr) != std::string::npos) | 
|  | 67 | { | 
|  | 68 | ss << line; | 
|  | 69 | } | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 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 |  | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 117 | std::string cmdline = readFileThenGrepIntoString(cmdlinePath); | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 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"; | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 196 | return parseTcommUtimeStimeString(readFileThenGrepIntoString(statPath), | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 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 |  | 
| Michael Shen | b63d631 | 2021-04-26 13:30:57 +0800 | [diff] [blame] | 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 | return true; | 
|  | 260 | } | 
|  | 261 |  | 
|  | 262 | // clang-format off | 
|  | 263 | /* | 
|  | 264 | *  power-on | 
|  | 265 | *  counter(start)                 uptime(start) | 
|  | 266 | *  firmware(Neg)    loader(Neg)   kernel(always 0)    initrd                 userspace              finish | 
|  | 267 | *  |----------------|-------------|-------------------|----------------------|----------------------| | 
|  | 268 | *  |----------------| <--- firmwareTime=firmware-loader | 
|  | 269 | *                   |-------------| <--- loaderTime=loader | 
|  | 270 | *  |------------------------------| <--- firmwareTime(Actually is firmware+loader)=counter-uptime \ | 
|  | 271 | *                                        (in this case we can treat this as firmware time \ | 
|  | 272 | *                                         since firmware consumes most of the time) | 
|  | 273 | *                                 |-------------------| <--- kernelTime=initrd (if initrd present) | 
|  | 274 | *                                 |------------------------------------------| <--- kernelTime=userspace (if no initrd) | 
|  | 275 | *                                                     |----------------------| <--- initrdTime=userspace-initrd (if initrd present) | 
|  | 276 | *                                                                            |----------------------| <--- userspaceTime=finish-userspace | 
|  | 277 | */ | 
|  | 278 | // clang-format on | 
|  | 279 | bool getBootTimesMonotonic(BootTimesMonotonic& btm) | 
|  | 280 | { | 
|  | 281 | // Timestamp name and its offset in the struct. | 
|  | 282 | std::vector<std::pair<std::string_view, size_t>> timeMap = { | 
|  | 283 | {"FirmwareTimestampMonotonic", | 
|  | 284 | offsetof(BootTimesMonotonic, firmwareTime)}, // negative value | 
|  | 285 | {"LoaderTimestampMonotonic", | 
|  | 286 | offsetof(BootTimesMonotonic, loaderTime)}, // negative value | 
|  | 287 | {"InitRDTimestampMonotonic", offsetof(BootTimesMonotonic, initrdTime)}, | 
|  | 288 | {"UserspaceTimestampMonotonic", | 
|  | 289 | offsetof(BootTimesMonotonic, userspaceTime)}, | 
|  | 290 | {"FinishTimestampMonotonic", offsetof(BootTimesMonotonic, finishTime)}}; | 
|  | 291 |  | 
|  | 292 | auto b = sdbusplus::bus::new_default_system(); | 
|  | 293 | auto m = b.new_method_call("org.freedesktop.systemd1", | 
|  | 294 | "/org/freedesktop/systemd1", | 
|  | 295 | "org.freedesktop.DBus.Properties", "GetAll"); | 
|  | 296 | m.append(""); | 
|  | 297 | auto reply = b.call(m); | 
|  | 298 | std::vector<std::pair<std::string, std::variant<uint64_t>>> timestamps; | 
|  | 299 | reply.read(timestamps); | 
|  | 300 |  | 
|  | 301 | // Parse timestamps from dbus result. | 
|  | 302 | auto btmPtr = reinterpret_cast<char*>(&btm); | 
|  | 303 | unsigned int recordCnt = 0; | 
|  | 304 | for (auto& t : timestamps) | 
|  | 305 | { | 
|  | 306 | for (auto& tm : timeMap) | 
|  | 307 | { | 
|  | 308 | if (tm.first.compare(t.first) == 0) | 
|  | 309 | { | 
|  | 310 | auto temp = std::get<uint64_t>(t.second); | 
|  | 311 | memcpy(btmPtr + tm.second, reinterpret_cast<char*>(&temp), | 
|  | 312 | sizeof(temp)); | 
|  | 313 | recordCnt++; | 
|  | 314 | break; | 
|  | 315 | } | 
|  | 316 | } | 
|  | 317 | if (recordCnt == timeMap.size()) | 
|  | 318 | { | 
|  | 319 | break; | 
|  | 320 | } | 
|  | 321 | } | 
|  | 322 | if (recordCnt != timeMap.size()) | 
|  | 323 | { | 
|  | 324 | log<level::ERR>("Didn't get desired timestamps"); | 
|  | 325 | return false; | 
|  | 326 | } | 
|  | 327 |  | 
|  | 328 | std::string cpuinfo = | 
|  | 329 | readFileThenGrepIntoString("/proc/cpuinfo", "Hardware"); | 
|  | 330 | // Nuvoton NPCM7XX chip has a counter which starts from power-on. | 
|  | 331 | if (cpuinfo.find("NPCM7XX") != std::string::npos) | 
|  | 332 | { | 
|  | 333 | // Get elapsed seconds from SEC_CNT register | 
|  | 334 | const uint32_t SEC_CNT_ADDR = 0xf0801068; | 
|  | 335 | uint32_t memResult = 0; | 
|  | 336 | if (readMem(SEC_CNT_ADDR, memResult)) | 
|  | 337 | { | 
|  | 338 | btm.powerOnSecCounterTime = static_cast<uint64_t>(memResult); | 
|  | 339 | } | 
|  | 340 | else | 
|  | 341 | { | 
|  | 342 | log<level::ERR>("Read memory SEC_CNT_ADDR(0xf0801068) failed"); | 
|  | 343 | return false; | 
|  | 344 | } | 
|  | 345 | } | 
|  | 346 |  | 
|  | 347 | return true; | 
|  | 348 | } | 
|  | 349 |  | 
| Sui Chen | 03eba28 | 2021-02-11 11:35:56 -0800 | [diff] [blame] | 350 | } // namespace metric_blob |