xref: /developtools/hiperf/src/report.cpp (revision 48f512ce)
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 "report.h"
18
19#include <memory>
20#include <set>
21#include <sstream>
22
23#if defined(is_mingw) && is_mingw
24#include <windows.h>
25#else
26#include <sys/ioctl.h>
27#endif
28
29#include "hiperf_hilog.h"
30
31using namespace std::placeholders;
32namespace OHOS {
33namespace Developtools {
34namespace HiPerf {
35unsigned long long ReportItem::allIndex_ = 0;
36void Report::AddReportItem(const PerfRecordSample &sample, bool includeCallStack)
37{
38    size_t configIndex = GetConfigIndex(sample.data_.id);
39    HLOG_ASSERT_MESSAGE(configs_.size() > configIndex,
40                        "in %zu configs found index %zu, from ids %llu", configs_.size(),
41                        configIndex, sample.data_.id);
42    VirtualThread &thread = virtualRuntime_.GetThread(sample.data_.pid, sample.data_.tid);
43    HLOG_ASSERT(sample.callFrames_.size() > 0);
44    if (sample.callFrames_.size() > 0) {
45        // if we need callstack ?
46        if (includeCallStack) {
47            // we will use caller mode , from last to first
48            auto frameIt = sample.callFrames_.rbegin();
49            ReportItem &item = configs_[configIndex].reportItems_.emplace_back(
50                sample.data_.pid, sample.data_.tid, thread.name_, frameIt->mapName,
51                frameIt->funcName, frameIt->funcOffset, sample.data_.period);
52            HLOGD("ReportItem: %s", item.ToDebugString().c_str());
53            HLOG_ASSERT(!item.func_.empty());
54
55            std::vector<ReportItemCallFrame> *currentCallFrames = &item.callStacks_;
56            for (frameIt = sample.callFrames_.rbegin(); frameIt != sample.callFrames_.rend();
57                 frameIt++) {
58                HLOG_ASSERT(frameIt->pc < PERF_CONTEXT_MAX);
59                // in add items case , right one should only have 1 callstack
60                // so just new callfames and move to next level
61                ReportItemCallFrame &nextCallFrame = currentCallFrames->emplace_back(
62                    frameIt->funcName, frameIt->funcOffset, frameIt->mapName,
63                    sample.data_.period,
64                    (std::next(frameIt) == sample.callFrames_.rend()) ? sample.data_.period : 0);
65                HLOGV("add callframe %s", nextCallFrame.ToDebugString().c_str());
66                currentCallFrames = &nextCallFrame.childs;
67            }
68            HLOGV("callstack %zu", item.callStacks_.size());
69            if (item.callStacks_.size() > 0) {
70                HLOGV("callstack 2nd level %zu", item.callStacks_[0].childs.size());
71            }
72        } else {
73            auto frameIt = sample.callFrames_.begin();
74            if (frameIt != sample.callFrames_.end()) {
75                HLOG_ASSERT(frameIt->pc < PERF_CONTEXT_MAX);
76                // for arkjs frame, skip the stub.an frame
77                if (StringEndsWith(frameIt->mapName, "stub.an") && sample.callFrames_.size() > 1) {
78                    HLOGV("stub.an frame, go to next, mapname %s", frameIt->mapName.c_str());
79                    frameIt++;
80                }
81                ReportItem &item = configs_[configIndex].reportItems_.emplace_back(
82                    sample.data_.pid, sample.data_.tid, thread.name_, frameIt->mapName,
83                    frameIt->funcName, frameIt->funcOffset, sample.data_.period);
84                HLOGV("%s", item.ToDebugString().c_str());
85                HLOG_ASSERT(!item.func_.empty());
86            }
87        }
88    }
89    configs_[configIndex].sampleCount_++;
90    configs_[configIndex].eventCount_ += sample.data_.period;
91}
92
93void Report::AddReportItemBranch(const PerfRecordSample &sample)
94{
95    size_t configIndex = GetConfigIndex(sample.data_.id);
96    HLOG_ASSERT(configs_.size() > configIndex);
97    VirtualThread &thread = virtualRuntime_.GetThread(sample.data_.pid, sample.data_.tid);
98    for (u64 i = 0; i < sample.data_.bnr; i++) {
99        DfxSymbol symbolTo =
100            virtualRuntime_.GetSymbol(sample.data_.lbr[i].to, sample.data_.pid, sample.data_.tid);
101        DfxSymbol symbolFrom =
102            virtualRuntime_.GetSymbol(sample.data_.lbr[i].from, sample.data_.pid, sample.data_.tid);
103
104        // branch only have 1 time only for period
105        ReportItem &item = configs_[configIndex].reportItems_.emplace_back(
106            sample.data_.pid, sample.data_.tid, thread.name_, symbolTo.module_, symbolTo.GetName(),
107            symbolTo.funcVaddr_, 1u);
108
109        item.fromDso_ = symbolFrom.module_;
110        item.fromFunc_ = symbolFrom.GetName();
111
112        HLOGV("%s 0x%" PRIx64 "", item.ToDebugString().c_str(), symbolTo.taskVaddr_);
113    }
114    configs_[configIndex].sampleCount_++;
115    configs_[configIndex].eventCount_ += sample.data_.bnr;
116}
117
118void Report::StatisticsRecords()
119{
120    for (auto &config : configs_) {
121        size_t duplicates = 0;
122        size_t totalReportCount = config.reportItems_.size();
123        // merge duplicate
124        HLOGD("uniquing %zu", totalReportCount);
125        auto last = std::unique(config.reportItems_.begin(), config.reportItems_.end(),
126            [this] (ReportItem &l, ReportItem &r) -> bool {
127                return this->MultiLevelSameAndUpdateCount(l, r);
128            });
129
130        config.reportItems_.erase(last, config.reportItems_.end());
131
132        duplicates = totalReportCount - config.reportItems_.size();
133        HLOGD("duplicates %zu, %zu -> %zu", duplicates, totalReportCount,
134              config.reportItems_.size());
135    }
136}
137
138void Report::FilterDisplayRecords()
139{
140    // remove the item with not in fliter
141    for (auto &config : configs_) {
142        size_t filterOuts = 0;
143        size_t totalReportCount = config.reportItems_.size();
144        for (const auto &reportKeyPair : reportKeyMap_) {
145            auto reportKey = reportKeyPair.second;
146            if (reportKey.displayFilter_.size() != 0) {
147                auto itemIt = config.reportItems_.begin();
148                while (itemIt != config.reportItems_.end()) {
149                    if (!reportKey.ShouldDisplay(*itemIt)) {
150                        HLOGM("filter out %s", itemIt->ToDebugString().c_str());
151
152                        // we need recalc the heating ,so also remove in total count
153                        config.eventCount_ -= itemIt->eventCount_;
154
155                        // after update total eventCount remove this
156                        itemIt = config.reportItems_.erase(itemIt);
157                        filterOuts++;
158                    } else {
159                        itemIt++;
160                    }
161                }
162            }
163        }
164        HLOGD("filter out %zu, %zu -> %zu", filterOuts, totalReportCount,
165              config.reportItems_.size());
166    }
167}
168
169void Report::UpdateReportItemsAfterAdjust()
170{
171    for (auto &config : configs_) {
172        HLOGV("percentage %zu items", config.reportItems_.size());
173        uint64_t totalEventCount = 0; // just for debug check
174        for (auto &item : config.reportItems_) {
175            item.heat = Percentage(item.eventCount_, config.eventCount_);
176            totalEventCount += item.eventCount_;
177            HLOGM("%s percentage from %5.2f%% %" PRIu64 "/ %" PRIu64 "",
178                  item.ToDebugString().c_str(), item.heat, item.eventCount_, config.eventCount_);
179            for (auto keyPair : reportKeyMap_) {
180                reportKeyMap_.at(keyPair.first).UpdateValueMaxLen(keyPair.second.GetValue(item));
181            }
182        }
183        // check again
184        HLOGV("recalc totalEventCount is %" PRIu64 " old totalEventCount is %" PRIu64 "",
185              totalEventCount, config.eventCount_);
186        HLOG_ASSERT(totalEventCount == config.eventCount_);
187    }
188}
189
190void Report::AdjustReportItems()
191{
192    HLOGD("Adjust Record Order ....");
193    for (auto &config : configs_) {
194        uint64_t totalReportCount = config.reportItems_.size();
195        if (option_.debug_) {
196            for (auto &reportItem : config.reportItems_) {
197                HLOGV("reportItem %s", reportItem.ToDebugString().c_str());
198            }
199        }
200        // sort first.
201        HLOGD("MultiLevelSorting %" PRIu64 "", totalReportCount);
202        std::sort(config.reportItems_.begin(), config.reportItems_.end(),
203            [this] (const ReportItem &a, const ReportItem &b) -> bool {
204                return this->MultiLevelSorting(a, b);
205            });
206        HLOGD("MultiLevelSorting %" PRIu64 " done", totalReportCount);
207        // reorder the callstack
208        if (option_.debug_) {
209            for (auto &reportItem : config.reportItems_) {
210                HLOGV("reportItem %s", reportItem.ToDebugString().c_str());
211            }
212        }
213        StatisticsRecords();
214        FilterDisplayRecords();
215
216        // reorder by count
217        std::sort(config.reportItems_.begin(), config.reportItems_.end(),
218                  &ReportItem::CompareSortingEventCount);
219
220        // reorder the callstack
221        for (auto &reportItem : config.reportItems_) {
222            ReportItemCallFrame::OrderCallFrames(reportItem.callStacks_);
223        }
224        HLOGD("afater sorting and unique, we have %zu report items,", config.reportItems_.size());
225    }
226    // udpate percentage
227    UpdateReportItemsAfterAdjust();
228}
229
230int Report::MultiLevelCompare(const ReportItem &a, const ReportItem &b)
231{
232    HLOGM("MultiLevelCompare %s vs %s sort order %s", a.ToDebugString().c_str(),
233          b.ToDebugString().c_str(), VectorToString(option_.sortKeys_).c_str());
234
235    // check each key user care
236    for (auto it = option_.sortKeys_.begin(); it != option_.sortKeys_.end(); ++it) {
237        int result = reportKeyMap_.at(*it).compareFunction_(a, b);
238        if (result == 0) {
239            // this key is same , check  the next one
240            continue;
241        } else {
242            // if onekey is not same ,  returl as not same
243            HLOGM("not same because %s %d : %s vs %s", it->c_str(), result,
244                  reportKeyMap_.at(*it).GetValue(a).c_str(),
245                  reportKeyMap_.at(*it).GetValue(b).c_str());
246            return result;
247        }
248    }
249    // all the key is same
250    return 0;
251}
252
253bool Report::MultiLevelSame(const ReportItem &a, const ReportItem &b)
254{
255    return MultiLevelCompare(a, b) == 0;
256}
257
258void Report::MergeCallFrameCount(ReportItem &leftItem, ReportItem &rightItem)
259{
260    // add to left (right to left)
261    std::vector<ReportItemCallFrame> *leftCallFrames = &leftItem.callStacks_;
262    const std::vector<ReportItemCallFrame> *rightCallFrames = &rightItem.callStacks_;
263    uint64_t maxEventCount = leftItem.eventCount_;
264    // right should only have one call stack
265    int level = 0;
266    while (rightCallFrames->size() != 0) {
267        HLOG_ASSERT(rightCallFrames->size() == 1u);
268        const ReportItemCallFrame &rightFrame = rightCallFrames->at(0);
269        auto leftFrameIt = std::find(leftCallFrames->begin(), leftCallFrames->end(), rightFrame);
270        if (leftFrameIt == leftCallFrames->end()) {
271            // new callfames
272            auto &leftCallFrame = leftCallFrames->emplace_back(rightFrame);
273            HLOGV("%*s create frame %s in %s", level, "", leftCallFrame.ToDebugString().c_str(),
274                  leftItem.ToDebugString().c_str());
275            HLOG_ASSERT(leftCallFrame.eventCount_ <= maxEventCount);
276            // this is a new call stack ,
277            // all the child in rightFrame has been copy to left.
278            break;
279        } else {
280            // already have , add count
281            leftFrameIt->eventCount_ += rightFrame.eventCount_;
282            leftFrameIt->selfEventCount_ += rightFrame.selfEventCount_;
283            // left move to next
284            leftCallFrames = &(leftFrameIt->childs);
285            HLOGM("%*s udpate frame +%" PRIu64 " %s in %s", level, "", rightFrame.eventCount_,
286                  leftFrameIt->ToDebugString().c_str(), leftItem.ToDebugString().c_str());
287            HLOG_ASSERT_MESSAGE(leftFrameIt->eventCount_ <= maxEventCount,
288                                " maxEventCount is %" PRIu64 "", maxEventCount);
289            maxEventCount = leftFrameIt->eventCount_;
290        }
291        // move to next level
292        rightCallFrames = &(rightFrame.childs);
293        level++;
294    }
295}
296
297bool Report::MultiLevelSameAndUpdateCount(ReportItem &l, ReportItem &r)
298{
299    if (MultiLevelCompare(l, r) == 0) {
300        l.eventCount_ += r.eventCount_;
301        HLOGM("l %" PRIu64 " %s c:%zu vs r %" PRIu64 " %s c:%zu", l.eventCount_, l.func_.data(),
302              l.callStacks_.size(), r.eventCount_, r.func_.data(), r.callStacks_.size());
303        // if it have call stack?
304        if (r.callStacks_.size() != 0) {
305            // add to left (right to left)
306            MergeCallFrameCount(l, r);
307        }
308        return true;
309    } else {
310        return false;
311    }
312}
313
314bool Report::MultiLevelSorting(const ReportItem &a, const ReportItem &b)
315{
316    /*
317    The value returned indicates whether the element passed as first argument is
318    considered to go before the second in the specific strict weak ordering it defines.
319    */
320    bool result = MultiLevelCompare(a, b) > 0;
321#ifdef HIPERF_DEBUG
322    if (DebugLogger::GetInstance()->GetLogLevel() <= LEVEL_VERBOSE) {
323        bool result2 = MultiLevelCompare(b, a) > 0;
324        if (result and result == result2) {
325            HLOGE("MultiLevelSorting a->b %d vs b->a %d", result, result2);
326            HLOGE("left %s", a.ToDebugString().c_str());
327            HLOGE("right %s", b.ToDebugString().c_str());
328            HLOG_ASSERT(false);
329        }
330    }
331#endif
332    return result;
333}
334
335void Report::OutputStdStatistics(ReportEventConfigItem &config)
336{
337    if (fprintf(output_, "\n") < 0) {
338        return;
339    } // make a blank line for new event
340    if (fprintf(output_, "Event: %s (type %" PRIu32 " id %" PRIu64 ")\n", config.eventName_.c_str(),
341            config.type_, config.config_) < 0) {
342        return;
343    }
344    if (fprintf(output_, "Samples Count: %" PRIu64 "\n", config.sampleCount_) < 0) {
345        return;
346    }
347    if (!config.coutMode_) {
348        fprintf(output_, "Time in ns: ");
349    } else {
350        fprintf(output_, "Event Count: ");
351    }
352    fprintf(output_, "%" PRIu64 "\n", config.eventCount_);
353}
354
355void Report::OutputStdHead(ReportEventConfigItem &config, bool diffMode)
356{
357    // head print
358    const std::string head = "Heating";
359    if (fprintf(output_, "%-*s ", FULL_PERCENTAGE_LEN, head.c_str()) < 0) {
360        return;
361    }
362
363    if (diffMode) {
364        const std::string diff = "Diff";
365        fprintf(output_, "%-*s ", FULL_PERCENTAGE_DIFF_LEN, diff.c_str());
366    }
367
368    // merge sort key and no-sort key (like count)
369
370    displayKeyNames_ = option_.sortKeys_;
371    if (!option_.hideCount_) {
372        displayKeyNames_.insert(displayKeyNames_.begin(), "count");
373    }
374
375    unsigned int remainingWidth = consoleWidth_;
376    // sort key head
377    for (auto &keyName : displayKeyNames_) {
378        auto &key = reportKeyMap_.at(keyName);
379        remainingWidth -= key.maxLen_;
380        if (remainingWidth == 0) {
381            key.maxLen_ = 0;
382        }
383        if (fprintf(output_, "%-*s ", (remainingWidth > 0) ? static_cast<unsigned int>(key.maxLen_) : 0,
384            key.keyName_.c_str()) < 0) {
385            return;
386        }
387        HLOGD("'%s' max len %zu(from '%s') console width %d", key.keyName_.c_str(), key.maxLen_,
388              key.maxValue_.c_str(), remainingWidth);
389    }
390    if (fprintf(output_, "\n") < 0) {
391        return;
392    }
393}
394
395bool Report::OutputStdCallFrame(int indent, const std::string_view &funcName, uint64_t eventCount,
396                                uint64_t totalEventCount)
397{
398    float heat = Percentage(eventCount, totalEventCount);
399    float num = 100.0;
400    HLOGV("frame %f indent %d at %s", heat, indent, funcName.data());
401
402    CHECK_TRUE(heat < option_.callStackHeatLimit_, false, 0, ""); // don't print this three anymore
403
404    if (abs(heat - num) < ALMOST_ZERO) {
405        fprintf(output_, "%*s", indent, "   ");
406        fprintf(output_, "%*s  ", FULL_PERCENTAGE_NUM_LEN, " ");
407    } else {
408        fprintf(output_, "%*s", indent, "|- ");
409        fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, heat);
410    }
411    if (option_.debug_) {
412        fprintf(output_, "%" PRIu64 "/%" PRIu64 " %s\n", eventCount, totalEventCount,
413                funcName.data());
414    } else {
415        fprintf(output_, "%s\n", funcName.data());
416    }
417    return true;
418}
419
420void Report::PrepareConsole()
421{
422#if defined(is_mingw) && is_mingw
423    CONSOLE_SCREEN_BUFFER_INFO csbi;
424    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
425    consoleWidth_ = static_cast<unsigned int>(csbi.srWindow.Right - csbi.srWindow.Left + 1);
426    const auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
427    DWORD mode;
428    GetConsoleMode(handle, &mode);
429    mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
430    SetConsoleMode(handle, mode);
431#else
432    struct winsize w = {0, 0, 0, 0};
433    ioctl(fileno(stdout), TIOCGWINSZ, &w);
434    consoleWidth_ = static_cast<unsigned int>(w.ws_col);
435#endif
436    if (consoleWidth_ == 0) {
437        consoleWidth_ = ConsoleDefaultWidth;
438    }
439    HLOGD("consoleWidth_:%d", consoleWidth_);
440}
441
442void Report::OutputStdCallFrames(int indent, const ReportItemCallFrame &callFrame, uint64_t totalEventCount)
443{
444    /*
445    90% a
446        |- 80% b
447            c
448            d
449            |- 50% e
450            |- 50% f
451                   g
452    */
453    // this is the first call frame
454    // this tree will skipped.
455    CHECK_TRUE(!OutputStdCallFrame(indent, callFrame.func_, callFrame.eventCount_, totalEventCount),
456               NO_RETVAL, 0, "");
457
458    // print it self
459    if (callFrame.selfEventCount_ != 0 and callFrame.selfEventCount_ != callFrame.eventCount_) {
460        OutputStdCallFrame(indent + CALLSTACK_INDENT, "[run in self function]",
461                           callFrame.selfEventCount_, callFrame.eventCount_);
462    }
463
464    // printf children
465    // if only one children
466    if (callFrame.childs.size() == 1u and
467        callFrame.childs[0].eventCount_ == callFrame.eventCount_) {
468        HLOGV("childCallFream %*c %s", indent, ' ', callFrame.childs[0].func_.data());
469        // don't indent if same count (only one 100% children)
470        OutputStdCallFrames(indent, callFrame.childs[0], callFrame.eventCount_);
471    } else {
472        // else a lot children
473        for (const ReportItemCallFrame &childCallFrame : callFrame.childs) {
474            HLOGV("childCallFream %*c %s", indent, ' ', childCallFrame.func_.data());
475            OutputStdCallFrames(indent + CALLSTACK_INDENT, childCallFrame, callFrame.eventCount_);
476        }
477    }
478}
479
480void Report::OutputStdContent(ReportEventConfigItem &config)
481{
482    // content print
483    auto it = config.reportItems_.begin();
484    while (it != config.reportItems_.end()) {
485        const ReportItem &reportItem = it.operator*();
486        // if we need skip it ?
487        if (reportItem.heat < option_.heatLimit_) {
488            it++;
489            continue; // below limit
490        } else {
491            fprintf(output_, "%*.2f%%  ", FULL_PERCENTAGE_NUM_LEN, reportItem.heat);
492        }
493        OutputStdContentItem(reportItem);
494        if (reportItem.callStacks_.size() != 0) {
495            HLOGV("reportItem.callStacks_ %zu %s", reportItem.callStacks_.size(),
496                  reportItem.ToDebugString().c_str());
497            HLOG_ASSERT(reportItem.callStacks_.size() == 1u);
498            for (auto &callFrame : reportItem.callStacks_) {
499                OutputStdCallFrames(CALLSTACK_INDENT, callFrame, reportItem.eventCount_);
500            }
501        }
502        it++;
503    }
504}
505
506void Report::OutputStdContentItem(const ReportItem &reportItem)
507{
508    // output by sort keys
509    for (auto sortKey : displayKeyNames_) {
510        ReportKey &reportKey = Report::reportKeyMap_.at(sortKey);
511        if (fprintf(output_, "%s ", reportKey.GetValue(reportItem).c_str()) < 0) {
512            return;
513        }
514    }
515    if (fprintf(output_, "\n") < 0) {
516        return;
517    }
518}
519
520void Report::OutputStdItemHeating(float heat, float heat2)
521{
522    if (heat == heat2 and heat == 0.0f) {
523        fprintf(output_, "something error , all it is end.\n");
524    } else if (heat2 == 0) {
525        // only have first
526        fprintf(output_, "%*.2f%%  ", FULL_PERCENTAGE_NUM_LEN, heat);
527        fprintf(output_, "%*s ", FULL_PERCENTAGE_DIFF_LEN, "");
528    } else if (heat == 0) {
529        // only have second
530        fprintf(output_, "%*s  ", FULL_PERCENTAGE_LEN, "");
531        fprintf(output_, "%+*.2f%% ", FULL_PERCENTAGE_DIFF_NUM_LEN, heat2);
532    } else if (heat2 > heat) {
533        fprintf(output_, "%s%*.2f%%%s  ", TEXT_RED.c_str(), FULL_PERCENTAGE_NUM_LEN, heat,
534                TEXT_RESET.c_str());
535        fprintf(output_, "%s%+*.2f%%%s ", TEXT_GREEN.c_str(), FULL_PERCENTAGE_DIFF_NUM_LEN,
536                heat2 - heat, TEXT_RESET.c_str());
537    } else if (heat2 < heat) {
538        fprintf(output_, "%s%*.2f%%%s  ", TEXT_GREEN.c_str(), FULL_PERCENTAGE_NUM_LEN, heat,
539                TEXT_RESET.c_str());
540        fprintf(output_, "%s%+*.2f%%%s ", TEXT_RED.c_str(), FULL_PERCENTAGE_DIFF_NUM_LEN,
541                heat2 - heat, TEXT_RESET.c_str());
542    } else {
543        // same heating
544        fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, heat);
545        fprintf(output_, "%+*.2f%% ", FULL_PERCENTAGE_DIFF_NUM_LEN, heat2 - heat);
546    }
547}
548
549void Report::OutputStdContentDiff(ReportEventConfigItem &left, ReportEventConfigItem &right)
550{
551    // first we need found the match config
552    HLOGD("first count %zu second count %zu", left.reportItems_.size(), right.reportItems_.size());
553    ReportItemsConstIt it = left.reportItems_.begin();
554    ReportItemsConstIt it2 = right.reportItems_.begin();
555    while (it != left.reportItems_.end()) {
556        // still have it2 ?
557        if (it2 != right.reportItems_.end()) {
558            // find the same item in it2 by same sort key
559            while (it2 != right.reportItems_.end()) {
560                if (MultiLevelSame(*it, *it2)) {
561                    // we found the same item
562                    // output the diff heating
563                    if (it->heat > option_.heatLimit_ and it2->heat > option_.heatLimit_) {
564                        OutputStdItemHeating(it->heat, it2->heat);
565                        OutputStdContentItem(*it);
566                    }
567                    it++;
568                    it2++;
569                    break; // next it
570                } else {
571                    // only print it2 item
572                    if (it2->heat > option_.heatLimit_) {
573                        OutputStdItemHeating(0.0f, it2->heat);
574                        OutputStdContentItem(*it2);
575                    }
576                    it2++;
577                    continue; // next it2
578                }
579            }
580        } else {
581            // no more it2, go on print all the it
582            if (it->heat > option_.heatLimit_) {
583                OutputStdItemHeating(it->heat, 0.0f);
584                OutputStdContentItem(*it);
585            }
586            it++;
587            continue; // next it
588        }
589    }
590    while (it2 != right.reportItems_.end()) {
591        // if diff still have some item in it2 ,print it
592        OutputStdItemHeating(0, it2->heat);
593        OutputStdContentItem(*it2);
594        it2++;
595    }
596}
597
598void Report::OutputStd(FILE *output)
599{
600    output_ = output;
601    PrepareConsole();
602
603    for (auto &config : configs_) {
604        OutputStdStatistics(config);
605        OutputStdHead(config);
606        OutputStdContent(config);
607    }
608}
609
610void Report::OutputStdDiff(FILE *output, Report &other)
611{
612    output_ = output;
613    PrepareConsole();
614
615    auto left = configs_.begin();
616    while (left != configs_.end()) {
617        auto right = other.configs_.begin();
618        while (right != other.configs_.end()) {
619            if (*left == *right) {
620                OutputStdStatistics(*left);
621                OutputStdHead(*left, true);
622                OutputStdContentDiff(*left, *right);
623                break; // check next left
624            }
625            right++;
626        }
627        left++; // go on left
628    }
629}
630} // namespace HiPerf
631} // namespace Developtools
632} // namespace OHOS
633