1/*
2 * Copyright (c) 2022-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
16#include "thermal_dfx.h"
17#include <cerrno>
18#include <cstdio>
19#include <deque>
20#include <fcntl.h>
21#include <sys/socket.h>
22#include <sys/stat.h>
23#include <unistd.h>
24#include <hdf_log.h>
25#include <hdf_base.h>
26
27#include "directory_ex.h"
28#include "param_wrapper.h"
29#include "parameter.h"
30#include "securec.h"
31#include "string_ex.h"
32#include "sysparam_errno.h"
33#include "thermal_hdf_utils.h"
34#include "thermal_hitrace.h"
35#include "thermal_log.h"
36#include "thermal_zone_manager.h"
37#include "zlib.h"
38
39namespace OHOS {
40namespace HDI {
41namespace Thermal {
42namespace V1_1 {
43namespace {
44constexpr uint8_t LOG_INDEX_LEN = 4;
45constexpr int32_t MAX_FILE_NUM = 10;
46constexpr int32_t MAX_FILE_SIZE = 10 * 1024 * 1024;
47constexpr int32_t MAX_TIME_LEN = 20;
48constexpr int32_t TIME_FORMAT_1 = 1;
49constexpr int32_t TIME_FORMAT_2 = 2;
50constexpr int32_t COMPRESS_READ_BUF_SIZE = 4096;
51constexpr int32_t DEFAULT_WIDTH = 20;
52constexpr int32_t DEFAULT_INTERVAL = 5000;
53constexpr int32_t MIN_INTERVAL = 100;
54const std::string TIMESTAMP_TITLE = "timestamp";
55const std::string THERMAL_LOG_ENABLE = "persist.thermal.log.enable";
56const std::string THERMAL_LOG_WIDTH = "persist.thermal.log.width";
57const std::string THERMAL_LOG_INTERVAL = "persist.thermal.log.interval";
58uint32_t g_currentLogIndex = 0;
59bool g_firstCreate = true;
60std::deque<std::string> g_saveLogFile;
61std::string g_outPath = "";
62std::string g_logTime = "";
63}
64
65std::shared_ptr<ThermalDfx> ThermalDfx::instance_ = nullptr;
66std::mutex ThermalDfx::mutexInstance_;
67
68ThermalDfx& ThermalDfx::GetInstance()
69{
70    std::lock_guard<std::mutex> lock(mutexInstance_);
71    if (instance_ == nullptr) {
72        instance_ = std::make_shared<ThermalDfx>();
73    }
74    return *(instance_.get());
75}
76
77void ThermalDfx::DestroyInstance()
78{
79    std::lock_guard<std::mutex> lock(mutexInstance_);
80    instance_ = nullptr;
81}
82
83static std::string GetCurrentTime(const int32_t format)
84{
85    struct tm* pTime;
86    char strTime[MAX_TIME_LEN] = {0};
87    time_t t;
88    if (time(&t) == -1) {
89        THERMAL_HILOGW(COMP_HDI, "call time failed");
90        return "";
91    }
92
93    pTime = localtime(&t);
94    if (pTime == nullptr) {
95        THERMAL_HILOGW(COMP_HDI, "pTime Get localtime failed");
96        return "";
97    }
98    if (format == TIME_FORMAT_1) {
99        if (strftime(strTime, sizeof(strTime), "%Y%m%d-%H%M%S", pTime) == 0U) {
100            THERMAL_HILOGW(COMP_HDI, "call strfime failed");
101            return "";
102        }
103    } else if (format == TIME_FORMAT_2) {
104        if (strftime(strTime, sizeof(strTime), "%Y-%m-%d %H:%M:%S", pTime) == 0U) {
105            THERMAL_HILOGW(COMP_HDI, "call strfime failed");
106            return "";
107        }
108    } else {
109        THERMAL_HILOGW(COMP_HDI, "invalid format value");
110        return "";
111    }
112    return strTime;
113}
114
115ThermalDfx::ThermalDfx() :
116    width_(static_cast<uint8_t>(DEFAULT_WIDTH)), interval_(static_cast<uint32_t>(DEFAULT_INTERVAL)), enable_(true)
117{
118}
119
120ThermalDfx::~ThermalDfx()
121{
122    enable_ = false;
123}
124
125std::string ThermalDfx::GetFileNameIndex(const uint32_t index)
126{
127    char res[LOG_INDEX_LEN];
128    (void)snprintf_s(res, sizeof(res), sizeof(res) - 1, "%03d", index % MAX_FILE_NUM);
129    std::string fileNameIndex(res);
130    return fileNameIndex;
131}
132
133std::string ThermalDfx::CanonicalizeSpecPath(const char* src)
134{
135    if (src == nullptr || strlen(src) >= PATH_MAX) {
136        fprintf(stderr, "Error: CanonicalizeSpecPath %s failed", src);
137        return "";
138    }
139    char resolvedPath[PATH_MAX] = { 0 };
140    if (access(src, F_OK) == 0) {
141        if (realpath(src, resolvedPath) == nullptr) {
142            fprintf(stderr, "Error: realpath %s failed", src);
143            return "";
144        }
145    } else {
146        std::string fileName(src);
147        if (fileName.find("..") == std::string::npos) {
148            if (snprintf_s(resolvedPath, PATH_MAX, sizeof(resolvedPath) - 1, src) == -1) {
149                fprintf(stderr, "Error: sprintf_s %s failed", src);
150                return "";
151            }
152        } else {
153            fprintf(stderr, "Error: find .. %s failed", src);
154            return "";
155        }
156    }
157
158    std::string res(resolvedPath);
159    return res;
160}
161
162bool ThermalDfx::Compress(const std::string& dataFile, const std::string& destFile)
163{
164    std::string resolvedPath = CanonicalizeSpecPath(dataFile.c_str());
165    FILE* fp = fopen(resolvedPath.c_str(), "rb");
166    if (fp == nullptr) {
167        THERMAL_HILOGE(COMP_HDI, "Fail to open data file %{public}s", dataFile.c_str());
168        perror("Fail to fopen(rb)");
169        return false;
170    }
171
172    std::unique_ptr<gzFile_s, decltype(&gzclose)> fgz(gzopen(destFile.c_str(), "wb"), gzclose);
173    if (fgz == nullptr) {
174        THERMAL_HILOGE(COMP_HDI, "Fail to call gzopen(%{public}s)", destFile.c_str());
175        fclose(fp);
176        return false;
177    }
178
179    std::vector<char> buf(COMPRESS_READ_BUF_SIZE);
180    size_t len = 0;
181    while ((len = fread(buf.data(), sizeof(uint8_t), buf.size(), fp))) {
182        if (gzwrite(fgz.get(), buf.data(), len) == 0) {
183            THERMAL_HILOGE(COMP_HDI, "Fail to call gzwrite for %{public}zu bytes", len);
184            fclose(fp);
185            return false;
186        }
187    }
188    if (!feof(fp)) {
189        if (ferror(fp) != 0) {
190            THERMAL_HILOGE(COMP_HDI, "ferror return err");
191            fclose(fp);
192            return false;
193        }
194    }
195    if (fclose(fp) < 0) {
196        return false;
197    }
198    return true;
199}
200
201void ThermalDfx::CompressFile()
202{
203    ThermalHitrace trace("ThermalDfx_CompressFile");
204    THERMAL_HILOGI(COMP_HDI, "CompressFile start");
205    std::string unCompressFile = g_outPath + "/" + "thermal." + GetFileNameIndex(g_currentLogIndex) + "." + g_logTime;
206
207    FILE* fp = fopen(unCompressFile.c_str(), "rb");
208    if (fp == nullptr) {
209        THERMAL_HILOGE(COMP_HDI, "open uncompressfile failed");
210        return;
211    }
212
213    if (fseek(fp, SEEK_SET, SEEK_END) != 0) {
214        THERMAL_HILOGE(COMP_HDI, "fseek() failed");
215        fclose(fp);
216        return;
217    }
218
219    unsigned long size = ftell(fp);
220    if (size < MAX_FILE_SIZE) {
221        if (fclose(fp) < 0) {
222            THERMAL_HILOGW(COMP_HDI, "fclose() failed");
223        }
224        THERMAL_HILOGI(COMP_HDI, "file is not enough for compress");
225        return;
226    }
227    if (fclose(fp) < 0) {
228        THERMAL_HILOGW(COMP_HDI, "fclose() failed");
229    }
230
231    std::string compressFile =
232        g_outPath + "/" + "thermal." + GetFileNameIndex(g_currentLogIndex) + "." + g_logTime + ".gz";
233    if (!Compress(unCompressFile, compressFile)) {
234        THERMAL_HILOGE(COMP_HDI, "CompressFile fail");
235        return;
236    }
237
238    if (remove(unCompressFile.c_str()) != 0) {
239        THERMAL_HILOGW(COMP_HDI, "failed to remove file %{public}s", unCompressFile.c_str());
240    }
241
242    if (g_saveLogFile.size() >= MAX_FILE_NUM) {
243        if (remove(g_saveLogFile.front().c_str()) != 0) {
244            THERMAL_HILOGW(COMP_HDI, "failed to remove file %{public}s", compressFile.c_str());
245        }
246        g_saveLogFile.pop_front();
247    }
248    g_saveLogFile.push_back(compressFile);
249    g_currentLogIndex++;
250    g_logTime = GetCurrentTime(TIME_FORMAT_1);
251    THERMAL_HILOGI(COMP_HDI, "CompressFile done");
252}
253
254bool ThermalDfx::PrepareWriteDfxLog()
255{
256    if (g_outPath == "") {
257        THERMAL_HILOGW(COMP_HDI, "parse thermal_hdi_config.xml outpath fail");
258        return false;
259    }
260    if (!enable_) {
261        THERMAL_HILOGI(COMP_HDI, "param does not start recording");
262        return false;
263    }
264
265    return true;
266}
267
268void ThermalDfx::CreateLogFile()
269{
270    ThermalHitrace trace("ThermalDfx_CreateLogFile");
271    THERMAL_HILOGI(COMP_HDI, "CreateLogFile start");
272    if (!PrepareWriteDfxLog()) {
273        THERMAL_HILOGD(COMP_HDI, "prepare write dfx log failed");
274        return;
275    }
276    if (g_firstCreate) {
277        g_currentLogIndex = 0;
278        g_logTime = GetCurrentTime(TIME_FORMAT_1);
279        g_firstCreate = false;
280    }
281    std::string logFile = g_outPath + "/" + "thermal." + GetFileNameIndex(g_currentLogIndex) +
282        "." + g_logTime;
283    if (access(g_outPath.c_str(), 0) == -1) {
284        auto ret = ForceCreateDirectory(g_outPath.c_str());
285        if (!ret) {
286            THERMAL_HILOGE(COMP_HDI, "create output dir failed");
287            return;
288        }
289    }
290
291    bool isEmpty = false;
292    std::ifstream fin(logFile);
293    std::fstream file;
294    file.open(logFile, std::ios::in);
295    if (file.eof() || !fin) {
296        isEmpty = true;
297    }
298    file.close();
299
300    ProcessLogInfo(logFile, isEmpty);
301    THERMAL_HILOGI(COMP_HDI, "CreateLogFile done");
302}
303
304void ThermalDfx::ProcessLogInfo(std::string& logFile, bool isEmpty)
305{
306    std::string currentTime = GetCurrentTime(TIME_FORMAT_2);
307    std::ofstream wStream(logFile, std::ios::app);
308    if (wStream.is_open()) {
309        if (isEmpty) {
310            WriteToEmptyFile(wStream, currentTime);
311            return;
312        }
313
314        WriteToFile(wStream, currentTime);
315        wStream.close();
316    }
317}
318
319void ThermalDfx::WriteToEmptyFile(std::ofstream& wStream, std::string& currentTime)
320{
321    wStream << TIMESTAMP_TITLE;
322    for (uint8_t i = 0; i < width_; ++i) {
323        wStream << " ";
324    }
325    std::vector<DfxTraceInfo> logInfo = ThermalHdfConfig::GetInstance().GetTracingInfo();
326    for (const auto& info : logInfo) {
327        wStream << info.title;
328        if (info.valuePath == logInfo.back().valuePath && info.title == logInfo.back().title) {
329            break;
330        }
331        for (uint8_t i = 0; i < width_ - info.title.length(); ++i) {
332            wStream << " ";
333        }
334    }
335    wStream << "\n";
336
337    WriteToFile(wStream, currentTime);
338    wStream.close();
339}
340
341void ThermalDfx::WriteToFile(std::ofstream& wStream, std::string& currentTime)
342{
343    wStream << currentTime;
344    for (uint8_t i = 0; i < width_ + TIMESTAMP_TITLE.length() - currentTime.length(); ++i) {
345        wStream << " ";
346    }
347    std::vector<DfxTraceInfo>& logInfo = ThermalHdfConfig::GetInstance().GetTracingInfo();
348    std::string value;
349    for (const auto& info : logInfo) {
350        if (!ThermalHdfUtils::ReadNode(info.valuePath, value)) {
351            THERMAL_HILOGW(COMP_HDI, "Read node failed, title = %{public}s", info.title.c_str());
352        }
353        wStream << value;
354        if (info.valuePath == logInfo.back().valuePath && info.title == logInfo.back().title) {
355            break;
356        }
357        for (uint8_t i = 0; i < width_ - value.length(); ++i) {
358            wStream << " ";
359        }
360    }
361    wStream << "\n";
362}
363
364void ThermalDfx::InfoChangedCallback(const char* key, const char* value, void* context)
365{
366    if (key == nullptr || value == nullptr) {
367        return;
368    }
369    std::string keyStr(key);
370    std::string valueStr(value);
371    THERMAL_HILOGI(COMP_HDI, "thermal log param change, key = %{public}s, value = %{public}s", keyStr.c_str(),
372        valueStr.c_str());
373    auto& thermalDfx = ThermalDfx::GetInstance();
374    if (keyStr == THERMAL_LOG_ENABLE) {
375        thermalDfx.EnableWatchCallback(valueStr);
376    }
377    if (keyStr == THERMAL_LOG_WIDTH) {
378        thermalDfx.WidthWatchCallback(valueStr);
379    }
380    if (keyStr == THERMAL_LOG_INTERVAL) {
381        thermalDfx.IntervalWatchCallback(valueStr);
382    }
383}
384
385void ThermalDfx::WidthWatchCallback(const std::string& value)
386{
387    int32_t width = OHOS::StrToInt(value, width) ? width : DEFAULT_WIDTH;
388    width_ = static_cast<uint8_t>((width < DEFAULT_WIDTH) ? DEFAULT_WIDTH : width);
389}
390
391void ThermalDfx::IntervalWatchCallback(const std::string& value)
392{
393    int32_t interval = OHOS::StrToInt(value, interval) ? interval : DEFAULT_INTERVAL;
394    interval_ = static_cast<uint32_t>((interval < MIN_INTERVAL) ? MIN_INTERVAL : interval);
395}
396
397void ThermalDfx::EnableWatchCallback(const std::string& value)
398{
399    enable_ = (value == "true");
400}
401
402int32_t ThermalDfx::GetIntParameter(const std::string& key, const int32_t def, const int32_t minValue)
403{
404    int32_t value = OHOS::system::GetIntParameter(key, def);
405    return (value < minValue) ? def : value;
406}
407
408bool ThermalDfx::GetBoolParameter(const std::string& key, const bool def)
409{
410    std::string value;
411    if (OHOS::system::GetStringParameter(THERMAL_LOG_ENABLE, value) != 0) {
412        return def;
413    }
414    return (value == "true");
415}
416
417uint32_t ThermalDfx::GetInterval()
418{
419    return interval_;
420}
421
422void ThermalDfx::DoWork()
423{
424    if (enable_) {
425        CreateLogFile();
426        CompressFile();
427    }
428}
429
430void ThermalDfx::Init()
431{
432    interval_ = static_cast<uint32_t>(GetIntParameter(THERMAL_LOG_INTERVAL, DEFAULT_INTERVAL, MIN_INTERVAL));
433    width_ = static_cast<uint8_t>(GetIntParameter(THERMAL_LOG_WIDTH, DEFAULT_WIDTH, DEFAULT_WIDTH));
434    enable_ = GetBoolParameter(THERMAL_LOG_ENABLE, true);
435    THERMAL_HILOGI(COMP_HDI,
436        "The thermal log param is init, interval_ = %{public}d, width = %{public}d, enable = %{public}d",
437        interval_.load(), width_.load(), enable_.load());
438
439    WatchParameter(THERMAL_LOG_ENABLE.c_str(), InfoChangedCallback, nullptr);
440    WatchParameter(THERMAL_LOG_WIDTH.c_str(), InfoChangedCallback, nullptr);
441    int32_t code = WatchParameter(THERMAL_LOG_INTERVAL.c_str(), InfoChangedCallback, nullptr);
442    if (code != OHOSStartUpSysParamErrorCode::EC_SUCCESS) {
443        THERMAL_HILOGW(COMP_HDI, "thermal log watch parameters failed. error = %{public}d", code);
444    }
445
446    XmlTraceConfig& config = ThermalHdfConfig::GetInstance().GetXmlTraceConfig();
447    g_outPath = config.outPath;
448}
449} // V1_1
450} // Thermal
451} // HDI
452} // OHOS
453