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 #include "cpu_storage.h"
16 
17 #include <functional>
18 #include <map>
19 
20 #include "file_util.h"
21 #include "hisysevent.h"
22 #include "hiview_db_util.h"
23 #include "hiview_logger.h"
24 #include "parameter_ex.h"
25 #include "power_status_manager.h"
26 #include "process_collector.h"
27 #include "process_status.h"
28 #include "rdb_predicates.h"
29 #include "sql_util.h"
30 #include "string_util.h"
31 
32 namespace OHOS {
33 namespace HiviewDFX {
34 DEFINE_LOG_TAG("HiView-CpuStorage");
35 using namespace OHOS::HiviewDFX::UCollectUtil;
36 namespace {
37 constexpr int32_t DB_VERSION = 2;
38 const std::string CPU_COLLECTION_TABLE_NAME = "unified_collection_cpu";
39 const std::string THREAD_CPU_COLLECTION_TABLE_NAME = "unified_collection_hiview_cpu";
40 const std::string SYS_VERSION_TABLE_NAME = "version";
41 const std::string COLUMN_START_TIME = "start_time";
42 const std::string COLUMN_END_TIME = "end_time";
43 const std::string COLUMN_PID = "pid";
44 const std::string COLUMN_TID = "tid";
45 const std::string COLUMN_PROC_NAME = "proc_name";
46 const std::string COLUMN_THREAD_NAME = "thread_name";
47 const std::string COLUMN_PROC_STATE = "proc_state";
48 const std::string COLUMN_CPU_LOAD = "cpu_load";
49 const std::string COLUMN_CPU_USAGE = "cpu_usage";
50 const std::string COLUMN_THREAD_CNT = "thread_cnt";
51 const std::string COLUMN_VERSION_NAME = "name";
52 constexpr uint32_t DEFAULT_PRECISION_OF_DECIMAL = 6; // 0.123456
53 constexpr int32_t MEM_CG_PROCESS_FLAG = 100;
54 
CreateDbFileName()55 std::string CreateDbFileName()
56 {
57     return HiviewDbUtil::CreateFileNameByDate("cpu_stat_");
58 }
59 
IsValidProcess(const ProcessCpuStatInfo & cpuCollectionInfo)60 bool IsValidProcess(const ProcessCpuStatInfo& cpuCollectionInfo)
61 {
62     return (cpuCollectionInfo.pid > 0) && (!cpuCollectionInfo.procName.empty());
63 }
64 
IsValidCpuLoad(const ProcessCpuStatInfo & cpuCollectionInfo)65 bool IsValidCpuLoad(const ProcessCpuStatInfo& cpuCollectionInfo)
66 {
67     constexpr double storeFilteringThresholdOfCpuLoad = 0.0005; // 0.05%
68     return cpuCollectionInfo.cpuLoad >= storeFilteringThresholdOfCpuLoad;
69 }
70 
IsInvalidCpuLoad(const ProcessCpuStatInfo & cpuCollectionInfo)71 bool IsInvalidCpuLoad(const ProcessCpuStatInfo& cpuCollectionInfo)
72 {
73     return cpuCollectionInfo.cpuLoad == 0;
74 }
75 
IsValidCpuUsage(const ProcessCpuStatInfo & cpuCollectionInfo)76 bool IsValidCpuUsage(const ProcessCpuStatInfo& cpuCollectionInfo)
77 {
78     constexpr double storeFilteringThresholdOfCpuUsage = 0.0005; // 0.05%
79     return cpuCollectionInfo.cpuUsage >= storeFilteringThresholdOfCpuUsage;
80 }
81 
NeedStoreInDb(const ProcessCpuStatInfo & cpuCollectionInfo)82 bool NeedStoreInDb(const ProcessCpuStatInfo& cpuCollectionInfo)
83 {
84     if (!IsValidProcess(cpuCollectionInfo)) {
85         static uint32_t invalidProcNum = 0;
86         invalidProcNum++;
87         constexpr uint32_t logLimitNum = 1000;
88         if (invalidProcNum % logLimitNum == 0) {
89             HIVIEW_LOGW("invalid process num=%{public}u, pid=%{public}d, name=%{public}s",
90                 invalidProcNum, cpuCollectionInfo.pid, cpuCollectionInfo.procName.c_str());
91         }
92         return false;
93     }
94     return IsValidCpuLoad(cpuCollectionInfo)
95         || (IsInvalidCpuLoad(cpuCollectionInfo) && IsValidCpuUsage(cpuCollectionInfo));
96 }
97 
TruncateDecimalWithNBitPrecision(double decimal,uint32_t precision=DEFAULT_PRECISION_OF_DECIMAL)98 double TruncateDecimalWithNBitPrecision(double decimal, uint32_t precision = DEFAULT_PRECISION_OF_DECIMAL)
99 {
100     auto truncateCoefficient = std::pow(10, precision);
101     return std::floor(decimal * truncateCoefficient) / truncateCoefficient;
102 }
103 
IsForegroundStateInCollectionPeriod(const ProcessCpuStatInfo & cpuCollectionInfo)104 bool IsForegroundStateInCollectionPeriod(const ProcessCpuStatInfo& cpuCollectionInfo)
105 {
106     int32_t pid = cpuCollectionInfo.pid;
107     ProcessState procState = ProcessStatus::GetInstance().GetProcessState(pid);
108     if (procState == FOREGROUND) {
109         return true;
110     }
111     uint64_t procForegroundTime = ProcessStatus::GetInstance().GetProcessLastForegroundTime(pid);
112     return procForegroundTime >= cpuCollectionInfo.startTime;
113 }
114 
GetPowerProcessStateInCollectionPeriod(const ProcessCpuStatInfo & cpuCollectionInfo,const std::unordered_set<int32_t> & memCgProcs)115 int32_t GetPowerProcessStateInCollectionPeriod(const ProcessCpuStatInfo& cpuCollectionInfo,
116     const std::unordered_set<int32_t>& memCgProcs)
117 {
118     int32_t processState = IsForegroundStateInCollectionPeriod(cpuCollectionInfo) ? static_cast<int32_t>(FOREGROUND) :
119         static_cast<int32_t>(ProcessStatus::GetInstance().GetProcessState(cpuCollectionInfo.pid));
120 #ifdef POWER_MANAGER_ENABLE
121     int32_t powerState = PowerStatusManager::GetInstance().GetPowerState();
122     processState += powerState;
123 #endif
124     processState += (memCgProcs.find(cpuCollectionInfo.pid) != memCgProcs.end() ? MEM_CG_PROCESS_FLAG : 0);
125     return processState;
126 }
127 
CreateTable(NativeRdb::RdbStore & dbStore,const std::string & tableName,const std::vector<std::pair<std::string,std::string>> & fields)128 int32_t CreateTable(NativeRdb::RdbStore& dbStore, const std::string& tableName,
129     const std::vector<std::pair<std::string, std::string>>& fields)
130 {
131     std::string sql = SqlUtil::GenerateCreateSql(tableName, fields);
132     HIVIEW_LOGI("try to create %{public}s table, sql=%{public}s", tableName.c_str(), sql.c_str());
133     return dbStore.ExecuteSql(sql);
134 }
135 
StoreSysVersion(NativeRdb::RdbStore & dbStore,const std::string & version)136 int32_t StoreSysVersion(NativeRdb::RdbStore& dbStore, const std::string& version)
137 {
138     NativeRdb::ValuesBucket bucket;
139     bucket.PutString(COLUMN_VERSION_NAME, version);
140     int64_t seq = 0;
141     if (auto ret = dbStore.Insert(seq, SYS_VERSION_TABLE_NAME, bucket); ret != NativeRdb::E_OK) {
142         HIVIEW_LOGE("failed to insert %{public}s to version table", version.c_str());
143         return ret;
144     }
145     return NativeRdb::E_OK;
146 }
147 
CreateCpuCollectionTable(NativeRdb::RdbStore & dbStore)148 int32_t CreateCpuCollectionTable(NativeRdb::RdbStore& dbStore)
149 {
150     /**
151      * table: unified_collection_cpu
152      *
153      * |-----|------------|----------|-----|------------|-----------|----------|-----------|------------|
154      * |  id | start_time | end_time | pid | proc_state | proc_name | cpu_load | cpu_usage | thread_cnt |
155      * |-----|------------|----------|-----|------------|-----------|----------|-----------|------------|
156      * | INT |    INT64   |   INT64  | INT |    INT     |  VARCHAR  |  DOUBLE  |   DOUBLE  |    INT     |
157      * |-----|------------|----------|-----|------------|-----------|----------|-----------|------------|
158      */
159     const std::vector<std::pair<std::string, std::string>> fields = {
160         {COLUMN_START_TIME, SqlUtil::COLUMN_TYPE_INT},
161         {COLUMN_END_TIME, SqlUtil::COLUMN_TYPE_INT},
162         {COLUMN_PID, SqlUtil::COLUMN_TYPE_INT},
163         {COLUMN_PROC_STATE, SqlUtil::COLUMN_TYPE_INT},
164         {COLUMN_PROC_NAME, SqlUtil::COLUMN_TYPE_STR},
165         {COLUMN_CPU_LOAD, SqlUtil::COLUMN_TYPE_DOU},
166         {COLUMN_CPU_USAGE, SqlUtil::COLUMN_TYPE_DOU},
167         {COLUMN_THREAD_CNT, SqlUtil::COLUMN_TYPE_INT},
168     };
169     if (auto ret = CreateTable(dbStore, CPU_COLLECTION_TABLE_NAME, fields); ret != NativeRdb::E_OK) {
170         HIVIEW_LOGE("failed to create %{public}s table", CPU_COLLECTION_TABLE_NAME.c_str());
171         return ret;
172     }
173     return NativeRdb::E_OK;
174 }
175 
CreateThreadCpuCollectionTable(NativeRdb::RdbStore & dbStore)176 int32_t CreateThreadCpuCollectionTable(NativeRdb::RdbStore& dbStore)
177 {
178     /**
179      * table: unified_collection_hiview_cpu
180      *
181      * |-----|------------|----------|-----|-----------  |----------|-----------|
182      * |  id | start_time | end_time | tid | thread_name | cpu_load | cpu_usage |
183      * |-----|------------|----------|-----|-------------|----------|-----------|
184      * | INT |    INT64   |   INT64  | INT |  VARCHAR    |  DOUBLE  |   DOUBLE  |
185      * |-----|------------|----------|-----|-------------|----------|-----------|
186      */
187     const std::vector<std::pair<std::string, std::string>> fields = {
188         {COLUMN_START_TIME, SqlUtil::COLUMN_TYPE_INT},
189         {COLUMN_END_TIME, SqlUtil::COLUMN_TYPE_INT},
190         {COLUMN_TID, SqlUtil::COLUMN_TYPE_INT},
191         {COLUMN_THREAD_NAME, SqlUtil::COLUMN_TYPE_STR},
192         {COLUMN_CPU_LOAD, SqlUtil::COLUMN_TYPE_DOU},
193         {COLUMN_CPU_USAGE, SqlUtil::COLUMN_TYPE_DOU},
194     };
195     if (auto ret = CreateTable(dbStore, THREAD_CPU_COLLECTION_TABLE_NAME, fields); ret != NativeRdb::E_OK) {
196         HIVIEW_LOGE("failed to create %{public}s table", THREAD_CPU_COLLECTION_TABLE_NAME.c_str());
197         return ret;
198     }
199     return NativeRdb::E_OK;
200 }
201 
CreateVersionTable(NativeRdb::RdbStore & dbStore)202 int32_t CreateVersionTable(NativeRdb::RdbStore& dbStore)
203 {
204     /**
205      * table: version
206      *
207      * |-----|-----------|
208      * |  id |    name   |
209      * |-----|-----------|
210      * | INT |  VARCHAR  |
211      * |-----|-----------|
212      */
213     const std::vector<std::pair<std::string, std::string>> fields = {
214         {COLUMN_VERSION_NAME, SqlUtil::COLUMN_TYPE_STR},
215     };
216     if (auto ret = CreateTable(dbStore, SYS_VERSION_TABLE_NAME, fields); ret != NativeRdb::E_OK) {
217         HIVIEW_LOGE("failed to create %{public}s table", SYS_VERSION_TABLE_NAME.c_str());
218         return ret;
219     }
220     return NativeRdb::E_OK;
221 }
222 }
223 
OnCreate(NativeRdb::RdbStore & rdbStore)224 int CpuStorageDbCallback::OnCreate(NativeRdb::RdbStore& rdbStore)
225 {
226     HIVIEW_LOGD("create dbStore");
227     if (auto ret = CreateVersionTable(rdbStore); ret != NativeRdb::E_OK) {
228         HIVIEW_LOGE("failed to create version table in db creation");
229         return ret;
230     }
231     if (auto ret = StoreSysVersion(rdbStore, Parameter::GetDisplayVersionStr()); ret != NativeRdb::E_OK) {
232         HIVIEW_LOGE("failed to insert system version into version table in db creation");
233         return ret;
234     }
235     if (auto ret = CreateCpuCollectionTable(rdbStore); ret != NativeRdb::E_OK) {
236         HIVIEW_LOGE("failed to create cpu collection table in db creation");
237         return ret;
238     }
239     if (auto ret = CreateThreadCpuCollectionTable(rdbStore); ret != NativeRdb::E_OK) {
240         HIVIEW_LOGE("failed to create cpu collection table in db creation");
241         return ret;
242     }
243     return NativeRdb::E_OK;
244 }
245 
OnUpgrade(NativeRdb::RdbStore & rdbStore,int oldVersion,int newVersion)246 int CpuStorageDbCallback::OnUpgrade(NativeRdb::RdbStore& rdbStore, int oldVersion, int newVersion)
247 {
248     HIVIEW_LOGD("oldVersion=%{public}d, newVersion=%{public}d", oldVersion, newVersion);
249     return NativeRdb::E_OK;
250 }
251 
CpuStorage(const std::string & workPath)252 CpuStorage::CpuStorage(const std::string& workPath) : workPath_(workPath)
253 {
254     InitDbStorePath();
255     InitDbStore();
256     if (dbStore_!= nullptr && GetStoredSysVersion() != Parameter::GetDisplayVersionStr()) {
257         HIVIEW_LOGI("system has been upgaded, report directly");
258         ReportDbRecords();
259     }
260 }
261 
InitDbStorePath()262 void CpuStorage::InitDbStorePath()
263 {
264     std::string tempDbStorePath = FileUtil::IncludeTrailingPathDelimiter(workPath_);
265     const std::string cpuDirName = "cpu";
266     tempDbStorePath = FileUtil::IncludeTrailingPathDelimiter(tempDbStorePath.append(cpuDirName));
267     if (!FileUtil::IsDirectory(tempDbStorePath) && !FileUtil::ForceCreateDirectory(tempDbStorePath)) {
268         HIVIEW_LOGE("failed to create dir=%{public}s", tempDbStorePath.c_str());
269         return;
270     }
271     tempDbStorePath.append(CreateDbFileName());
272     dbStorePath_ = tempDbStorePath;
273     HIVIEW_LOGI("succ to init db store path=%{public}s", dbStorePath_.c_str());
274 }
275 
InitDbStore()276 void CpuStorage::InitDbStore()
277 {
278     NativeRdb::RdbStoreConfig config(dbStorePath_);
279     config.SetSecurityLevel(NativeRdb::SecurityLevel::S1);
280     CpuStorageDbCallback callback;
281     auto ret = NativeRdb::E_OK;
282     dbStore_ = NativeRdb::RdbHelper::GetRdbStore(config, DB_VERSION, callback, ret);
283     if (ret != NativeRdb::E_OK) {
284         HIVIEW_LOGE("failed to init db store, db store path=%{public}s", dbStorePath_.c_str());
285         dbStore_ = nullptr;
286         return;
287     }
288 }
289 
StoreProcessDatas(const std::vector<ProcessCpuStatInfo> & cpuCollectionInfos)290 void CpuStorage::StoreProcessDatas(const std::vector<ProcessCpuStatInfo>& cpuCollectionInfos)
291 {
292     if (dbStore_ == nullptr) {
293         HIVIEW_LOGW("db store is null, path=%{public}s", dbStorePath_.c_str());
294         return;
295     }
296     auto processCollector = UCollectUtil::ProcessCollector::Create();
297     auto result = processCollector->GetMemCgProcesses();
298     for (auto& cpuCollectionInfo : cpuCollectionInfos) {
299         if (NeedStoreInDb(cpuCollectionInfo)) {
300             StoreProcessData(cpuCollectionInfo, result.data);
301         }
302     }
303 }
304 
StoreThreadDatas(const std::vector<ThreadCpuStatInfo> & cpuCollections)305 void CpuStorage::StoreThreadDatas(const std::vector<ThreadCpuStatInfo>& cpuCollections)
306 {
307     if (dbStore_ == nullptr) {
308         HIVIEW_LOGW("db store is null, path=%{public}s", dbStorePath_.c_str());
309         return;
310     }
311     for (auto& cpuCollectionInfo : cpuCollections) {
312         StoreThreadData(cpuCollectionInfo);
313     }
314 }
315 
StoreProcessData(const ProcessCpuStatInfo & cpuCollectionInfo,const std::unordered_set<int32_t> & memCgProcs)316 void CpuStorage::StoreProcessData(const ProcessCpuStatInfo& cpuCollectionInfo,
317     const std::unordered_set<int32_t>& memCgProcs)
318 {
319     NativeRdb::ValuesBucket bucket;
320     bucket.PutLong(COLUMN_START_TIME, static_cast<int64_t>(cpuCollectionInfo.startTime));
321     bucket.PutLong(COLUMN_END_TIME, static_cast<int64_t>(cpuCollectionInfo.endTime));
322     bucket.PutInt(COLUMN_PID, cpuCollectionInfo.pid);
323     bucket.PutInt(COLUMN_PROC_STATE, GetPowerProcessStateInCollectionPeriod(cpuCollectionInfo, memCgProcs));
324     bucket.PutString(COLUMN_PROC_NAME, cpuCollectionInfo.procName);
325     bucket.PutDouble(COLUMN_CPU_LOAD, TruncateDecimalWithNBitPrecision(cpuCollectionInfo.cpuLoad));
326     bucket.PutDouble(COLUMN_CPU_USAGE, TruncateDecimalWithNBitPrecision(cpuCollectionInfo.cpuUsage));
327     bucket.PutInt(COLUMN_THREAD_CNT, cpuCollectionInfo.threadCount);
328     int64_t seq = 0;
329     if (dbStore_->Insert(seq, CPU_COLLECTION_TABLE_NAME, bucket) != NativeRdb::E_OK) {
330         HIVIEW_LOGE("failed to insert cpu data to db store, pid=%{public}d, proc_name=%{public}s",
331             cpuCollectionInfo.pid, cpuCollectionInfo.procName.c_str());
332     }
333 }
334 
StoreThreadData(const ThreadCpuStatInfo & cpuCollection)335 void CpuStorage::StoreThreadData(const ThreadCpuStatInfo& cpuCollection)
336 {
337     NativeRdb::ValuesBucket bucket;
338     bucket.PutLong(COLUMN_START_TIME, static_cast<int64_t>(cpuCollection.startTime));
339     bucket.PutLong(COLUMN_END_TIME, static_cast<int64_t>(cpuCollection.endTime));
340     bucket.PutInt(COLUMN_TID, cpuCollection.tid);
341     bucket.PutString(COLUMN_THREAD_NAME, "");
342     bucket.PutDouble(COLUMN_CPU_LOAD, TruncateDecimalWithNBitPrecision(cpuCollection.cpuLoad));
343     bucket.PutDouble(COLUMN_CPU_USAGE, TruncateDecimalWithNBitPrecision(cpuCollection.cpuUsage));
344     int64_t seq = 0;
345     if (dbStore_->Insert(seq, THREAD_CPU_COLLECTION_TABLE_NAME, bucket) != NativeRdb::E_OK) {
346         HIVIEW_LOGE("failed to insert cpu data to db store, tid=%{public}d", cpuCollection.tid);
347     }
348 }
349 
Report()350 void CpuStorage::Report()
351 {
352     if (!NeedReport()) {
353         return;
354     }
355     ReportDbRecords();
356 }
357 
ReportDbRecords()358 void CpuStorage::ReportDbRecords()
359 {
360     HIVIEW_LOGI("start to report cpu collection event");
361     PrepareOldDbFilesBeforeReport();
362     ReportCpuCollectionEvent();
363     PrepareNewDbFilesAfterReport();
364 }
365 
GetStoredSysVersion()366 std::string CpuStorage::GetStoredSysVersion()
367 {
368     NativeRdb::RdbPredicates predicates(SYS_VERSION_TABLE_NAME);
369     std::vector<std::string> columns;
370     columns.emplace_back(COLUMN_VERSION_NAME);
371     std::string version;
372     std::shared_ptr<NativeRdb::ResultSet> allVersions = dbStore_->Query(predicates, columns);
373     if (allVersions == nullptr || allVersions->GoToFirstRow() != NativeRdb::E_OK) {
374         HIVIEW_LOGE("failed to get result set from db query");
375         return version;
376     }
377     NativeRdb::RowEntity entity;
378     if (allVersions->GetRow(entity) != NativeRdb::E_OK) {
379         HIVIEW_LOGE("failed to read row entity from result set");
380         return version;
381     }
382     if (entity.Get(COLUMN_VERSION_NAME).GetString(version) != NativeRdb::E_OK) {
383         HIVIEW_LOGE("failed to get version value");
384     }
385     HIVIEW_LOGI("stored version in db is %{public}s", version.c_str());
386     return version;
387 }
388 
NeedReport()389 bool CpuStorage::NeedReport()
390 {
391     if (dbStorePath_.empty()) {
392         HIVIEW_LOGI("the db file stored directory is empty");
393         return false;
394     }
395     std::string nowDbFileName = FileUtil::ExtractFileName(dbStorePath_);
396     std::string newDbFileName = CreateDbFileName();
397     return newDbFileName != nowDbFileName;
398 }
399 
PrepareOldDbFilesBeforeReport()400 void CpuStorage::PrepareOldDbFilesBeforeReport()
401 {
402     // 1. Close the current db file
403     ResetDbStore();
404     // 2. Init upload directory
405     if (!HiviewDbUtil::InitDbUploadPath(dbStorePath_, dbStoreUploadPath_)) {
406         return;
407     }
408     // 3. Move the db file to the upload directory
409     HiviewDbUtil::MoveDbFilesToUploadDir(dbStorePath_, dbStoreUploadPath_);
410     // 4. Aging upload db files, only the latest 7 db files are retained
411     HiviewDbUtil::TryToAgeUploadDbFiles(dbStoreUploadPath_);
412 }
413 
ResetDbStore()414 void CpuStorage::ResetDbStore()
415 {
416     dbStore_ = nullptr;
417 }
418 
ReportCpuCollectionEvent()419 void CpuStorage::ReportCpuCollectionEvent()
420 {
421     int32_t ret = HiSysEventWrite(HiSysEvent::Domain::HIVIEWDFX, "CPU_COLLECTION", HiSysEvent::EventType::FAULT);
422     if (ret != 0) {
423         HIVIEW_LOGW("failed to report cpu collection event, ret=%{public}d", ret);
424     }
425 }
426 
PrepareNewDbFilesAfterReport()427 void CpuStorage::PrepareNewDbFilesAfterReport()
428 {
429     InitDbStorePath();
430     InitDbStore();
431 }
432 }  // namespace HiviewDFX
433 }  // namespace OHOS
434