1 /*
2  * Copyright (C) 2023-2024  Huawei Device Co., Ltd.
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 #ifdef HAS_HIPERF
16 #include <charconv>
17 
18 #include "cpu_perf_dump.h"
19 #include "file_util.h"
20 #include "time_util.h"
21 #include "hiview_logger.h"
22 #include "parameter_ex.h"
23 
24 namespace OHOS {
25 namespace HiviewDFX {
26 DEFINE_LOG_TAG("HiView-CpuPerfDump");
27 namespace {
28 const double CPU_MONITOR_THRESHOLD_CPULOAD = 0.6;
29 const double CPU_MONITOR_THRESHOLD_CPUUSAGE = 0.8;
30 const double CPU_MONITOR_THRESHOLD_PRECISION = 0.00001;
31 const int TOP_N_PROCESS = 3;
32 const int PERF_COLLECT_TIME = 5;
33 constexpr char HIPERF_LOG_PATH[] = "/data/log/hiperf";
34 constexpr uint32_t MAX_NUM_OF_PERF_FILES = 7; // save files for one week
35 constexpr int64_t DUMP_HIPERF_INTERVAL = 15 * 60 * 1000; //ms
36 constexpr int64_t DUMP_HIPERF_DELAY_TIME = 2 * 60 * 1000; //ms
37 }
38 
CpuPerfDump()39 CpuPerfDump::CpuPerfDump()
40 {
41     perfCollector_ = UCollectUtil::PerfCollector::Create();
42     if (!FileUtil::FileExists(HIPERF_LOG_PATH)) {
43         FileUtil::ForceCreateDirectory(HIPERF_LOG_PATH, FileUtil::FILE_PERM_770);
44     }
45     systemUpTime_ = static_cast<int64_t>(TimeUtil::GetMilliseconds());
46     lastRecordTime_ = systemUpTime_;
47     isBetaVersion_ = Parameter::IsBetaVersion();
48 }
49 
DumpTopNCpuProcessPerfData()50 void CpuPerfDump::DumpTopNCpuProcessPerfData()
51 {
52     if (!topNProcs_.empty()) {
53         std::string filename = "hiperf-top3-";
54         filename += TimeUtil::TimestampFormatToDate(TimeUtil::GetMilliseconds() / TimeUtil::SEC_TO_MILLISEC,
55         "%Y%m%d%H%M%S");
56         filename += ".data";
57         perfCollector_->SetOutputFilename(filename);
58         perfCollector_->SetSelectPids(topNProcs_);
59         perfCollector_->SetTimeStopSec(PERF_COLLECT_TIME);
60         perfCollector_->StartPerf(HIPERF_LOG_PATH);
61     }
62 
63     lastRecordTime_ = static_cast<int64_t>(TimeUtil::GetMilliseconds());
64     TryToAgePerfFiles();
65 }
66 
CompareCpuLoad(const ProcessCpuStatInfo & info1,const ProcessCpuStatInfo & info2)67 bool CpuPerfDump::CompareCpuLoad(const ProcessCpuStatInfo &info1, const ProcessCpuStatInfo &info2)
68 {
69     return info1.cpuLoad > info2.cpuLoad;
70 }
71 
CompareCpuUsage(const ProcessCpuStatInfo & info1,const ProcessCpuStatInfo & info2)72 bool CpuPerfDump::CompareCpuUsage(const ProcessCpuStatInfo &info1, const ProcessCpuStatInfo &info2)
73 {
74     return info1.cpuUsage > info2.cpuUsage;
75 }
76 
CheckRecordInterval()77 bool CpuPerfDump::CheckRecordInterval()
78 {
79     if (!isBetaVersion_) {
80         return false;
81     }
82     int64_t nowTime = static_cast<int64_t>(TimeUtil::GetMilliseconds());
83     if (abs(nowTime - systemUpTime_) < DUMP_HIPERF_DELAY_TIME) {
84         return false;
85     }
86     if (systemUpTime_ != lastRecordTime_ && abs(nowTime - lastRecordTime_) < DUMP_HIPERF_INTERVAL) {
87         return false;
88     }
89     return true;
90 }
91 
CheckAndDumpPerfData(std::vector<ProcessCpuStatInfo> & cpuCollectionInfos)92 void CpuPerfDump::CheckAndDumpPerfData(std::vector<ProcessCpuStatInfo> &cpuCollectionInfos)
93 {
94     if (!CheckRecordInterval()) {
95         return;
96     }
97     if (cpuCollectionInfos.empty()) {
98         return;
99     }
100     topNProcs_.clear();
101     double sumCpuLoad = 0.0;
102     double sumCpuUsage = 0.0;
103     for (const auto &info : cpuCollectionInfos) {
104         sumCpuLoad += info.cpuLoad;
105         sumCpuUsage += info.cpuUsage;
106     }
107 
108     size_t middlePos = (cpuCollectionInfos.size() >= TOP_N_PROCESS) ? TOP_N_PROCESS : cpuCollectionInfos.size();
109     if (sumCpuLoad >= CPU_MONITOR_THRESHOLD_CPULOAD + CPU_MONITOR_THRESHOLD_PRECISION) {
110         std::partial_sort(cpuCollectionInfos.begin(), cpuCollectionInfos.begin() + middlePos,
111                           cpuCollectionInfos.end(), CompareCpuLoad);
112     } else if (sumCpuLoad < CPU_MONITOR_THRESHOLD_PRECISION &&
113                sumCpuUsage >= CPU_MONITOR_THRESHOLD_CPUUSAGE + CPU_MONITOR_THRESHOLD_PRECISION) {
114         std::partial_sort(cpuCollectionInfos.begin(), cpuCollectionInfos.begin() + middlePos,
115                           cpuCollectionInfos.end(), CompareCpuUsage);
116     } else {
117         return;
118     }
119 
120     for (const auto &info : cpuCollectionInfos) {
121         topNProcs_.emplace_back(info.pid);
122         if (topNProcs_.size() >= TOP_N_PROCESS) {
123             break;
124         }
125     }
126 
127     DumpTopNCpuProcessPerfData();
128 }
129 
NeedCleanPerfFiles(size_t size)130 bool CpuPerfDump::NeedCleanPerfFiles(size_t size)
131 {
132     return size > MAX_NUM_OF_PERF_FILES;
133 }
134 
GetTimestamp(const std::string & fileName)135 std::string CpuPerfDump::GetTimestamp(const std::string& fileName)
136 {
137     auto startPos = fileName.find_last_of('-');
138     if (startPos == std::string::npos) {
139         return "";
140     }
141     auto endPos = fileName.find_last_of('.');
142     if (endPos == std::string::npos) {
143         return "";
144     }
145     if (endPos <= startPos + 1) {
146         return "";
147     }
148     return fileName.substr(startPos + 1, endPos - startPos - 1);
149 }
150 
CompareFilenames(const std::string & name1,const std::string & name2)151 bool CpuPerfDump::CompareFilenames(const std::string &name1, const std::string &name2)
152 {
153     std::string timestamp1 = GetTimestamp(name1);
154     std::string timestamp2 = GetTimestamp(name2);
155     uint64_t time1 = 0;
156     uint64_t time2 = 0;
157     auto result1 = std::from_chars(timestamp1.c_str(), timestamp1.c_str() + timestamp1.size(), time1);
158     if (result1.ec != std::errc()) {
159         HIVIEW_LOGW("convert error, timestamp1: %{public}s", timestamp1.c_str());
160     }
161     auto result2 = std::from_chars(timestamp2.c_str(), timestamp2.c_str() + timestamp2.size(), time2);
162     if (result2.ec != std::errc()) {
163         HIVIEW_LOGW("convert error, timestamp2: %{public}s", timestamp2.c_str());
164     }
165     return time1 < time2;
166 }
167 
TryToAgePerfFiles()168 void CpuPerfDump::TryToAgePerfFiles()
169 {
170     std::vector<std::string> perfFiles;
171     FileUtil::GetDirFiles(HIPERF_LOG_PATH, perfFiles);
172     if (!NeedCleanPerfFiles(perfFiles.size())) {
173         return;
174     }
175     std::sort(perfFiles.begin(), perfFiles.end(), CompareFilenames);
176     uint32_t numOfCleanFiles = perfFiles.size() - MAX_NUM_OF_PERF_FILES;
177     for (size_t i = 0; i < numOfCleanFiles; i++) {
178         if (!FileUtil::RemoveFile(perfFiles[i])) {
179             HIVIEW_LOGW("failed to delete perf file: %{public}s", perfFiles[i].c_str());
180         }
181     }
182 }
183 }  // namespace HiviewDFX
184 }  // namespace OHOS
185 #endif // HAS_HIPERF
186