1 /*
2 * Copyright (C) 2023 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 <algorithm>
18 #include <cmath>
19
20 #include "file_util.h"
21 #include "hisysevent.h"
22 #include "logger.h"
23 #include "process_status.h"
24 #include "rdb_helper.h"
25 #include "sql_util.h"
26 #include "string_util.h"
27 #include "time_util.h"
28
29 namespace OHOS {
30 namespace HiviewDFX {
31 DEFINE_LOG_TAG("HiView-CpuStorage");
32 using namespace OHOS::HiviewDFX::UCollectUtil;
33 namespace {
34 constexpr int32_t DB_VERSION = 1;
35 const std::string TABLE_NAME = "unified_collection_cpu";
36 const std::string COLUMN_START_TIME = "start_time";
37 const std::string COLUMN_END_TIME = "end_time";
38 const std::string COLUMN_PID = "pid";
39 const std::string COLUMN_PROC_NAME = "proc_name";
40 const std::string COLUMN_PROC_STATE = "proc_state";
41 const std::string COLUMN_CPU_LOAD = "cpu_load";
42 const std::string COLUMN_CPU_USAGE = "cpu_usage";
43 constexpr uint32_t MAX_NUM_OF_DB_FILES = 7; // save files for one week
44 constexpr uint32_t DEFAULT_PRECISION_OF_DECIMAL = 6; // 0.123456
45
CreateDbFileName()46 std::string CreateDbFileName()
47 {
48 std::string dbFileName;
49 std::string dateStr = TimeUtil::TimestampFormatToDate(std::time(nullptr), "%Y%m%d");
50 dbFileName.append("cpu_stat_").append(dateStr).append(".db"); // cpu_stat_yyyymmdd.db
51 return dbFileName;
52 }
53
NeedCleanDbFiles(const std::vector<std::string> & dbFiles)54 bool NeedCleanDbFiles(const std::vector<std::string>& dbFiles)
55 {
56 return dbFiles.size() > MAX_NUM_OF_DB_FILES;
57 }
58
ClearDbFilesByTimestampOrder(const std::vector<std::string> & dbFiles)59 void ClearDbFilesByTimestampOrder(const std::vector<std::string>& dbFiles)
60 {
61 uint32_t numOfCleanFiles = dbFiles.size() - MAX_NUM_OF_DB_FILES;
62 for (size_t i = 0; i < numOfCleanFiles; i++) {
63 HIVIEW_LOGI("start to clear db file=%{public}s", dbFiles[i].c_str());
64 if (!FileUtil::RemoveFile(dbFiles[i])) {
65 HIVIEW_LOGW("failed to delete db file=%{public}s", dbFiles[i].c_str());
66 }
67 }
68 }
69
IsDbFile(const std::string & dbFilePath)70 bool IsDbFile(const std::string& dbFilePath)
71 {
72 std::string dbFileName = FileUtil::ExtractFileName(dbFilePath);
73 std::string dbFileExt = FileUtil::ExtractFileExt(dbFileName);
74 return dbFileExt == "db";
75 }
76
IsValidProcess(const ProcessCpuStatInfo & cpuCollectionInfo)77 bool IsValidProcess(const ProcessCpuStatInfo& cpuCollectionInfo)
78 {
79 return (cpuCollectionInfo.pid > 0) && (!cpuCollectionInfo.procName.empty());
80 }
81
IsValidCpuLoad(const ProcessCpuStatInfo & cpuCollectionInfo)82 bool IsValidCpuLoad(const ProcessCpuStatInfo& cpuCollectionInfo)
83 {
84 constexpr double storeFilteringThresholdOfCpuLoad = 0.0005; // 0.05%
85 return cpuCollectionInfo.cpuLoad >= storeFilteringThresholdOfCpuLoad;
86 }
87
IsInvalidCpuLoad(const ProcessCpuStatInfo & cpuCollectionInfo)88 bool IsInvalidCpuLoad(const ProcessCpuStatInfo& cpuCollectionInfo)
89 {
90 return cpuCollectionInfo.cpuLoad == 0;
91 }
92
IsValidCpuUsage(const ProcessCpuStatInfo & cpuCollectionInfo)93 bool IsValidCpuUsage(const ProcessCpuStatInfo& cpuCollectionInfo)
94 {
95 constexpr double storeFilteringThresholdOfCpuUsage = 0.0005; // 0.05%
96 return cpuCollectionInfo.cpuUsage >= storeFilteringThresholdOfCpuUsage;
97 }
98
NeedStoreInDb(const ProcessCpuStatInfo & cpuCollectionInfo)99 bool NeedStoreInDb(const ProcessCpuStatInfo& cpuCollectionInfo)
100 {
101 if (!IsValidProcess(cpuCollectionInfo)) {
102 static uint32_t invalidProcNum = 0;
103 invalidProcNum++;
104 constexpr uint32_t logLimitNum = 1000;
105 if (invalidProcNum % logLimitNum == 0) {
106 HIVIEW_LOGW("invalid process num=%{public}u, pid=%{public}d, name=%{public}s",
107 invalidProcNum, cpuCollectionInfo.pid, cpuCollectionInfo.procName.c_str());
108 }
109 return false;
110 }
111 return IsValidCpuLoad(cpuCollectionInfo)
112 || (IsInvalidCpuLoad(cpuCollectionInfo) && IsValidCpuUsage(cpuCollectionInfo));
113 }
114
TruncateDecimalWithNBitPrecision(double decimal,uint32_t precision=DEFAULT_PRECISION_OF_DECIMAL)115 double TruncateDecimalWithNBitPrecision(double decimal, uint32_t precision = DEFAULT_PRECISION_OF_DECIMAL)
116 {
117 auto truncateCoefficient = std::pow(10, precision);
118 return std::floor(decimal * truncateCoefficient) / truncateCoefficient;
119 }
120
IsForegroundStateInCollectionPeriod(const ProcessCpuStatInfo & cpuCollectionInfo)121 bool IsForegroundStateInCollectionPeriod(const ProcessCpuStatInfo& cpuCollectionInfo)
122 {
123 int32_t pid = cpuCollectionInfo.pid;
124 ProcessState procState = ProcessStatus::GetInstance().GetProcessState(pid);
125 if (procState == FOREGROUND) {
126 return true;
127 }
128 uint64_t procForegroundTime = ProcessStatus::GetInstance().GetProcessLastForegroundTime(pid);
129 return (procForegroundTime >= cpuCollectionInfo.startTime && procForegroundTime < cpuCollectionInfo.endTime);
130 }
131
GetProcessStateInCollectionPeriod(const ProcessCpuStatInfo & cpuCollectionInfo)132 int32_t GetProcessStateInCollectionPeriod(const ProcessCpuStatInfo& cpuCollectionInfo)
133 {
134 return IsForegroundStateInCollectionPeriod(cpuCollectionInfo)
135 ? static_cast<int32_t>(FOREGROUND)
136 : static_cast<int32_t>(ProcessStatus::GetInstance().GetProcessState(cpuCollectionInfo.pid));
137 }
138 }
139
140 class CpuDbStoreCallback : public NativeRdb::RdbOpenCallback {
141 public:
142 int OnCreate(NativeRdb::RdbStore &rdbStore) override;
143 int OnUpgrade(NativeRdb::RdbStore &rdbStore, int oldVersion, int newVersion) override;
144 };
145
OnCreate(NativeRdb::RdbStore & rdbStore)146 int CpuDbStoreCallback::OnCreate(NativeRdb::RdbStore& rdbStore)
147 {
148 HIVIEW_LOGD("create dbStore");
149 return NativeRdb::E_OK;
150 }
151
OnUpgrade(NativeRdb::RdbStore & rdbStore,int oldVersion,int newVersion)152 int CpuDbStoreCallback::OnUpgrade(NativeRdb::RdbStore& rdbStore, int oldVersion, int newVersion)
153 {
154 HIVIEW_LOGD("oldVersion=%{public}d, newVersion=%{public}d", oldVersion, newVersion);
155 return NativeRdb::E_OK;
156 }
157
CpuStorage(const std::string & workPath)158 CpuStorage::CpuStorage(const std::string& workPath) : workPath_(workPath)
159 {
160 InitDbStorePath();
161 InitDbStore();
162 }
163
InitDbStorePath()164 void CpuStorage::InitDbStorePath()
165 {
166 std::string tempDbStorePath = FileUtil::IncludeTrailingPathDelimiter(workPath_);
167 const std::string cpuDirName = "cpu";
168 tempDbStorePath = FileUtil::IncludeTrailingPathDelimiter(tempDbStorePath.append(cpuDirName));
169 if (!FileUtil::IsDirectory(tempDbStorePath) && !FileUtil::ForceCreateDirectory(tempDbStorePath)) {
170 HIVIEW_LOGE("failed to create dir=%{public}s", tempDbStorePath.c_str());
171 return;
172 }
173 tempDbStorePath.append(CreateDbFileName());
174 dbStorePath_ = tempDbStorePath;
175 HIVIEW_LOGI("succ to init db store path=%{public}s", dbStorePath_.c_str());
176 }
177
InitDbStore()178 void CpuStorage::InitDbStore()
179 {
180 NativeRdb::RdbStoreConfig config(dbStorePath_);
181 config.SetSecurityLevel(NativeRdb::SecurityLevel::S1);
182 CpuDbStoreCallback callback;
183 auto ret = NativeRdb::E_OK;
184 dbStore_ = NativeRdb::RdbHelper::GetRdbStore(config, DB_VERSION, callback, ret);
185 if (ret != NativeRdb::E_OK) {
186 HIVIEW_LOGE("failed to init db store, db store path=%{public}s", dbStorePath_.c_str());
187 dbStore_ = nullptr;
188 return;
189 }
190 }
191
Store(const std::vector<ProcessCpuStatInfo> & cpuCollectionInfos)192 void CpuStorage::Store(const std::vector<ProcessCpuStatInfo>& cpuCollectionInfos)
193 {
194 if (dbStore_ == nullptr) {
195 HIVIEW_LOGW("db store is null, path=%{public}s", dbStorePath_.c_str());
196 return;
197 }
198
199 for (auto& cpuCollectionInfo : cpuCollectionInfos) {
200 if (NeedStoreInDb(cpuCollectionInfo)) {
201 Store(cpuCollectionInfo);
202 }
203 }
204 }
205
Store(const ProcessCpuStatInfo & cpuCollectionInfo)206 void CpuStorage::Store(const ProcessCpuStatInfo& cpuCollectionInfo)
207 {
208 InsertTable(cpuCollectionInfo);
209 }
210
InsertTable(const ProcessCpuStatInfo & cpuCollectionInfo)211 void CpuStorage::InsertTable(const ProcessCpuStatInfo& cpuCollectionInfo)
212 {
213 if (CreateTable() != 0) {
214 return;
215 }
216 NativeRdb::ValuesBucket bucket;
217 bucket.PutLong(COLUMN_START_TIME, static_cast<int64_t>(cpuCollectionInfo.startTime));
218 bucket.PutLong(COLUMN_END_TIME, static_cast<int64_t>(cpuCollectionInfo.endTime));
219 bucket.PutInt(COLUMN_PID, cpuCollectionInfo.pid);
220 bucket.PutInt(COLUMN_PROC_STATE, GetProcessStateInCollectionPeriod(cpuCollectionInfo));
221 bucket.PutString(COLUMN_PROC_NAME, cpuCollectionInfo.procName);
222 bucket.PutDouble(COLUMN_CPU_LOAD, TruncateDecimalWithNBitPrecision(cpuCollectionInfo.cpuLoad));
223 bucket.PutDouble(COLUMN_CPU_USAGE, TruncateDecimalWithNBitPrecision(cpuCollectionInfo.cpuUsage));
224 int64_t seq = 0;
225 if (dbStore_->Insert(seq, TABLE_NAME, bucket) != NativeRdb::E_OK) {
226 HIVIEW_LOGE("failed to insert cpu data to db store, pid=%{public}d, proc_name=%{public}s", 0, "");
227 }
228 }
229
CreateTable()230 int32_t CpuStorage::CreateTable()
231 {
232 /**
233 * table: unified_collection_cpu
234 *
235 * |-----|------------|----------|-----|------------|-----------|----------|-----------|
236 * | id | start_time | end_time | pid | proc_state | proc_name | cpu_load | cpu_usage |
237 * |-----|------------|----------|-----|------------|-----------|----------|-----------|
238 * | INT | INT64 | INT64 | INT | INT | VARCHAR | DOUBLE | DOUBLE |
239 * |-----|------------|----------|-----|------------|-----------|----------|-----------|
240 */
241 const std::vector<std::pair<std::string, std::string>> fields = {
242 {COLUMN_START_TIME, SqlUtil::COLUMN_TYPE_INT},
243 {COLUMN_END_TIME, SqlUtil::COLUMN_TYPE_INT},
244 {COLUMN_PID, SqlUtil::COLUMN_TYPE_INT},
245 {COLUMN_PROC_STATE, SqlUtil::COLUMN_TYPE_INT},
246 {COLUMN_PROC_NAME, SqlUtil::COLUMN_TYPE_STR},
247 {COLUMN_CPU_LOAD, SqlUtil::COLUMN_TYPE_DOU},
248 {COLUMN_CPU_USAGE, SqlUtil::COLUMN_TYPE_DOU},
249 };
250 std::string sql = SqlUtil::GenerateCreateSql(TABLE_NAME, fields);
251 if (dbStore_->ExecuteSql(sql) != NativeRdb::E_OK) {
252 HIVIEW_LOGE("failed to create table, sql=%{public}s", sql.c_str());
253 return -1;
254 }
255 return 0;
256 }
257
Report()258 void CpuStorage::Report()
259 {
260 if (!NeedReport()) {
261 return;
262 }
263 HIVIEW_LOGI("start to report cpu collection event");
264 PrepareOldDbFilesBeforeReport();
265 ReportCpuCollectionEvent();
266 PrepareNewDbFilesAfterReport();
267 }
268
NeedReport()269 bool CpuStorage::NeedReport()
270 {
271 if (dbStorePath_.empty()) {
272 return false;
273 }
274 std::string nowDbFileName = FileUtil::ExtractFileName(dbStorePath_);
275 std::string newDbFileName = CreateDbFileName();
276 return newDbFileName != nowDbFileName;
277 }
278
PrepareOldDbFilesBeforeReport()279 void CpuStorage::PrepareOldDbFilesBeforeReport()
280 {
281 // 1. Close the current db file
282 ResetDbStore();
283 // 2. Init upload directory
284 if (!InitDbStoreUploadPath()) {
285 return;
286 }
287 // 3. Move the db file to the upload directory
288 MoveDbFilesToUploadDir();
289 // 4. Aging upload db files, only the latest N db files are retained
290 TryToAgeUploadDbFiles();
291 }
292
ResetDbStore()293 void CpuStorage::ResetDbStore()
294 {
295 dbStore_ = nullptr;
296 }
297
InitDbStoreUploadPath()298 bool CpuStorage::InitDbStoreUploadPath()
299 {
300 if (!dbStoreUploadPath_.empty()) {
301 return true;
302 }
303 const std::string uploadDirName = "upload";
304 std::string tmpUploadPath = FileUtil::IncludeTrailingPathDelimiter(
305 FileUtil::ExtractFilePath(dbStorePath_)).append(uploadDirName);
306 if (!FileUtil::IsDirectory(tmpUploadPath) && !FileUtil::ForceCreateDirectory(tmpUploadPath)) {
307 HIVIEW_LOGE("failed to create upload dir=%{public}s", tmpUploadPath.c_str());
308 return false;
309 }
310 dbStoreUploadPath_ = tmpUploadPath;
311 HIVIEW_LOGI("init db upload path=%{public}s", dbStoreUploadPath_.c_str());
312 return true;
313 }
314
MoveDbFilesToUploadDir()315 void CpuStorage::MoveDbFilesToUploadDir()
316 {
317 std::vector<std::string> dbFiles;
318 FileUtil::GetDirFiles(FileUtil::ExtractFilePath(dbStorePath_), dbFiles, false);
319 for (auto& dbFile : dbFiles) {
320 // upload only xxx.db, and delete xxx.db-shm/xxx.db-wal
321 if (IsDbFile(dbFile)) {
322 MoveDbFileToUploadDir(dbFile);
323 continue;
324 }
325 HIVIEW_LOGI("start to remove db file=%{public}s", dbFile.c_str());
326 if (!FileUtil::RemoveFile(dbFile)) {
327 HIVIEW_LOGW("failed to remove db file=%{public}s", dbFile.c_str());
328 }
329 }
330 }
331
MoveDbFileToUploadDir(const std::string dbFilePath)332 void CpuStorage::MoveDbFileToUploadDir(const std::string dbFilePath)
333 {
334 std::string uploadFilePath = FileUtil::IncludeTrailingPathDelimiter(dbStoreUploadPath_)
335 .append(FileUtil::ExtractFileName(dbFilePath));
336 HIVIEW_LOGI("start to move db file, src=%{public}s, dst=%{public}s", dbFilePath.c_str(), uploadFilePath.c_str());
337 if (FileUtil::CopyFile(dbFilePath, uploadFilePath) != 0) {
338 HIVIEW_LOGW("failed to copy db file");
339 return;
340 }
341 if (!FileUtil::RemoveFile(dbFilePath)) {
342 HIVIEW_LOGW("failed to delete db file=%{public}s", dbFilePath.c_str());
343 }
344 }
345
TryToAgeUploadDbFiles()346 void CpuStorage::TryToAgeUploadDbFiles()
347 {
348 std::vector<std::string> dbFiles;
349 FileUtil::GetDirFiles(dbStoreUploadPath_, dbFiles);
350 if (!NeedCleanDbFiles(dbFiles)) {
351 return;
352 }
353 HIVIEW_LOGI("start to clean db files, size=%{public}zu", dbFiles.size());
354 std::sort(dbFiles.begin(), dbFiles.end());
355 ClearDbFilesByTimestampOrder(dbFiles);
356 }
357
ReportCpuCollectionEvent()358 void CpuStorage::ReportCpuCollectionEvent()
359 {
360 int32_t ret = HiSysEventWrite(HiSysEvent::Domain::HIVIEWDFX, "CPU_COLLECTION", HiSysEvent::EventType::FAULT);
361 if (ret != 0) {
362 HIVIEW_LOGW("failed to report cpu collection event, ret=%{public}d", ret);
363 }
364 }
365
PrepareNewDbFilesAfterReport()366 void CpuStorage::PrepareNewDbFilesAfterReport()
367 {
368 InitDbStorePath();
369 InitDbStore();
370 }
371 } // namespace HiviewDFX
372 } // namespace OHOS
373