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 "Report"
16
17#include <set>
18#include "report_json_file.h"
19
20#if defined(is_mingw) && is_mingw
21#include <windows.h>
22#else
23#include <sys/ioctl.h>
24#endif
25
26#include "hiperf_hilog.h"
27
28namespace OHOS {
29namespace Developtools {
30namespace HiPerf {
31bool ReportJsonFile::debug_ = false;
32
33void ReportJsonFile::AddNewFunction(int libId, std::string name)
34{
35    auto it = functionMap_.find(libId);
36    if (it == functionMap_.end()) {
37        it = functionMap_.try_emplace(libId).first;
38    }
39    it->second.insert_or_assign(name, ReportFuncMapItem(libId, name, functionId_++));
40}
41
42void ReportJsonFile::OutputJsonFunctionMap(FILE *output)
43{
44    std::string key = "SymbolMap";
45    if (fprintf(output, "\"%s\":{", key.c_str()) != -1) {
46        bool first = true;
47
48        for (const auto& [libId, funcMap] : functionMap_) {
49            for (const auto& [_, reportFuncMapItem] : funcMap) {
50                OutputJsonPair(output, reportFuncMapItem.reportFuncId_, reportFuncMapItem, first);
51                first = false;
52            }
53        }
54
55        fprintf(output, "}");
56    }
57}
58
59void ReportJsonFile::ProcessSymbolsFiles(
60    const std::vector<std::unique_ptr<SymbolsFile>> &symbolsFiles)
61{
62    auto symbolsFileIt = symbolsFiles.begin();
63    while (symbolsFileIt != symbolsFiles.end()) {
64        size_t libId = libList_.size();
65        libList_.emplace_back(symbolsFileIt->get()->filePath_);
66        const auto &symbols = symbolsFileIt->get()->GetSymbols();
67        auto symbolIt = symbols.begin();
68        while (symbolIt != symbols.end()) {
69            AddNewFunction(libId, std::string(symbolIt->GetName()));
70            symbolIt++;
71        }
72        symbolsFileIt++;
73    }
74}
75
76void ReportJsonFile::UpdateCallNodeEventCount()
77{
78    for (auto &config : reportConfigItems_) {
79        HLOGV("Config %s", config.second.eventName_.c_str());
80        for (auto &process : config.second.processes_) {
81            for (auto &thread : process.second.threads_) {
82                thread.second.callNode.UpdateChildrenEventCount();
83                thread.second.callNodeReverse.UpdateChildrenEventCount();
84            }
85        }
86    }
87}
88
89ReportConfigItem &ReportJsonFile::GetConfig(uint64_t id)
90{
91    for (auto &configpair : reportConfigItems_) {
92        if (find(configpair.first.begin(), configpair.first.end(), id) != configpair.first.end()) {
93            return configpair.second;
94        }
95    }
96    HLOGE("unable found config item for config id %" PRIu64 "", id);
97    // return default one
98    return reportConfigItems_.begin()->second;
99}
100
101int ReportJsonFile::GetFunctionID(int libId, const std::string &function)
102{
103    auto functionMapIt = functionMap_.find(libId);
104    if (functionMapIt == functionMap_.end()) {
105        functionMapIt = functionMap_.try_emplace(libId).first;
106    }
107    auto funcMapIt = functionMapIt->second.find(function);
108    if (funcMapIt == functionMapIt->second.end()) {
109        HLOGW("'%s' not found in function list in lib %d", function.data(), libId);
110        // make a new function for unknown name
111        AddNewFunction(libId, function);
112        // return the last index
113        return functionId_ - 1;
114    }
115    return funcMapIt->second.reportFuncId_;
116}
117
118void ReportJsonFile::UpdateReportSample(uint64_t id, pid_t pid, pid_t tid, uint64_t eventCount)
119{
120    auto &config = GetConfig(id);
121
122    config.eventCount_ += eventCount;
123    auto &process = GetOrCreateMapItem(config.processes_, pid);
124    process.eventCount_ += eventCount;
125    auto &thread = GetOrCreateMapItem(process.threads_, tid);
126    thread.eventCount_ += eventCount;
127    thread.sampleCount_++;
128    sampleCount_++;
129}
130
131void ReportJsonFile::AddReportCallStack(uint64_t eventCount, ReportCallNodeItem &callNode,
132                                        const std::vector<DfxFrame> &frames)
133{
134    std::map<int, ReportCallNodeItem> *child = &callNode.childrenMap;
135    auto it = frames.begin();
136    while (it != frames.end()) {
137        int libId = GetLibID(it->mapName);
138        if (libId >= 0) {
139            int funcId = GetFunctionID(libId, it->funcName);
140            // new children funid
141            ReportCallNodeItem &grandchildren = GetOrCreateMapItem(*child, funcId);
142            if (debug_) {
143                grandchildren.nodeIndex_ = nodeIndex_++;
144                grandchildren.funcName_ = it->funcName;
145                grandchildren.reverseCaller_ = true;
146            }
147            // only the last one need count
148            if (it + 1 == frames.end()) {
149                grandchildren.selfEventCount_ += eventCount;
150            }
151            // new children's children
152            child = &grandchildren.childrenMap;
153
154            HLOGV("add child %*s %d-%d %s @%d %s", static_cast<int>(it - frames.begin()), "", libId,
155                  funcId, it->funcName.data(), grandchildren.nodeIndex_, it->mapName.data());
156        } else {
157            HLOGV("add child failed at %s", it->ToSymbolString().c_str());
158        }
159        it++;
160    }
161}
162
163void ReportJsonFile::AddReportCallStackReverse(uint64_t eventCount, ReportCallNodeItem &callNode,
164                                               const std::vector<DfxFrame> &frames)
165{
166    std::map<int, ReportCallNodeItem> *child = &callNode.childrenMap;
167    auto it = frames.rbegin();
168    while (it != frames.rend()) {
169        int libId = GetLibID(it->mapName);
170        if (libId >= 0) {
171            int funcId = GetFunctionID(libId, it->funcName);
172            // new children funid
173            ReportCallNodeItem &grandchildren = GetOrCreateMapItem(*child, funcId);
174            if (debug_) {
175                grandchildren.nodeIndex_ = nodeIndex_++;
176                grandchildren.funcName_ = it->funcName;
177            }
178            // only the last one need count
179            if (it + 1 == frames.rend()) {
180                grandchildren.selfEventCount_ += eventCount;
181            }
182            // new children's children
183            child = &grandchildren.childrenMap;
184
185            HLOGV("add child %*s %d-%d %s @%d %s", static_cast<int>(it - frames.rbegin()), "",
186                  libId, funcId, it->funcName.data(), grandchildren.nodeIndex_,
187                  it->mapName.data());
188        } else {
189            HLOGV("add child failed at %s", it->ToSymbolString().c_str());
190        }
191        it++;
192    }
193}
194
195uint32_t ReportJsonFile::GetConfigIndex(uint64_t id)
196{
197    return GetConfig(id).index_;
198}
199
200std::string ReportJsonFile::GetConfigName(uint64_t id)
201{
202    auto &config = GetConfig(id);
203    return config.eventName_;
204}
205
206int ReportJsonFile::GetLibID(std::string_view filepath)
207{
208    auto it = find(libList_.begin(), libList_.end(), filepath);
209    if (it != libList_.end()) {
210        return it - libList_.begin();
211    } else {
212        HLOGE("'%s' not found in lib list", filepath.data());
213        return -1;
214    }
215}
216
217void ReportJsonFile::UpdateReportCallStack(uint64_t id, pid_t pid, pid_t tid, uint64_t eventCount,
218                                           std::vector<DfxFrame> &frames)
219{
220    auto &config = GetConfig(id);
221    std::set<int> RepeatFunctionId;
222    CHECK_TRUE(frames.size() == 0, NO_RETVAL, 0, ""); // do nothing with no frame
223    auto &process = GetOrCreateMapItem(config.processes_, pid);
224    auto &thread = GetOrCreateMapItem(process.threads_, tid);
225    auto it = frames.begin();
226    bool jsFrame = StringEndsWith(it->mapName, "stub.an");
227    size_t skipFrame = 0;
228    while (it != frames.end()) {
229        int libId = GetLibID(it->mapName);
230        if (libId < 0) {
231            HLOGW("not found lib path %s", it->mapName.data());
232            it++;
233            continue;
234        }
235        ReportLibItem &lib = thread.libs_[libId];
236        lib.libId_ = libId;
237        int funcId = GetFunctionID(libId, it->funcName);
238        // we will always have a funId, it will create a new one if not found
239        // so that we can see abc+0x123 in the html
240        HLOG_ASSERT(funcId >= 0);
241        if (RepeatFunctionId.count(funcId) != 0) {
242            it++;
243            continue;
244        } else {
245            RepeatFunctionId.emplace(funcId);
246        }
247
248        ReportFuncItem &func = GetOrCreateMapItem(lib.funcs_, funcId);
249
250        // always count subtree
251        func.subTreeEventCount_ += eventCount;
252
253        // only calc the first frame event count
254        if (jsFrame && frames.size() > 1) {
255            skipFrame = 1; // 1 : for arkjs frame,skip the stub.an frame
256        }
257        if (it == frames.begin() + skipFrame) {
258            func.eventCount_ += eventCount;
259            func.sampleCount_ += 1;
260            lib.eventCount_ += eventCount;
261        }
262        // go on next frame
263        it++;
264    }
265    /*
266        frames are the other way around
267        0 is the last called.
268        So the order of json callstack should be 0 at the end
269        callNode is Reverse Order of frames
270        callNodeReverse is Normal Order frames
271    */
272    AddReportCallStackReverse(eventCount, thread.callNode, frames);
273    AddReportCallStack(eventCount, thread.callNodeReverse, frames);
274}
275
276void ReportJsonFile::OutputJsonFeatureString()
277{
278    OutputJsonPair(output_, "deviceTime",
279                   recordFileReader_->GetFeatureString(FEATURE::HIPERF_RECORD_TIME), true);
280    std::string device = recordFileReader_->GetFeatureString(FEATURE::HOSTNAME);
281    device.append(" " + recordFileReader_->GetFeatureString(FEATURE::OSRELEASE));
282    device.append(" " + recordFileReader_->GetFeatureString(FEATURE::ARCH));
283
284    OutputJsonPair(output_, "deviceType", device);
285
286    OutputJsonPair(output_, "osVersion", recordFileReader_->GetFeatureString(FEATURE::OSRELEASE));
287
288    OutputJsonPair(output_, "deviceCommandLine",
289                   recordFileReader_->GetFeatureString(FEATURE::CMDLINE));
290
291    OutputJsonPair(output_, "totalRecordSamples", sampleCount_);
292}
293
294void ReportJsonFile::OutputJsonRuntimeInfo()
295{
296    const auto &threadMaps = virtualRuntime_.GetThreads();
297    std::map<std::string, std::string> jsonProcesses;
298    std::map<std::string, std::string> jsonThreads;
299    for (const auto &pair : threadMaps) {
300        const VirtualThread &thread = pair.second;
301        if (thread.pid_ == thread.tid_) {
302            jsonProcesses.emplace(std::to_string(thread.pid_), thread.name_);
303        }
304        // process also is a thread.
305        jsonThreads.emplace(std::to_string(thread.tid_), thread.name_);
306    }
307
308    OutputJsonMap(output_, "processNameMap", jsonProcesses);
309
310    OutputJsonMap(output_, "threadNameMap", jsonThreads);
311
312    const auto &symbolsFiles = virtualRuntime_.GetSymbolsFiles();
313    jsonStringVector jsonFilePaths;
314    for (const auto &symbolsFile : symbolsFiles) {
315        jsonFilePaths.emplace_back(symbolsFile->filePath_);
316    }
317
318    OutputJsonVectorList(output_, "symbolsFileList", jsonFilePaths);
319    if (fprintf(output_, ",") < 0) {
320        return;
321    }
322
323    OutputJsonFunctionMap(output_);
324    if (fprintf(output_, ",") < 0) {
325        return;
326    }
327
328    OutputJsonMapList(output_, "recordSampleInfo", reportConfigItems_, true);
329}
330
331bool ReportJsonFile::OutputJson(FILE *output)
332{
333    CHECK_TRUE(output == nullptr, false, 0, "");
334    output_ = output;
335    if (fprintf(output, "{") < 0) {
336        return false;
337    }
338    OutputJsonFeatureString();
339    OutputJsonRuntimeInfo();
340
341    if (fprintf(output, "}") < 0) {
342        return false;
343    }
344    return true;
345}
346} // namespace HiPerf
347} // namespace Developtools
348} // namespace OHOS
349