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
16#include <cerrno>
17#include <cstdio>
18#include <fstream>
19#include <memory>
20#include <string>
21#include <sys/stat.h>
22
23#include "ecmascript/log_wrapper.h"
24#include "ecmascript/mem/c_string.h"
25#include "ecmascript/ohos/enable_aot_list_helper.h"
26#include "ecmascript/pgo_profiler/ap_file/pgo_file_info.h"
27#include "ecmascript/pgo_profiler/pgo_profiler_decoder.h"
28#include "ecmascript/pgo_profiler/pgo_profiler_encoder.h"
29#include "ecmascript/pgo_profiler/pgo_profiler_manager.h"
30#include "ecmascript/pgo_profiler/pgo_trace.h"
31#include "ecmascript/pgo_profiler/pgo_utils.h"
32#include "ecmascript/platform/file.h"
33#include "ecmascript/platform/mutex.h"
34#include "ecmascript/platform/os.h"
35
36namespace panda::ecmascript::pgo {
37void PGOProfilerEncoder::Destroy()
38{
39    LockHolder lock(mutex_);
40    {
41        WriteLockHolder rwLock(rwLock_);
42        pandaFileInfos_->Clear();
43        abcFilePool_->Clear();
44    }
45    if (!isProfilingInitialized_) {
46        return;
47    }
48    PGOProfilerHeader::Destroy(&header_);
49    globalRecordInfos_->Clear();
50    globalRecordInfos_.reset();
51    isProfilingInitialized_ = false;
52}
53
54bool PGOProfilerEncoder::ResetOutPathByModuleName(const std::string &moduleName)
55{
56    LockHolder lock(mutex_);
57    // only first assign takes effect
58    if (!moduleName_.empty() || moduleName.empty()) {
59        return false;
60    }
61    moduleName_ = moduleName;
62    return ResetOutPath(ApNameUtils::GetRuntimeApName(moduleName_));
63}
64
65bool PGOProfilerEncoder::ResetOutPath(const std::string &profileFileName)
66{
67    if (!RealPath(outDir_, realOutPath_, false)) {
68        return false;
69    }
70
71    auto suffixLength = ApNameUtils::AP_SUFFIX.length();
72    if (realOutPath_.compare(realOutPath_.length() - suffixLength, suffixLength, ApNameUtils::AP_SUFFIX)) {
73        realOutPath_ += "/" + profileFileName;
74    }
75
76    SetSecurityLabel(realOutPath_);
77
78    LOG_ECMA(INFO) << "Save profiler to file:" << realOutPath_;
79    return true;
80}
81
82bool PGOProfilerEncoder::InitializeData()
83{
84    LockHolder lock(mutex_);
85    if (!isProfilingInitialized_) {
86        if (!ResetOutPath(ApNameUtils::DEFAULT_AP_NAME)) {
87            return false;
88        }
89        PGOProfilerHeader::Build(&header_, PGOProfilerHeader::LastSize());
90        globalRecordInfos_ = std::make_shared<PGORecordDetailInfos>(hotnessThreshold_);
91        isProfilingInitialized_ = true;
92    }
93    return true;
94}
95
96void PGOProfilerEncoder::SamplePandaFileInfo(uint32_t checksum, const CString &abcName)
97{
98    WriteLockHolder lock(rwLock_);
99    pandaFileInfos_->Sample(checksum);
100    ApEntityId entryId(0);
101    abcFilePool_->TryAdd(abcName, entryId);
102}
103
104bool PGOProfilerEncoder::GetPandaFileId(const CString &abcName, ApEntityId &entryId)
105{
106    ReadLockHolder lock(rwLock_);
107    return abcFilePool_->GetEntryId(abcName, entryId);
108}
109
110bool PGOProfilerEncoder::GetPandaFileDesc(ApEntityId abcId, CString &desc)
111{
112    if (!isProfilingInitialized_) {
113        return false;
114    }
115    ReadLockHolder lock(rwLock_);
116    const auto *entry = abcFilePool_->GetEntry(abcId);
117    if (entry == nullptr) {
118        return false;
119    }
120    desc = entry->GetData();
121    return true;
122}
123
124void PGOProfilerEncoder::Merge(const PGORecordDetailInfos &recordInfos)
125{
126    if (!isProfilingInitialized_) {
127        return;
128    }
129    LockHolder lock(mutex_);
130    globalRecordInfos_->Merge(recordInfos);
131}
132
133void PGOProfilerEncoder::Merge(const PGOPandaFileInfos &pandaFileInfos)
134{
135    WriteLockHolder lock(rwLock_);
136    return pandaFileInfos_->Merge(pandaFileInfos);
137}
138
139void PGOProfilerEncoder::Merge(const PGOProfilerEncoder &encoder)
140{
141    Merge(*encoder.pandaFileInfos_);
142    Merge(*encoder.globalRecordInfos_);
143}
144
145bool PGOProfilerEncoder::VerifyPandaFileMatched(const PGOPandaFileInfos &pandaFileInfos, const std::string &base,
146                                                const std::string &incoming) const
147{
148    return pandaFileInfos_->VerifyChecksum(pandaFileInfos, base, incoming);
149}
150
151bool PGOProfilerEncoder::Save()
152{
153    if (!isProfilingInitialized_) {
154        return false;
155    }
156    LockHolder lock(mutex_);
157    return InternalSave();
158}
159
160void PGOProfilerEncoder::MergeWithExistProfile(PGOProfilerEncoder &runtimeEncoder, PGOProfilerDecoder &decoder,
161                                               const SaveTask *task)
162{
163    // inherit some info from runtime encoder
164    ASSERT(header_ != nullptr);
165    ASSERT(runtimeEncoder.header_ != nullptr);
166    header_->SetVersion(runtimeEncoder.header_->GetVersion());
167    bundleName_ = runtimeEncoder.bundleName_;
168    moduleName_ = runtimeEncoder.moduleName_;
169
170    // copy abcFilePool from runtime to temp merger.
171    ASSERT(abcFilePool_->GetPool()->Empty());
172    {
173        WriteLockHolder lock(rwLock_);
174        abcFilePool_->Copy(runtimeEncoder.abcFilePool_);
175    }
176    if (!decoder.LoadFull(abcFilePool_)) {
177        LOG_ECMA(ERROR) << "Fail to load ap: " << realOutPath_;
178    } else {
179        Merge(decoder.GetPandaFileInfos());
180        globalRecordInfos_ = decoder.GetRecordDetailInfosPtr();
181    }
182    if (task && task->IsTerminate()) {
183        LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
184        return;
185    }
186    Merge(*runtimeEncoder.pandaFileInfos_);
187    if (task && task->IsTerminate()) {
188        LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
189        return;
190    }
191    Merge(*runtimeEncoder.globalRecordInfos_);
192}
193
194bool PGOProfilerEncoder::SaveAndRename(const SaveTask *task)
195{
196    ClockScope start;
197    umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
198    static const char *tempSuffix = ".tmp";
199    auto tmpOutPath = realOutPath_ + "." + std::to_string(getpid()) + tempSuffix;
200    std::fstream fileStream(tmpOutPath.c_str(),
201                            std::fstream::binary | std::fstream::out | std::fstream::in | std::fstream::trunc);
202    if (!fileStream.is_open()) {
203        LOG_ECMA(ERROR) << "The file path(" << tmpOutPath << ") open failure! errno: " << errno;
204        return false;
205    }
206    if (header_ == nullptr) {
207        LOG_ECMA(FATAL) << "PGOProfilerEncoder::SaveAndRename:header_ is nullptr";
208    }
209    pandaFileInfos_->ProcessToBinary(fileStream, header_->GetPandaInfoSection());
210    globalRecordInfos_->ProcessToBinary(task, fileStream, header_);
211    {
212        ReadLockHolder lock(rwLock_);
213        PGOFileSectionInterface::ProcessSectionToBinary(*globalRecordInfos_, fileStream, header_,
214                                                        *abcFilePool_->GetPool());
215    }
216    header_->SetFileSize(static_cast<uint32_t>(fileStream.tellp()));
217    header_->SetCompatibleAnVersion(AOTFileVersion::AN_VERSION);
218    header_->ProcessToBinary(fileStream);
219    if (header_->SupportFileConsistency()) {
220        AddChecksum(fileStream);
221    }
222    fileStream.close();
223    if (task && task->IsTerminate()) {
224        LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
225        return false;
226    }
227    if (FileExist(realOutPath_.c_str()) && remove(realOutPath_.c_str())) {
228        LOG_ECMA(ERROR) << "Remove " << realOutPath_ << " failure!, errno: " << errno;
229        return false;
230    }
231    if (rename(tmpOutPath.c_str(), realOutPath_.c_str())) {
232        LOG_ECMA(ERROR) << "Rename " << tmpOutPath << " --> " << realOutPath_ << " failure!, errno: " << errno;
233        return false;
234    }
235    RequestAot();
236    if (PGOTrace::GetInstance()->IsEnable()) {
237        PGOTrace::GetInstance()->SetSaveTime(start.TotalSpentTime());
238        PGOTrace::GetInstance()->Print();
239    }
240    return true;
241}
242
243void PGOProfilerEncoder::RequestAot()
244{
245    if (bundleName_.empty() || moduleName_.empty()) {
246        return;
247    }
248
249    LOG_ECMA(INFO) << "Request local aot, bundle: " << bundleName_ << ", module: " << moduleName_;
250    if (!PGOProfilerManager::GetInstance()->RequestAot(bundleName_, moduleName_, RequestAotMode::RE_COMPILE_ON_IDLE)) {
251        LOG_ECMA(ERROR) << "Request aot failed, bundle: " << bundleName_ << ", module: " << moduleName_;
252    }
253}
254
255bool PGOProfilerEncoder::InternalSave(const SaveTask *task)
256{
257    ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "PGOProfilerEncoder::InternalSave");
258    if (!isProfilingInitialized_) {
259        return false;
260    }
261    if ((mode_ == MERGE) && FileExist(realOutPath_.c_str())) {
262        PGOProfilerEncoder encoder(realOutPath_, hotnessThreshold_, mode_);
263        encoder.InitializeData();
264        PGOProfilerDecoder decoder(realOutPath_, hotnessThreshold_);
265        {
266            ClockScope start;
267            encoder.MergeWithExistProfile(*this, decoder, task);
268            if (PGOTrace::GetInstance()->IsEnable()) {
269                PGOTrace::GetInstance()->SetMergeWithExistProfileTime(start.TotalSpentTime());
270            }
271        }
272        auto saveAndRenameResult = encoder.SaveAndRename(task);
273        encoder.Destroy();
274        return saveAndRenameResult;
275    }
276    return SaveAndRename(task);
277}
278
279void PGOProfilerEncoder::AddChecksum(std::fstream &fileStream)
280{
281    static constexpr uint32_t KILO_BYTES = 1024;
282    static constexpr uint32_t STEP_IN_KB = 256;
283    static constexpr uint32_t STEP_SIZE = STEP_IN_KB * KILO_BYTES;
284    uint32_t size = static_cast<uint32_t>(fileStream.seekp(0, std::fstream::end).tellp());
285    std::unique_ptr<std::vector<uint8_t>> buffer = std::make_unique<std::vector<uint8_t>>(STEP_SIZE);
286    // first, calculate the version field's checksum.
287    fileStream.seekg(PGOProfilerHeader::MAGIC_SIZE, std::fstream::beg)
288        .read(reinterpret_cast<char *>(buffer->data()), PGOProfilerHeader::VERSION_SIZE);
289    uint32_t checksum = adler32(0, reinterpret_cast<const Bytef *>(buffer->data()), PGOProfilerHeader::VERSION_SIZE);
290    // second, calculate the checksum for remaining content(exclude checksum field).
291    uint32_t remainingSize = size - PGOProfilerHeader::CHECKSUM_END_OFFSET;
292    fileStream.seekg(PGOProfilerHeader::CHECKSUM_END_OFFSET);
293    while (remainingSize > 0) {
294        uint32_t readSize = std::min(STEP_SIZE, remainingSize);
295        remainingSize = remainingSize - readSize;
296        fileStream.read(reinterpret_cast<char *>(buffer->data()), readSize);
297        checksum = adler32(checksum, reinterpret_cast<const Bytef *>(buffer->data()), readSize);
298    }
299    // third, write the checksum back to the checksum field in the output stream.
300    fileStream.seekp(PGOProfilerHeader::MAGIC_SIZE + PGOProfilerHeader::VERSION_SIZE, std::fstream::beg);
301    fileStream.write(reinterpret_cast<char *>(&checksum), sizeof(checksum));
302}
303
304void PGOProfilerEncoder::TerminateSaveTask()
305{
306    if (!isProfilingInitialized_) {
307        return;
308    }
309    Taskpool::GetCurrentTaskpool()->TerminateTask(GLOBAL_TASK_ID, TaskType::PGO_SAVE_TASK);
310}
311
312void PGOProfilerEncoder::PostSaveTask()
313{
314    if (!isProfilingInitialized_) {
315        return;
316    }
317    Taskpool::GetCurrentTaskpool()->PostTask(std::make_unique<SaveTask>(this, GLOBAL_TASK_ID));
318}
319
320void PGOProfilerEncoder::PostResetOutPathTask(const std::string &moduleName)
321{
322    if (moduleName.empty()) {
323        LOG_ECMA(ERROR) << "PostSetModuleNameTask: moduleName is empty.";
324        return;
325    }
326    // only post moduleName once
327    bool hasPost = false;
328    if (!hasPostModuleName_.compare_exchange_strong(hasPost, true)) {
329        return;
330    }
331    Taskpool::GetCurrentTaskpool()->PostTask(std::make_unique<ResetOutPathTask>(this, moduleName, GLOBAL_TASK_ID));
332}
333
334void PGOProfilerEncoder::StartSaveTask(const SaveTask *task)
335{
336    if (task == nullptr) {
337        return;
338    }
339    if (task->IsTerminate()) {
340        LOG_ECMA(ERROR) << "StartSaveTask: task is already terminate";
341        return;
342    }
343    LockHolder lock(mutex_);
344    InternalSave(task);
345}
346
347bool PGOProfilerEncoder::LoadAPTextFile(const std::string &inPath)
348{
349    if (!isProfilingInitialized_) {
350        return false;
351    }
352    std::string realPath;
353    if (!RealPath(inPath, realPath)) {
354        return false;
355    }
356
357    std::ifstream fileStream(realPath.c_str());
358    if (!fileStream.is_open()) {
359        LOG_ECMA(ERROR) << "The file path(" << realOutPath_ << ") open failure!";
360        return false;
361    }
362
363    if (!header_->ParseFromText(fileStream)) {
364        LOG_ECMA(ERROR) << "header format error";
365        return false;
366    }
367    if (!pandaFileInfos_->ParseFromText(fileStream)) {
368        LOG_ECMA(ERROR) << "panda file info format error";
369        return false;
370    }
371    if (!globalRecordInfos_->ParseFromText(fileStream)) {
372        LOG_ECMA(ERROR) << "record info format error";
373        return false;
374    }
375
376    return true;
377}
378} // namespace panda::ecmascript::pgo
379