1/* 2 * Copyright (c) Huawei Technologies Co., Ltd. 2021. All rights reserved. 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 "process_data_plugin.h" 16 17#include <fcntl.h> 18#include <fstream> 19#include <iostream> 20#include <sstream> 21 22#include "buffer_splitter.h" 23#include "process_plugin_result.pbencoder.h" 24#include "securec.h" 25 26namespace { 27using namespace OHOS::Developtools::Profiler; 28constexpr size_t READ_BUFFER_SIZE = 1024 * 16; 29constexpr int DEC_BASE = 10; 30constexpr int STAT_COUNT = 13; 31constexpr int CPU_USER_HZ_L = 100; 32constexpr int CPU_USER_HZ_H = 1000; 33constexpr int CPU_HZ_H = 10; 34const int PERCENT = 100; 35} // namespace 36 37ProcessDataPlugin::ProcessDataPlugin() : err_(-1) 38{ 39 SetPath("/proc/"); 40 buffer_ = std::make_unique<uint8_t[]>(READ_BUFFER_SIZE); 41} 42 43ProcessDataPlugin::~ProcessDataPlugin() 44{ 45 PROFILER_LOG_INFO(LOG_CORE, "%s:~ProcessDataPlugin!", __func__); 46 47 buffer_ = nullptr; 48 49 return; 50} 51 52int ProcessDataPlugin::Start(const uint8_t* configData, uint32_t configSize) 53{ 54 CHECK_NOTNULL(buffer_, RET_FAIL, "%s:buffer_ == null", __func__); 55 56 CHECK_TRUE(protoConfig_.ParseFromArray(configData, configSize) > 0, RET_FAIL, 57 "%s:parseFromArray failed!", __func__); 58 59 PROFILER_LOG_INFO(LOG_CORE, "%s:start success!", __func__); 60 return RET_SUCC; 61} 62 63int ProcessDataPlugin::ReportOptimize(RandomWriteCtx* randomWrite) 64{ 65 ProtoEncoder::ProcessData dataProto(randomWrite); 66 if (protoConfig_.report_process_tree()) { 67 WriteProcesseList(dataProto); 68 } 69 70 int msgSize = dataProto.Finish(); 71 return msgSize; 72} 73 74int ProcessDataPlugin::Report(uint8_t* data, uint32_t dataSize) 75{ 76 ProcessData dataProto; 77 uint32_t length; 78 79 if (protoConfig_.report_process_tree()) { 80 WriteProcesseList(dataProto); 81 } 82 83 length = dataProto.ByteSizeLong(); 84 if (length > dataSize) { 85 return -length; 86 } 87 if (dataProto.SerializeToArray(data, length) > 0) { 88 return length; 89 } 90 return 0; 91} 92 93int ProcessDataPlugin::Stop() 94{ 95 pids_.clear(); 96 cpuTime_.clear(); 97 bootTime_.clear(); 98 99 PROFILER_LOG_INFO(LOG_CORE, "%s:stop success!", __func__); 100 return 0; 101} 102 103DIR* ProcessDataPlugin::OpenDestDir(const char* dirPath) 104{ 105 DIR* destDir = nullptr; 106 107 destDir = opendir(dirPath); 108 if (destDir == nullptr) { 109 PROFILER_LOG_ERROR(LOG_CORE, "%s:failed to opendir(%s), errno=%d", __func__, dirPath, errno); 110 } 111 112 return destDir; 113} 114 115int32_t ProcessDataPlugin::GetValidPid(DIR* dirp) 116{ 117 if (!dirp) { 118 return 0; 119 } 120 while (struct dirent* dirEnt = readdir(dirp)) { 121 if (dirEnt->d_type != DT_DIR) { 122 continue; 123 } 124 125 int32_t pid = atoi(dirEnt->d_name); 126 if (pid) { 127 return pid; 128 } 129 } 130 return 0; 131} 132 133int32_t ProcessDataPlugin::ReadProcPidFile(int32_t pid, const char* pFileName) 134{ 135 char fileName[PATH_MAX + 1] = {0}; 136 char realPath[PATH_MAX + 1] = {0}; 137 int fd = -1; 138 ssize_t bytesRead = 0; 139 140 if (snprintf_s(fileName, sizeof(fileName), sizeof(fileName) - 1, "%s%d/%s", path_.c_str(), pid, pFileName) < 0) { 141 PROFILER_LOG_ERROR(LOG_CORE, "%s:snprintf_s error", __func__); 142 } 143 if (realpath(fileName, realPath) == nullptr) { 144 PROFILER_LOG_ERROR(LOG_CORE, "%s:realpath failed, errno=%d", __func__, errno); 145 return RET_FAIL; 146 } 147 fd = open(realPath, O_RDONLY | O_CLOEXEC); 148 if (fd == -1) { 149 PROFILER_LOG_INFO(LOG_CORE, "%s:failed to open(%s), errno=%d", __func__, fileName, errno); 150 err_ = errno; 151 return RET_FAIL; 152 } 153 if (buffer_.get() == nullptr) { 154 PROFILER_LOG_INFO(LOG_CORE, "%s:empty address, buffer_ is NULL", __func__); 155 err_ = RET_NULL_ADDR; 156 close(fd); 157 return RET_FAIL; 158 } 159 bytesRead = read(fd, buffer_.get(), READ_BUFFER_SIZE - 1); 160 if (bytesRead < 0) { 161 close(fd); 162 PROFILER_LOG_INFO(LOG_CORE, "%s:failed to read(%s), errno=%d", __func__, fileName, errno); 163 err_ = errno; 164 return RET_FAIL; 165 } 166 buffer_.get()[bytesRead] = '\0'; 167 close(fd); 168 169 return bytesRead; 170} 171 172bool ProcessDataPlugin::BufnCmp(const char* src, int srcLen, const char* key, int keyLen) 173{ 174 if (!src || !key || (srcLen < keyLen)) { 175 return false; 176 } 177 for (int i = 0; i < keyLen; i++) { 178 if (*src++ != *key++) { 179 return false; 180 } 181 } 182 return true; 183} 184 185bool ProcessDataPlugin::addPidBySort(int32_t pid) 186{ 187 auto pidsEnd = pids_.end(); 188 auto it = std::lower_bound(pids_.begin(), pidsEnd, pid); 189 if (it != pidsEnd && *it == pid) { 190 return false; 191 } 192 it = pids_.insert(it, std::move(pid)); 193 return true; 194} 195 196template <typename T> 197void ProcessDataPlugin::WriteProcess(T& processinfo, const char* pFile, uint32_t fileLen, int32_t pid) 198{ 199 BufferSplitter totalbuffer(const_cast<const char*>(pFile), fileLen + 1); 200 201 do { 202 totalbuffer.NextWord(':'); 203 if (!totalbuffer.CurWord()) { 204 return; 205 } 206 207 if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Name", strlen("Name"))) { 208 totalbuffer.NextWord('\n'); 209 if (!totalbuffer.CurWord()) { 210 return; 211 } 212 processinfo.set_name(totalbuffer.CurWord(), totalbuffer.CurWordSize()); 213 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Pid", strlen("Pid"))) { 214 totalbuffer.NextWord('\n'); 215 if (!totalbuffer.CurWord()) { 216 return; 217 } 218 char* end = nullptr; 219 int32_t value = static_cast<int32_t>(strtoul(totalbuffer.CurWord(), &end, DEC_BASE)); 220 if (value < 0) { 221 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__); 222 } 223 processinfo.set_pid(value); 224 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "PPid", strlen("PPid"))) { 225 totalbuffer.NextWord('\n'); 226 if (!totalbuffer.CurWord()) { 227 return; 228 } 229 char* end = nullptr; 230 int32_t value = static_cast<int32_t>(strtoul(totalbuffer.CurWord(), &end, DEC_BASE)); 231 if (value < 0) { 232 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__); 233 } 234 processinfo.set_ppid(value); 235 } else if (BufnCmp(totalbuffer.CurWord(), totalbuffer.CurWordSize(), "Uid", strlen("Uid"))) { 236 totalbuffer.NextWord('\n'); 237 if (!totalbuffer.CurWord()) { 238 return; 239 } 240 std::string curWord = std::string(totalbuffer.CurWord(), totalbuffer.CurWordSize()); 241 curWord = curWord.substr(0, curWord.find(" ")); 242 char* end = nullptr; 243 int32_t value = static_cast<int32_t>(strtoul(curWord.c_str(), &end, DEC_BASE)); 244 if (value < 0) { 245 PROFILER_LOG_ERROR(LOG_CORE, "%s:strtoull value failed", __func__); 246 } 247 processinfo.set_uid(value); 248 break; 249 } else { 250 totalbuffer.NextWord('\n'); 251 if (!totalbuffer.CurWord()) { 252 continue; 253 } 254 } 255 } while (totalbuffer.NextLine()); 256 // update process name 257 int32_t ret = ReadProcPidFile(pid, "cmdline"); 258 if (ret > 0) { 259 processinfo.set_name(reinterpret_cast<char*>(buffer_.get()), strlen(reinterpret_cast<char*>(buffer_.get()))); 260 } 261} 262 263template <typename T> void ProcessDataPlugin::WriteProcessInfo(T& processData, int32_t pid) 264{ 265 int32_t ret = ReadProcPidFile(pid, "status"); 266 if (ret == RET_FAIL) { 267 return; 268 } 269 if ((buffer_.get() == nullptr) || (ret == 0)) { 270 return; 271 } 272 auto* processinfo = processData.add_processesinfo(); 273 WriteProcess(*processinfo, reinterpret_cast<char*>(buffer_.get()), ret, pid); 274 if (protoConfig_.report_cpu()) { 275 auto* cpuInfo = processinfo->mutable_cpuinfo(); 276 std::vector<uint64_t> cpuUsageVec; 277 std::vector<uint64_t> bootTime; 278 WriteCpuUsageData(pid, *cpuInfo); 279 WriteThreadData(pid, *cpuInfo); 280 } 281 if (protoConfig_.report_diskio()) { 282 WriteDiskioData(pid, *processinfo); 283 } 284 if (protoConfig_.report_pss()) { 285 WritePssData(pid, *processinfo); 286 } 287} 288 289template <typename T> bool ProcessDataPlugin::WriteProcesseList(T& processData) 290{ 291 DIR* procDir = nullptr; 292 293 procDir = OpenDestDir(path_.c_str()); 294 if (procDir == nullptr) { 295 return false; 296 } 297 298 pids_.clear(); 299 while (int32_t pid = GetValidPid(procDir)) { 300 if (pid <= 0) { 301 closedir(procDir); 302 PROFILER_LOG_WARN(LOG_CORE, "%s: get pid[%d] failed", __func__, pid); 303 return false; 304 } 305 addPidBySort(pid); 306 } 307 308 for (unsigned int i = 0; i < pids_.size(); i++) { 309 WriteProcessInfo(processData, pids_[i]); 310 } 311 312 closedir(procDir); 313 return true; 314} 315 316template <typename T> bool ProcessDataPlugin::WriteThreadData(int pid, T& cpuInfo) 317{ 318 DIR* procDir = nullptr; 319 std::string path = path_ + std::to_string(pid) + "/task"; 320 procDir = OpenDestDir(path.c_str()); 321 if (procDir == nullptr) { 322 return false; 323 } 324 325 uint32_t i = 0; 326 while (int32_t tid = GetValidPid(procDir)) { 327 if (tid <= 0) { 328 closedir(procDir); 329 PROFILER_LOG_WARN(LOG_CORE, "%s: get pid[%d] failed", __func__, tid); 330 return false; 331 } 332 i++; 333 } 334 cpuInfo.set_thread_sum(i); 335 closedir(procDir); 336 return true; 337} 338 339int64_t ProcessDataPlugin::GetUserHz() 340{ 341 int64_t hz = -1; 342 int64_t user_hz = sysconf(_SC_CLK_TCK); 343 switch (user_hz) { 344 case CPU_USER_HZ_L: 345 hz = CPU_HZ_H; 346 break; 347 case CPU_USER_HZ_H: 348 hz = 1; 349 break; 350 default: 351 break; 352 } 353 return hz; 354} 355 356template <typename T> bool ProcessDataPlugin::WriteCpuUsageData(int pid, T& cpuInfo) 357{ 358 uint64_t prevCpuTime = 0; 359 uint64_t cpuTime = 0; 360 uint64_t prevBootTime = 0; 361 uint64_t bootTime = 0; 362 double usage = 0.0; 363 ReadCpuUsage(pid, cpuTime); 364 ReadBootTime(pid, bootTime); 365 if (cpuTime_.find(pid) != cpuTime_.end()) { 366 prevCpuTime = cpuTime_[pid]; 367 } 368 if (bootTime_.find(pid) != bootTime_.end()) { 369 prevBootTime = bootTime_[pid]; 370 } 371 if (bootTime - prevBootTime == 0 || bootTime == 0) { 372 return false; 373 } 374 if (cpuTime < prevCpuTime) { 375 return false; 376 } 377 if (prevCpuTime == 0) { 378 usage = static_cast<double>(cpuTime) / (bootTime); 379 } else { 380 usage = static_cast<double>(cpuTime - prevCpuTime) / (bootTime - prevBootTime); 381 } 382 383 if (usage > 0) { 384 cpuInfo.set_cpu_usage(usage * PERCENT); 385 } 386 cpuInfo.set_cpu_time_ms(cpuTime); 387 cpuTime_[pid] = cpuTime; 388 bootTime_[pid] = bootTime; 389 return true; 390} 391 392bool ProcessDataPlugin::ReadBootTime(int pid, uint64_t& bootTime) 393{ 394 std::string path = path_ + "stat"; 395 std::ifstream input(path, std::ios::in); 396 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno); 397 do { 398 if (!input.good()) { 399 return false; 400 } 401 std::string line; 402 getline(input, line); 403 404 auto pos = line.find("cpu "); 405 if (pos != std::string::npos) { 406 line += '\n'; 407 GetBootData(line, bootTime); 408 } 409 } while (0); 410 input.close(); 411 412 return true; 413} 414 415uint32_t ProcessDataPlugin::GetBootData(const std::string& line, uint64_t& bootTime) 416{ 417 uint64_t num; 418 uint32_t count = 0; 419 char* end = nullptr; 420 char* pTmp = const_cast<char*>(line.c_str()); 421 constexpr uint32_t cntVec = 8; 422 423 std::vector<uint64_t> bootTimeVec; 424 bootTime = 0; 425 while (pTmp != nullptr && *pTmp != '\n') { 426 CHECK_TRUE(FindFirstNum(&pTmp), count, "%s: FindFirstNum failed", __func__); 427 num = strtoull(pTmp, &end, DEC_BASE); 428 CHECK_TRUE(num >= 0, count, "%s:strtoull failed", __func__); 429 bootTimeVec.push_back(num); 430 bootTime += num; 431 pTmp = end; 432 if (++count >= cntVec) { 433 break; 434 } 435 } 436 bootTime = bootTime * (uint64_t)GetUserHz(); 437 return count; 438} 439 440bool ProcessDataPlugin::ReadCpuUsage(int pid, uint64_t& cpuTime) 441{ 442 std::string path = path_ + std::to_string(pid) + "/stat"; 443 std::ifstream input(path, std::ios::in); 444 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno); 445 do { 446 if (!input.good()) { 447 return false; 448 } 449 std::string line; 450 getline(input, line); 451 line += '\n'; 452 GetCpuUsageData(line, cpuTime); 453 } while (0); 454 input.close(); 455 456 return true; 457} 458 459uint32_t ProcessDataPlugin::GetCpuUsageData(const std::string& line, uint64_t& cpuTime) 460{ 461 uint64_t num; 462 uint32_t count = 0; 463 char* end = nullptr; 464 char* pTmp = const_cast<char*>(line.c_str()); 465 int i = 0; 466 constexpr uint32_t cntVec = 4; 467 468 while (FindFirstSpace(&pTmp)) { 469 pTmp++; 470 if (++i >= STAT_COUNT) { 471 break; 472 } 473 } 474 std::vector<uint64_t> cpuUsageVec; 475 cpuTime = 0; 476 while (pTmp != nullptr && *pTmp != '\n') { 477 CHECK_TRUE(FindFirstNum(&pTmp), count, "%s: FindFirstNum failed", __func__); 478 num = strtoull(pTmp, &end, DEC_BASE); 479 cpuUsageVec.push_back(num); 480 cpuTime += num; 481 pTmp = end; 482 if (++count >= cntVec) { 483 break; 484 } 485 } 486 cpuTime = cpuTime * (uint64_t)GetUserHz(); 487 return count; 488} 489 490template <typename T> bool ProcessDataPlugin::WriteDiskioData(int pid, T& processinfo) 491{ 492 std::string path = path_ + std::to_string(pid) + "/io"; 493 std::ifstream input(path, std::ios::in); 494 if (input.fail()) { 495 return false; 496 } 497 498 auto* diskInfo = processinfo.mutable_diskinfo(); 499 do { 500 if (!input.good()) { 501 return false; 502 } 503 std::string line; 504 getline(input, line); 505 line += '\n'; 506 GetDiskioData(line, *diskInfo); 507 } while (!input.eof()); 508 input.close(); 509 510 return true; 511} 512 513template <typename T> bool ProcessDataPlugin::GetDiskioData(std::string& line, T& diskioInfo) 514{ 515 char* pTmp = const_cast<char*>(line.c_str()); 516 CHECK_NOTNULL(pTmp, false, "param invalid!"); 517 518 uint64_t num; 519 if (!std::strncmp(pTmp, "rchar:", strlen("rchar:"))) { 520 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get rchar failed", __func__); 521 diskioInfo.set_rchar(num); 522 } else if (!std::strncmp(pTmp, "wchar:", strlen("wchar:"))) { 523 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get wchar failed", __func__); 524 diskioInfo.set_wchar(num); 525 } else if (!std::strncmp(pTmp, "syscr:", strlen("syscr:"))) { 526 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get syscr failed", __func__); 527 diskioInfo.set_syscr(num); 528 } else if (!std::strncmp(pTmp, "syscw:", strlen("syscw:"))) { 529 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get syscw failed", __func__); 530 diskioInfo.set_syscw(num); 531 } else if (!std::strncmp(pTmp, "read_bytes:", strlen("read_bytes:"))) { 532 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get read_bytes failed", __func__); 533 diskioInfo.set_rbytes(num); 534 } else if (!std::strncmp(pTmp, "write_bytes:", strlen("write_bytes:"))) { 535 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get write_bytes failed", __func__); 536 diskioInfo.set_wbytes(num); 537 } else if (!std::strncmp(pTmp, "cancelled_write_bytes:", strlen("cancelled_write_bytes:"))) { 538 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: get cancelled_write_bytes failed", __func__); 539 diskioInfo.set_cancelled_wbytes(num); 540 } 541 542 return true; 543} 544 545bool ProcessDataPlugin::FindFirstSpace(char** p) 546{ 547 CHECK_NOTNULL(*p, false, "ProcessDataPlugin:%s", __func__); 548 while (**p != ' ') { 549 if (**p == '\0' || **p == '\n') { 550 return false; 551 } 552 (*p)++; 553 } 554 return true; 555} 556 557bool ProcessDataPlugin::FindFirstNum(char** p) 558{ 559 CHECK_NOTNULL(*p, false, "ProcessDataPlugin:%s", __func__); 560 while (**p > '9' || **p < '0') { 561 if (**p == '\0' || **p == '\n') { 562 return false; 563 } 564 (*p)++; 565 } 566 return true; 567} 568 569bool ProcessDataPlugin::GetValidValue(char* p, uint64_t& num) 570{ 571 char* end = nullptr; 572 CHECK_TRUE(FindFirstNum(&p), false, "%s: FindFirstNum failed", __func__); 573 num = strtoull(p, &end, DEC_BASE); 574 CHECK_TRUE(num >= 0, false, "%s:strtoull failed", __func__); 575 return true; 576} 577 578// read /proc/pid/smaps_rollup 579template <typename T> bool ProcessDataPlugin::WritePssData(int pid, T& processInfo) 580{ 581 std::string path = path_ + std::to_string(pid) + "/smaps_rollup"; 582 std::ifstream input(path, std::ios::in); 583 584 // Not capturing ENOENT (file does not exist) errors, it is common for node smaps_rollup files to be unreadable. 585 CHECK_TRUE(!input.fail(), false, "%s open %s failed, errno = %d", __func__, path.c_str(), errno); 586 587 auto* pssInfo = processInfo.mutable_pssinfo(); 588 do { 589 if (!input.good()) { 590 return false; 591 } 592 std::string line; 593 getline(input, line); 594 line += '\n'; 595 std::string::size_type pos = 0u; 596 if (line.find("Pss:", pos) != std::string::npos) { 597 char* pTmp = const_cast<char*>(line.c_str()); 598 uint64_t num; 599 CHECK_TRUE(GetValidValue(pTmp, num), false, "%s: FindFirstNum failed", __func__); 600 pssInfo->set_pss_info(num); 601 return true; 602 } 603 } while (!input.eof()); 604 input.close(); 605 606 return false; 607} 608