1 /*
2 * Copyright (c) 2021-2022 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 "perf_file_writer.h"
16
17 #include <cinttypes>
18 #include <cstdlib>
19 #include <unistd.h>
20
21 #include "utilities.h"
22 using namespace std::chrono;
23 namespace OHOS {
24 namespace Developtools {
25 namespace HiPerf {
~PerfFileWriter()26 PerfFileWriter::~PerfFileWriter()
27 {
28 // if file was not closed properly, remove it before exit
29 if (fp_ != nullptr) {
30 fclose(fp_);
31 fp_ = nullptr;
32 if (remove(fileName_.c_str()) != 0) {
33 HLOGE("fail to remove file(%s).", fileName_.c_str());
34 }
35 }
36 }
37
Open(const std::string & fileName,bool compressData)38 bool PerfFileWriter::Open(const std::string &fileName, bool compressData)
39 {
40 // check file existence, if exist, remove it
41 if (access(fileName.c_str(), F_OK) == 0) {
42 // file exists
43 if (remove(fileName.c_str()) != 0) {
44 char errInfo[ERRINFOLEN] = { 0 };
45 strerror_r(errno, errInfo, ERRINFOLEN);
46 printf("can't remove exist file(%s). %d:%s\n", fileName.c_str(), errno,
47 errInfo);
48 return false;
49 }
50 }
51 std::string resolvedPath = CanonicalizeSpecPath(fileName.c_str());
52 fp_ = fopen(resolvedPath.c_str(), "web+");
53 if (fp_ == nullptr) {
54 char errInfo[ERRINFOLEN] = { 0 };
55 strerror_r(errno, errInfo, ERRINFOLEN);
56 printf("can't create file(%s). %d:%s\n", fileName.c_str(), errno, errInfo);
57 return false;
58 }
59
60 fileName_ = fileName;
61 compressData_ = compressData;
62 attrSection_.offset = 0;
63 attrSection_.size = 0;
64 dataSection_ = attrSection_;
65 header_.size = sizeof(header_);
66 fileBuffer_.resize(WRITER_BUFFER_SIZE);
67 if (setvbuf(fp_, fileBuffer_.data(), _IOFBF, WRITER_BUFFER_SIZE) != 0) {
68 HLOGD("setvbuf failed");
69 }
70
71 return true;
72 }
73
Close()74 bool PerfFileWriter::Close()
75 {
76 HLOG_ASSERT(fp_ != nullptr);
77 bool rc = true;
78
79 if (!WriteHeader()) {
80 rc = false;
81 }
82 if (!WriteFeatureData()) {
83 rc = false;
84 }
85
86 if (fclose(fp_) != 0) {
87 HLOGD("fail to close fp ");
88 rc = false;
89 }
90 fp_ = nullptr;
91
92 // compress and rename .perf.data to gzip file
93 if (compressData_) {
94 std::string gzName = fileName_ + ".gz";
95 if (CompressFile(fileName_, gzName)) {
96 if (remove(fileName_.c_str()) != 0) {
97 char errInfo[ERRINFOLEN] = { 0 };
98 strerror_r(errno, errInfo, ERRINFOLEN);
99 printf("can't remove file(%s). %d:%s\n",
100 fileName_.c_str(), errno, errInfo);
101 }
102 if (rename(gzName.c_str(), fileName_.c_str()) != 0) {
103 char errInfo[ERRINFOLEN] = { 0 };
104 strerror_r(errno, errInfo, ERRINFOLEN);
105 printf("can't rename file(%s) to (%s). %d:%s\n", gzName.c_str(), fileName_.c_str(),
106 errno, errInfo);
107 }
108 } else {
109 char errInfo[ERRINFOLEN] = { 0 };
110 strerror_r(errno, errInfo, ERRINFOLEN);
111 printf("failed to compress file(%s). %d:%s\n", fileName_.c_str(), errno,
112 errInfo);
113 }
114 }
115
116 return rc;
117 }
118
WriteRecord(const PerfEventRecord & record)119 bool PerfFileWriter::WriteRecord(const PerfEventRecord &record)
120 {
121 if (!isWritingRecord) {
122 HLOGV("need write <attrs> first");
123 return false;
124 }
125
126 HLOGV("write '%s'", record.GetName().c_str());
127
128 if (record.GetSize() > RECORD_SIZE_LIMIT) {
129 HLOGD("%s record size exceed limit", record.GetName().c_str());
130 return false;
131 }
132 // signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0xb64eb195
133 static std::vector<u8> buf(RECORD_SIZE_LIMIT);
134
135 if (!record.GetBinary(buf)) {
136 return false;
137 }
138
139 if (!Write(buf.data(), record.GetSize())) {
140 return false;
141 }
142 dataSection_.size += record.GetSize();
143
144 ++recordCount_;
145
146 return true;
147 }
148
ReadDataSection(ProcessRecordCB & callback)149 bool PerfFileWriter::ReadDataSection(ProcessRecordCB &callback)
150 {
151 HLOG_ASSERT(fp_ != nullptr);
152 if (fseek(fp_, dataSection_.offset, SEEK_SET) != 0) {
153 HLOGE("fseek() failed");
154 return false;
155 }
156 HLOGD("dataSection_ at offset %" PRIu64 " + %" PRIu64 "", dataSection_.offset,
157 dataSection_.size);
158
159 return ReadRecords(callback);
160 }
161
ReadRecords(ProcessRecordCB & callback)162 bool PerfFileWriter::ReadRecords(ProcessRecordCB &callback)
163 {
164 // record size can not exceed 64K
165 HIPERF_BUF_ALIGN uint8_t buf[RECORD_SIZE_LIMIT];
166 // diff with reader
167 uint64_t remainingSize = dataSection_.size;
168 size_t recordNumber = 0;
169 while (remainingSize > 0) {
170 if (remainingSize < sizeof(perf_event_header)) {
171 HLOGW("not enough sizeof(perf_event_header).");
172 return false;
173 } else if (Read(buf, sizeof(perf_event_header)) <= 0) {
174 HLOGW("read perf_event_header failed.");
175 return false;
176 } else {
177 perf_event_header *header = reinterpret_cast<perf_event_header *>(buf);
178 HLOG_ASSERT(header->size < sizeof(buf));
179 if (remainingSize >= header->size) {
180 size_t headerSize = sizeof(perf_event_header);
181 if (Read(buf + headerSize, header->size - headerSize)) {
182 uint8_t *data = buf;
183 // the record is allowed from a cache memory, does not free memory after use
184 auto record = GetPerfSampleFromCacheMain(static_cast<perf_event_type>(header->type),
185 data, defaultEventAttr_);
186 // skip unknown record
187 if (record == nullptr)
188 return true;
189 remainingSize -= header->size;
190 // call callback to process, do not destroy the record
191 callback(std::move(record));
192 recordNumber++;
193 }
194 } else {
195 HLOGW("not enough header->size.");
196 }
197 }
198 }
199 HLOGD("read back %zu records", recordNumber);
200 return true;
201 }
202
Read(void * buf,size_t len)203 bool PerfFileWriter::Read(void *buf, size_t len)
204 {
205 HLOG_ASSERT(buf != nullptr);
206 HLOG_ASSERT(fp_ != nullptr);
207 HLOG_ASSERT(len > 0);
208
209 if (fread(buf, len, 1, fp_) != 1) {
210 HLOGD("failed to read file");
211 return false;
212 }
213 return true;
214 }
215
GetDataSize() const216 uint64_t PerfFileWriter::GetDataSize() const
217 {
218 return dataSection_.size;
219 }
220
GetRecordCount() const221 uint PerfFileWriter::GetRecordCount() const
222 {
223 return recordCount_;
224 }
225
GetFilePos(uint64_t & pos) const226 bool PerfFileWriter::GetFilePos(uint64_t &pos) const
227 {
228 off_t offset = ftello(fp_);
229 if (offset == -1) {
230 HLOGD("RecordFileWriter ftello fail");
231 return false;
232 }
233 pos = static_cast<uint64_t>(offset);
234 return true;
235 }
236
Write(const void * buf,size_t len)237 bool PerfFileWriter::Write(const void *buf, size_t len)
238 {
239 #ifdef HIPERF_DEBUG_TIME
240 const auto startTime = steady_clock::now();
241 #endif
242 if (len != 0u && fwrite(buf, len, 1, fp_) != 1) {
243 HLOGD("PerfFileWriter fwrite fail ");
244 return false;
245 }
246 #ifdef HIPERF_DEBUG_TIME
247 writeTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
248 #endif
249 return true;
250 }
251
WriteAttrAndId(const std::vector<AttrWithId> & attrIds)252 bool PerfFileWriter::WriteAttrAndId(const std::vector<AttrWithId> &attrIds)
253 {
254 if (attrIds.empty()) {
255 return false;
256 }
257
258 // Skip file header part.
259 if (fp_ == nullptr) {
260 return false;
261 } else if (fseek(fp_, header_.size, SEEK_SET) == -1) {
262 return false;
263 }
264
265 // Write id section.
266 uint64_t idSectionOffset;
267 if (!GetFilePos(idSectionOffset)) {
268 return false;
269 }
270
271 HLOGD("attrIds %zu", attrIds.size());
272 for (auto &attrId : attrIds) {
273 HLOGD(" attrIds ids %zu", attrId.ids.size());
274 if (!Write(attrId.ids.data(), attrId.ids.size() * sizeof(uint64_t))) {
275 return false;
276 }
277 }
278
279 // Write attr section.
280 uint64_t attrSectionOffset;
281 if (!GetFilePos(attrSectionOffset)) {
282 return false;
283 }
284 for (auto &attrId : attrIds) {
285 perf_file_attr fileAttr;
286 fileAttr.attr = attrId.attr;
287 fileAttr.ids.offset = idSectionOffset;
288 fileAttr.ids.size = attrId.ids.size() * sizeof(uint64_t);
289 idSectionOffset += fileAttr.ids.size;
290
291 if (!Write(&fileAttr, sizeof(fileAttr))) {
292 return false;
293 }
294 }
295
296 uint64_t dataSectionOffset;
297 if (!GetFilePos(dataSectionOffset)) {
298 return false;
299 }
300
301 attrSection_.offset = attrSectionOffset;
302 attrSection_.size = dataSectionOffset - attrSectionOffset;
303 dataSection_.offset = dataSectionOffset;
304
305 defaultEventAttr_ = attrIds[0].attr;
306
307 isWritingRecord = true;
308
309 return true;
310 }
311
LeftLessRight(const std::unique_ptr<PerfFileSection> & l,const std::unique_ptr<PerfFileSection> & r)312 static bool LeftLessRight(const std::unique_ptr<PerfFileSection> &l,
313 const std::unique_ptr<PerfFileSection> &r)
314 {
315 return l->featureId_ < r->featureId_;
316 }
317 // to write perf_file_header to file
318 // can only be called after all attr and records has been written.
WriteHeader()319 bool PerfFileWriter::WriteHeader()
320 {
321 header_.attrSize = sizeof(perf_file_attr);
322 header_.attrs = attrSection_;
323 header_.data = dataSection_;
324
325 // ignore event_types
326 header_.eventTypes.size = 0;
327 header_.eventTypes.offset = 0;
328
329 // save header
330 if (fseek(fp_, 0, SEEK_SET) == -1) {
331 HLOGD("fseek return error ");
332 return false;
333 }
334 if (!Write(&header_, sizeof(header_))) {
335 return false;
336 }
337 return true;
338 }
339
WriteFeatureData()340 bool PerfFileWriter::WriteFeatureData()
341 {
342 long featureOffset = 0;
343 if (fseek(fp_, 0, SEEK_END) != 0) {
344 HLOGD("fseek SEEK_END return error ");
345 return false;
346 }
347 if ((featureOffset = ftell(fp_)) == -1) {
348 HLOGD("ftell return error ");
349 return false;
350 }
351
352 for (size_t i = 0; i < sizeof(header_.features); i++) {
353 if (header_.features[i] != 0) {
354 HLOGV(" features['%zu'] '0x%x'", i, header_.features[i]);
355 }
356 }
357
358 unsigned long contentOffset = featureOffset + featureSections_.size() * sizeof(perf_file_section);
359 HLOGV("features start at file '0x%lx' content at '0x%lx'", featureOffset, contentOffset);
360
361 // reorder
362 std::sort(featureSections_.begin(), featureSections_.end(), LeftLessRight);
363 // save features head
364 int i = 0;
365 for (auto &featureSection : featureSections_) {
366 featureSection->header.offset = contentOffset;
367 featureSection->header.size = featureSection->GetSize();
368 contentOffset += featureSection->header.size;
369 HLOGV("save features[%d] head offset '0x%" PRIx64 "' size '0x%" PRIx64 "'", i,
370 featureSection->header.offset, featureSection->header.size);
371 i++;
372
373 // write features heads
374 if (!Write(&featureSection->header, sizeof(featureSection->header))) {
375 return false;
376 }
377 }
378 long offset = ftell(fp_);
379 if (offset < 0) {
380 return false;
381 }
382 HLOGV("features data at file '0x%lx'", offset);
383
384 i = 0;
385 for (auto &featureSection : featureSections_) {
386 std::vector<char> buf(featureSection->header.size);
387 featureSection->GetBinary(&buf[0], featureSection->header.size);
388 HLOGV("save features[%d] content size '0x%" PRIx64 "'", i, featureSection->header.size);
389 i++;
390
391 // write features content
392 if (!Write(&buf[0], featureSection->header.size)) {
393 HLOGE("write failed %" PRIu64 ".", featureSection->header.size);
394 return false;
395 }
396 }
397
398 return true;
399 }
400
AddNrCpusFeature(FEATURE feature,uint32_t nrCpusAvailable,uint32_t nrCpusOnline)401 bool PerfFileWriter::AddNrCpusFeature(FEATURE feature, uint32_t nrCpusAvailable,
402 uint32_t nrCpusOnline)
403 {
404 HLOGV("add feature '%s': nrCpusAvailable %u, nrCpusOnline %u",
405 PerfFileSection::GetFeatureName(FEATURE::NRCPUS).c_str(), nrCpusAvailable, nrCpusOnline);
406 featureSections_.emplace_back(
407 std::make_unique<PerfFileSectionNrCpus>(feature, nrCpusAvailable, nrCpusOnline));
408
409 // update header feature bits
410 header_.features[(int)FEATURE::NRCPUS / BITS_IN_BYTE] |=
411 1 << ((int)FEATURE::NRCPUS % BITS_IN_BYTE); // bit
412 return true;
413 }
414
AddEventDescFeature(FEATURE feature,const std::vector<AttrWithId> & eventDesces)415 bool PerfFileWriter::AddEventDescFeature(FEATURE feature,
416 const std::vector<AttrWithId> &eventDesces)
417 {
418 HLOGV("add feature '%s' %zu", PerfFileSection::GetFeatureName(FEATURE::EVENT_DESC).c_str(),
419 eventDesces.size());
420 featureSections_.emplace_back(std::make_unique<PerfFileSectionEventDesc>(feature, eventDesces));
421
422 header_.features[(int)FEATURE::EVENT_DESC / BITS_IN_BYTE] |=
423 1 << ((int)FEATURE::EVENT_DESC % BITS_IN_BYTE); // bit
424 return true;
425 }
426
AddStringFeature(FEATURE feature,std::string string)427 bool PerfFileWriter::AddStringFeature(FEATURE feature, std::string string)
428 {
429 HLOGV("add feature '%s' string '%s'", PerfFileSection::GetFeatureName(feature).c_str(),
430 string.c_str());
431 featureSections_.emplace_back(std::make_unique<PerfFileSectionString>(feature, string));
432
433 // update header feature bits
434 header_.features[(int)feature / BITS_IN_BYTE] |= 1 << ((int)feature % BITS_IN_BYTE); // bit
435 return true;
436 }
437
AddBoolFeature(FEATURE feature)438 bool PerfFileWriter::AddBoolFeature(FEATURE feature)
439 {
440 // same as u64, just use 1 as value
441 return AddU64Feature(feature, 1u);
442 }
443
AddU64Feature(FEATURE feature,uint64_t v)444 bool PerfFileWriter::AddU64Feature(FEATURE feature, uint64_t v)
445 {
446 HLOGV("add feature '%s' uint64_t '%" PRIu64 "'",
447 PerfFileSection::GetFeatureName(feature).c_str(), v);
448 featureSections_.emplace_back(std::make_unique<PerfFileSectionU64>(feature, v));
449
450 // update header feature bits
451 header_.features[(int)feature / BITS_IN_BYTE] |= 1 << ((int)feature % BITS_IN_BYTE); // bit
452 return true;
453 }
454
AddUniStackTableFeature(const ProcessStackMap * table)455 bool PerfFileWriter::AddUniStackTableFeature(const ProcessStackMap *table)
456 {
457 const FEATURE feature = FEATURE::HIPERF_FILES_UNISTACK_TABLE;
458 featureSections_.emplace_back(
459 std::make_unique<PerfFileSectionUniStackTable>(feature, table));
460 header_.features[(int)feature / BITS_IN_BYTE] |= 1 << ((int)feature % BITS_IN_BYTE);
461 return true;
462 }
463
AddSymbolsFeature(const std::vector<std::unique_ptr<SymbolsFile>> & symbolsFiles)464 bool PerfFileWriter::AddSymbolsFeature(
465 const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)
466 {
467 const FEATURE feature = FEATURE::HIPERF_FILES_SYMBOL;
468 HLOGV("add feature symbolsFiles %zu", symbolsFiles.size());
469 std::vector<SymbolFileStruct> symbolFileStructs {};
470 for (auto &symbolsFile : symbolsFiles) {
471 if (symbolsFile->SymbolsLoaded()) {
472 auto &symbolsFileStruct = symbolFileStructs.emplace_back();
473 symbolsFile->ExportSymbolToFileFormat(symbolsFileStruct);
474 }
475 }
476 featureSections_.emplace_back(
477 std::make_unique<PerfFileSectionSymbolsFiles>(feature, symbolFileStructs));
478 // update header feature bits
479 header_.features[(int)feature / BITS_IN_BYTE] |= 1 << ((int)feature % BITS_IN_BYTE); // bit
480 return true;
481 }
482 } // namespace HiPerf
483 } // namespace Developtools
484 } // namespace OHOS
485