* 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.
*/
#define HILOG_TAG "Report"
#include "report.h"
#include <memory>
#include <set>
#include <sstream>
#if defined(is_mingw) && is_mingw
#include <windows.h>
#else
#include <sys/ioctl.h>
#endif
#include "hiperf_hilog.h"
using namespace std::placeholders;
namespace OHOS {
namespace Developtools {
namespace HiPerf {
unsigned long long ReportItem::allIndex_ = 0;
std::string Report::GetAdltExtendMapName(const std::string &mapName, const std::string &originSoName)
{
if (!originSoName.empty()) {
return mapName + ":" + originSoName;
}
return mapName;
}
void Report::FillReportItemCounterValues(ReportItem &item, const PerfRecordSample &sample) const
{
if (addCounterNames_.empty()) {
return;
}
item.counts_.assign(addCounterNames_.size(), 0);
item.accCounts_.assign(addCounterNames_.size(), 0);
if ((sample.sampleType_ & PERF_SAMPLE_READ) == 0 || sample.data_.read_nr == 0 ||
sample.data_.read_values == nullptr || sample.data_.read_ids == nullptr) {
return;
}
for (uint64_t i = 0; i < sample.data_.read_nr; ++i) {
uint64_t readId = sample.GetReadIdByIndex(i);
auto it = addCounterIdIndexMaps_.find(readId);
if (it == addCounterIdIndexMaps_.end()) {
continue;
}
size_t idx = it->second;
if (idx >= item.counts_.size()) {
continue;
}
uint64_t readValue = sample.GetReadValueByIndex(i);
item.counts_[idx] = readValue;
item.accCounts_[idx] = readValue;
}
}
std::string Report::FormatCounterValues(const std::vector<uint64_t> &values,
const std::vector<std::string> &counterNames) const
{
if (values.empty() || counterNames.empty() || values.size() != counterNames.size()) {
return "";
}
std::string result;
for (size_t i = 0; i < values.size(); ++i) {
if (!result.empty()) {
result.append(",");
}
result.append(StringPrintf("%s=%" PRIu64 "", counterNames[i].c_str(), values[i]));
}
return result;
}
void Report::AddReportItem(const PerfRecordSample &sample, const bool includeCallStack)
{
size_t configIndex = GetConfigIndex(sample.data_.id);
HLOG_ASSERT_MESSAGE(configs_.size() > configIndex, "in %zu configs found index %zu, from ids %llu",
configs_.size(), configIndex, sample.data_.id);
configs_[configIndex].sampleCount_++;
configs_[configIndex].eventCount_ += sample.data_.period;
if (sample.callFrames_.empty()) {
return;
}
VirtualThread &thread = virtualRuntime_.GetThread(sample.data_.pid, sample.data_.tid);
if (includeCallStack) {
auto frameIt = sample.callFrames_.rbegin();
auto mapName = GetAdltExtendMapName(frameIt->mapName, frameIt->originSoName);
ReportItem &item = configs_[configIndex].reportItems_.emplace_back(
sample.data_.pid, sample.data_.tid, thread.name_, mapName,
frameIt->funcName, frameIt->funcOffset, sample.data_.period);
FillReportItemCounterValues(item, sample);
HLOGD("ReportItem: %s", item.ToDebugString().c_str());
HLOG_ASSERT(!item.func_.empty());
std::vector<ReportItemCallFrame> *currentCallFrames = &item.callStacks_;
for (frameIt = sample.callFrames_.rbegin(); frameIt != sample.callFrames_.rend();
frameIt++) {
HLOG_ASSERT(frameIt->pc < PERF_CONTEXT_MAX);
ReportItemCallFrame &nextCallFrame = currentCallFrames->emplace_back(
frameIt->funcName, frameIt->funcOffset, frameIt->mapName, sample.data_.period,
(std::next(frameIt) == sample.callFrames_.rend()) ? sample.data_.period : 0);
HLOGV("add callframe %s", nextCallFrame.ToDebugString().c_str());
currentCallFrames = &nextCallFrame.childs;
}
HLOGV("callstack %zu", item.callStacks_.size());
if (item.callStacks_.size() > 0) {
HLOGV("callstack 2nd level %zu", item.callStacks_[0].childs.size());
}
} else {
auto frameIt = sample.callFrames_.begin();
if (frameIt != sample.callFrames_.end()) {
HLOG_ASSERT(frameIt->pc < PERF_CONTEXT_MAX);
if (StringEndsWith(frameIt->mapName, "stub.an") && sample.callFrames_.size() > 1) {
HLOGV("stub.an frame, go to next, mapname %s", frameIt->mapName.c_str());
frameIt++;
}
auto mapName = GetAdltExtendMapName(frameIt->mapName, frameIt->originSoName);
ReportItem &item = configs_[configIndex].reportItems_.emplace_back(sample.data_.pid, sample.data_.tid,
thread.name_, mapName, frameIt->funcName, frameIt->funcOffset, sample.data_.period);
FillReportItemCounterValues(item, sample);
HLOGV("%s", item.ToDebugString().c_str());
HLOG_ASSERT(!item.func_.empty());
}
}
}
void Report::AddReportItemBranch(const PerfRecordSample &sample)
{
size_t configIndex = GetConfigIndex(sample.data_.id);
HLOG_ASSERT(configs_.size() > configIndex);
VirtualThread &thread = virtualRuntime_.GetThread(sample.data_.pid, sample.data_.tid);
for (u64 i = 0; i < sample.data_.bnr; i++) {
DfxSymbol symbolTo =
virtualRuntime_.GetSymbol(sample.data_.lbr[i].to, sample.data_.pid, sample.data_.tid);
DfxSymbol symbolFrom =
virtualRuntime_.GetSymbol(sample.data_.lbr[i].from, sample.data_.pid, sample.data_.tid);
ReportItem &item = configs_[configIndex].reportItems_.emplace_back(
sample.data_.pid, sample.data_.tid, thread.name_, symbolTo.module_, symbolTo.GetName(),
symbolTo.funcVaddr_, 1u);
FillReportItemCounterValues(item, sample);
item.fromDso_ = symbolFrom.module_;
item.fromFunc_ = symbolFrom.GetName();
HLOGV("%s 0x%" PRIx64 "", item.ToDebugString().c_str(), symbolTo.taskVaddr_);
}
configs_[configIndex].sampleCount_++;
configs_[configIndex].eventCount_ += sample.data_.bnr;
}
void Report::StatisticsRecords()
{
for (auto &config : configs_) {
size_t duplicates = 0;
size_t totalReportCount = config.reportItems_.size();
HLOGD("uniquing %zu", totalReportCount);
auto last = std::unique(config.reportItems_.begin(), config.reportItems_.end(),
[this] (ReportItem &l, ReportItem &r) -> bool {
return this->MultiLevelSameAndUpdateCount(l, r);
});
config.reportItems_.erase(last, config.reportItems_.end());
duplicates = totalReportCount - config.reportItems_.size();
HLOGD("duplicates %zu, %zu -> %zu", duplicates, totalReportCount,
config.reportItems_.size());
}
}
void Report::FilterDisplayRecords()
{
for (auto &config : configs_) {
size_t filterOuts = 0;
size_t totalReportCount = config.reportItems_.size();
for (const auto &reportKeyPair : reportKeyMap_) {
auto reportKey = reportKeyPair.second;
if (reportKey.displayFilter_.size() != 0) {
auto itemIt = config.reportItems_.begin();
while (itemIt != config.reportItems_.end()) {
if (!reportKey.ShouldDisplay(*itemIt)) {
HLOGM("filter out %s", itemIt->ToDebugString().c_str());
config.eventCount_ -= itemIt->eventCount_;
itemIt = config.reportItems_.erase(itemIt);
filterOuts++;
} else {
itemIt++;
}
}
}
}
HLOGD("filter out %zu, %zu -> %zu", filterOuts, totalReportCount,
config.reportItems_.size());
}
}
void Report::UpdateReportItemsAfterAdjust()
{
for (auto &config : configs_) {
HLOGV("percentage %zu items", config.reportItems_.size());
uint64_t totalEventCount = 0;
for (auto &item : config.reportItems_) {
item.heat = Percentage(item.eventCount_, config.eventCount_);
totalEventCount += item.eventCount_;
HLOGM("%s percentage from %5.2f%% %" PRIu64 "/ %" PRIu64 "",
item.ToDebugString().c_str(), item.heat, item.eventCount_, config.eventCount_);
for (auto keyPair : reportKeyMap_) {
reportKeyMap_.at(keyPair.first).UpdateValueMaxLen(keyPair.second.GetValue(item));
}
}
HLOGV("recalc totalEventCount is %" PRIu64 " old totalEventCount is %" PRIu64 "",
totalEventCount, config.eventCount_);
HLOG_ASSERT(totalEventCount == config.eventCount_);
}
}
void Report::AdjustReportItems()
{
HLOGD("Adjust Record Order ....");
for (auto &config : configs_) {
uint64_t totalReportCount = config.reportItems_.size();
if (option_.debug_) {
for (auto &reportItem : config.reportItems_) {
HLOGV("reportItem %s", reportItem.ToDebugString().c_str());
}
}
HLOGD("MultiLevelSorting %" PRIu64 "", totalReportCount);
std::sort(config.reportItems_.begin(), config.reportItems_.end(),
[this] (const ReportItem &a, const ReportItem &b) -> bool {
return this->MultiLevelSorting(a, b);
});
HLOGD("MultiLevelSorting %" PRIu64 " done", totalReportCount);
if (option_.debug_) {
for (auto &reportItem : config.reportItems_) {
HLOGV("reportItem %s", reportItem.ToDebugString().c_str());
}
}
StatisticsRecords();
FilterDisplayRecords();
std::sort(config.reportItems_.begin(), config.reportItems_.end(),
&ReportItem::CompareSortingEventCount);
for (auto &reportItem : config.reportItems_) {
ReportItemCallFrame::OrderCallFrames(reportItem.callStacks_);
}
HLOGD("afater sorting and unique, we have %zu report items,", config.reportItems_.size());
}
UpdateReportItemsAfterAdjust();
}
int Report::MultiLevelCompare(const ReportItem &a, const ReportItem &b)
{
HLOGM("MultiLevelCompare %s vs %s sort order %s", a.ToDebugString().c_str(),
b.ToDebugString().c_str(), VectorToString(option_.sortKeys_).c_str());
for (auto it = option_.sortKeys_.begin(); it != option_.sortKeys_.end(); ++it) {
int result = reportKeyMap_.at(*it).compareFunction_(a, b);
if (result == 0) {
continue;
} else {
HLOGM("not same because %s %d : %s vs %s", it->c_str(), result,
reportKeyMap_.at(*it).GetValue(a).c_str(),
reportKeyMap_.at(*it).GetValue(b).c_str());
return result;
}
}
return 0;
}
bool Report::MultiLevelSame(const ReportItem &a, const ReportItem &b)
{
return MultiLevelCompare(a, b) == 0;
}
void Report::MergeCallFrameCount(ReportItem &leftItem, ReportItem &rightItem)
{
std::vector<ReportItemCallFrame> *leftCallFrames = &leftItem.callStacks_;
const std::vector<ReportItemCallFrame> *rightCallFrames = &rightItem.callStacks_;
uint64_t maxEventCount = leftItem.eventCount_;
int level = 0;
while (rightCallFrames->size() != 0) {
HLOG_ASSERT(rightCallFrames->size() == 1u);
const ReportItemCallFrame &rightFrame = rightCallFrames->at(0);
auto leftFrameIt = std::find(leftCallFrames->begin(), leftCallFrames->end(), rightFrame);
if (leftFrameIt == leftCallFrames->end()) {
auto &leftCallFrame = leftCallFrames->emplace_back(rightFrame);
HLOGV("%*s create frame %s in %s", level, "", leftCallFrame.ToDebugString().c_str(),
leftItem.ToDebugString().c_str());
HLOG_ASSERT(leftCallFrame.eventCount_ <= maxEventCount);
break;
} else {
leftFrameIt->eventCount_ += rightFrame.eventCount_;
leftFrameIt->selfEventCount_ += rightFrame.selfEventCount_;
leftCallFrames = &(leftFrameIt->childs);
HLOGM("%*s udpate frame +%" PRIu64 " %s in %s", level, "", rightFrame.eventCount_,
leftFrameIt->ToDebugString().c_str(), leftItem.ToDebugString().c_str());
HLOG_ASSERT_MESSAGE(leftFrameIt->eventCount_ <= maxEventCount,
" maxEventCount is %" PRIu64 "", maxEventCount);
maxEventCount = leftFrameIt->eventCount_;
}
rightCallFrames = &(rightFrame.childs);
level++;
}
}
bool Report::MultiLevelSameAndUpdateCount(ReportItem &l, ReportItem &r)
{
if (MultiLevelCompare(l, r) != 0) {
return false;
}
l.eventCount_ += r.eventCount_;
l.mergedSampleCount_ += r.mergedSampleCount_;
if (!l.accCounts_.empty() && l.accCounts_.size() == r.accCounts_.size()) {
for (size_t i = 0; i < l.accCounts_.size(); ++i) {
l.accCounts_[i] += r.accCounts_[i];
if (l.mergedSampleCount_ > 0) {
l.counts_[i] = l.accCounts_[i] / l.mergedSampleCount_;
}
}
} else if (l.accCounts_.empty() && !r.accCounts_.empty()) {
l.accCounts_ = r.accCounts_;
l.counts_ = r.counts_;
}
HLOGM("l %" PRIu64 " %s c:%zu vs r %" PRIu64 " %s c:%zu", l.eventCount_, l.func_.data(),
l.callStacks_.size(), r.eventCount_, r.func_.data(), r.callStacks_.size());
if (r.callStacks_.size() != 0) {
MergeCallFrameCount(l, r);
}
return true;
}
bool Report::MultiLevelSorting(const ReportItem &a, const ReportItem &b)
{
The value returned indicates whether the element passed as first argument is
considered to go before the second in the specific strict weak ordering it defines.
*/
bool result = MultiLevelCompare(a, b) > 0;
#ifdef HIPERF_DEBUG
if (DebugLogger::GetInstance()->GetLogLevel() <= LEVEL_VERBOSE) {
bool result2 = MultiLevelCompare(b, a) > 0;
if (result && result == result2) {
HLOGE("MultiLevelSorting a->b %d vs b->a %d", result, result2);
HLOGE("left %s", a.ToDebugString().c_str());
HLOGE("right %s", b.ToDebugString().c_str());
HLOG_ASSERT(false);
}
}
#endif
return result;
}
void Report::OutputStdStatistics(ReportEventConfigItem &config)
{
if (fprintf(output_, "\n") < 0) {
return;
}
if (fprintf(output_, "Event: %s (type %" PRIu32 " id %" PRIu64 ")\n", config.eventName_.c_str(),
config.type_, config.config_) < 0) {
return;
}
if (fprintf(output_, "Samples Count: %" PRIu64 "\n", config.sampleCount_) < 0) {
return;
}
if (!config.coutMode_) {
fprintf(output_, "Time in ns: ");
} else {
fprintf(output_, "Event Count: ");
}
fprintf(output_, "%" PRIu64 "\n", config.eventCount_);
if (!config.addCounterNames_.empty()) {
fprintf(output_, "Add counters: %s\n", VectorToString(config.addCounterNames_).c_str());
}
}
void Report::OutputStdHead(ReportEventConfigItem &config, const bool diffMode)
{
const std::string head = "Heating";
if (fprintf(output_, "%-*s ", FULL_PERCENTAGE_LEN, head.c_str()) < 0) {
return;
}
if (diffMode) {
const std::string diff = "Diff";
fprintf(output_, "%-*s ", FULL_PERCENTAGE_DIFF_LEN, diff.c_str());
}
displayKeyNames_ = option_.sortKeys_;
if (!option_.hideCount_) {
displayKeyNames_.insert(displayKeyNames_.begin(), "count");
}
unsigned int remainingWidth = consoleWidth_;
for (auto &keyName : displayKeyNames_) {
auto &key = reportKeyMap_.at(keyName);
remainingWidth -= key.maxLen_;
if (remainingWidth == 0) {
key.maxLen_ = 0;
}
if (fprintf(output_, "%-*s ", (remainingWidth > 0) ? static_cast<unsigned int>(key.maxLen_) : 0,
key.keyName_.c_str()) < 0) {
return;
}
HLOGD("'%s' max len %zu(from '%s') console width %d", key.keyName_.c_str(), key.maxLen_,
key.maxValue_.c_str(), remainingWidth);
}
if (!config.addCounterNames_.empty()) {
size_t counterColumnLen = std::max(static_cast<size_t>(24),
VectorToString(config.addCounterNames_).size() + 8);
int counterColLen = static_cast<int>(counterColumnLen);
fprintf(output_, "%-*s ", counterColLen, "counts");
fprintf(output_, "%-*s ", counterColLen, "acc_counts");
}
if (fprintf(output_, "\n") < 0) {
return;
}
}
bool Report::OutputStdCallFrame(const int indent, const std::string_view &funcName, const uint64_t eventCount,
const uint64_t totalEventCount)
{
float heat = Percentage(eventCount, totalEventCount);
float num = 100.0;
HLOGV("frame %f indent %d at %s", heat, indent, funcName.data());
CHECK_TRUE(heat >= option_.callStackHeatLimit_, false, 0, "");
if (abs(heat - num) < ALMOST_ZERO) {
fprintf(output_, "%*s", indent, " ");
fprintf(output_, "%*s ", FULL_PERCENTAGE_NUM_LEN, " ");
} else {
fprintf(output_, "%*s", indent, "|- ");
fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, heat);
}
if (option_.debug_) {
fprintf(output_, "%" PRIu64 "/%" PRIu64 " %s\n", eventCount, totalEventCount,
funcName.data());
} else {
fprintf(output_, "%s\n", funcName.data());
}
return true;
}
void Report::PrepareConsole()
{
#if defined(is_mingw) && is_mingw
CONSOLE_SCREEN_BUFFER_INFO csbi;
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
consoleWidth_ = static_cast<unsigned int>(csbi.srWindow.Right - csbi.srWindow.Left + 1);
const auto handle = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode;
GetConsoleMode(handle, &mode);
mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(handle, mode);
#else
struct winsize w = {0, 0, 0, 0};
ioctl(fileno(stdout), TIOCGWINSZ, &w);
consoleWidth_ = static_cast<unsigned int>(w.ws_col);
#endif
if (consoleWidth_ == 0) {
consoleWidth_ = ConsoleDefaultWidth;
}
HLOGD("consoleWidth_:%d", consoleWidth_);
}
void Report::OutputStdCallFrames(const int indent, const ReportItemCallFrame &callFrame,
const uint64_t totalEventCount)
{
90% a
|- 80% b
c
d
|- 50% e
|- 50% f
g
*/
CHECK_TRUE(OutputStdCallFrame(indent, callFrame.func_, callFrame.eventCount_, totalEventCount),
NO_RETVAL, 0, "");
if (callFrame.selfEventCount_ != 0 && callFrame.selfEventCount_ != callFrame.eventCount_) {
OutputStdCallFrame(indent + CALLSTACK_INDENT, "[run in self function]",
callFrame.selfEventCount_, callFrame.eventCount_);
}
if (callFrame.childs.size() == 1u &&
callFrame.childs[0].eventCount_ == callFrame.eventCount_) {
HLOGV("childCallFream %*c %s", indent, ' ', callFrame.childs[0].func_.data());
OutputStdCallFrames(indent, callFrame.childs[0], callFrame.eventCount_);
} else {
for (const ReportItemCallFrame &childCallFrame : callFrame.childs) {
HLOGV("childCallFream %*c %s", indent, ' ', childCallFrame.func_.data());
OutputStdCallFrames(indent + CALLSTACK_INDENT, childCallFrame, callFrame.eventCount_);
}
}
}
void Report::OutputStdContent(ReportEventConfigItem &config)
{
auto it = config.reportItems_.begin();
while (it != config.reportItems_.end()) {
const ReportItem &reportItem = it.operator*();
if (reportItem.heat < option_.heatLimit_) {
it++;
continue;
} else {
fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, reportItem.heat);
}
OutputStdContentItem(config, reportItem);
if (reportItem.callStacks_.size() != 0) {
HLOGV("reportItem.callStacks_ %zu %s", reportItem.callStacks_.size(),
reportItem.ToDebugString().c_str());
HLOG_ASSERT(reportItem.callStacks_.size() == 1u);
for (auto &callFrame : reportItem.callStacks_) {
OutputStdCallFrames(CALLSTACK_INDENT, callFrame, reportItem.eventCount_);
}
}
it++;
}
}
void Report::OutputStdContentItem(const ReportEventConfigItem &config, const ReportItem &reportItem)
{
for (auto sortKey : displayKeyNames_) {
ReportKey &reportKey = Report::reportKeyMap_.at(sortKey);
if (fprintf(output_, "%s ", reportKey.GetValue(reportItem).c_str()) < 0) {
return;
}
}
if (!config.addCounterNames_.empty()) {
size_t counterColumnLen = std::max(static_cast<size_t>(24),
VectorToString(config.addCounterNames_).size() + 8);
int counterColLen = static_cast<int>(counterColumnLen);
std::string counts = FormatCounterValues(reportItem.counts_, config.addCounterNames_);
std::string accCounts = FormatCounterValues(reportItem.accCounts_, config.addCounterNames_);
fprintf(output_, "%-*s ", counterColLen, counts.c_str());
fprintf(output_, "%-*s ", counterColLen, accCounts.c_str());
}
if (fprintf(output_, "\n") < 0) {
return;
}
}
void Report::OutputStdItemHeating(const float heat, const float heat2)
{
if (heat == heat2 && heat == 0.0f) {
fprintf(output_, "something error , all it is end.\n");
} else if (heat2 == 0) {
fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, heat);
fprintf(output_, "%*s ", FULL_PERCENTAGE_DIFF_LEN, "");
} else if (heat == 0) {
fprintf(output_, "%*s ", FULL_PERCENTAGE_LEN, "");
fprintf(output_, "%+*.2f%% ", FULL_PERCENTAGE_DIFF_NUM_LEN, heat2);
} else if (heat2 > heat) {
fprintf(output_, "%s%*.2f%%%s ", TEXT_RED.c_str(), FULL_PERCENTAGE_NUM_LEN, heat,
TEXT_RESET.c_str());
fprintf(output_, "%s%+*.2f%%%s ", TEXT_GREEN.c_str(), FULL_PERCENTAGE_DIFF_NUM_LEN,
heat2 - heat, TEXT_RESET.c_str());
} else if (heat2 < heat) {
fprintf(output_, "%s%*.2f%%%s ", TEXT_GREEN.c_str(), FULL_PERCENTAGE_NUM_LEN, heat,
TEXT_RESET.c_str());
fprintf(output_, "%s%+*.2f%%%s ", TEXT_RED.c_str(), FULL_PERCENTAGE_DIFF_NUM_LEN,
heat2 - heat, TEXT_RESET.c_str());
} else {
fprintf(output_, "%*.2f%% ", FULL_PERCENTAGE_NUM_LEN, heat);
fprintf(output_, "%+*.2f%% ", FULL_PERCENTAGE_DIFF_NUM_LEN, heat2 - heat);
}
}
void Report::OutputStdContentDiff(ReportEventConfigItem &left, ReportEventConfigItem &right)
{
HLOGD("first count %zu second count %zu", left.reportItems_.size(), right.reportItems_.size());
ReportItemsConstIt it = left.reportItems_.begin();
ReportItemsConstIt it2 = right.reportItems_.begin();
while (it != left.reportItems_.end()) {
if (it2 != right.reportItems_.end()) {
while (it2 != right.reportItems_.end()) {
if (MultiLevelSame(*it, *it2)) {
if (it->heat > option_.heatLimit_ && it2->heat > option_.heatLimit_) {
OutputStdItemHeating(it->heat, it2->heat);
OutputStdContentItem(left, *it);
}
it++;
it2++;
break;
} else {
if (it2->heat > option_.heatLimit_) {
OutputStdItemHeating(0.0f, it2->heat);
OutputStdContentItem(right, *it2);
}
it2++;
continue;
}
}
} else {
if (it->heat > option_.heatLimit_) {
OutputStdItemHeating(it->heat, 0.0f);
OutputStdContentItem(left, *it);
}
it++;
continue;
}
}
while (it2 != right.reportItems_.end()) {
OutputStdItemHeating(0, it2->heat);
OutputStdContentItem(right, *it2);
it2++;
}
}
void Report::OutputStd(FILE *output)
{
output_ = output;
PrepareConsole();
for (auto &config : configs_) {
OutputStdStatistics(config);
OutputStdHead(config);
OutputStdContent(config);
}
}
void Report::OutputStdDiff(FILE *output, Report &other)
{
output_ = output;
PrepareConsole();
auto left = configs_.begin();
while (left != configs_.end()) {
auto right = other.configs_.begin();
while (right != other.configs_.end()) {
if (*left == *right) {
OutputStdStatistics(*left);
OutputStdHead(*left, true);
OutputStdContentDiff(*left, *right);
break;
}
right++;
}
left++;
}
}
}
}
}