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