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_reader.h"
16
17#include <bitset>
18#include <cinttypes>
19#include <cstdlib>
20#include <memory>
21
22#include <sys/stat.h>
23#include <unistd.h>
24
25#include "hiperf_hilog.h"
26#include "utilities.h"
27
28using namespace std::chrono;
29namespace OHOS {
30namespace Developtools {
31namespace HiPerf {
32const int FETURE_MAX = 256;
33const int SIZE_FETURE_COUNT = 8;
34
35std::unique_ptr<PerfFileReader> PerfFileReader::Instance(const std::string &fileName)
36{
37    std::string resolvedPath = CanonicalizeSpecPath(fileName.c_str());
38    FILE *fp = fopen(resolvedPath.c_str(), "rb");
39    if (fp == nullptr) {
40        HLOGE("fail to open file %s", fileName.c_str());
41        return nullptr;
42    }
43
44    std::unique_ptr<PerfFileReader> reader = std::make_unique<PerfFileReader>(fileName, fp);
45    if (!reader->ReadFileHeader()) {
46        // Fail to read header, maybe its compressed
47        if (reader->IsGzipFile()) {
48            fclose(fp);
49            reader->fp_ = nullptr;
50
51            CHECK_TRUE(!UncompressFile(fileName, ".perf.data"), nullptr, 1,
52                       "Fail to UncompressFile(%s)", fileName.c_str());
53
54            // open the uncompressed hidden file .perf.data
55            FILE *fp2 = fopen(".perf.data", "rb");
56            if (fp2 == nullptr) {
57                HLOGE("fail to open uncompressed file .perf.data");
58                return nullptr;
59            }
60
61            reader->fp_ = fp2;
62            reader->compressData_ = true;
63
64            if (!reader->ReadFileHeader()) {
65                HLOGE("fail to read header of file .perf.data");
66                return nullptr;
67            }
68            goto end;
69        }
70        return nullptr;
71    }
72end:
73    CHECK_TRUE(!reader->ReadAttrSection(), nullptr, 0, "");
74    return reader;
75}
76
77PerfFileReader::PerfFileReader(const std::string &fileName, FILE *fp) : fp_(fp), fileName_(fileName)
78{
79    featureSectionOffset_ = 0;
80    struct stat fileStat;
81    if (fp != nullptr) {
82        if (fstat(fileno(fp), &fileStat) != -1 and fileStat.st_size > 0) {
83            fileSize_ = fileStat.st_size;
84        }
85    }
86}
87
88PerfFileReader::~PerfFileReader()
89{
90    // if file was not closed properly
91    if (fp_ != nullptr) {
92        fclose(fp_);
93        fp_ = nullptr;
94    }
95
96    // remove the uncompressed .perf.data
97    if (compressData_) {
98        if (remove(".perf.data") != 0) {
99            HLOGE("Fail to remove uncompressed file .perf.data");
100            perror("Fail to remove temp file");
101        }
102    }
103}
104
105bool PerfFileReader::IsValidDataFile()
106{
107    return (memcmp(header_.magic, PERF_MAGIC, sizeof(header_.magic)) == 0);
108}
109
110bool PerfFileReader::IsGzipFile()
111{
112    return header_.magic[0] == '\x1f' and header_.magic[1] == '\x8b';
113}
114
115bool PerfFileReader::ReadFileHeader()
116{
117    if (Read(&header_, sizeof(header_))) {
118        dataSectionSize_ = header_.data.size;
119        if (IsValidDataFile()) {
120            featureSectionOffset_ = header_.data.offset + header_.data.size;
121            for (int i = 0; i < FETURE_MAX / SIZE_FETURE_COUNT; i++) {
122                std::bitset<SIZE_FETURE_COUNT> features(header_.features[i]);
123                for (int j = 0; j < SIZE_FETURE_COUNT; j++) {
124                    if (features.test(j)) {
125                        features_.emplace_back((FEATURE)(((uint64_t)i) * SIZE_FETURE_COUNT + j));
126                    }
127                }
128            }
129            return true;
130        }
131    }
132    return false;
133}
134
135bool PerfFileReader::ReadAttrSection()
136{
137    if (header_.attrSize != sizeof(perf_file_attr)) {
138        // 4.19 and 5.1 use diff size , 128 vs 136
139        HLOGW("attr size %" PRId64 " doesn't match expected size %zu", header_.attrSize,
140              sizeof(perf_file_attr));
141    }
142    CHECK_TRUE(header_.attrSize == 0, false, 0, "");
143    int attrCount = header_.attrs.size / header_.attrSize;
144    CHECK_TRUE(attrCount == 0, false, 1, "no attr in file");
145    if (fseek(fp_, header_.attrs.offset, SEEK_SET) != 0) {
146        HLOGE("fseek() failed");
147        return false;
148    }
149    for (int i = 0; i < attrCount; ++i) {
150        std::vector<char> buf(header_.attrSize);
151        CHECK_TRUE(!Read(buf.data(), buf.size()), false, 0, "");
152        // size of perf_event_attr change between different linux kernel versions.
153        // can not memcpy to perf_file_attr as a whole
154        perf_file_attr attr {};
155        size_t attrSize = header_.attrSize - sizeof(attr.ids);
156
157        // If the size is smaller, you can use a pointer to point directly.
158        // Our UAPI is 4.19. is less than 5.1
159        if (header_.attrSize < sizeof(perf_event_attr)) {
160            HLOGE("size not match, ptr of perf_event_attr maybe overfollow %zu vs %zu",
161                  sizeof(perf_event_attr), attrSize);
162        }
163
164        attr.attr = *(reinterpret_cast<perf_event_attr *>(&buf[0]));
165        attr.ids = *(reinterpret_cast<perf_file_section *>(&buf[attrSize]));
166        vecAttr_.push_back(attr);
167    }
168
169    // read ids for attr
170    for (size_t i = 0; i < vecAttr_.size(); ++i) {
171        std::vector<uint64_t> ids;
172        CHECK_TRUE(!ReadIdsForAttr(vecAttr_[i], &ids), false, 0, "");
173        vecAttrIds_.push_back(ids);
174
175        // map ids to attr index
176        for (auto id : ids) {
177            mapId2Attr_[id] = i;
178        }
179    }
180
181    return true;
182}
183
184bool PerfFileReader::ReadIdsForAttr(const perf_file_attr &attr, std::vector<uint64_t> *ids)
185{
186    if (attr.ids.size > 0) {
187        size_t count = attr.ids.size / sizeof(uint64_t);
188        if (fseek(fp_, attr.ids.offset, SEEK_SET) != 0) {
189            HLOGE("fseek() failed");
190            return false;
191        }
192        CHECK_TRUE(ids == nullptr, false, 0, "");
193        ids->resize(count);
194        CHECK_TRUE(!Read(ids->data(), attr.ids.size), false, 0, "");
195    }
196    return true;
197}
198
199std::vector<AttrWithId> PerfFileReader::GetAttrSection() const
200{
201    std::vector<AttrWithId> result(vecAttr_.size());
202
203    for (size_t i = 0; i < vecAttr_.size(); ++i) {
204        result[i].attr = vecAttr_[i].attr;
205        result[i].ids = vecAttrIds_[i];
206    }
207    return result;
208}
209
210bool PerfFileReader::ReadDataSection(ProcessRecordCB &callback)
211{
212    if (fseek(fp_, header_.data.offset, SEEK_SET) != 0) {
213        HLOGE("fseek() failed");
214        return false;
215    }
216
217    HLOGD("dataSection_ at offset %" PRId64 " + %" PRId64 "", header_.data.offset,
218          header_.data.size);
219
220    CHECK_TRUE(!ReadRecord(callback), false, LOG_TYPE_PRINTF, "some record format is error!\n");
221
222#ifdef HIPERF_DEBUG_TIME
223    printf("readRecordTime: %" PRId64 " ms\n",
224           duration_cast<milliseconds>(readRecordTime_).count());
225    printf("readCallbackTime: %" PRId64 " ms\n",
226           duration_cast<milliseconds>(readCallbackTime_).count());
227#endif
228    return dataSectionSize_ == 0;
229}
230
231const perf_event_attr *PerfFileReader::GetDefaultAttr()
232{
233    CHECK_TRUE(vecAttr_.empty(), nullptr, 0, "");
234    return &(vecAttr_[0].attr);
235}
236
237bool PerfFileReader::ReadRecord(ProcessRecordCB &callback)
238{
239#ifdef HIPERF_DEBUG_TIME
240    const auto startReadTime = steady_clock::now();
241#endif
242    // record size can not exceed 64K
243    HIPERF_BUF_ALIGN static uint8_t buf[RECORD_SIZE_LIMIT_SPE];
244    // diff with reader
245    uint64_t remainingSize = header_.data.size;
246    size_t recordNumber = 0;
247    const perf_event_attr *attr = GetDefaultAttr();
248    CHECK_TRUE(attr == nullptr, false, 1, "attr is null");
249    while (remainingSize > 0) {
250        if (remainingSize < sizeof(perf_event_header)) {
251            HLOGW("not enough sizeof perf_event_header");
252            return false;
253        } else if (!Read(buf, sizeof(perf_event_header))) {
254            HLOGW("read perf_event_header failed.");
255            return false;
256        } else {
257            perf_event_header *header = reinterpret_cast<perf_event_header *>(buf);
258            if (header == nullptr) {
259                HLOGE("read record header is null");
260                return false;
261            } else if (header->size > RECORD_SIZE_LIMIT) {
262                HLOGE("read record header size error %hu", header->size);
263                return false;
264            }
265            if (remainingSize >= header->size) {
266                size_t headerSize = sizeof(perf_event_header);
267                if (Read(buf + headerSize, header->size - headerSize)) {
268                    size_t speSize = 0;
269                    if (header->type == PERF_RECORD_AUXTRACE) {
270                        struct PerfRecordAuxtraceData *auxtrace = reinterpret_cast<struct PerfRecordAuxtraceData *>
271                                                                  (header + 1);
272                        speSize = auxtrace->size;
273                        if (speSize > 0) {
274                            Read(buf + header->size, auxtrace->size);
275                        }
276                    }
277                    uint8_t *data = buf;
278                    std::unique_ptr<PerfEventRecord> record = GetPerfEventRecord(
279                        static_cast<perf_event_type>(header->type), data, *attr);
280                    // unknown record , break the process
281                    if (!record) {
282                        return false;
283                    } else {
284                        HLOGV("record type %u", record->GetType());
285                    }
286                    remainingSize = remainingSize - header->size - speSize;
287#ifdef HIPERF_DEBUG_TIME
288                    const auto startCallbackTime = steady_clock::now();
289#endif
290                    // call callback to process, then destroy record
291                    callback(std::move(record));
292                    recordNumber++;
293#ifdef HIPERF_DEBUG_TIME
294                    readCallbackTime_ +=
295                        duration_cast<microseconds>(steady_clock::now() - startCallbackTime);
296#endif
297                } else {
298                    HLOGE("read record data size failed %zu", header->size - headerSize);
299                    return false;
300                }
301            } else {
302                HLOGE("not enough header->size.");
303                return false;
304            }
305        }
306    }
307    HLOGD("read back %zu records", recordNumber);
308#ifdef HIPERF_DEBUG_TIME
309    readRecordTime_ += duration_cast<microseconds>(steady_clock::now() - startReadTime);
310#endif
311    return true;
312}
313
314bool PerfFileReader::Read(void *buf, size_t len)
315{
316    if (buf == nullptr || len == 0) {
317        HLOG_ASSERT(buf != nullptr);
318        HLOG_ASSERT(len > 0);
319        return false;
320    }
321
322    if (fread(buf, len, 1, fp_) != 1) {
323        printf("failed to read file: %d", errno);
324        return false;
325    }
326    return true;
327}
328
329const perf_file_header &PerfFileReader::GetHeader() const
330{
331    return header_;
332}
333
334bool PerfFileReader::Read(char *buf, uint64_t offset, size_t len)
335{
336    if (buf == nullptr || len == 0) {
337        HLOG_ASSERT(buf != nullptr);
338        HLOG_ASSERT(len > 0);
339        return false;
340    }
341    if (fseek(fp_, offset, SEEK_SET) != 0) {
342        HLOGE("fseek() failed");
343        return false;
344    }
345
346    CHECK_TRUE(fread(buf, len, 1, fp_) != 1, false, LOG_TYPE_PRINTF, "failed to read file: %d", errno);
347    HLOGM("offset %" PRIx64 " len %zu buf %x %x %x %x", offset, len, buf[0], buf[1], buf[2],
348          buf[3]);
349    return true;
350}
351const std::vector<FEATURE> &PerfFileReader::GetFeatures() const
352{
353    return features_;
354}
355
356const std::vector<std::unique_ptr<PerfFileSection>> &PerfFileReader::GetFeatureSections() const
357{
358    return perfFileSections_;
359}
360
361const std::string PerfFileReader::GetFeatureString(const FEATURE feature) const
362{
363    std::string featureName = PerfFileSection::GetFeatureName(feature);
364    HLOGV("GetFeatureSection %s", featureName.c_str());
365    if (!IsFeatrureStringSection(feature)) {
366        HLOGV("not a string feature: %s", featureName.c_str());
367    } else {
368        const PerfFileSection *featureSection = GetFeatureSection(feature);
369        if (featureSection != nullptr) {
370            const PerfFileSectionString *sectionString =
371                static_cast<const PerfFileSectionString *>(featureSection);
372            return sectionString->ToString();
373        } else {
374            HLOGV("have not found: %s", featureName.c_str());
375        }
376    }
377    return EMPTY_STRING;
378}
379
380const PerfFileSection *PerfFileReader::GetFeatureSection(FEATURE feature) const
381{
382    for (auto const &it : perfFileSections_) {
383        HLOGV("perfFileSections");
384        if (it->featureId_ == feature) {
385            return it.get();
386        }
387    }
388    return nullptr;
389}
390
391bool PerfFileReader::ReadFeatureSection()
392{
393    uint64_t featureSectionOffsetRead = featureSectionOffset_;
394    HLOGV(" ReadDataSection data offset '0x%" PRIx64 " ", featureSectionOffset_);
395
396    for (FEATURE feature : features_) {
397        perf_file_section sectionHeader;
398        // read failed ??
399        CHECK_TRUE(!Read((char *)&sectionHeader, featureSectionOffsetRead, sizeof(sectionHeader)),
400                   false, LOG_TYPE_PRINTF,
401                   "file format not correct. featureSectionOffsetRead '0x%" PRIx64 "\n", featureSectionOffsetRead);
402
403        HLOGV("process feature %d:%s", feature, PerfFileSection::GetFeatureName(feature).c_str());
404        HLOGV(" sectionHeader -> read offset '0x%" PRIx64 " size '0x%" PRIx64 "'",
405              sectionHeader.offset, sectionHeader.size);
406        CHECK_TRUE(sectionHeader.size == 0 or sectionHeader.size > fileSize_, false, 1,
407                   "sectionHeader.size %" PRIu64 " is not correct", sectionHeader.size);
408
409        std::vector<char> buf(sectionHeader.size);
410        // read failed ??
411        CHECK_TRUE(!Read(&buf[0], sectionHeader.offset, buf.size()), false, LOG_TYPE_PRINTF,
412                   "file format not correct. featureSectionDataOffset '0x%" PRIx64 "\n", sectionHeader.offset);
413        if (IsFeatrureStringSection(feature)) {
414            perfFileSections_.emplace_back(
415                std::make_unique<PerfFileSectionString>(feature, (char *)&buf[0], buf.size()));
416        } else if (feature == FEATURE::HIPERF_FILES_SYMBOL) {
417            perfFileSections_.emplace_back(std::make_unique<PerfFileSectionSymbolsFiles>(
418                feature, (char *)&buf[0], buf.size()));
419        } else if (feature == FEATURE::EVENT_DESC) {
420            perfFileSections_.emplace_back(
421                std::make_unique<PerfFileSectionEventDesc>(feature, (char *)&buf[0], buf.size()));
422        } else if (feature == FEATURE::HIPERF_FILES_UNISTACK_TABLE) {
423            perfFileSections_.emplace_back(
424                std::make_unique<PerfFileSectionUniStackTable>(feature, (char *)&buf[0], buf.size()));
425            PerfRecordSample::dumpRemoveStack_ = true;
426        } else {
427            HLOGW("still not imp how to process with feature %d", feature);
428        }
429
430        featureSectionOffsetRead += sizeof(sectionHeader); // next feaure
431    }
432    return true;
433}
434} // namespace HiPerf
435} // namespace Developtools
436} // namespace OHOS
437