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