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
22 #include "ecmascript/log_wrapper.h"
23 #include "ecmascript/pgo_profiler/pgo_profiler_decoder.h"
24 #include "ecmascript/pgo_profiler/pgo_profiler_encoder.h"
25
26 #include "ecmascript/platform/file.h"
27 #include "zlib.h"
28
29 namespace panda::ecmascript {
30 static const std::string PROFILE_FILE_NAME = "/modules.ap";
Destroy()31 void PGOProfilerEncoder::Destroy()
32 {
33 if (!isInitialized_) {
34 return;
35 }
36 PGOProfilerHeader::Destroy(&header_);
37 pandaFileInfos_.reset();
38 globalRecordInfos_->Clear();
39 globalRecordInfos_.reset();
40 isInitialized_ = false;
41 }
42
InitializeData()43 bool PGOProfilerEncoder::InitializeData()
44 {
45 if (!isInitialized_) {
46 if (!RealPath(outDir_, realOutPath_, false)) {
47 return false;
48 }
49
50 static const std::string endString = ".ap";
51 if (realOutPath_.compare(realOutPath_.length() - endString.length(), endString.length(), endString)) {
52 realOutPath_ += PROFILE_FILE_NAME;
53 }
54 LOG_ECMA(INFO) << "Save profiler to file:" << realOutPath_;
55 PGOProfilerHeader::Build(&header_, PGOProfilerHeader::LastSize());
56 pandaFileInfos_ = std::make_unique<PGOPandaFileInfos>();
57 globalRecordInfos_ = std::make_shared<PGORecordDetailInfos>(hotnessThreshold_);
58 isInitialized_ = true;
59 }
60 return true;
61 }
62
SamplePandaFileInfo(uint32_t checksum)63 void PGOProfilerEncoder::SamplePandaFileInfo(uint32_t checksum)
64 {
65 if (!isInitialized_) {
66 return;
67 }
68 pandaFileInfos_->Sample(checksum);
69 }
70
Merge(const PGORecordDetailInfos & recordInfos)71 void PGOProfilerEncoder::Merge(const PGORecordDetailInfos &recordInfos)
72 {
73 if (!isInitialized_) {
74 return;
75 }
76 os::memory::LockHolder lock(mutex_);
77 globalRecordInfos_->Merge(recordInfos);
78 }
79
Merge(const PGOPandaFileInfos & pandaFileInfos)80 void PGOProfilerEncoder::Merge(const PGOPandaFileInfos &pandaFileInfos)
81 {
82 return pandaFileInfos_->Merge(pandaFileInfos);
83 }
84
VerifyPandaFileMatched(const PGOPandaFileInfos & pandaFileInfos,const std::string & base,const std::string & incoming) const85 bool PGOProfilerEncoder::VerifyPandaFileMatched(const PGOPandaFileInfos &pandaFileInfos, const std::string &base,
86 const std::string &incoming) const
87 {
88 return pandaFileInfos_->VerifyChecksum(pandaFileInfos, base, incoming);
89 }
90
Save()91 bool PGOProfilerEncoder::Save()
92 {
93 if (!isInitialized_) {
94 return false;
95 }
96 os::memory::LockHolder lock(mutex_);
97 return InternalSave();
98 }
99
MergeWithExistProfile(PGOProfilerEncoder & encoder,PGOProfilerDecoder & decoder,const SaveTask * task)100 void PGOProfilerEncoder::MergeWithExistProfile(PGOProfilerEncoder &encoder, PGOProfilerDecoder &decoder,
101 const SaveTask *task)
102 {
103 if (!decoder.LoadFull()) {
104 LOG_ECMA(ERROR) << "Fail to load ap: " << realOutPath_;
105 } else {
106 Merge(decoder.GetPandaFileInfos());
107 globalRecordInfos_ = decoder.GetRecordDetailInfosPtr();
108 }
109 if (task && task->IsTerminate()) {
110 LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
111 return;
112 }
113 Merge(*encoder.pandaFileInfos_);
114 if (task && task->IsTerminate()) {
115 LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
116 return;
117 }
118 Merge(*encoder.globalRecordInfos_);
119 }
120
SaveAndRename(const SaveTask * task)121 bool PGOProfilerEncoder::SaveAndRename(const SaveTask *task)
122 {
123 static const char *tempSuffix = ".tmp";
124 auto tmpOutPath = realOutPath_ + "." + std::to_string(getpid()) + "." + tempSuffix;
125 std::fstream fileStream(tmpOutPath.c_str(),
126 std::fstream::binary | std::fstream::out | std::fstream::in | std::fstream::trunc);
127 if (!fileStream.is_open()) {
128 LOG_ECMA(ERROR) << "The file path(" << tmpOutPath << ") open failure! errno: " << errno;
129 return false;
130 }
131 pandaFileInfos_->ProcessToBinary(fileStream, header_->GetPandaInfoSection());
132 globalRecordInfos_->ProcessToBinary(task, fileStream, header_);
133 header_->ProcessToBinary(fileStream);
134 if (header_->SupportFileConsistency()) {
135 AddChecksum(fileStream);
136 }
137 fileStream.close();
138 if (task && task->IsTerminate()) {
139 LOG_ECMA(DEBUG) << "ProcessProfile: task is already terminate";
140 return false;
141 }
142 if (FileExist(realOutPath_.c_str()) && remove(realOutPath_.c_str())) {
143 LOG_ECMA(ERROR) << "Remove " << realOutPath_ << " failure!, errno: " << errno;
144 return false;
145 }
146 if (rename(tmpOutPath.c_str(), realOutPath_.c_str())) {
147 LOG_ECMA(ERROR) << "Rename " << tmpOutPath << " --> " << realOutPath_ << " failure!, errno: " << errno;
148 return false;
149 }
150 return true;
151 }
152
InternalSave(const SaveTask * task)153 bool PGOProfilerEncoder::InternalSave(const SaveTask *task)
154 {
155 ECMA_BYTRACE_NAME(HITRACE_TAG_ARK, "PGOProfilerEncoder::InternalSave");
156 if (!isInitialized_) {
157 return false;
158 }
159 if ((mode_ == MERGE) && FileExist(realOutPath_.c_str())) {
160 PGOProfilerEncoder encoder(realOutPath_, hotnessThreshold_, mode_);
161 encoder.InitializeData();
162 PGOProfilerDecoder decoder(realOutPath_, hotnessThreshold_);
163 encoder.MergeWithExistProfile(*this, decoder, task);
164 return encoder.SaveAndRename(task);
165 }
166 return SaveAndRename(task);
167 }
168
AddChecksum(std::fstream & fileStream)169 void PGOProfilerEncoder::AddChecksum(std::fstream &fileStream)
170 {
171 static constexpr uint32_t KILO_BYTES = 1024;
172 static constexpr uint32_t STEP_IN_KB = 256;
173 static constexpr uint32_t STEP_SIZE = STEP_IN_KB * KILO_BYTES;
174 uint32_t size = static_cast<uint32_t>(fileStream.seekp(0, std::fstream::end).tellp());
175 std::unique_ptr<std::vector<uint8_t>> buffer = std::make_unique<std::vector<uint8_t>>(STEP_SIZE);
176 // first, calculate the version field's checksum.
177 fileStream.seekg(PGOProfilerHeader::MAGIC_SIZE, std::fstream::beg)
178 .read(reinterpret_cast<char *>(buffer->data()), PGOProfilerHeader::VERSION_SIZE);
179 uint32_t checksum = adler32(0, reinterpret_cast<const Bytef *>(buffer->data()), PGOProfilerHeader::VERSION_SIZE);
180 // second, calculate the checksum for remaining content(exclude checksum field).
181 uint32_t remainingSize = size - PGOProfilerHeader::CHECKSUM_END_OFFSET;
182 fileStream.seekg(PGOProfilerHeader::CHECKSUM_END_OFFSET);
183 while (remainingSize > 0) {
184 uint32_t readSize = std::min(STEP_SIZE, remainingSize);
185 remainingSize = remainingSize - readSize;
186 fileStream.read(reinterpret_cast<char *>(buffer->data()), readSize);
187 checksum = adler32(checksum, reinterpret_cast<const Bytef *>(buffer->data()), readSize);
188 }
189 // third, write the checksum back to the checksum field in the output stream.
190 fileStream.seekp(PGOProfilerHeader::MAGIC_SIZE + PGOProfilerHeader::VERSION_SIZE, std::fstream::beg);
191 fileStream.write(reinterpret_cast<char *>(&checksum), sizeof(checksum));
192 }
193
TerminateSaveTask()194 void PGOProfilerEncoder::TerminateSaveTask()
195 {
196 if (!isInitialized_) {
197 return;
198 }
199 Taskpool::GetCurrentTaskpool()->TerminateTask(GLOBAL_TASK_ID, TaskType::PGO_SAVE_TASK);
200 }
201
PostSaveTask()202 void PGOProfilerEncoder::PostSaveTask()
203 {
204 if (!isInitialized_) {
205 return;
206 }
207 Taskpool::GetCurrentTaskpool()->PostTask(std::make_unique<SaveTask>(this, GLOBAL_TASK_ID));
208 }
209
StartSaveTask(const SaveTask * task)210 void PGOProfilerEncoder::StartSaveTask(const SaveTask *task)
211 {
212 if (task == nullptr) {
213 return;
214 }
215 if (task->IsTerminate()) {
216 LOG_ECMA(ERROR) << "StartSaveTask: task is already terminate";
217 return;
218 }
219 os::memory::LockHolder lock(mutex_);
220 InternalSave(task);
221 }
222
LoadAPTextFile(const std::string & inPath)223 bool PGOProfilerEncoder::LoadAPTextFile(const std::string &inPath)
224 {
225 if (!isInitialized_) {
226 return false;
227 }
228 std::string realPath;
229 if (!RealPath(inPath, realPath)) {
230 return false;
231 }
232
233 std::ifstream fileStream(realPath.c_str());
234 if (!fileStream.is_open()) {
235 LOG_ECMA(ERROR) << "The file path(" << realOutPath_ << ") open failure!";
236 return false;
237 }
238
239 if (!header_->ParseFromText(fileStream)) {
240 LOG_ECMA(ERROR) << "header format error";
241 return false;
242 }
243 if (!pandaFileInfos_->ParseFromText(fileStream)) {
244 LOG_ECMA(ERROR) << "panda file info format error";
245 return false;
246 }
247 if (!globalRecordInfos_->ParseFromText(fileStream)) {
248 LOG_ECMA(ERROR) << "record info format error";
249 return false;
250 }
251
252 return true;
253 }
254 } // namespace panda::ecmascript
255