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 #define HILOG_TAG "Protobuf"
16 
17 #include "report_protobuf_file.h"
18 #include "utilities.h"
19 
20 using namespace Proto;
21 namespace OHOS {
22 namespace Developtools {
23 namespace HiPerf {
24 // output
~ReportProtobufFileWriter()25 ReportProtobufFileWriter::~ReportProtobufFileWriter()
26 {
27     Close();
28 }
29 
BeforeClose()30 void ReportProtobufFileWriter::BeforeClose()
31 {
32     HiperfRecord record;
33     SampleStatistic *message = record.mutable_statistic();
34 
35     message->set_count(recordCount_);
36     message->set_lost(recordLost_);
37     protpbufCodedOutputStream_->WriteLittleEndian32(record.ByteSizeLong());
38     record.SerializeToCodedStream(protpbufCodedOutputStream_.get());
39 }
40 
Close()41 void ReportProtobufFileWriter::Close()
42 {
43     if (protobufFileStream_->is_open()) {
44         BeforeClose();
45         // write 0 as end
46         protpbufCodedOutputStream_->WriteLittleEndian32(0);
47         protpbufCodedOutputStream_.reset(nullptr);
48         protpbufOutputStream_.reset(nullptr);
49         protobufFileStream_->close();
50     }
51 }
52 
Write(const void *buffer, int size)53 bool ReportProtobufFileWriter::Write(const void *buffer, int size)
54 {
55     if (protobufFileStream_->is_open()) {
56         try {
57             protobufFileStream_->write(static_cast<const char *>(buffer), size);
58             HLOGM("writed %d bytes", size);
59             return true;
60         } catch (std::ofstream::failure &writeErr) {
61             HLOGE("write file failed %s", fileName_.c_str());
62         }
63     } else {
64         printf("no file open for write (request %d bytes).\n", size);
65     }
66     return false;
67 }
68 
Create(std::string fileName)69 bool ReportProtobufFileWriter::Create(std::string fileName)
70 {
71     fileName_ = fileName;
72     try {
73         protobufFileStream_->exceptions(std::ofstream::failbit | std::ofstream::badbit |
74                                         std::ofstream::eofbit);
75         std::string resolvedPath = CanonicalizeSpecPath(fileName_.c_str());
76         protobufFileStream_->open(resolvedPath.c_str(),
77                                   std::fstream::out | std::fstream::trunc | std::fstream::binary);
78         protpbufOutputStream_ =
79             std::make_unique<google::protobuf::io::CopyingOutputStreamAdaptor>(this);
80         protpbufCodedOutputStream_ =
81             std::make_unique<google::protobuf::io::CodedOutputStream>(protpbufOutputStream_.get());
82 
83         printf("open proto buf file succeed.\n");
84 
85         Write(FILE_MAGIC, sizeof(FILE_MAGIC) - 1);
86         Write(&FILE_VERSION, sizeof(FILE_VERSION));
87 
88         printf("create proto buf file succeed.\n");
89         return true;
90     } catch (const std::fstream::failure &e) {
91         printf("open proto buf file faild. %s\n", e.what());
92     }
93     return false;
94 }
95 
IsOpen()96 bool ReportProtobufFileWriter::IsOpen()
97 {
98     return protobufFileStream_->is_open();
99 }
100 
ProcessRecord(const PerfEventRecord &record)101 bool ReportProtobufFileWriter::ProcessRecord(const PerfEventRecord &record)
102 {
103     HLOGM("ProcessRecord %d", record.GetType());
104     if (record.GetType() == PERF_RECORD_SAMPLE) {
105         HLOGF("record.GetType() == PERF_RECORD_SAMPLE");
106     } else if (record.GetType() == PERF_RECORD_LOST_SAMPLES) {
107         ProcessRecord(*static_cast<const PerfRecordLost *>(&record));
108     } else if (record.GetType() == PERF_RECORD_COMM) {
109         ProcessRecord(*static_cast<const PerfRecordComm *>(&record));
110     } else {
111         HLOGM("skip record type %d", record.GetType());
112         return false;
113     }
114     return true;
115 }
116 
ProcessSampleRecord( const PerfRecordSample &recordSample, uint32_t configIndex, const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)117 bool ReportProtobufFileWriter::ProcessSampleRecord(
118     const PerfRecordSample &recordSample, uint32_t configIndex,
119     const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)
120 {
121     HiperfRecord record;
122     CallStackSample *sample = record.mutable_sample();
123     sample->set_time(recordSample.data_.time);
124     sample->set_tid(recordSample.data_.tid);
125     for (const DfxFrame &frame : recordSample.callFrames_) {
126         auto callframe = sample->add_callstackframe();
127         callframe->set_symbols_vaddr(frame.funcOffset);
128         callframe->set_loaded_vaddr(frame.pc - frame.mapOffset);
129         if (frame.symbolFileIndex >= 0) {
130             callframe->set_symbols_file_id(frame.symbolFileIndex);
131             callframe->set_function_name_id(frame.index);
132         }
133     }
134     sample->set_event_count(recordSample.data_.period);
135     sample->set_config_name_id(configIndex);
136 
137     protpbufCodedOutputStream_->WriteLittleEndian32(record.ByteSizeLong());
138     record.SerializeToCodedStream(protpbufCodedOutputStream_.get());
139     recordCount_++;
140 
141     return true;
142 }
ProcessReportInfo(const std::vector<std::string> &configNames, const std::string &workloadCmd)143 bool ReportProtobufFileWriter::ProcessReportInfo(const std::vector<std::string> &configNames,
144                                                  const std::string &workloadCmd)
145 {
146     HiperfRecord record;
147     ReportInfo *info = record.mutable_info();
148     HLOGV("configNames:%zu", configNames.size());
149     for (auto configName : configNames) {
150         info->add_config_name(configName);
151     }
152     info->set_workload_cmd(workloadCmd);
153 
154     protpbufCodedOutputStream_->WriteLittleEndian32(record.ByteSizeLong());
155     record.SerializeToCodedStream(protpbufCodedOutputStream_.get());
156     return true;
157 }
158 
ProcessRecord(const PerfRecordLost &recordLost)159 bool ReportProtobufFileWriter::ProcessRecord(const PerfRecordLost &recordLost)
160 {
161     recordLost.DumpLog(__FUNCTION__);
162     recordLost_ += recordLost.data_.lost;
163     return true;
164 }
165 
ProcessRecord(const PerfRecordComm &recordComm)166 bool ReportProtobufFileWriter::ProcessRecord(const PerfRecordComm &recordComm)
167 {
168     recordComm.DumpLog(__FUNCTION__);
169     HiperfRecord record;
170     VirtualThreadInfo *thread = record.mutable_thread();
171 
172     thread->set_tid(recordComm.data_.tid);
173     thread->set_pid(recordComm.data_.pid);
174     thread->set_name(recordComm.data_.comm);
175 
176     protpbufCodedOutputStream_->WriteLittleEndian32(record.ByteSizeLong());
177     record.SerializeToCodedStream(protpbufCodedOutputStream_.get());
178     return true;
179 }
180 
ProcessSymbolsFiles( const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)181 bool ReportProtobufFileWriter::ProcessSymbolsFiles(
182     const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)
183 {
184     uint32_t id = 0;
185     for (auto &symbolsFile : symbolsFiles) {
186         HiperfRecord record;
187         SymbolTableFile *message = record.mutable_file();
188 
189         message->set_id(id++);
190         message->set_path(symbolsFile->filePath_);
191 
192         for (auto &symbol : symbolsFile->GetSymbols()) {
193             message->add_function_name(symbol.GetName().data());
194         }
195 
196         protpbufCodedOutputStream_->WriteLittleEndian32(record.ByteSizeLong());
197         record.SerializeToCodedStream(protpbufCodedOutputStream_.get());
198     }
199     return true;
200 }
201 
202 // input
Read(void *buffer, int size)203 int ReportProtobufFileReader::Read(void *buffer, int size)
204 {
205     if (protobufFileStream_->is_open()) {
206         try {
207             protobufFileStream_->read(static_cast<char *>(buffer), size);
208             HLOGM("readed %d bytes", size);
209             return size;
210         } catch (std::ifstream::failure &readErr) {
211             if (protobufFileStream_->eof()) {
212                 // this is not a issue
213                 HLOGW("read file %d bytes failed eof. only return %zu\n", size,
214                       protobufFileStream_->gcount());
215 
216                 return protobufFileStream_->gcount();
217             }
218             printf("read file %d bytes failed %s : %s\n", size, fileName_.c_str(), readErr.what());
219         }
220     } else {
221         printf("no file open for read (request %d bytes).\n", size);
222     }
223     return 0;
224 }
225 
CheckFileMagic()226 bool ReportProtobufFileReader::CheckFileMagic()
227 {
228     char fileMagic[sizeof(FILE_MAGIC)] = {0};
229     Read(fileMagic, sizeof(FILE_MAGIC) - 1);
230     if (memcmp(fileMagic, FILE_MAGIC, sizeof(FILE_MAGIC) - 1) != 0) {
231         printf("file magic is NOT correct. %s: %x\n", fileMagic, fileMagic[0]);
232         return false;
233     }
234 
235     uint16_t version = 0;
236     Read(&version, sizeof(version));
237     if (version != FILE_VERSION) {
238         printf("file version is NOT correct.\n");
239         return false;
240     }
241 
242     return true;
243 }
244 
Dump(std::string fileName, ProtobufReadBack readBack)245 bool ReportProtobufFileReader::Dump(std::string fileName, ProtobufReadBack readBack)
246 {
247     const int defaultIndent = 0;
248     fileName_ = fileName;
249     try {
250         protobufFileStream_->exceptions(std::ifstream::failbit | std::ifstream::badbit);
251         std::string resolvedPath = CanonicalizeSpecPath(fileName_.c_str());
252         protobufFileStream_->open(resolvedPath.c_str(), std::fstream::in | std::fstream::binary);
253         printf("open proto buf file succeed.\n");
254         if (!CheckFileMagic()) {
255             return false;
256         }
257         protpbufInputStream_ = std::make_unique<google::protobuf::io::CopyingInputStreamAdaptor>(this);
258         protpbufCodedInputStream_ =
259             std::make_unique<google::protobuf::io::CodedInputStream>(protpbufInputStream_.get());
260         uint32_t recordLength = 0;
261         do {
262             protpbufCodedInputStream_->ReadLittleEndian32(&recordLength);
263             if (recordLength != 0) {
264                 PRINT_INDENT(defaultIndent, "record length:%u (%x)\n", recordLength, recordLength);
265                 HiperfRecord record;
266                 std::string recordBuf;
267                 recordBuf.resize(recordLength);
268                 if (!protpbufCodedInputStream_->ReadString(&recordBuf, recordLength)) {
269                     printf("read record error\n");
270                     return false;
271                 }
272                 if (!record.ParseFromString(recordBuf)) {
273                     printf("parse format error\n");
274                     return false;
275                 } else {
276                     if (readBack == nullptr) {
277                         PRINT_INDENT(defaultIndent, "\n");
278                         Dump(record, defaultIndent);
279                     } else {
280                         readBack(record);
281                     }
282                 }
283             } else {
284                 if (readBack == nullptr) {
285                     printf("no more record\n");
286                 }
287                 break;
288             }
289         } while (recordLength != 0);
290         return true;
291     } catch (const std::fstream::failure &e) {
292         HLOGE("open proto buf file faild. %s\n", e.what());
293     }
294     return false;
295 }
296 
Dump(const CallStackSample &message, int indent)297 bool ReportProtobufFileReader::Dump(const CallStackSample &message, int indent)
298 {
299     PRINT_INDENT(indent, "%s:\n", message.GetTypeName().c_str());
300     if (message.has_time()) {
301         PRINT_INDENT(indent + 1, "time:%" PRId64 "\n", message.time());
302     }
303     if (message.has_tid()) {
304         PRINT_INDENT(indent + 1, "tid:%u\n", message.tid());
305     }
306     for (int i = 0; i < message.callstackframe_size(); i++) {
307         PRINT_INDENT(indent + 1, "%d:\n", i);
308         auto &callframe = message.callstackframe(i);
309         if (callframe.has_symbols_vaddr()) {
310             PRINT_INDENT(indent + INDENT_TWO, "symbols_vaddr: 0x%" PRIx64 " \n",
311                         callframe.symbols_vaddr());
312         }
313         if (callframe.has_symbols_file_id()) {
314             PRINT_INDENT(indent + INDENT_TWO, "symbols_file_id: %u\n", callframe.symbols_file_id());
315         }
316         if (callframe.has_function_name_id()) {
317             PRINT_INDENT(indent + INDENT_TWO, "function_name_id: %d\n", callframe.function_name_id());
318         }
319     }
320     if (message.has_event_count()) {
321         PRINT_INDENT(indent + 1, "event_count:%" PRIu64 "\n", message.event_count());
322     }
323     if (message.has_config_name_id()) {
324         PRINT_INDENT(indent + 1, "config_name_id:%u\n", message.config_name_id());
325     }
326     return true;
327 }
328 
Dump(const SampleStatistic &message, int indent)329 bool ReportProtobufFileReader::Dump(const SampleStatistic &message, int indent)
330 {
331     PRINT_INDENT(indent, "%s:\n", message.GetTypeName().c_str());
332     if (message.has_count()) {
333         PRINT_INDENT(indent + 1, "count:%" PRIu64 "\n", message.count());
334     }
335     if (message.has_lost()) {
336         PRINT_INDENT(indent + 1, "lost:%" PRIu64 "\n", message.lost());
337     }
338     return false;
339 }
340 
Dump(const SymbolTableFile &message, int indent)341 bool ReportProtobufFileReader::Dump(const SymbolTableFile &message, int indent)
342 {
343     PRINT_INDENT(indent, "%s:\n", message.GetTypeName().c_str());
344     if (message.has_id()) {
345         PRINT_INDENT(indent + 1, "id: %u\n", message.id());
346     }
347     if (message.has_path()) {
348         PRINT_INDENT(indent + 1, "path: %s\n", message.path().c_str());
349     }
350     for (int i = 0; i < message.function_name_size(); i++) {
351         PRINT_INDENT(indent + INDENT_TWO, "%d:%s\n", i, message.function_name(i).c_str());
352     }
353     return false;
354 }
Dump(const VirtualThreadInfo &message, int indent)355 bool ReportProtobufFileReader::Dump(const VirtualThreadInfo &message, int indent)
356 {
357     PRINT_INDENT(indent, "%s:\n", message.GetTypeName().c_str());
358     if (message.has_pid()) {
359         PRINT_INDENT(indent + 1, "pid:%u\n", message.pid());
360     }
361     if (message.has_tid()) {
362         PRINT_INDENT(indent + 1, "tid:%u\n", message.tid());
363     }
364     if (message.has_pid()) {
365         PRINT_INDENT(indent + 1, "name:%s\n", message.name().c_str());
366     }
367     return false;
368 }
Dump(const ReportInfo &message, int indent)369 bool ReportProtobufFileReader::Dump(const ReportInfo &message, int indent)
370 {
371     PRINT_INDENT(indent, "%s:\n", message.GetTypeName().c_str());
372     for (int i = 0; i < message.config_name_size(); i++) {
373         PRINT_INDENT(indent + 1, "config_name:%d:%s\n", i, message.config_name(i).c_str());
374     }
375     if (message.has_workload_cmd()) {
376         PRINT_INDENT(indent + 1, "workload:%s\n", message.workload_cmd().c_str());
377     }
378     return true;
379 }
Dump(const HiperfRecord &record, int indent)380 bool ReportProtobufFileReader::Dump(const HiperfRecord &record, int indent)
381 {
382     PRINT_INDENT(indent, "%s:\n", record.GetTypeName().c_str());
383     if (record.has_sample()) {
384         return Dump(record.sample(), indent + 1);
385     } else if (record.has_statistic()) {
386         return Dump(record.statistic(), indent + 1);
387     } else if (record.has_file()) {
388         return Dump(record.file(), indent + 1);
389     } else if (record.has_thread()) {
390         return Dump(record.thread(), indent + 1);
391     } else if (record.has_info()) {
392         return Dump(record.info(), indent + 1);
393     } else {
394         printf("unknow proto buf format\n");
395         return false;
396     }
397 }
IsOpen()398 bool ReportProtobufFileReader::IsOpen()
399 {
400     return protobufFileStream_->is_open();
401 }
402 } // namespace HiPerf
403 } // namespace Developtools
404 } // namespace OHOS
405