// Copyright 2021 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "util.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace metric_blob { using phosphor::logging::log; using level = phosphor::logging::level; char controlCharsToSpace(char c) { if (c < 32) { c = ' '; } return c; } long getTicksPerSec() { return sysconf(_SC_CLK_TCK); } std::string readFileThenGrepIntoString(const std::string_view fileName, const std::string_view grepStr) { std::stringstream ss; std::ifstream ifs(fileName.data()); while (ifs.good()) { std::string line; std::getline(ifs, line); if (line.find(grepStr) != std::string::npos) { ss << line; } if (ifs.good()) ss << std::endl; } return ss.str(); } bool isNumericPath(const std::string_view path, int& value) { size_t p = path.rfind('/'); if (p == std::string::npos) { return false; } int id = 0; for (size_t i = p + 1; i < path.size(); ++i) { const char ch = path[i]; if (ch < '0' || ch > '9') return false; else { id = id * 10 + (ch - '0'); } } value = id; return true; } // Trims all control characters at the end of a string. std::string trimStringRight(std::string_view s) { std::string ret(s.data()); while (!ret.empty()) { if (ret.back() <= 32) ret.pop_back(); else break; } return ret; } std::string getCmdLine(const int pid) { const std::string& cmdlinePath = "/proc/" + std::to_string(pid) + "/cmdline"; std::string cmdline = readFileThenGrepIntoString(cmdlinePath); for (size_t i = 0; i < cmdline.size(); ++i) { cmdline[i] = controlCharsToSpace(cmdline[i]); } // Trim empty strings cmdline = trimStringRight(cmdline); return cmdline; } // strtok is used in this function in order to avoid usage of . // However, that would require us to create a temporary std::string. TcommUtimeStime parseTcommUtimeStimeString(std::string_view content, const long ticksPerSec) { TcommUtimeStime ret; ret.tcomm = ""; ret.utime = ret.stime = 0; const float invTicksPerSec = 1.0f / static_cast(ticksPerSec); // pCol now points to the first part in content after content is split by // space. // This is not ideal, std::string temp(content); char* pCol = strtok(temp.data(), " "); if (pCol != nullptr) { const int fields[] = {1, 13, 14}; // tcomm, utime, stime int fieldIdx = 0; for (int colIdx = 0; colIdx < 15; ++colIdx) { if (fieldIdx < 3 && colIdx == fields[fieldIdx]) { switch (fieldIdx) { case 0: { ret.tcomm = std::string(pCol); break; } case 1: [[fallthrough]]; case 2: { int ticks = std::atoi(pCol); float t = static_cast(ticks) * invTicksPerSec; if (fieldIdx == 1) { ret.utime = t; } else if (fieldIdx == 2) { ret.stime = t; } break; } } ++fieldIdx; } pCol = strtok(nullptr, " "); } } if (ticksPerSec <= 0) { log("ticksPerSec is equal or less than zero"); } return ret; } TcommUtimeStime getTcommUtimeStime(const int pid, const long ticksPerSec) { const std::string& statPath = "/proc/" + std::to_string(pid) + "/stat"; return parseTcommUtimeStimeString(readFileThenGrepIntoString(statPath), ticksPerSec); } // Returns true if successfully parsed and false otherwise. If parsing was // successful, value is set accordingly. // Input: "MemAvailable: 1234 kB" // Returns true, value set to 1234 bool parseMeminfoValue(const std::string_view content, const std::string_view keyword, int& value) { size_t p = content.find(keyword); if (p != std::string::npos) { std::string_view v = content.substr(p + keyword.size()); p = v.find("kB"); if (p != std::string::npos) { v = v.substr(0, p); value = std::atoi(v.data()); return true; } } return false; } bool parseProcUptime(const std::string_view content, double& uptime, double& idleProcessTime) { double t0, t1; // Attempts to parse uptime and idleProcessTime int ret = sscanf(content.data(), "%lf %lf", &t0, &t1); if (ret == 2 && std::isfinite(t0) && std::isfinite(t1)) { uptime = t0; idleProcessTime = t1; return true; } return false; } bool readMem(const uint32_t target, uint32_t& memResult) { int fd = open("/dev/mem", O_RDONLY | O_SYNC); if (fd < 0) { return false; } int pageSize = getpagesize(); uint32_t pageOffset = target & ~static_cast(pageSize - 1); uint32_t offsetInPage = target & static_cast(pageSize - 1); void* mapBase = mmap(NULL, pageSize * 2, PROT_READ, MAP_SHARED, fd, pageOffset); if (mapBase == MAP_FAILED) { close(fd); return false; } char* virtAddr = reinterpret_cast(mapBase) + offsetInPage; memResult = *(reinterpret_cast(virtAddr)); close(fd); return true; } // clang-format off /* * power-on * counter(start) uptime(start) * firmware(Neg) loader(Neg) kernel(always 0) initrd userspace finish * |----------------|-------------|-------------------|----------------------|----------------------| * |----------------| <--- firmwareTime=firmware-loader * |-------------| <--- loaderTime=loader * |------------------------------| <--- firmwareTime(Actually is firmware+loader)=counter-uptime \ * (in this case we can treat this as firmware time \ * since firmware consumes most of the time) * |-------------------| <--- kernelTime=initrd (if initrd present) * |------------------------------------------| <--- kernelTime=userspace (if no initrd) * |----------------------| <--- initrdTime=userspace-initrd (if initrd present) * |----------------------| <--- userspaceTime=finish-userspace */ // clang-format on bool getBootTimesMonotonic(BootTimesMonotonic& btm) { // Timestamp name and its offset in the struct. std::vector> timeMap = { {"FirmwareTimestampMonotonic", offsetof(BootTimesMonotonic, firmwareTime)}, // negative value {"LoaderTimestampMonotonic", offsetof(BootTimesMonotonic, loaderTime)}, // negative value {"InitRDTimestampMonotonic", offsetof(BootTimesMonotonic, initrdTime)}, {"UserspaceTimestampMonotonic", offsetof(BootTimesMonotonic, userspaceTime)}, {"FinishTimestampMonotonic", offsetof(BootTimesMonotonic, finishTime)}}; auto b = sdbusplus::bus::new_default_system(); auto m = b.new_method_call("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.DBus.Properties", "GetAll"); m.append(""); auto reply = b.call(m); std::vector>> timestamps; reply.read(timestamps); // Parse timestamps from dbus result. auto btmPtr = reinterpret_cast(&btm); unsigned int recordCnt = 0; for (auto& t : timestamps) { for (auto& tm : timeMap) { if (tm.first.compare(t.first) == 0) { auto temp = std::get(t.second); memcpy(btmPtr + tm.second, reinterpret_cast(&temp), sizeof(temp)); recordCnt++; break; } } if (recordCnt == timeMap.size()) { break; } } if (recordCnt != timeMap.size()) { log("Didn't get desired timestamps"); return false; } std::string cpuinfo = readFileThenGrepIntoString("/proc/cpuinfo", "Hardware"); // Nuvoton NPCM7XX chip has a counter which starts from power-on. if (cpuinfo.find("NPCM7XX") != std::string::npos) { // Get elapsed seconds from SEC_CNT register const uint32_t SEC_CNT_ADDR = 0xf0801068; uint32_t memResult = 0; if (readMem(SEC_CNT_ADDR, memResult)) { btm.powerOnSecCounterTime = static_cast(memResult); } else { log("Read memory SEC_CNT_ADDR(0xf0801068) failed"); return false; } } return true; } } // namespace metric_blob