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
20using namespace Proto;
21namespace OHOS {
22namespace Developtools {
23namespace HiPerf {
24// output
25ReportProtobufFileWriter::~ReportProtobufFileWriter()
26{
27    Close();
28}
29
30void 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
41void 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
53bool 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
69bool 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
96bool ReportProtobufFileWriter::IsOpen()
97{
98    return protobufFileStream_->is_open();
99}
100
101bool 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
117bool 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}
143bool 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
159bool ReportProtobufFileWriter::ProcessRecord(const PerfRecordLost &recordLost)
160{
161    recordLost.DumpLog(__FUNCTION__);
162    recordLost_ += recordLost.data_.lost;
163    return true;
164}
165
166bool 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
181bool 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
203int 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
226bool 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
245bool 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
297bool 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
329bool 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
341bool 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}
355bool 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}
369bool 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}
380bool 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}
398bool ReportProtobufFileReader::IsOpen()
399{
400    return protobufFileStream_->is_open();
401}
402} // namespace HiPerf
403} // namespace Developtools
404} // namespace OHOS
405