* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "perf_file_reader.h"
#include <bitset>
#include <cinttypes>
#include <cstdlib>
#include <memory>
#include <sys/stat.h>
#include <unistd.h>
#include "hiperf_hilog.h"
#include "utilities.h"
using namespace std::chrono;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
const int FETURE_MAX = 256;
const int SIZE_FETURE_COUNT = 8;
constexpr size_t MAX_VECTOR_RESIZE_COUNT = 100000;
constexpr char UNCOMPRESS_TMP_FILE[] = "/data/local/tmp/.perf.data";
std::unique_ptr<PerfFileReader> PerfFileReader::Instance(const std::string &fileName)
{
std::string resolvedPath = CanonicalizeSpecPath(fileName.c_str());
FILE *fp = fopen(resolvedPath.c_str(), "rb");
if (fp == nullptr) {
HLOGE("fail to open file %s", fileName.c_str());
return nullptr;
}
std::unique_ptr<PerfFileReader> reader = std::make_unique<PerfFileReader>(fileName, fp);
if (!reader->ReadFileHeader()) {
if (reader->IsGzipFile()) {
if (fp != nullptr) {
fclose(fp);
}
reader->fp_ = nullptr;
CHECK_TRUE(UncompressFile(fileName, UNCOMPRESS_TMP_FILE), nullptr, 1,
"Fail to UncompressFile(%s)", fileName.c_str());
FILE *fp2 = fopen(UNCOMPRESS_TMP_FILE, "rb");
if (fp2 == nullptr) {
HLOGE("fail to open uncompressed file .perf.data");
return nullptr;
}
reader->fp_ = fp2;
reader->compressData_ = true;
if (!reader->ReadFileHeader()) {
HLOGE("fail to read header of file .perf.data");
return nullptr;
}
goto end;
}
return nullptr;
}
end:
CHECK_TRUE(reader->ReadAttrSection(), nullptr, 0, "");
return reader;
}
PerfFileReader::PerfFileReader(const std::string &fileName, FILE *fp) : fp_(fp), fileName_(fileName)
{
featureSectionOffset_ = 0;
struct stat fileStat;
if (fp != nullptr) {
if (fstat(fileno(fp), &fileStat) != -1 && fileStat.st_size > 0) {
fileSize_ = fileStat.st_size;
}
}
}
PerfFileReader::~PerfFileReader()
{
if (fp_ != nullptr && fp_ != stdout) {
fclose(fp_);
}
fp_ = nullptr;
if (compressData_) {
if (remove(UNCOMPRESS_TMP_FILE) != 0) {
HLOGE("Fail to remove uncompressed file .perf.data");
perror("Fail to remove temp file");
}
}
}
bool PerfFileReader::IsValidDataFile()
{
return (memcmp(header_.magic, PERF_MAGIC, sizeof(header_.magic)) == 0);
}
bool PerfFileReader::IsGzipFile()
{
return header_.magic[0] == '\x1f' && header_.magic[1] == '\x8b';
}
bool PerfFileReader::ReadFileHeader()
{
if (Read(&header_, sizeof(header_))) {
dataSectionSize_ = header_.data.size;
if (IsValidDataFile()) {
featureSectionOffset_ = header_.data.offset + header_.data.size;
for (int i = 0; i < FETURE_MAX / SIZE_FETURE_COUNT; i++) {
std::bitset<SIZE_FETURE_COUNT> features(header_.features[i]);
for (int j = 0; j < SIZE_FETURE_COUNT; j++) {
if (features.test(j)) {
features_.emplace_back((FEATURE)(((uint64_t)i) * SIZE_FETURE_COUNT + j));
}
}
}
return true;
}
}
return false;
}
bool PerfFileReader::ReadAttrSection()
{
if (header_.attrSize != sizeof(perf_file_attr)) {
HLOGW("attr size %" PRId64 " doesn't match expected size %zu", header_.attrSize,
sizeof(perf_file_attr));
}
CHECK_TRUE(header_.attrSize != 0, false, 0, "");
CHECK_TRUE(header_.attrSize <= THOUSANDS, false, 1, "attr size exceeds 1000");
size_t attrCount = header_.attrs.size / header_.attrSize;
CHECK_TRUE(attrCount != 0, false, 1, "no attr in file");
if (fseek(fp_, header_.attrs.offset, SEEK_SET) != 0) {
HLOGE("fseek() failed");
return false;
}
for (size_t i = 0; i < attrCount; ++i) {
std::vector<char> buf(header_.attrSize);
CHECK_TRUE(Read(buf.data(), buf.size()), false, 0, "");
perf_file_attr attr {};
size_t attrSize = header_.attrSize - sizeof(attr.ids);
if (header_.attrSize < sizeof(perf_event_attr)) {
HLOGE("size not match, ptr of perf_event_attr maybe overfollow %zu vs %zu",
sizeof(perf_event_attr), attrSize);
}
attr.attr = *(reinterpret_cast<perf_event_attr *>(&buf[0]));
attr.ids = *(reinterpret_cast<perf_file_section *>(&buf[attrSize]));
vecAttr_.push_back(attr);
}
for (size_t i = 0; i < vecAttr_.size(); ++i) {
std::vector<uint64_t> ids;
CHECK_TRUE(ReadIdsForAttr(vecAttr_[i], &ids), false, 0, "");
vecAttrIds_.push_back(ids);
for (auto id : ids) {
mapId2Attr_[id] = i;
}
}
return true;
}
bool PerfFileReader::ReadIdsForAttr(const perf_file_attr &attr, std::vector<uint64_t> *ids)
{
if (attr.ids.size > 0) {
size_t count = (attr.ids.size + sizeof(uint64_t) - 1) / sizeof(uint64_t);
if (count > MAX_VECTOR_RESIZE_COUNT) {
HLOGE("count(%zu) out of range", count);
return false;
}
if (fseek(fp_, attr.ids.offset, SEEK_SET) != 0) {
HLOGE("fseek() failed");
return false;
}
CHECK_TRUE(ids != nullptr, false, 0, "");
ids->resize(count);
CHECK_TRUE(Read(ids->data(), attr.ids.size), false, 0, "");
}
return true;
}
std::vector<AttrWithId> PerfFileReader::GetAttrSection() const
{
std::vector<AttrWithId> result(vecAttr_.size());
for (size_t i = 0; i < vecAttr_.size(); ++i) {
result[i].attr = vecAttr_[i].attr;
result[i].ids = vecAttrIds_[i];
}
return result;
}
bool PerfFileReader::ReadDataSection(ProcessRecordCB &callback)
{
if (fseek(fp_, header_.data.offset, SEEK_SET) != 0) {
HLOGE("fseek() failed");
return false;
}
HLOGD("dataSection_ at offset %" PRId64 " + %" PRId64 "", header_.data.offset,
header_.data.size);
CHECK_TRUE(ReadRecord(callback), false, LOG_TYPE_PRINTF, "some record format is error!\n");
#ifdef HIPERF_DEBUG_TIME
printf("readRecordTime: %" PRId64 " ms\n",
duration_cast<milliseconds>(readRecordTime_).count());
printf("readCallbackTime: %" PRId64 " ms\n",
duration_cast<milliseconds>(readCallbackTime_).count());
#endif
return dataSectionSize_ == 0;
}
const perf_event_attr *PerfFileReader::GetDefaultAttr()
{
CHECK_TRUE(!vecAttr_.empty(), nullptr, 0, "");
return &(vecAttr_[0].attr);
}
void PerfFileReader::ReadSpeRecord(perf_event_header *header, uint8_t *buf, size_t &speSize)
{
CHECK_TRUE(header != nullptr && buf != nullptr, NO_RETVAL, 0, "");
struct PerfRecordAuxtraceData *auxtrace =
reinterpret_cast<struct PerfRecordAuxtraceData *>(header + 1);
speSize = auxtrace->size;
if (speSize > 0 && header->size + auxtrace->size <= RECORD_SIZE_LIMIT_SPE) {
Read(buf + header->size, auxtrace->size);
}
}
bool PerfFileReader::ReadRecordByAttr(ProcessRecordCB &callback, uint8_t *buf,
uint64_t &remainingSize, size_t &recordNumber, const perf_event_attr *attr)
{
CHECK_TRUE(buf != nullptr && attr != nullptr, false, 0, "");
if (remainingSize < sizeof(perf_event_header)) {
HLOGW("not enough sizeof perf_event_header");
return false;
} else if (!Read(buf, sizeof(perf_event_header))) {
HLOGW("read perf_event_header failed.");
return false;
}
perf_event_header *header = reinterpret_cast<perf_event_header *>(buf);
if (header->size > RECORD_SIZE_LIMIT || header->size < sizeof(perf_event_header)) {
HLOGE("read record header size error %hu", header->size);
return false;
}
if (remainingSize < header->size) {
HLOGE("not enough header->size.");
return false;
}
size_t headerSize = sizeof(perf_event_header);
if (!Read(buf + headerSize, header->size - headerSize)) {
HLOGE("read record data size failed %zu", header->size - headerSize);
return false;
}
size_t speSize = 0;
if (header->type == PERF_RECORD_AUXTRACE) {
ReadSpeRecord(header, buf, speSize);
}
uint8_t *data = buf;
PerfEventRecord& record = PerfEventRecordFactory::GetPerfEventRecord(
static_cast<perf_event_type>(header->type), data, *attr);
if (record.GetName() == nullptr) {
return false;
}
HLOGV("record type %u", record.GetType());
remainingSize = remainingSize - header->size - speSize;
#ifdef HIPERF_DEBUG_TIME
const auto startCallbackTime = steady_clock::now();
#endif
callback(record);
recordNumber++;
#ifdef HIPERF_DEBUG_TIME
readCallbackTime_ +=
duration_cast<microseconds>(steady_clock::now() - startCallbackTime);
#endif
return true;
}
bool PerfFileReader::ValidateSMOReadRecord(uint8_t *buf, perf_event_header *header,
uint64_t &remainingSize)
{
if (header->size > RECORD_SIZE_LIMIT || header->size < sizeof(perf_event_header)) {
HLOGE("read record header size error %hu", header->size);
return false;
}
if (remainingSize < header->size) {
HLOGE("not enough header->size.");
return false;
}
size_t headerSize = sizeof(perf_event_header);
if (!Read(buf + headerSize, header->size - headerSize)) {
HLOGE("read record data size failed %zu", header->size - headerSize);
return false;
}
return true;
}
bool PerfFileReader::SMOReadRecordByAttr(ProcessRecordCB &callback, uint8_t *buf,
uint64_t &remainingSize, size_t &recordNumber, const perf_event_attr *attr)
{
CHECK_TRUE(buf != nullptr && attr != nullptr, false, 0, "");
if (remainingSize < sizeof(perf_event_header)) {
HLOGW("not enough sizeof perf_event_header");
return false;
} else if (!Read(buf, sizeof(perf_event_header))) {
HLOGW("read perf_event_header failed.");
return false;
}
perf_event_header *header = reinterpret_cast<perf_event_header *>(buf);
if (!ValidateSMOReadRecord(buf, header, remainingSize)) {
return false;
}
size_t speSize = 0;
if (header->type == PERF_RECORD_AUXTRACE) {
ReadSpeRecord(header, buf, speSize);
}
uint8_t *data = buf;
if (header->type == PERF_RECORD_TYPE_SMO_NUM) {
PerfEventRecord& record = PerfEventRecordFactory::GetPerfEventRecord(
static_cast<perf_event_type>(header->type), data, *attr);
if (record.GetName() == nullptr) {
return false;
}
HLOGV("record type %u", record.GetType());
remainingSize = remainingSize - header->size - speSize;
#ifdef HIPERF_DEBUG_TIME
const auto startCallbackTime = steady_clock::now();
#endif
callback(record);
recordNumber++;
#ifdef HIPERF_DEBUG_TIME
readCallbackTime_ +=
duration_cast<microseconds>(steady_clock::now() - startCallbackTime);
#endif
return true;
}
remainingSize = remainingSize - header->size - speSize;
#ifdef HIPERF_DEBUG_TIME
const auto startCallbackTime = steady_clock::now();
#endif
recordNumber++;
#ifdef HIPERF_DEBUG_TIME
readCallbackTime_ +=
duration_cast<microseconds>(steady_clock::now() - startCallbackTime);
#endif
return true;
}
bool PerfFileReader::ReadRecord(ProcessRecordCB &callback)
{
#ifdef HIPERF_DEBUG_TIME
const auto startReadTime = steady_clock::now();
#endif
HIPERF_BUF_ALIGN static uint8_t buf[RECORD_SIZE_LIMIT_SPE];
uint64_t remainingSize = header_.data.size;
size_t recordNumber = 0;
const perf_event_attr *attr = GetDefaultAttr();
CHECK_TRUE(attr != nullptr, false, 1, "attr is null");
uint64_t smoRemainingSize = header_.data.size;
size_t smoRecordNumber = 0;
HIPERF_BUF_ALIGN static uint8_t smoBuf[RECORD_SIZE_LIMIT_SPE];
const perf_event_attr *smoAttr = GetDefaultAttr();
CHECK_TRUE(smoAttr != nullptr, false, 1, "smoAttr is null");
long originalPosition = ftell(fp_);
while (smoRemainingSize > 0) {
if (!SMOReadRecordByAttr(callback, smoBuf, smoRemainingSize, smoRecordNumber, smoAttr)) {
return false;
}
}
if (fseek(fp_, originalPosition, SEEK_SET)) {
return false;
}
while (remainingSize > 0) {
if (!ReadRecordByAttr(callback, buf, remainingSize, recordNumber, attr)) {
return false;
}
}
HLOGD("read back %zu records", recordNumber);
#ifdef HIPERF_DEBUG_TIME
readRecordTime_ += duration_cast<microseconds>(steady_clock::now() - startReadTime);
#endif
return true;
}
bool PerfFileReader::Read(void *buf, const size_t len)
{
if (buf == nullptr || len == 0) {
HLOG_ASSERT(buf != nullptr);
HLOG_ASSERT(len > 0);
return false;
}
if (fread(buf, len, 1, fp_) != 1) {
printf("failed to read file");
return false;
}
return true;
}
const perf_file_header &PerfFileReader::GetHeader() const
{
return header_;
}
bool PerfFileReader::Read(char *buf, const uint64_t offset, const size_t len)
{
if (buf == nullptr || len == 0) {
HLOG_ASSERT(buf != nullptr);
HLOG_ASSERT(len > 0);
return false;
}
if (fseek(fp_, offset, SEEK_SET) != 0) {
HLOGE("fseek() failed");
return false;
}
#if defined(is_ohos) && is_ohos
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
CHECK_TRUE(fread(buf, len, 1, fp_) == 1, false, LOG_TYPE_PRINTF, "failed to read file: %d:%s", errno, errInfo);
#else
CHECK_TRUE(fread(buf, len, 1, fp_) == 1, false, LOG_TYPE_PRINTF, "failed to read file: %d", errno);
#endif
HLOGM("offset %" PRIx64 " len %zu buf %x %x %x %x", offset, len, buf[0], buf[1], buf[2],
buf[3]);
return true;
}
const std::vector<FEATURE> &PerfFileReader::GetFeatures() const
{
return features_;
}
const std::vector<std::unique_ptr<PerfFileSection>> &PerfFileReader::GetFeatureSections() const
{
return perfFileSections_;
}
const std::string PerfFileReader::GetFeatureString(const FEATURE feature) const
{
std::string featureName = PerfFileSection::GetFeatureName(feature);
HLOGV("GetFeatureSection %s", featureName.c_str());
if (!IsFeatureStringSection(feature)) {
HLOGV("not a string feature: %s", featureName.c_str());
} else {
const PerfFileSection *featureSection = GetFeatureSection(feature);
if (featureSection != nullptr) {
const PerfFileSectionString *sectionString =
static_cast<const PerfFileSectionString *>(featureSection);
return sectionString->ToString();
} else {
HLOGV("have not found: %s", featureName.c_str());
}
}
return EMPTY_STRING;
}
const PerfFileSection *PerfFileReader::GetFeatureSection(const FEATURE feature) const
{
for (auto const &it : perfFileSections_) {
HLOGV("perfFileSections");
if (it->featureId_ == feature) {
return it.get();
}
}
return nullptr;
}
bool PerfFileReader::ReadFeatureSection()
{
uint64_t featureSectionOffsetRead = featureSectionOffset_;
HLOGV(" ReadDataSection data offset '0x%" PRIx64 " ", featureSectionOffset_);
for (FEATURE feature : features_) {
perf_file_section sectionHeader;
CHECK_TRUE(Read((char *)§ionHeader, featureSectionOffsetRead, sizeof(sectionHeader)),
false, LOG_TYPE_PRINTF,
"file format not correct. featureSectionOffsetRead '0x%" PRIx64 "\n", featureSectionOffsetRead);
HLOGV("process feature %d:%s", feature, PerfFileSection::GetFeatureName(feature).c_str());
HLOGV(" sectionHeader -> read offset '0x%" PRIx64 " size '0x%" PRIx64 "'",
sectionHeader.offset, sectionHeader.size);
CHECK_TRUE(sectionHeader.size != 0 && sectionHeader.size <= fileSize_, false, 1,
"sectionHeader.size %" PRIu64 " is not correct", sectionHeader.size);
std::vector<char> buf(sectionHeader.size);
CHECK_TRUE(Read(&buf[0], sectionHeader.offset, buf.size()), false, LOG_TYPE_PRINTF,
"file format not correct. featureSectionDataOffset '0x%" PRIx64 "\n", sectionHeader.offset);
if (IsFeatureStringSection(feature)) {
perfFileSections_.emplace_back(
std::make_unique<PerfFileSectionString>(feature, (char *)&buf[0], buf.size()));
} else if (feature == FEATURE::HIPERF_FILES_SYMBOL) {
perfFileSections_.emplace_back(std::make_unique<PerfFileSectionSymbolsFiles>(
feature, (char *)&buf[0], buf.size()));
} else if (feature == FEATURE::EVENT_DESC) {
perfFileSections_.emplace_back(
std::make_unique<PerfFileSectionEventDesc>(feature, (char *)&buf[0], buf.size()));
} else if (feature == FEATURE::HIPERF_FILES_UNISTACK_TABLE) {
perfFileSections_.emplace_back(
std::make_unique<PerfFileSectionUniStackTable>(feature, (char *)&buf[0], buf.size()));
PerfRecordSample::SetDumpRemoveStack(true);
} else {
HLOGW("still not imp how to process with feature %d", feature);
}
featureSectionOffsetRead += sizeof(sectionHeader);
}
return true;
}
}
}
}