1/*
2 * Copyright (C) 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
16#include "trace_utils.h"
17
18#include <cinttypes>
19#include <ctime>
20#include <dirent.h>
21#include <fcntl.h>
22#include <filesystem>
23#include <sys/file.h>
24#include <sys/stat.h>
25#include <unistd.h>
26
27#include "hilog/log.h"
28
29namespace OHOS {
30namespace HiviewDFX {
31namespace Hitrace {
32namespace {
33const std::string TRACE_DEFAULT_DIR = "/data/log/hitrace/";
34const std::string TRACE_EVENT_FORMT = "saved_events_format";
35const std::string TRACE_SNAPSHOT_PREFIX = "trace_";
36const std::string TRACE_RECORDING_PREFIX = "record_trace_";
37const size_t DEFAULT_TRACE_FILE_LIMIT = 15;
38
39struct FileWithTime {
40    std::string filename;
41    time_t ctime;
42};
43
44void RemoveFile(const std::string& fileName)
45{
46    int fd = open(fileName.c_str(), O_RDONLY | O_NONBLOCK);
47    if (fd == -1) {
48        HILOG_WARN(LOG_CORE, "RemoveFile :: open file failed: %{public}s", fileName.c_str());
49        return;
50    }
51    if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
52        HILOG_WARN(LOG_CORE, "RemoveFile :: get file lock failed, skip remove: %{public}s", fileName.c_str());
53        close(fd);
54        return;
55    }
56    if (remove(fileName.c_str()) == 0) {
57        HILOG_INFO(LOG_CORE, "RemoveFile :: Delete %{public}s success.", fileName.c_str());
58    } else {
59        HILOG_WARN(LOG_CORE, "RemoveFile :: Delete %{public}s failed.", fileName.c_str());
60    }
61    flock(fd, LOCK_UN);
62    close(fd);
63}
64
65void GetRecordTraceFilesInDir(std::vector<FileWithTime>& fileList)
66{
67    struct stat fileStat;
68    for (const auto &entry : std::filesystem::directory_iterator(TRACE_DEFAULT_DIR)) {
69        if (!entry.is_regular_file()) {
70            continue;
71        }
72        std::string fileName = entry.path().filename().string();
73        if (fileName.substr(0, TRACE_RECORDING_PREFIX.size()) == TRACE_RECORDING_PREFIX) {
74            if (stat((TRACE_DEFAULT_DIR + fileName).c_str(), &fileStat) == 0) {
75                fileList.push_back({fileName, fileStat.st_ctime});
76            }
77        }
78    }
79    std::sort(fileList.begin(), fileList.end(), [](const FileWithTime& a, const FileWithTime& b) {
80        return a.ctime < b.ctime;
81    });
82}
83}
84
85std::string GenerateTraceFileName(bool isSnapshot)
86{
87    // eg: /data/log/hitrace/trace_localtime@boottime.sys
88    std::string name = TRACE_DEFAULT_DIR;
89
90    if (isSnapshot) {
91        name += TRACE_SNAPSHOT_PREFIX;
92    } else {
93        name += TRACE_RECORDING_PREFIX;
94    }
95
96    // get localtime
97    time_t currentTime = time(nullptr);
98    struct tm timeInfo = {};
99    const int bufferSize = 16;
100    char timeStr[bufferSize] = {0};
101    if (localtime_r(&currentTime, &timeInfo) == nullptr) {
102        HILOG_ERROR(LOG_CORE, "Failed to get localtime.");
103        return "";
104    }
105    (void)strftime(timeStr, bufferSize, "%Y%m%d%H%M%S", &timeInfo);
106    name += std::string(timeStr);
107    // get boottime
108    struct timespec bts = {0, 0};
109    clock_gettime(CLOCK_BOOTTIME, &bts);
110    name += "@" + std::to_string(bts.tv_sec) + "-" + std::to_string(bts.tv_nsec) + ".sys";
111
112    struct timespec mts = {0, 0};
113    clock_gettime(CLOCK_MONOTONIC, &mts);
114    HILOG_INFO(LOG_CORE, "output trace: %{public}s, boot_time(%{public}" PRId64 "), mono_time(%{public}" PRId64 ").",
115        name.c_str(), static_cast<int64_t>(bts.tv_sec), static_cast<int64_t>(mts.tv_sec));
116    return name;
117}
118
119/**
120 * When the SERVICE_MODE is started, clear the remaining trace files in the folder.
121*/
122void DelSnapshotTraceFile(const bool deleteSavedFmt, const int keepFileCount)
123{
124    if (access(TRACE_DEFAULT_DIR.c_str(), F_OK) != 0) {
125        return;
126    }
127    DIR* dirPtr = opendir(TRACE_DEFAULT_DIR.c_str());
128    if (dirPtr == nullptr) {
129        HILOG_ERROR(LOG_CORE, "Failed to opendir %{public}s.", TRACE_DEFAULT_DIR.c_str());
130        return;
131    }
132    std::vector<FileWithTime> snapshotTraceFiles;
133    struct dirent* ptr = nullptr;
134    struct stat fileStat;
135    while ((ptr = readdir(dirPtr)) != nullptr) {
136        if (ptr->d_type == DT_REG) {
137            std::string name = std::string(ptr->d_name);
138            if (deleteSavedFmt && name.compare(0, TRACE_EVENT_FORMT.size(), TRACE_EVENT_FORMT) == 0) {
139                RemoveFile(TRACE_DEFAULT_DIR + name);
140                continue;
141            }
142            if (name.compare(0, TRACE_SNAPSHOT_PREFIX.size(), TRACE_SNAPSHOT_PREFIX) != 0) {
143                continue;
144            }
145            if (stat((TRACE_DEFAULT_DIR + name).c_str(), &fileStat) == 0) {
146                snapshotTraceFiles.push_back({name, fileStat.st_ctime});
147            }
148        }
149    }
150    closedir(dirPtr);
151
152    std::sort(snapshotTraceFiles.begin(), snapshotTraceFiles.end(), [](const FileWithTime& a, const FileWithTime& b) {
153        return a.ctime < b.ctime;
154    });
155
156    int snapshotTraceFilesSize = static_cast<int>(snapshotTraceFiles.size());
157    int deleteFileCnt = snapshotTraceFilesSize - keepFileCount;
158    for (int i = 0; i < deleteFileCnt && i < snapshotTraceFilesSize; i++) {
159        RemoveFile(TRACE_DEFAULT_DIR + snapshotTraceFiles[i].filename);
160    }
161}
162
163/**
164 * open trace file aging mechanism
165 */
166void DelOldRecordTraceFile(const int& fileLimit)
167{
168    size_t traceFileLimit = DEFAULT_TRACE_FILE_LIMIT;
169    if (fileLimit != 0) {
170        traceFileLimit = static_cast<size_t>(fileLimit);
171    }
172    HILOG_INFO(LOG_CORE, "DelOldRecordTraceFile: activate aging mechanism with file limit %{public}zu", traceFileLimit);
173
174    std::vector<FileWithTime> fileList;
175    GetRecordTraceFilesInDir(fileList);
176
177    if (fileList.size() <= traceFileLimit) {
178        HILOG_INFO(LOG_CORE, "DelOldRecordTraceFile: no record trace file need be deleted.");
179        return;
180    }
181
182    size_t deleteNum = fileList.size() - traceFileLimit;
183    for (size_t i = 0; i < deleteNum; ++i) {
184        if (remove((TRACE_DEFAULT_DIR + fileList[i].filename).c_str()) == 0) {
185            HILOG_INFO(LOG_CORE, "DelOldRecordTraceFile: delete first: %{public}s success.",
186                fileList[i].filename.c_str());
187        } else {
188            HILOG_ERROR(LOG_CORE, "DelOldRecordTraceFile: delete first: %{public}s failed, errno: %{public}d.",
189                fileList[i].filename.c_str(), errno);
190        }
191    }
192}
193
194void ClearOldTraceFile(std::vector<std::string>& fileLists, const int& fileLimit)
195{
196    if (fileLists.size() == 0) {
197        return;
198    }
199
200    size_t traceFileLimit = DEFAULT_TRACE_FILE_LIMIT;
201    if (fileLimit != 0) {
202        traceFileLimit = static_cast<size_t>(fileLimit);
203    }
204    HILOG_INFO(LOG_CORE, "ClearOldTraceFile: activate aging mechanism with file limit %{public}zu", traceFileLimit);
205
206    if (fileLists.size() > traceFileLimit && access(fileLists[0].c_str(), F_OK) == 0) {
207        if (remove(fileLists[0].c_str()) == 0) {
208            fileLists.erase(fileLists.begin());
209            HILOG_INFO(LOG_CORE, "ClearOldTraceFile: delete first success.");
210        } else {
211            HILOG_ERROR(LOG_CORE, "ClearOldTraceFile: delete first failed, errno: %{public}d.", errno);
212        }
213    }
214}
215
216/**
217 * When the raw trace is started, clear the saved_events_format files in the folder.
218 */
219void DelSavedEventsFormat()
220{
221    const std::string savedEventsFormatPath = TRACE_DEFAULT_DIR + TRACE_EVENT_FORMT;
222    if (access(savedEventsFormatPath.c_str(), F_OK) != 0) {
223        // saved_events_format not exit
224        return;
225    }
226    // saved_events_format exit
227    if (remove(savedEventsFormatPath.c_str()) == 0) {
228        HILOG_INFO(LOG_CORE, "Delete saved_events_format success.");
229    } else {
230        HILOG_ERROR(LOG_CORE, "Delete saved_events_format failed.");
231    }
232}
233} // namespace Hitrace
234} // namespace HiviewDFX
235} // namespace OHOS
236