* 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_writer.h"
#include <cinttypes>
#include <cstdlib>
#include <unistd.h>
#include "hiperf_hilog.h"
#include "utilities.h"
using namespace std::chrono;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
PerfFileWriter::~PerfFileWriter()
{
if (fp_ != nullptr) {
fclose(fp_);
fp_ = nullptr;
if (remove(fileName_.c_str()) != 0) {
HLOGE("fail to remove file(%s).", fileName_.c_str());
}
}
}
bool PerfFileWriter::Open(const std::string &fileName, bool compressData)
{
if (access(fileName.c_str(), F_OK) == 0) {
if (remove(fileName.c_str()) != 0) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("can't remove exist file(%s). %d:%s\n", fileName.c_str(), errno,
errInfo);
return false;
}
}
std::string resolvedPath = CanonicalizeSpecPath(fileName.c_str());
fp_ = fopen(resolvedPath.c_str(), "wb+");
if (fp_ == nullptr) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("can't create file(%s). %d:%s\n", fileName.c_str(), errno, errInfo);
return false;
}
fileName_ = fileName;
compressData_ = compressData;
attrSection_.offset = 0;
attrSection_.size = 0;
dataSection_ = attrSection_;
header_.size = sizeof(header_);
fileBuffer_.resize(WRITER_BUFFER_SIZE);
if (setvbuf(fp_, fileBuffer_.data(), _IOFBF, WRITER_BUFFER_SIZE) != 0) {
HLOGD("setvbuf failed");
}
return true;
}
bool PerfFileWriter::Close()
{
HLOG_ASSERT(fp_ != nullptr);
bool rc = true;
if (!WriteHeader()) {
rc = false;
}
if (!WriteFeatureData()) {
rc = false;
}
if (fclose(fp_) != 0) {
HLOGD("fail to close fp ");
rc = false;
}
fp_ = nullptr;
if (compressData_) {
std::string gzName = fileName_ + ".gz";
if (CompressFile(fileName_, gzName)) {
if (remove(fileName_.c_str()) != 0) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("can't remove file(%s). %d:%s\n",
fileName_.c_str(), errno, errInfo);
}
if (rename(gzName.c_str(), fileName_.c_str()) != 0) {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("can't rename file(%s) to (%s). %d:%s\n", gzName.c_str(), fileName_.c_str(),
errno, errInfo);
}
} else {
char errInfo[ERRINFOLEN] = { 0 };
strerror_r(errno, errInfo, ERRINFOLEN);
printf("failed to compress file(%s). %d:%s\n", fileName_.c_str(), errno,
errInfo);
}
}
return rc;
}
bool PerfFileWriter::WriteRecord(const PerfEventRecord &record)
{
if (!isWritingRecord) {
HLOGV("need write <attrs> first");
return false;
}
HLOGV("write '%s', size %zu", record.GetName(), record.GetSize());
CHECK_TRUE(record.GetSize() <= RECORD_SIZE_LIMIT_SPE, false, 1,
"%s record size exceed limit", record.GetName());
static std::vector<u8> buf(RECORD_SIZE_LIMIT_SPE);
CHECK_TRUE(record.GetBinary(buf), false, 0, "");
CHECK_TRUE(Write(buf.data(), record.GetSize()), false, 0, "");
dataSection_.size += record.GetSize();
++recordCount_;
return true;
}
bool PerfFileWriter::ReadDataSection(ProcessRecordCB &callback)
{
HLOG_ASSERT(fp_ != nullptr);
if (fseek(fp_, dataSection_.offset, SEEK_SET) != 0) {
HLOGE("fseek() failed");
return false;
}
HLOGD("dataSection_ at offset %" PRIu64 " + %" PRIu64 "", dataSection_.offset,
dataSection_.size);
return ReadRecords(callback);
}
bool PerfFileWriter::ReadRecords(ProcessRecordCB &callback)
{
HIPERF_BUF_ALIGN static uint8_t buf[RECORD_SIZE_LIMIT_SPE];
uint64_t remainingSize = dataSection_.size;
size_t recordNumber = 0;
while (remainingSize > 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;
}
size_t headerSize = sizeof(perf_event_header);
if (remainingSize >= header->size && Read(buf + headerSize, header->size - headerSize)) {
size_t speSize = 0;
if (header->type == PERF_RECORD_AUXTRACE) {
struct PerfRecordAuxtraceData *auxtrace =
reinterpret_cast<struct PerfRecordAuxtraceData *>(header + 1);
speSize = auxtrace->size;
if (speSize > 0) {
Read(buf + header->size, auxtrace->size);
}
}
uint8_t *data = buf;
PerfEventRecord& record = PerfEventRecordFactory::GetPerfEventRecord(
static_cast<perf_event_type>(header->type), data, defaultEventAttr_);
CHECK_TRUE(record.GetName() != nullptr, true, 0, "");
remainingSize = remainingSize - header->size - speSize;
callback(record);
recordNumber++;
} else {
HLOGW("not enough header->size.");
}
}
HLOGD("read back %zu records", recordNumber);
return true;
}
bool PerfFileWriter::Read(void *buf, size_t len)
{
HLOG_ASSERT(buf != nullptr);
HLOG_ASSERT(fp_ != nullptr);
HLOG_ASSERT(len > 0);
CHECK_TRUE(fread(buf, len, 1, fp_) == 1, false, 1, "failed to read file");
return true;
}
void PerfFileWriter::SetWriteRecordStat(bool isWrite)
{
isWritingRecord = isWrite;
}
uint64_t PerfFileWriter::GetDataSize() const
{
return dataSection_.size;
}
uint PerfFileWriter::GetRecordCount() const
{
return recordCount_;
}
bool PerfFileWriter::GetFilePos(uint64_t &pos) const
{
off_t offset = ftello(fp_);
if (offset == -1) {
HLOGD("RecordFileWriter ftello fail");
return false;
}
pos = static_cast<uint64_t>(offset);
return true;
}
bool PerfFileWriter::Write(const void *buf, size_t len)
{
#ifdef HIPERF_DEBUG_TIME
const auto startTime = steady_clock::now();
#endif
CHECK_TRUE(len == 0u || fwrite(buf, len, 1, fp_) == 1, false, 1, "PerfFileWriter fwrite fail ");
#ifdef HIPERF_DEBUG_TIME
writeTimes_ += duration_cast<microseconds>(steady_clock::now() - startTime);
#endif
return true;
}
bool PerfFileWriter::WriteAttrAndId(const std::vector<AttrWithId> &attrIds, bool isSpe)
{
CHECK_TRUE(!attrIds.empty(), false, 0, "");
if (fp_ == nullptr) {
return false;
} else if (fseek(fp_, header_.size, SEEK_SET) == -1) {
return false;
}
uint64_t idSectionOffset;
CHECK_TRUE(GetFilePos(idSectionOffset), false, 0, "");
HLOGD("attrIds %zu", attrIds.size());
for (auto &attrId : attrIds) {
HLOGD(" attrIds ids %zu", attrId.ids.size());
CHECK_TRUE(Write(attrId.ids.data(), attrId.ids.size() * sizeof(uint64_t)), false, 0, "");
}
uint64_t attrSectionOffset;
CHECK_TRUE(GetFilePos(attrSectionOffset), false, 0, "");
for (auto &attrId : attrIds) {
perf_file_attr fileAttr;
fileAttr.attr = attrId.attr;
fileAttr.ids.offset = idSectionOffset;
fileAttr.ids.size = attrId.ids.size() * sizeof(uint64_t);
idSectionOffset += fileAttr.ids.size;
if (!Write(&fileAttr, sizeof(fileAttr))) {
return false;
}
}
uint64_t dataSectionOffset;
if (!GetFilePos(dataSectionOffset)) {
return false;
}
attrSection_.offset = attrSectionOffset;
attrSection_.size = dataSectionOffset - attrSectionOffset;
dataSection_.offset = dataSectionOffset;
defaultEventAttr_ = attrIds[0].attr;
if (!WriteAuxTraceEvent(isSpe)) {
HLOGE("WriteAuxTraceEvent failed");
return false;
}
isWritingRecord = true;
return true;
}
bool PerfFileWriter::WriteTimeConvEvent()
{
perf_event_header header;
PerfRecordTtimeConvData auxTimeConv;
header.type = PERF_RECORD_TIME_CONV;
header.misc = PERF_RECORD_MISC_KERNEL;
header.size = static_cast<uint16_t>(sizeof(perf_event_header) + sizeof(PerfRecordTtimeConvData));
constexpr uint32_t timeShift = 21;
auxTimeConv.time_shift = timeShift;
auxTimeConv.time_mult = 1;
auxTimeConv.time_zero = 1;
auxTimeConv.time_cycles = 1;
auxTimeConv.cap_user_time_zero = 1;
auxTimeConv.cap_user_time_short = 1;
if (!Write(&header, sizeof(header))) {
return false;
}
if (!Write(&auxTimeConv, sizeof(auxTimeConv))) {
return false;
}
dataSection_.size = dataSection_.size + sizeof(header) + sizeof(auxTimeConv);
return true;
}
bool PerfFileWriter::WriteAuxTraceInfoEvent()
{
perf_event_header header;
PerfRecordAuxtraceInfoData auxTraceEvent;
header.type = PERF_RECORD_AUXTRACE_INFO;
header.misc = PERF_RECORD_MISC_KERNEL;
header.size = static_cast<uint16_t>(sizeof(perf_event_header) + sizeof(PerfRecordAuxtraceInfoData));
constexpr uint32_t auxTraceType = 4;
constexpr uint64_t armSpe = 7;
constexpr uint64_t cpuMmaps = 2;
auxTraceEvent.type = auxTraceType;
auxTraceEvent.priv[0] = armSpe;
auxTraceEvent.priv[1] = cpuMmaps;
if (!Write(&header, sizeof(header))) {
return false;
}
if (!Write(&auxTraceEvent, sizeof(auxTraceEvent))) {
return false;
}
dataSection_.size = dataSection_.size + sizeof(header) + sizeof(auxTraceEvent);
return true;
}
bool PerfFileWriter::WriteCpuMapEvent()
{
perf_event_header header;
PerfRecordCpuMapData cpuMap;
header.type = PERF_RECORD_CPU_MAP;
header.misc = PERF_RECORD_MISC_KERNEL;
header.size = static_cast<uint16_t>(sizeof(perf_event_header) + sizeof(PerfRecordCpuMapData));
constexpr uint32_t nrNum = 16;
cpuMap.nr = nrNum;
for (uint i = 0; i < cpuMap.nr; i++) {
cpuMap.cpu[i] = i;
}
if (!Write(&header, sizeof(header))) {
return false;
}
if (!Write(&cpuMap, sizeof(cpuMap))) {
return false;
}
dataSection_.size = dataSection_.size + sizeof(header) + sizeof(cpuMap);
return true;
}
bool PerfFileWriter::WriteAuxTraceEvent(bool isSpe)
{
if (!isSpe) {
return true;
}
if (!WriteTimeConvEvent()) {
HLOGE("WriteTimeConvEvent failed");
return false;
}
if (!WriteAuxTraceInfoEvent()) {
HLOGE("WriteAuxTraceInfoEvent failed");
return false;
}
if (!WriteCpuMapEvent()) {
HLOGE("WriteCpuMapEvent failed");
return false;
}
return true;
}
static bool LeftLessRight(const std::unique_ptr<PerfFileSection> &l,
const std::unique_ptr<PerfFileSection> &r)
{
CHECK_TRUE(l != nullptr && r != nullptr, false, 0, "");
return l->featureId_ < r->featureId_;
}
bool PerfFileWriter::WriteHeader()
{
header_.attrSize = sizeof(perf_file_attr);
header_.attrs = attrSection_;
header_.data = dataSection_;
header_.eventTypes.size = 0;
header_.eventTypes.offset = 0;
if (fseek(fp_, 0, SEEK_SET) == -1) {
HLOGD("fseek return error ");
return false;
}
CHECK_TRUE(Write(&header_, sizeof(header_)), false, 0, "");
return true;
}
bool PerfFileWriter::WriteFeatureData()
{
long featureOffset = 0;
if (fseek(fp_, 0, SEEK_END) != 0) {
HLOGD("fseek SEEK_END return error ");
return false;
}
CHECK_TRUE((featureOffset = ftell(fp_)) != -1, false, 1, "ftell return error ");
for (size_t i = 0; i < sizeof(header_.features); i++) {
if (header_.features[i] != 0) {
HLOGV(" features['%zu'] '0x%x'", i, header_.features[i]);
}
}
unsigned long contentOffset = featureOffset + featureSections_.size() * sizeof(perf_file_section);
HLOGV("features start at file '0x%lx' content at '0x%lx'", featureOffset, contentOffset);
std::sort(featureSections_.begin(), featureSections_.end(), LeftLessRight);
int i = 0;
for (auto &featureSection : featureSections_) {
featureSection->header.offset = contentOffset;
featureSection->header.size = featureSection->GetSize();
contentOffset += featureSection->header.size;
HLOGV("save features[%d] head offset '0x%" PRIx64 "' size '0x%" PRIx64 "'", i,
featureSection->header.offset, featureSection->header.size);
i++;
CHECK_TRUE(Write(&featureSection->header, sizeof(featureSection->header)), false, 0, "");
}
long offset = ftell(fp_);
CHECK_TRUE(offset >= 0, false, 0, "");
HLOGV("features data at file '0x%lx'", offset);
i = 0;
for (auto &featureSection : featureSections_) {
std::vector<char> buf(featureSection->header.size);
featureSection->GetBinary(&buf[0], featureSection->header.size);
HLOGV("save features[%d] content size '0x%" PRIx64 "'", i, featureSection->header.size);
i++;
if (!Write(&buf[0], featureSection->header.size)) {
HLOGE("write failed %" PRIu64 ".", featureSection->header.size);
return false;
}
}
return true;
}
bool PerfFileWriter::AddNrCpusFeature(FEATURE feature, uint32_t nrCpusAvailable,
uint32_t nrCpusOnline)
{
HLOGV("add feature '%s': nrCpusAvailable %u, nrCpusOnline %u",
PerfFileSection::GetFeatureName(FEATURE::NRCPUS).c_str(), nrCpusAvailable, nrCpusOnline);
featureSections_.emplace_back(
std::make_unique<PerfFileSectionNrCpus>(feature, nrCpusAvailable, nrCpusOnline));
header_.features[static_cast<int>(FEATURE::NRCPUS) / BITS_IN_BYTE] |=
1 << (static_cast<int>(FEATURE::NRCPUS) % BITS_IN_BYTE);
return true;
}
bool PerfFileWriter::AddEventDescFeature(FEATURE feature,
const std::vector<AttrWithId> &eventDesces)
{
HLOGV("add feature '%s' %zu", PerfFileSection::GetFeatureName(FEATURE::EVENT_DESC).c_str(),
eventDesces.size());
featureSections_.emplace_back(std::make_unique<PerfFileSectionEventDesc>(feature, eventDesces));
header_.features[static_cast<int>(FEATURE::EVENT_DESC) / BITS_IN_BYTE] |=
1 << (static_cast<int>(FEATURE::EVENT_DESC) % BITS_IN_BYTE);
return true;
}
bool PerfFileWriter::AddStringFeature(FEATURE feature, const std::string& string)
{
HLOGV("add feature '%s' string '%s'", PerfFileSection::GetFeatureName(feature).c_str(),
string.c_str());
featureSections_.emplace_back(std::make_unique<PerfFileSectionString>(feature, string));
header_.features[static_cast<int>(feature) / BITS_IN_BYTE] |= 1 << (static_cast<int>(feature) % BITS_IN_BYTE);
return true;
}
bool PerfFileWriter::AddBoolFeature(const FEATURE feature)
{
return AddU64Feature(feature, 1u);
}
bool PerfFileWriter::AddU64Feature(const FEATURE feature, const uint64_t v)
{
HLOGV("add feature '%s' uint64_t '%" PRIu64 "'",
PerfFileSection::GetFeatureName(feature).c_str(), v);
featureSections_.emplace_back(std::make_unique<PerfFileSectionU64>(feature, v));
header_.features[static_cast<int>(feature) / BITS_IN_BYTE] |= 1 << (static_cast<int>(feature) % BITS_IN_BYTE);
return true;
}
bool PerfFileWriter::AddUniStackTableFeature(const ProcessStackMap *table)
{
const FEATURE feature = FEATURE::HIPERF_FILES_UNISTACK_TABLE;
featureSections_.emplace_back(
std::make_unique<PerfFileSectionUniStackTable>(feature, table));
header_.features[static_cast<int>(feature) / BITS_IN_BYTE] |= 1 << (static_cast<int>(feature) % BITS_IN_BYTE);
return true;
}
bool PerfFileWriter::AddSymbolsFeature(
const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)
{
const FEATURE feature = FEATURE::HIPERF_FILES_SYMBOL;
HLOGV("add feature symbolsFiles %zu", symbolsFiles.size());
std::vector<SymbolFileStruct> symbolFileStructs {};
for (auto &symbolsFile : symbolsFiles) {
HLOGV("add feature symbolsFile %s", symbolsFile->filePath_.c_str());
if (symbolsFile->SymbolsLoaded()) {
auto &symbolsFileStruct = symbolFileStructs.emplace_back();
symbolsFile->ExportSymbolToFileFormat(symbolsFileStruct);
symbolsFile->ReleaseSymbols();
}
}
featureSections_.emplace_back(
std::make_unique<PerfFileSectionSymbolsFiles>(feature, symbolFileStructs));
header_.features[static_cast<int>(feature) / BITS_IN_BYTE] |= 1 << (static_cast<int>(feature) % BITS_IN_BYTE);
return true;
}
}
}
}