diff options
author | Yabin Cui <yabinc@google.com> | 2016-05-25 21:07:19 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2016-05-25 21:07:19 +0000 |
commit | c8642b2dcfd60f12a0cdbd1f9361967d264d2a3a (patch) | |
tree | 7c727380e64c5165b5039b368a9b2ebad3e80556 /simpleperf | |
parent | 5dcf54a6945f237fa9f3afbd76687359f1b018f9 (diff) | |
parent | b64a86327afe2b77dab7d724d363386c674163b6 (diff) | |
download | extras-c8642b2dcfd60f12a0cdbd1f9361967d264d2a3a.tar.gz |
Merge "simpleperf: refactor SampleTree for reuse."
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/Android.mk | 2 | ||||
-rw-r--r-- | simpleperf/SampleComparator.h | 104 | ||||
-rw-r--r-- | simpleperf/SampleDisplayer.h | 223 | ||||
-rw-r--r-- | simpleperf/callchain.cpp | 128 | ||||
-rw-r--r-- | simpleperf/callchain.h | 125 | ||||
-rw-r--r-- | simpleperf/cmd_report.cpp | 786 | ||||
-rw-r--r-- | simpleperf/record.h | 15 | ||||
-rw-r--r-- | simpleperf/sample_tree.cpp | 161 | ||||
-rw-r--r-- | simpleperf/sample_tree.h | 377 | ||||
-rw-r--r-- | simpleperf/sample_tree_test.cpp | 236 | ||||
-rw-r--r-- | simpleperf/thread_tree.cpp | 9 | ||||
-rw-r--r-- | simpleperf/thread_tree.h | 2 |
12 files changed, 1180 insertions, 988 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index 5a64a3f0..842991e3 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -74,7 +74,6 @@ simpleperf_ldlibs_host_linux := -lrt # libsimpleperf # ========================================================= libsimpleperf_src_files := \ - callchain.cpp \ cmd_dumprecord.cpp \ cmd_help.cpp \ cmd_report.cpp \ @@ -87,7 +86,6 @@ libsimpleperf_src_files := \ read_elf.cpp \ record.cpp \ record_file_reader.cpp \ - sample_tree.cpp \ thread_tree.cpp \ utils.cpp \ diff --git a/simpleperf/SampleComparator.h b/simpleperf/SampleComparator.h new file mode 100644 index 00000000..8f9b2e4b --- /dev/null +++ b/simpleperf/SampleComparator.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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. + */ + +#ifndef SIMPLE_PERF_SAMPLE_COMPARATOR_H_ +#define SIMPLE_PERF_SAMPLE_COMPARATOR_H_ + +#include <string.h> + +#include <vector> + +// The compare functions below are used to compare two samples by their item +// content. + +template <typename T> +int Compare(const T& a, const T& b) { + if (a != b) { + return a < b ? -1 : 1; + } + return 0; +} + +#define BUILD_COMPARE_VALUE_FUNCTION(function_name, compare_part) \ + template <typename EntryT> \ + int function_name(const EntryT* sample1, const EntryT* sample2) { \ + return Compare(sample1->compare_part, sample2->compare_part); \ + } + +#define BUILD_COMPARE_VALUE_FUNCTION_REVERSE(function_name, compare_part) \ + template <typename EntryT> \ + int function_name(const EntryT* sample1, const EntryT* sample2) { \ + return Compare(sample2->compare_part, sample1->compare_part); \ + } + +#define BUILD_COMPARE_STRING_FUNCTION(function_name, compare_part) \ + template <typename EntryT> \ + int function_name(const EntryT* sample1, const EntryT* sample2) { \ + return strcmp(sample1->compare_part, sample2->compare_part); \ + } + +BUILD_COMPARE_VALUE_FUNCTION(ComparePid, thread->pid); +BUILD_COMPARE_VALUE_FUNCTION(CompareTid, thread->tid); +BUILD_COMPARE_VALUE_FUNCTION_REVERSE(CompareSampleCount, sample_count); +BUILD_COMPARE_STRING_FUNCTION(CompareComm, thread_comm); +BUILD_COMPARE_STRING_FUNCTION(CompareDso, map->dso->Path().c_str()); +BUILD_COMPARE_STRING_FUNCTION(CompareSymbol, symbol->DemangledName()); +BUILD_COMPARE_STRING_FUNCTION(CompareDsoFrom, + branch_from.map->dso->Path().c_str()); +BUILD_COMPARE_STRING_FUNCTION(CompareSymbolFrom, + branch_from.symbol->DemangledName()); + +template <typename EntryT> +int CompareTotalPeriod(const EntryT* sample1, const EntryT* sample2) { + uint64_t period1 = sample1->period + sample1->accumulated_period; + uint64_t period2 = sample2->period + sample2->accumulated_period; + return Compare(period2, period1); +} + +// SampleComparator is a class using a collection of compare functions to +// compare two samples. + +template <typename EntryT> +class SampleComparator { + public: + typedef int (*compare_sample_func_t)(const EntryT*, const EntryT*); + + void AddCompareFunction(compare_sample_func_t func) { + compare_v_.push_back(func); + } + + void AddComparator(const SampleComparator<EntryT>& other) { + compare_v_.insert(compare_v_.end(), other.compare_v_.begin(), + other.compare_v_.end()); + } + + bool operator()(const EntryT* sample1, const EntryT* sample2) { + for (const auto& func : compare_v_) { + int ret = func(sample1, sample2); + if (ret != 0) { + return ret < 0; + } + } + return false; + } + + bool empty() const { return compare_v_.empty(); } + + private: + std::vector<compare_sample_func_t> compare_v_; +}; + +#endif // SIMPLE_PERF_SAMPLE_COMPARATOR_H_ diff --git a/simpleperf/SampleDisplayer.h b/simpleperf/SampleDisplayer.h new file mode 100644 index 00000000..c1a058be --- /dev/null +++ b/simpleperf/SampleDisplayer.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * 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. + */ + +#ifndef SIMPLE_PERF_SAMPLE_DISPLAYER_H_ +#define SIMPLE_PERF_SAMPLE_DISPLAYER_H_ + +#include <inttypes.h> + +#include <string> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +// The display functions below are used to show items in a sample. + +template <typename EntryT, typename InfoT> +std::string DisplayAccumulatedOverhead(const EntryT* sample, + const InfoT* info) { + uint64_t period = sample->period + sample->accumulated_period; + uint64_t total_period = info->total_period; + double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0; + return android::base::StringPrintf("%.2f%%", percentage); +} + +template <typename EntryT, typename InfoT> +std::string DisplaySelfOverhead(const EntryT* sample, const InfoT* info) { + uint64_t period = sample->period; + uint64_t total_period = info->total_period; + double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0; + return android::base::StringPrintf("%.2f%%", percentage); +} + +#define BUILD_DISPLAY_UINT64_FUNCTION(function_name, display_part) \ + template <typename EntryT> \ + std::string function_name(const EntryT* sample) { \ + return android::base::StringPrintf("%" PRIu64, sample->display_part); \ + } + +BUILD_DISPLAY_UINT64_FUNCTION(DisplaySampleCount, sample_count); + +template <typename EntryT> +std::string DisplayPid(const EntryT* sample) { + return android::base::StringPrintf("%d", sample->thread->pid); +} + +template <typename EntryT> +std::string DisplayTid(const EntryT* sample) { + return android::base::StringPrintf("%d", sample->thread->tid); +} + +template <typename EntryT> +std::string DisplayComm(const EntryT* sample) { + return sample->thread_comm; +} + +template <typename EntryT> +std::string DisplayDso(const EntryT* sample) { + return sample->map->dso->Path(); +} + +template <typename EntryT> +std::string DisplaySymbol(const EntryT* sample) { + return sample->symbol->DemangledName(); +} + +template <typename EntryT> +std::string DisplayDsoFrom(const EntryT* sample) { + return sample->branch_from.map->dso->Path(); +} + +template <typename EntryT> +std::string DisplaySymbolFrom(const EntryT* sample) { + return sample->branch_from.symbol->DemangledName(); +} + +template <typename CallChainNodeT> +void DisplayCallGraphEntry(FILE* fp, size_t depth, std::string prefix, + const std::unique_ptr<CallChainNodeT>& node, + uint64_t parent_period, bool last) { + if (depth > 20) { + LOG(WARNING) << "truncated callgraph at depth " << depth; + return; + } + prefix += "|"; + fprintf(fp, "%s\n", prefix.c_str()); + if (last) { + prefix.back() = ' '; + } + std::string percentage_s = "-- "; + if (node->period + node->children_period != parent_period) { + double percentage = + 100.0 * (node->period + node->children_period) / parent_period; + percentage_s = android::base::StringPrintf("--%.2f%%-- ", percentage); + } + fprintf(fp, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(), + node->chain[0]->symbol->DemangledName()); + prefix.append(percentage_s.size(), ' '); + for (size_t i = 1; i < node->chain.size(); ++i) { + fprintf(fp, "%s%s\n", prefix.c_str(), + node->chain[i]->symbol->DemangledName()); + } + for (size_t i = 0; i < node->children.size(); ++i) { + DisplayCallGraphEntry(fp, depth + 1, prefix, node->children[i], + node->children_period, + (i + 1 == node->children.size())); + } +} + +template <typename EntryT> +void DisplayCallgraph(FILE* fp, const EntryT* sample) { + std::string prefix = " "; + fprintf(fp, "%s|\n", prefix.c_str()); + fprintf(fp, "%s-- %s\n", prefix.c_str(), sample->symbol->DemangledName()); + prefix.append(3, ' '); + for (size_t i = 0; i < sample->callchain.children.size(); ++i) { + DisplayCallGraphEntry(fp, 1, prefix, sample->callchain.children[i], + sample->callchain.children_period, + (i + 1 == sample->callchain.children.size())); + } +} + +// SampleDisplayer is a class using a collections of display functions to show a +// sample. + +template <typename EntryT, typename InfoT> +class SampleDisplayer { + public: + typedef std::string (*display_sample_func_t)(const EntryT*); + typedef std::string (*display_sample_with_info_func_t)(const EntryT*, + const InfoT*); + typedef void (*exclusive_display_sample_func_t)(FILE* fp, const EntryT*); + + private: + struct Item { + std::string name; + size_t width; + display_sample_func_t func; + display_sample_with_info_func_t func_with_info; + }; + + public: + void SetInfo(const InfoT* info) { info_ = info; } + + void AddDisplayFunction(const std::string& name, display_sample_func_t func) { + Item item; + item.name = name; + item.width = name.size(); + item.func = func; + item.func_with_info = nullptr; + display_v_.push_back(item); + } + + void AddDisplayFunction(const std::string& name, + display_sample_with_info_func_t func_with_info) { + Item item; + item.name = name; + item.width = name.size(); + item.func = nullptr; + item.func_with_info = func_with_info; + display_v_.push_back(item); + } + + void AddExclusiveDisplayFunction(exclusive_display_sample_func_t func) { + exclusive_display_v_.push_back(func); + } + + void AdjustWidth(const EntryT* sample) { + for (auto& item : display_v_) { + std::string data = (item.func != nullptr) + ? item.func(sample) + : item.func_with_info(sample, info_); + item.width = std::max(item.width, data.size()); + } + } + + void PrintNames(FILE* fp) { + for (size_t i = 0; i < display_v_.size(); ++i) { + auto& item = display_v_[i]; + if (i != display_v_.size() - 1) { + fprintf(fp, "%-*s ", static_cast<int>(item.width), item.name.c_str()); + } else { + fprintf(fp, "%s\n", item.name.c_str()); + } + } + } + + void PrintSample(FILE* fp, const EntryT* sample) { + for (size_t i = 0; i < display_v_.size(); ++i) { + auto& item = display_v_[i]; + std::string data = (item.func != nullptr) + ? item.func(sample) + : item.func_with_info(sample, info_); + if (i != display_v_.size() - 1) { + fprintf(fp, "%-*s ", static_cast<int>(item.width), data.c_str()); + } else { + fprintf(fp, "%s\n", data.c_str()); + } + } + for (auto& func : exclusive_display_v_) { + func(fp, sample); + } + } + + private: + const InfoT* info_; + std::vector<Item> display_v_; + std::vector<exclusive_display_sample_func_t> exclusive_display_v_; +}; + +#endif // SIMPLE_PERF_SAMPLE_DISPLAYER_H_ diff --git a/simpleperf/callchain.cpp b/simpleperf/callchain.cpp deleted file mode 100644 index 8fdafc43..00000000 --- a/simpleperf/callchain.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * 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. - */ - -#include "callchain.h" - -#include <string.h> - -#include <queue> - -#include <android-base/logging.h> -#include "sample_tree.h" - -static bool MatchSampleByName(const SampleEntry* sample1, const SampleEntry* sample2) { - return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0; -} - -static size_t GetMatchingLengthInNode(const CallChainNode* node, - const std::vector<SampleEntry*>& chain, size_t chain_start) { - size_t i, j; - for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); ++i, ++j) { - if (!MatchSampleByName(node->chain[i], chain[j])) { - break; - } - } - return i; -} - -static CallChainNode* FindMatchingNode(const std::vector<std::unique_ptr<CallChainNode>>& nodes, - const SampleEntry* sample) { - for (auto& node : nodes) { - if (MatchSampleByName(node->chain.front(), sample)) { - return node.get(); - } - } - return nullptr; -} - -static std::unique_ptr<CallChainNode> AllocateNode(const std::vector<SampleEntry*>& chain, - size_t chain_start, uint64_t period, - uint64_t children_period) { - std::unique_ptr<CallChainNode> node(new CallChainNode); - for (size_t i = chain_start; i < chain.size(); ++i) { - node->chain.push_back(chain[i]); - } - node->period = period; - node->children_period = children_period; - return node; -} - -static void SplitNode(CallChainNode* parent, size_t parent_length) { - std::unique_ptr<CallChainNode> child = - AllocateNode(parent->chain, parent_length, parent->period, parent->children_period); - child->children = std::move(parent->children); - parent->period = 0; - parent->children_period = child->period + child->children_period; - parent->chain.resize(parent_length); - parent->children.clear(); - parent->children.push_back(std::move(child)); -} - -void CallChainRoot::AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period) { - children_period += period; - CallChainNode* p = FindMatchingNode(children, callchain[0]); - if (p == nullptr) { - std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, 0, period, 0); - children.push_back(std::move(new_node)); - return; - } - size_t callchain_pos = 0; - while (true) { - size_t match_length = GetMatchingLengthInNode(p, callchain, callchain_pos); - CHECK_GT(match_length, 0u); - callchain_pos += match_length; - bool find_child = true; - if (match_length < p->chain.size()) { - SplitNode(p, match_length); - find_child = false; // No need to find matching node in p->children. - } - if (callchain_pos == callchain.size()) { - p->period += period; - return; - } - p->children_period += period; - if (find_child) { - CallChainNode* np = FindMatchingNode(p->children, callchain[callchain_pos]); - if (np != nullptr) { - p = np; - continue; - } - } - std::unique_ptr<CallChainNode> new_node = AllocateNode(callchain, callchain_pos, period, 0); - p->children.push_back(std::move(new_node)); - break; - } -} - -static bool CompareNodeByPeriod(const std::unique_ptr<CallChainNode>& n1, - const std::unique_ptr<CallChainNode>& n2) { - uint64_t period1 = n1->period + n1->children_period; - uint64_t period2 = n2->period + n2->children_period; - return period1 > period2; -} - -void CallChainRoot::SortByPeriod() { - std::queue<std::vector<std::unique_ptr<CallChainNode>>*> queue; - queue.push(&children); - while (!queue.empty()) { - std::vector<std::unique_ptr<CallChainNode>>* v = queue.front(); - queue.pop(); - std::sort(v->begin(), v->end(), CompareNodeByPeriod); - for (auto& node : *v) { - queue.push(&node->children); - } - } -} diff --git a/simpleperf/callchain.h b/simpleperf/callchain.h index 4b8a9d41..54820b14 100644 --- a/simpleperf/callchain.h +++ b/simpleperf/callchain.h @@ -17,27 +17,140 @@ #ifndef SIMPLE_PERF_CALLCHAIN_H_ #define SIMPLE_PERF_CALLCHAIN_H_ +#include <string.h> + +#include <algorithm> #include <memory> +#include <queue> #include <vector> -struct SampleEntry; +#include <android-base/logging.h> +template <typename EntryT> struct CallChainNode { uint64_t period; uint64_t children_period; - std::vector<SampleEntry*> chain; + std::vector<EntryT*> chain; std::vector<std::unique_ptr<CallChainNode>> children; }; +template <typename EntryT> struct CallChainRoot { + typedef CallChainNode<EntryT> NodeT; uint64_t children_period; - std::vector<std::unique_ptr<CallChainNode>> children; + std::vector<std::unique_ptr<NodeT>> children; + + CallChainRoot() : children_period(0) {} + + void AddCallChain(const std::vector<EntryT*>& callchain, uint64_t period) { + children_period += period; + NodeT* p = FindMatchingNode(children, callchain[0]); + if (p == nullptr) { + std::unique_ptr<NodeT> new_node = AllocateNode(callchain, 0, period, 0); + children.push_back(std::move(new_node)); + return; + } + size_t callchain_pos = 0; + while (true) { + size_t match_length = + GetMatchingLengthInNode(p, callchain, callchain_pos); + CHECK_GT(match_length, 0u); + callchain_pos += match_length; + bool find_child = true; + if (match_length < p->chain.size()) { + SplitNode(p, match_length); + find_child = false; // No need to find matching node in p->children. + } + if (callchain_pos == callchain.size()) { + p->period += period; + return; + } + p->children_period += period; + if (find_child) { + NodeT* np = FindMatchingNode(p->children, callchain[callchain_pos]); + if (np != nullptr) { + p = np; + continue; + } + } + std::unique_ptr<NodeT> new_node = + AllocateNode(callchain, callchain_pos, period, 0); + p->children.push_back(std::move(new_node)); + break; + } + } + + void SortByPeriod() { + std::queue<std::vector<std::unique_ptr<NodeT>>*> queue; + queue.push(&children); + while (!queue.empty()) { + std::vector<std::unique_ptr<NodeT>>* v = queue.front(); + queue.pop(); + std::sort(v->begin(), v->end(), CallChainRoot::CompareNodeByPeriod); + for (auto& node : *v) { + if (!node->children.empty()) { + queue.push(&node->children); + } + } + } + } + + private: + NodeT* FindMatchingNode(const std::vector<std::unique_ptr<NodeT>>& nodes, + const EntryT* sample) { + for (auto& node : nodes) { + if (MatchSampleByName(node->chain.front(), sample)) { + return node.get(); + } + } + return nullptr; + } + + size_t GetMatchingLengthInNode(NodeT* node, const std::vector<EntryT*>& chain, + size_t chain_start) { + size_t i, j; + for (i = 0, j = chain_start; i < node->chain.size() && j < chain.size(); + ++i, ++j) { + if (!MatchSampleByName(node->chain[i], chain[j])) { + break; + } + } + return i; + } + + bool MatchSampleByName(const EntryT* sample1, const EntryT* sample2) { + return strcmp(sample1->symbol->Name(), sample2->symbol->Name()) == 0; + } + + void SplitNode(NodeT* parent, size_t parent_length) { + std::unique_ptr<NodeT> child = AllocateNode( + parent->chain, parent_length, parent->period, parent->children_period); + child->children = std::move(parent->children); + parent->period = 0; + parent->children_period = child->period + child->children_period; + parent->chain.resize(parent_length); + parent->children.clear(); + parent->children.push_back(std::move(child)); + } - CallChainRoot() : children_period(0) { + std::unique_ptr<NodeT> AllocateNode(const std::vector<EntryT*>& chain, + size_t chain_start, uint64_t period, + uint64_t children_period) { + std::unique_ptr<NodeT> node(new NodeT); + for (size_t i = chain_start; i < chain.size(); ++i) { + node->chain.push_back(chain[i]); + } + node->period = period; + node->children_period = children_period; + return node; } - void AddCallChain(const std::vector<SampleEntry*>& callchain, uint64_t period); - void SortByPeriod(); + static bool CompareNodeByPeriod(const std::unique_ptr<NodeT>& n1, + const std::unique_ptr<NodeT>& n2) { + uint64_t period1 = n1->period + n1->children_period; + uint64_t period2 = n2->period + n2->children_period; + return period1 > period2; + } }; #endif // SIMPLE_PERF_CALLCHAIN_H_ diff --git a/simpleperf/cmd_report.cpp b/simpleperf/cmd_report.cpp index cf41b24e..e2cd39c0 100644 --- a/simpleperf/cmd_report.cpp +++ b/simpleperf/cmd_report.cpp @@ -41,276 +41,250 @@ #include "thread_tree.h" #include "utils.h" -class Displayable { - public: - explicit Displayable(const std::string& name) : name_(name), width_(name.size()) { - } - - virtual ~Displayable() { - } - - const std::string& Name() const { - return name_; - } - size_t Width() const { - return width_; - } - - virtual std::string Show(const SampleEntry& sample) const = 0; - void AdjustWidth(const SampleEntry& sample) { - size_t size = Show(sample).size(); - width_ = std::max(width_, size); - } - - private: - const std::string name_; - size_t width_; -}; - -class AccumulatedOverheadItem : public Displayable { - public: - explicit AccumulatedOverheadItem(const SampleTree& sample_tree) - : Displayable("Children"), sample_tree_(sample_tree) { - } - - std::string Show(const SampleEntry& sample) const override { - uint64_t period = sample.period + sample.accumulated_period; - uint64_t total_period = sample_tree_.TotalPeriod(); - double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0; - return android::base::StringPrintf("%.2lf%%", percentage); - } - - private: - const SampleTree& sample_tree_; -}; - -class SelfOverheadItem : public Displayable { - public: - explicit SelfOverheadItem(const SampleTree& sample_tree, const std::string& name = "Self") - : Displayable(name), sample_tree_(sample_tree) { - } - - std::string Show(const SampleEntry& sample) const override { - uint64_t period = sample.period; - uint64_t total_period = sample_tree_.TotalPeriod(); - double percentage = (total_period != 0) ? 100.0 * period / total_period : 0.0; - return android::base::StringPrintf("%.2lf%%", percentage); - } - - private: - const SampleTree& sample_tree_; -}; - -class SampleCountItem : public Displayable { - public: - SampleCountItem() : Displayable("Sample") { - } - - std::string Show(const SampleEntry& sample) const override { - return android::base::StringPrintf("%" PRId64, sample.sample_count); - } -}; - -class Comparable { - public: - virtual ~Comparable() { - } - - virtual int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const = 0; -}; - -class PidItem : public Displayable, public Comparable { - public: - PidItem() : Displayable("Pid") { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return sample1.thread->pid - sample2.thread->pid; - } +namespace { - std::string Show(const SampleEntry& sample) const override { - return android::base::StringPrintf("%d", sample.thread->pid); - } -}; - -class TidItem : public Displayable, public Comparable { - public: - TidItem() : Displayable("Tid") { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return sample1.thread->tid - sample2.thread->tid; - } - - std::string Show(const SampleEntry& sample) const override { - return android::base::StringPrintf("%d", sample.thread->tid); - } +static std::set<std::string> branch_sort_keys = { + "dso_from", "dso_to", "symbol_from", "symbol_to", }; +struct BranchFromEntry { + uint64_t ip; + const MapEntry* map; + const Symbol* symbol; + uint64_t flags; -class CommItem : public Displayable, public Comparable { - public: - CommItem() : Displayable("Command") { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return strcmp(sample1.thread_comm, sample2.thread_comm); - } - - std::string Show(const SampleEntry& sample) const override { - return sample.thread_comm; - } + BranchFromEntry() : ip(0), map(nullptr), symbol(nullptr), flags(0) {} }; -class DsoItem : public Displayable, public Comparable { - public: - explicit DsoItem(const std::string& name = "Shared Object") : Displayable(name) { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return strcmp(sample1.map->dso->Path().c_str(), sample2.map->dso->Path().c_str()); - } - - std::string Show(const SampleEntry& sample) const override { - return sample.map->dso->Path(); - } +struct SampleEntry { + uint64_t ip; + uint64_t time; + uint64_t period; + // accumuated when appearing in other sample's callchain + uint64_t accumulated_period; + uint64_t sample_count; + const ThreadEntry* thread; + const char* thread_comm; + const MapEntry* map; + const Symbol* symbol; + BranchFromEntry branch_from; + // a callchain tree representing all callchains in the sample + CallChainRoot<SampleEntry> callchain; + + SampleEntry(uint64_t ip, uint64_t time, uint64_t period, + uint64_t accumulated_period, uint64_t sample_count, + const ThreadEntry* thread, const MapEntry* map, + const Symbol* symbol) + : ip(ip), + time(time), + period(period), + accumulated_period(accumulated_period), + sample_count(sample_count), + thread(thread), + thread_comm(thread->comm), + map(map), + symbol(symbol) {} + + // The data member 'callchain' can only move, not copy. + SampleEntry(SampleEntry&&) = default; + SampleEntry(SampleEntry&) = delete; }; -class SymbolItem : public Displayable, public Comparable { - public: - explicit SymbolItem(const std::string& name = "Symbol") : Displayable(name) { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return strcmp(sample1.symbol->DemangledName(), sample2.symbol->DemangledName()); - } - - std::string Show(const SampleEntry& sample) const override { - return sample.symbol->DemangledName(); - } +struct SampleTree { + std::vector<SampleEntry*> samples; + uint64_t total_samples; + uint64_t total_period; }; -class DsoFromItem : public Displayable, public Comparable { +class ReportCmdSampleTreeBuilder + : public SampleTreeBuilder<SampleEntry, uint64_t> { public: - DsoFromItem() : Displayable("Source Shared Object") { - } - - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return strcmp(sample1.branch_from.map->dso->Path().c_str(), - sample2.branch_from.map->dso->Path().c_str()); + ReportCmdSampleTreeBuilder(SampleComparator<SampleEntry> sample_comparator, + ThreadTree* thread_tree) + : SampleTreeBuilder(sample_comparator), + thread_tree_(thread_tree), + total_samples_(0), + total_period_(0) {} + + void SetFilters(const std::unordered_set<int>& pid_filter, + const std::unordered_set<int>& tid_filter, + const std::unordered_set<std::string>& comm_filter, + const std::unordered_set<std::string>& dso_filter) { + pid_filter_ = pid_filter; + tid_filter_ = tid_filter; + comm_filter_ = comm_filter; + dso_filter_ = dso_filter; + } + + SampleTree GetSampleTree() const { + SampleTree sample_tree; + sample_tree.samples = GetSamples(); + sample_tree.total_samples = total_samples_; + sample_tree.total_period = total_period_; + return sample_tree; + } + + protected: + SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel, + uint64_t* acc_info) override { + const ThreadEntry* thread = + thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); + const MapEntry* map = + thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel); + const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip); + *acc_info = r.period_data.period; + return InsertSample(std::unique_ptr<SampleEntry>( + new SampleEntry(r.ip_data.ip, r.time_data.time, r.period_data.period, 0, + 1, thread, map, symbol))); + } + + SampleEntry* CreateBranchSample(const SampleRecord& r, + const BranchStackItemType& item) override { + const ThreadEntry* thread = + thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); + const MapEntry* from_map = thread_tree_->FindMap(thread, item.from); + const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, item.from); + const MapEntry* to_map = thread_tree_->FindMap(thread, item.to); + const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to); + std::unique_ptr<SampleEntry> sample( + new SampleEntry(item.to, r.time_data.time, r.period_data.period, 0, 1, + thread, to_map, to_symbol)); + sample->branch_from.ip = item.from; + sample->branch_from.map = from_map; + sample->branch_from.symbol = from_symbol; + sample->branch_from.flags = item.flags; + return InsertSample(std::move(sample)); + } + + SampleEntry* CreateCallChainSample(const SampleEntry* sample, uint64_t ip, + bool in_kernel, + const std::vector<SampleEntry*>& callchain, + const uint64_t& acc_info) override { + const ThreadEntry* thread = sample->thread; + const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel); + const Symbol* symbol = thread_tree_->FindSymbol(map, ip); + std::unique_ptr<SampleEntry> callchain_sample( + new SampleEntry(ip, sample->time, 0, acc_info, 0, thread, map, symbol)); + return InsertCallChainSample(std::move(callchain_sample), callchain); + } + + const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override { + return sample->thread; + } + + void InsertCallChainForSample(SampleEntry* sample, + const std::vector<SampleEntry*>& callchain, + const uint64_t& acc_info) override { + sample->callchain.AddCallChain(callchain, acc_info); + } + + bool FilterSample(const SampleEntry* sample) override { + if (!pid_filter_.empty() && + pid_filter_.find(sample->thread->pid) == pid_filter_.end()) { + return false; + } + if (!tid_filter_.empty() && + tid_filter_.find(sample->thread->tid) == tid_filter_.end()) { + return false; + } + if (!comm_filter_.empty() && + comm_filter_.find(sample->thread_comm) == comm_filter_.end()) { + return false; + } + if (!dso_filter_.empty() && + dso_filter_.find(sample->map->dso->Path()) == dso_filter_.end()) { + return false; + } + return true; } - std::string Show(const SampleEntry& sample) const override { - return sample.branch_from.map->dso->Path(); + void UpdateSummary(const SampleEntry* sample) override { + total_samples_ += sample->sample_count; + total_period_ += sample->period; } -}; -class DsoToItem : public DsoItem { - public: - DsoToItem() : DsoItem("Target Shared Object") { + void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override { + sample1->period += sample2->period; + sample1->accumulated_period += sample2->accumulated_period; + sample1->sample_count += sample2->sample_count; } -}; -class SymbolFromItem : public Displayable, public Comparable { - public: - SymbolFromItem() : Displayable("Source Symbol") { - } + private: + ThreadTree* thread_tree_; - int Compare(const SampleEntry& sample1, const SampleEntry& sample2) const override { - return strcmp(sample1.branch_from.symbol->DemangledName(), - sample2.branch_from.symbol->DemangledName()); - } + std::unordered_set<int> pid_filter_; + std::unordered_set<int> tid_filter_; + std::unordered_set<std::string> comm_filter_; + std::unordered_set<std::string> dso_filter_; - std::string Show(const SampleEntry& sample) const override { - return sample.branch_from.symbol->DemangledName(); - } + uint64_t total_samples_; + uint64_t total_period_; }; -class SymbolToItem : public SymbolItem { - public: - SymbolToItem() : SymbolItem("Target Symbol") { - } -}; - -static std::set<std::string> branch_sort_keys = { - "dso_from", "dso_to", "symbol_from", "symbol_to", -}; +using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>; +using ReportCmdSampleTreeDisplayer = + SampleTreeDisplayer<SampleEntry, SampleTree>; class ReportCommand : public Command { public: ReportCommand() : Command( "report", "report sampling information in perf.data", - "Usage: simpleperf report [options]\n" - " -b Use the branch-to addresses in sampled take branches instead of\n" - " the instruction addresses. Only valid for perf.data recorded with\n" - " -b/-j option.\n" - " --children Print the overhead accumulated by appearing in the callchain.\n" - " --comms comm1,comm2,...\n" - " Report only for selected comms.\n" - " --dsos dso1,dso2,...\n" - " Report only for selected dsos.\n" - " -g [callee|caller]\n" - " Print call graph. If callee mode is used, the graph shows how\n" - " functions are called from others. Otherwise, the graph shows how\n" - " functions call others. Default is callee mode.\n" - " -i <file> Specify path of record file, default is perf.data.\n" - " -n Print the sample count for each item.\n" - " --no-demangle Don't demangle symbol names.\n" - " -o report_file_name Set report file name, default is stdout.\n" - " --pids pid1,pid2,...\n" - " Report only for selected pids.\n" - " --sort key1,key2,...\n" - " Select the keys to sort and print the report. Possible keys\n" - " include pid, tid, comm, dso, symbol, dso_from, dso_to, symbol_from\n" - " symbol_to. dso_from, dso_to, symbol_from, symbol_to can only be\n" - " used with -b option. Default keys are \"comm,pid,tid,dso,symbol\"\n" - " --symfs <dir> Look for files with symbols relative to this directory.\n" - " --tids tid1,tid2,...\n" - " Report only for selected tids.\n" - " --vmlinux <file>\n" - " Parse kernel symbols from <file>.\n"), + // clang-format off +"Usage: simpleperf report [options]\n" +"-b Use the branch-to addresses in sampled take branches instead of the\n" +" instruction addresses. Only valid for perf.data recorded with -b/-j\n" +" option.\n" +"--children Print the overhead accumulated by appearing in the callchain.\n" +"--comms comm1,comm2,... Report only for selected comms.\n" +"--dsos dso1,dso2,... Report only for selected dsos.\n" +"-g [callee|caller] Print call graph. If callee mode is used, the graph\n" +" shows how functions are called from others. Otherwise,\n" +" the graph shows how functions call others.\n" +" Default is callee mode.\n" +"-i <file> Specify path of record file, default is perf.data.\n" +"-n Print the sample count for each item.\n" +"--no-demangle Don't demangle symbol names.\n" +"-o report_file_name Set report file name, default is stdout.\n" +"--pids pid1,pid2,... Report only for selected pids.\n" +"--sort key1,key2,... Select the keys to sort and print the report.\n" +" Possible keys include pid, tid, comm, dso, symbol,\n" +" dso_from, dso_to, symbol_from, symbol_to.\n" +" dso_from, dso_to, symbol_from, symbol_to can only be\n" +" used with -b option.\n" +" Default keys are \"comm,pid,tid,dso,symbol\"\n" +"--symfs <dir> Look for files with symbols relative to this directory.\n" +"--tids tid1,tid2,... Report only for selected tids.\n" +"--vmlinux <file> Parse kernel symbols from <file>.\n" + // clang-format on + ), record_filename_("perf.data"), record_file_arch_(GetBuildArch()), use_branch_address_(false), system_wide_collection_(false), accumulate_callchain_(false), print_callgraph_(false), - callgraph_show_callee_(true), - report_fp_(nullptr) { - compare_sample_func_t compare_sample_callback = std::bind( - &ReportCommand::CompareSampleEntry, this, std::placeholders::_1, std::placeholders::_2); - sample_tree_ = - std::unique_ptr<SampleTree>(new SampleTree(&thread_tree_, compare_sample_callback)); - } + callgraph_show_callee_(true) {} bool Run(const std::vector<std::string>& args); private: bool ParseOptions(const std::vector<std::string>& args); bool ReadEventAttrFromRecordFile(); - void ReadSampleTreeFromRecordFile(); - void ProcessRecord(std::unique_ptr<Record> record); - void ProcessSampleRecord(const SampleRecord& r); bool ReadFeaturesFromRecordFile(); - int CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2); + bool ReadSampleTreeFromRecordFile(); + bool ProcessRecord(std::unique_ptr<Record> record); bool PrintReport(); - void PrintReportContext(); - void CollectReportWidth(); - void CollectReportEntryWidth(const SampleEntry& sample); - void PrintReportHeader(); - void PrintReportEntry(const SampleEntry& sample); - void PrintCallGraph(const SampleEntry& sample); - void PrintCallGraphEntry(size_t depth, std::string prefix, const std::unique_ptr<CallChainNode>& node, - uint64_t parent_period, bool last); + void PrintReportContext(FILE* fp); std::string record_filename_; ArchType record_file_arch_; std::unique_ptr<RecordFileReader> record_file_reader_; std::vector<perf_event_attr> event_attrs_; - std::vector<std::unique_ptr<Displayable>> displayable_items_; - std::vector<Comparable*> comparable_items_; ThreadTree thread_tree_; - std::unique_ptr<SampleTree> sample_tree_; + SampleTree sample_tree_; + std::unique_ptr<ReportCmdSampleTreeBuilder> sample_tree_builder_; + std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_; + std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_; bool use_branch_address_; std::string record_cmdline_; bool system_wide_collection_; @@ -319,7 +293,6 @@ class ReportCommand : public Command { bool callgraph_show_callee_; std::string report_filename_; - FILE* report_fp_; }; bool ReportCommand::Run(const std::vector<std::string>& args) { @@ -341,7 +314,9 @@ bool ReportCommand::Run(const std::vector<std::string>& args) { return false; } ScopedCurrentArch scoped_arch(record_file_arch_); - ReadSampleTreeFromRecordFile(); + if (!ReadSampleTreeFromRecordFile()) { + return false; + } // 3. Show collected information. if (!PrintReport()) { @@ -368,7 +343,8 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) { } else if (args[i] == "--children") { accumulate_callchain_ = true; } else if (args[i] == "--comms" || args[i] == "--dsos") { - std::unordered_set<std::string>& filter = (args[i] == "--comms" ? comm_filter : dso_filter); + std::unordered_set<std::string>& filter = + (args[i] == "--comms" ? comm_filter : dso_filter); if (!NextArgumentOrError(args, &i)) { return false; } @@ -420,7 +396,8 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) { } ids.push_back(id); } - std::unordered_set<int>& filter = (args[i] == "--pids" ? pid_filter : tid_filter); + std::unordered_set<int>& filter = + (args[i] == "--pids" ? pid_filter : tid_filter); filter.insert(ids.begin(), ids.end()); } else if (args[i] == "--sort") { @@ -453,69 +430,77 @@ bool ReportCommand::ParseOptions(const std::vector<std::string>& args) { Dso::SetVmlinux(vmlinux); } - if (!accumulate_callchain_) { - displayable_items_.push_back( - std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_, "Overhead"))); + SampleDisplayer<SampleEntry, SampleTree> displayer; + SampleComparator<SampleEntry> comparator; + + if (accumulate_callchain_) { + displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead); + displayer.AddDisplayFunction("Self", DisplaySelfOverhead); } else { - displayable_items_.push_back( - std::unique_ptr<Displayable>(new AccumulatedOverheadItem(*sample_tree_))); - displayable_items_.push_back(std::unique_ptr<Displayable>(new SelfOverheadItem(*sample_tree_))); + displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead); + } + if (print_callgraph_) { + displayer.AddExclusiveDisplayFunction(DisplayCallgraph); } if (print_sample_count) { - displayable_items_.push_back(std::unique_ptr<Displayable>(new SampleCountItem)); + displayer.AddDisplayFunction("Sample", DisplaySampleCount); } + for (auto& key : sort_keys) { - if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) { + if (!use_branch_address_ && + branch_sort_keys.find(key) != branch_sort_keys.end()) { LOG(ERROR) << "sort key '" << key << "' can only be used with -b option."; return false; } if (key == "pid") { - PidItem* item = new PidItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(ComparePid); + displayer.AddDisplayFunction("Pid", DisplayPid); } else if (key == "tid") { - TidItem* item = new TidItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareTid); + displayer.AddDisplayFunction("Tid", DisplayTid); } else if (key == "comm") { - CommItem* item = new CommItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareComm); + displayer.AddDisplayFunction("Command", DisplayComm); } else if (key == "dso") { - DsoItem* item = new DsoItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareDso); + displayer.AddDisplayFunction("Shared Object", DisplayDso); } else if (key == "symbol") { - SymbolItem* item = new SymbolItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareSymbol); + displayer.AddDisplayFunction("Symbol", DisplaySymbol); } else if (key == "dso_from") { - DsoFromItem* item = new DsoFromItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareDsoFrom); + displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom); } else if (key == "dso_to") { - DsoToItem* item = new DsoToItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareDso); + displayer.AddDisplayFunction("Target Shared Object", DisplayDso); } else if (key == "symbol_from") { - SymbolFromItem* item = new SymbolFromItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareSymbolFrom); + displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom); } else if (key == "symbol_to") { - SymbolToItem* item = new SymbolToItem; - displayable_items_.push_back(std::unique_ptr<Displayable>(item)); - comparable_items_.push_back(item); + comparator.AddCompareFunction(CompareSymbol); + displayer.AddDisplayFunction("Target Symbol", DisplaySymbol); } else { LOG(ERROR) << "Unknown sort key: " << key; return false; } } - sample_tree_->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter); + + sample_tree_builder_.reset( + new ReportCmdSampleTreeBuilder(comparator, &thread_tree_)); + sample_tree_builder_->SetFilters(pid_filter, tid_filter, comm_filter, + dso_filter); + + SampleComparator<SampleEntry> sort_comparator; + sort_comparator.AddCompareFunction(CompareTotalPeriod); + sort_comparator.AddComparator(comparator); + sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator)); + sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer)); return true; } bool ReportCommand::ReadEventAttrFromRecordFile() { - const std::vector<PerfFileFormat::FileAttr>& file_attrs = record_file_reader_->AttrSection(); + const std::vector<PerfFileFormat::FileAttr>& file_attrs = + record_file_reader_->AttrSection(); for (const auto& attr : file_attrs) { event_attrs_.push_back(attr.attr); } @@ -528,127 +513,25 @@ bool ReportCommand::ReadEventAttrFromRecordFile() { } } if (!has_branch_stack) { - LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option."; + LOG(ERROR) << record_filename_ + << " is not recorded with branch stack sampling option."; return false; } } return true; } -void ReportCommand::ReadSampleTreeFromRecordFile() { - thread_tree_.AddThread(0, 0, "swapper"); - record_file_reader_->ReadDataSection([this](std::unique_ptr<Record> record) { - ProcessRecord(std::move(record)); - return true; - }); -} - -void ReportCommand::ProcessRecord(std::unique_ptr<Record> record) { - BuildThreadTree(*record, &thread_tree_); - if (record->header.type == PERF_RECORD_SAMPLE) { - ProcessSampleRecord(*static_cast<const SampleRecord*>(record.get())); - } -} - -void ReportCommand::ProcessSampleRecord(const SampleRecord& r) { - if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) { - for (auto& item : r.branch_stack_data.stack) { - if (item.from != 0 && item.to != 0) { - sample_tree_->AddBranchSample(r.tid_data.pid, r.tid_data.tid, item.from, item.to, - item.flags, r.time_data.time, r.period_data.period); - } - } - } else { - bool in_kernel = (r.header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL; - SampleEntry* sample = sample_tree_->AddSample(r.tid_data.pid, r.tid_data.tid, r.ip_data.ip, - r.time_data.time, r.period_data.period, in_kernel); - if (sample == nullptr) { - return; - } - if (accumulate_callchain_) { - std::vector<uint64_t> ips; - if (r.sample_type & PERF_SAMPLE_CALLCHAIN) { - ips.insert(ips.end(), r.callchain_data.ips.begin(), r.callchain_data.ips.end()); - } - // Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to make up for - // the missing kernel patch in N9. See b/22612370. - if ((r.sample_type & PERF_SAMPLE_REGS_USER) && (r.regs_user_data.reg_mask != 0) && - (r.sample_type & PERF_SAMPLE_STACK_USER) && (!r.stack_user_data.data.empty())) { - RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); - std::vector<char> stack(r.stack_user_data.data.begin(), - r.stack_user_data.data.begin() + r.stack_user_data.data.size()); - ArchType arch = GetArchForAbi(ScopedCurrentArch::GetCurrentArch(), r.regs_user_data.abi); - // Normally do strict arch check when unwinding stack. But allow unwinding 32-bit processes - // on 64-bit devices for system wide profiling. - bool strict_arch_check = !system_wide_collection_; - std::vector<uint64_t> unwind_ips = - UnwindCallChain(arch, *sample->thread, regs, stack, strict_arch_check); - if (!unwind_ips.empty()) { - ips.push_back(PERF_CONTEXT_USER); - ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end()); - } - } - - std::vector<SampleEntry*> callchain; - callchain.push_back(sample); - - bool first_ip = true; - for (auto& ip : ips) { - if (ip >= PERF_CONTEXT_MAX) { - switch (ip) { - case PERF_CONTEXT_KERNEL: - in_kernel = true; - break; - case PERF_CONTEXT_USER: - in_kernel = false; - break; - default: - LOG(ERROR) << "Unexpected perf_context in callchain: " << ip; - } - } else { - if (first_ip) { - first_ip = false; - // Remove duplication with sampled ip. - if (ip == r.ip_data.ip) { - continue; - } - } - SampleEntry* sample = - sample_tree_->AddCallChainSample(r.tid_data.pid, r.tid_data.tid, ip, r.time_data.time, - r.period_data.period, in_kernel, callchain); - callchain.push_back(sample); - } - } - - if (print_callgraph_) { - std::set<SampleEntry*> added_set; - if (!callgraph_show_callee_) { - std::reverse(callchain.begin(), callchain.end()); - } - while (callchain.size() >= 2) { - SampleEntry* sample = callchain[0]; - callchain.erase(callchain.begin()); - // Add only once for recursive calls on callchain. - if (added_set.find(sample) != added_set.end()) { - continue; - } - added_set.insert(sample); - sample_tree_->InsertCallChainForSample(sample, callchain, r.period_data.period); - } - } - } - } -} - bool ReportCommand::ReadFeaturesFromRecordFile() { - std::vector<BuildIdRecord> records = record_file_reader_->ReadBuildIdFeature(); + std::vector<BuildIdRecord> records = + record_file_reader_->ReadBuildIdFeature(); std::vector<std::pair<std::string, BuildId>> build_ids; for (auto& r : records) { build_ids.push_back(std::make_pair(r.filename, r.build_id)); } Dso::SetBuildIds(build_ids); - std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH); + std::string arch = + record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH); if (!arch.empty()) { record_file_arch_ = GetArchType(arch); if (record_file_arch_ == ARCH_UNSUPPORTED) { @@ -659,15 +542,16 @@ bool ReportCommand::ReadFeaturesFromRecordFile() { std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature(); if (!cmdline.empty()) { record_cmdline_ = android::base::Join(cmdline, ' '); - // TODO: the code to detect system wide collection option is fragile, remove it once we can - // do cross unwinding. + // TODO: the code to detect system wide collection option is fragile, remove + // it once we can do cross unwinding. for (size_t i = 0; i < cmdline.size(); i++) { std::string& s = cmdline[i]; if (s == "-a") { system_wide_collection_ = true; break; - } else if (s == "--call-graph" || s == "--cpu" || s == "-e" || s == "-f" || s == "-F" || - s == "-j" || s == "-m" || s == "-o" || s == "-p" || s == "-t") { + } else if (s == "--call-graph" || s == "--cpu" || s == "-e" || + s == "-f" || s == "-F" || s == "-j" || s == "-m" || + s == "-o" || s == "-p" || s == "-t") { i++; } else if (!s.empty() && s[0] != '-') { break; @@ -677,44 +561,60 @@ bool ReportCommand::ReadFeaturesFromRecordFile() { return true; } -int ReportCommand::CompareSampleEntry(const SampleEntry& sample1, const SampleEntry& sample2) { - for (auto& item : comparable_items_) { - int result = item->Compare(sample1, sample2); - if (result != 0) { - return result; - } +bool ReportCommand::ReadSampleTreeFromRecordFile() { + thread_tree_.AddThread(0, 0, "swapper"); + sample_tree_builder_->SetBranchSampleOption(use_branch_address_); + // Normally do strict arch check when unwinding stack. But allow unwinding + // 32-bit processes on 64-bit devices for system wide profiling. + bool strict_unwind_arch_check = !system_wide_collection_; + sample_tree_builder_->SetCallChainSampleOptions( + accumulate_callchain_, print_callgraph_, !callgraph_show_callee_, + strict_unwind_arch_check); + if (!record_file_reader_->ReadDataSection( + [this](std::unique_ptr<Record> record) { + return ProcessRecord(std::move(record)); + })) { + return false; + } + sample_tree_ = sample_tree_builder_->GetSampleTree(); + sample_tree_sorter_->Sort(sample_tree_.samples, print_callgraph_); + return true; +} + +bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) { + BuildThreadTree(*record, &thread_tree_); + if (record->header.type == PERF_RECORD_SAMPLE) { + sample_tree_builder_->ProcessSampleRecord( + *static_cast<const SampleRecord*>(record.get())); } - return 0; + return true; } bool ReportCommand::PrintReport() { std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose); - if (report_filename_.empty()) { - report_fp_ = stdout; - } else { - report_fp_ = fopen(report_filename_.c_str(), "w"); - if (report_fp_ == nullptr) { + FILE* report_fp = stdout; + if (!report_filename_.empty()) { + report_fp = fopen(report_filename_.c_str(), "w"); + if (report_fp == nullptr) { PLOG(ERROR) << "failed to open file " << report_filename_; return false; } - file_handler.reset(report_fp_); - } - PrintReportContext(); - CollectReportWidth(); - PrintReportHeader(); - sample_tree_->VisitAllSamples( - std::bind(&ReportCommand::PrintReportEntry, this, std::placeholders::_1)); - fflush(report_fp_); - if (ferror(report_fp_) != 0) { + file_handler.reset(report_fp); + } + PrintReportContext(report_fp); + sample_tree_displayer_->DisplaySamples(report_fp, sample_tree_.samples, + &sample_tree_); + fflush(report_fp); + if (ferror(report_fp) != 0) { PLOG(ERROR) << "print report failed"; return false; } return true; } -void ReportCommand::PrintReportContext() { +void ReportCommand::PrintReportContext(FILE* report_fp) { if (!record_cmdline_.empty()) { - fprintf(report_fp_, "Cmdline: %s\n", record_cmdline_.c_str()); + fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str()); } for (const auto& attr : event_attrs_) { const EventType* event_type = FindEventTypeByConfig(attr.type, attr.config); @@ -722,88 +622,16 @@ void ReportCommand::PrintReportContext() { if (event_type != nullptr) { name = event_type->name; } - fprintf(report_fp_, "Event: %s (type %u, config %llu)\n", name.c_str(), attr.type, attr.config); + fprintf(report_fp, "Event: %s (type %u, config %llu)\n", name.c_str(), + attr.type, attr.config); } - fprintf(report_fp_, "Samples: %" PRIu64 "\n", sample_tree_->TotalSamples()); - fprintf(report_fp_, "Event count: %" PRIu64 "\n\n", sample_tree_->TotalPeriod()); + fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree_.total_samples); + fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree_.total_period); } -void ReportCommand::CollectReportWidth() { - sample_tree_->VisitAllSamples( - std::bind(&ReportCommand::CollectReportEntryWidth, this, std::placeholders::_1)); -} - -void ReportCommand::CollectReportEntryWidth(const SampleEntry& sample) { - for (auto& item : displayable_items_) { - item->AdjustWidth(sample); - } -} - -void ReportCommand::PrintReportHeader() { - for (size_t i = 0; i < displayable_items_.size(); ++i) { - auto& item = displayable_items_[i]; - if (i != displayable_items_.size() - 1) { - fprintf(report_fp_, "%-*s ", static_cast<int>(item->Width()), item->Name().c_str()); - } else { - fprintf(report_fp_, "%s\n", item->Name().c_str()); - } - } -} - -void ReportCommand::PrintReportEntry(const SampleEntry& sample) { - for (size_t i = 0; i < displayable_items_.size(); ++i) { - auto& item = displayable_items_[i]; - if (i != displayable_items_.size() - 1) { - fprintf(report_fp_, "%-*s ", static_cast<int>(item->Width()), item->Show(sample).c_str()); - } else { - fprintf(report_fp_, "%s\n", item->Show(sample).c_str()); - } - } - if (print_callgraph_) { - PrintCallGraph(sample); - } -} - -void ReportCommand::PrintCallGraph(const SampleEntry& sample) { - std::string prefix = " "; - fprintf(report_fp_, "%s|\n", prefix.c_str()); - fprintf(report_fp_, "%s-- %s\n", prefix.c_str(), sample.symbol->DemangledName()); - prefix.append(3, ' '); - for (size_t i = 0; i < sample.callchain.children.size(); ++i) { - PrintCallGraphEntry(1, prefix, sample.callchain.children[i], sample.callchain.children_period, - (i + 1 == sample.callchain.children.size())); - } -} - -void ReportCommand::PrintCallGraphEntry(size_t depth, std::string prefix, - const std::unique_ptr<CallChainNode>& node, - uint64_t parent_period, bool last) { - if (depth > 20) { - LOG(WARNING) << "truncated callgraph at depth " << depth; - return; - } - prefix += "|"; - fprintf(report_fp_, "%s\n", prefix.c_str()); - if (last) { - prefix.back() = ' '; - } - std::string percentage_s = "-- "; - if (node->period + node->children_period != parent_period) { - double percentage = 100.0 * (node->period + node->children_period) / parent_period; - percentage_s = android::base::StringPrintf("--%.2lf%%-- ", percentage); - } - fprintf(report_fp_, "%s%s%s\n", prefix.c_str(), percentage_s.c_str(), node->chain[0]->symbol->DemangledName()); - prefix.append(percentage_s.size(), ' '); - for (size_t i = 1; i < node->chain.size(); ++i) { - fprintf(report_fp_, "%s%s\n", prefix.c_str(), node->chain[i]->symbol->DemangledName()); - } - - for (size_t i = 0; i < node->children.size(); ++i) { - PrintCallGraphEntry(depth + 1, prefix, node->children[i], node->children_period, - (i + 1 == node->children.size())); - } -} +} // namespace void RegisterReportCommand() { - RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); }); + RegisterCommand("report", + [] { return std::unique_ptr<Command>(new ReportCommand()); }); } diff --git a/simpleperf/record.h b/simpleperf/record.h index 8b17bc10..85dcbc70 100644 --- a/simpleperf/record.h +++ b/simpleperf/record.h @@ -81,12 +81,13 @@ struct PerfSampleRawType { std::vector<char> data; }; +struct BranchStackItemType { + uint64_t from; + uint64_t to; + uint64_t flags; +}; + struct PerfSampleBranchStackType { - struct BranchStackItemType { - uint64_t from; - uint64_t to; - uint64_t flags; - }; std::vector<BranchStackItemType> stack; }; @@ -152,6 +153,10 @@ struct Record { return header.type; } + bool InKernel() const { + return (header.misc & PERF_RECORD_MISC_CPUMODE_MASK) == PERF_RECORD_MISC_KERNEL; + } + void Dump(size_t indent = 0) const; virtual std::vector<char> BinaryFormat() const = 0; virtual uint64_t Timestamp() const; diff --git a/simpleperf/sample_tree.cpp b/simpleperf/sample_tree.cpp deleted file mode 100644 index a34107b1..00000000 --- a/simpleperf/sample_tree.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * 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. - */ - -#include "sample_tree.h" - -#include <android-base/logging.h> - -#include "environment.h" - -void SampleTree::SetFilters(const std::unordered_set<int>& pid_filter, - const std::unordered_set<int>& tid_filter, - const std::unordered_set<std::string>& comm_filter, - const std::unordered_set<std::string>& dso_filter) { - pid_filter_ = pid_filter; - tid_filter_ = tid_filter; - comm_filter_ = comm_filter; - dso_filter_ = dso_filter; -} - -SampleEntry* SampleTree::AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period, - bool in_kernel) { - const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid); - const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel); - const Symbol* symbol = thread_tree_->FindSymbol(map, ip); - - SampleEntry value(ip, time, period, 0, 1, thread, map, symbol); - - if (IsFilteredOut(value)) { - return nullptr; - } - return InsertSample(value); -} - -void SampleTree::AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip, - uint64_t branch_flags, uint64_t time, uint64_t period) { - const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid); - const MapEntry* from_map = thread_tree_->FindMap(thread, from_ip, false); - if (from_map == thread_tree_->UnknownMap()) { - from_map = thread_tree_->FindMap(thread, from_ip, true); - } - const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, from_ip); - const MapEntry* to_map = thread_tree_->FindMap(thread, to_ip, false); - if (to_map == thread_tree_->UnknownMap()) { - to_map = thread_tree_->FindMap(thread, to_ip, true); - } - const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, to_ip); - - SampleEntry value(to_ip, time, period, 0, 1, thread, to_map, to_symbol); - value.branch_from.ip = from_ip; - value.branch_from.map = from_map; - value.branch_from.symbol = from_symbol; - value.branch_from.flags = branch_flags; - - if (IsFilteredOut(value)) { - return; - } - InsertSample(value); -} - -SampleEntry* SampleTree::AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time, - uint64_t period, bool in_kernel, - const std::vector<SampleEntry*>& callchain) { - const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid); - const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel); - const Symbol* symbol = thread_tree_->FindSymbol(map, ip); - - SampleEntry value(ip, time, 0, period, 0, thread, map, symbol); - - if (IsFilteredOut(value)) { - // Store in callchain_sample_tree_ for use in other SampleEntry's callchain. - auto it = callchain_sample_tree_.find(&value); - if (it != callchain_sample_tree_.end()) { - return *it; - } - SampleEntry* sample = AllocateSample(value); - callchain_sample_tree_.insert(sample); - return sample; - } - - auto it = sample_tree_.find(&value); - if (it != sample_tree_.end()) { - SampleEntry* sample = *it; - // Process only once for recursive function call. - if (std::find(callchain.begin(), callchain.end(), sample) != callchain.end()) { - return sample; - } - } - return InsertSample(value); -} - -bool SampleTree::IsFilteredOut(const SampleEntry& value) { - if (!pid_filter_.empty() && pid_filter_.find(value.thread->pid) == pid_filter_.end()) { - return true; - } - if (!tid_filter_.empty() && tid_filter_.find(value.thread->tid) == tid_filter_.end()) { - return true; - } - if (!comm_filter_.empty() && comm_filter_.find(value.thread_comm) == comm_filter_.end()) { - return true; - } - if (!dso_filter_.empty() && dso_filter_.find(value.map->dso->Path()) == dso_filter_.end()) { - return true; - } - return false; -} - -SampleEntry* SampleTree::InsertSample(SampleEntry& value) { - SampleEntry* result; - auto it = sample_tree_.find(&value); - if (it == sample_tree_.end()) { - result = AllocateSample(value); - auto pair = sample_tree_.insert(result); - CHECK(pair.second); - } else { - result = *it; - result->period += value.period; - result->accumulated_period += value.accumulated_period; - result->sample_count += value.sample_count; - } - total_samples_ += value.sample_count; - total_period_ += value.period; - return result; -} - -SampleEntry* SampleTree::AllocateSample(SampleEntry& value) { - SampleEntry* sample = new SampleEntry(std::move(value)); - sample_storage_.push_back(std::unique_ptr<SampleEntry>(sample)); - return sample; -} - -void SampleTree::InsertCallChainForSample(SampleEntry* sample, - const std::vector<SampleEntry*>& callchain, - uint64_t period) { - sample->callchain.AddCallChain(callchain, period); -} - -void SampleTree::VisitAllSamples(std::function<void(const SampleEntry&)> callback) { - if (sorted_sample_tree_.size() != sample_tree_.size()) { - sorted_sample_tree_.clear(); - for (auto& sample : sample_tree_) { - sample->callchain.SortByPeriod(); - sorted_sample_tree_.insert(sample); - } - } - for (auto& sample : sorted_sample_tree_) { - callback(*sample); - } -} diff --git a/simpleperf/sample_tree.h b/simpleperf/sample_tree.h index 6eb73722..c35a91a2 100644 --- a/simpleperf/sample_tree.h +++ b/simpleperf/sample_tree.h @@ -17,146 +17,299 @@ #ifndef SIMPLE_PERF_SAMPLE_TREE_H_ #define SIMPLE_PERF_SAMPLE_TREE_H_ -#include <limits.h> -#include <functional> -#include <set> -#include <string> -#include <unordered_map> -#include <unordered_set> -#include <vector> - #include "callchain.h" +#include "dwarf_unwind.h" +#include "perf_regs.h" +#include "record.h" +#include "SampleComparator.h" +#include "SampleDisplayer.h" #include "thread_tree.h" -struct BranchFromEntry { - uint64_t ip; - const MapEntry* map; - const Symbol* symbol; - uint64_t flags; +// A SampleTree is a collection of samples. A profiling report is mainly about +// constructing a SampleTree and display it. There are three steps involved: +// build the tree, sort the tree, and display it. For example, if we want to +// show how many cpu-cycles are spent in different functions, we should do as +// follows: +// 1. Build a SampleTree from SampleRecords with each sample containing +// (cpu-cycles, function name). When building the tree, we should merge +// samples containing the same function name. +// 2. Sort the SampleTree by cpu-cycles in the sample. As we want to display the +// samples in a decreasing order of cpu-cycles, we should sort it like this. +// 3. Display the SampleTree, each sample prints its (cpu-cycles, function name) +// pair. +// +// We represent the three steps with three template classes. +// 1. A SampleTree is built by SampleTreeBuilder. The comparator passed in +// SampleTreeBuilder's constructor decides the property of samples should be +// merged together. +// 2. After a SampleTree is built and got from SampleTreeBuilder, it should be +// sorted by SampleTreeSorter. The sort result decides the order to show +// samples. +// 3. At last, the sorted SampleTree is passed to SampleTreeDisplayer, which +// displays each sample in the SampleTree. + +template <typename EntryT, typename AccumulateInfoT> +class SampleTreeBuilder { + public: + SampleTreeBuilder(SampleComparator<EntryT> comparator) + : sample_set_(comparator), + callchain_sample_set_(comparator), + use_branch_address_(false), + accumulate_callchain_(false), + build_callchain_(false), + use_caller_as_callchain_root_(false), + strict_unwind_arch_check_(false) {} + + virtual ~SampleTreeBuilder() {} - BranchFromEntry() : ip(0), map(nullptr), symbol(nullptr), flags(0) { + void SetBranchSampleOption(bool use_branch_address) { + use_branch_address_ = use_branch_address; } -}; -struct SampleEntry { - uint64_t ip; - uint64_t time; - uint64_t period; - uint64_t accumulated_period; // Accumulated when appearing in other samples' callchain. - uint64_t sample_count; - const ThreadEntry* thread; - const char* thread_comm; // It refers to the thread comm when the sample happens. - const MapEntry* map; - const Symbol* symbol; - BranchFromEntry branch_from; - CallChainRoot callchain; // A callchain tree representing all callchains in the sample records. - - SampleEntry(uint64_t ip, uint64_t time, uint64_t period, uint64_t accumulated_period, - uint64_t sample_count, const ThreadEntry* thread, const MapEntry* map, - const Symbol* symbol) - : ip(ip), - time(time), - period(period), - accumulated_period(accumulated_period), - sample_count(sample_count), - thread(thread), - thread_comm(thread->comm), - map(map), - symbol(symbol) { + void SetCallChainSampleOptions(bool accumulate_callchain, + bool build_callchain, + bool use_caller_as_callchain_root, + bool strict_unwind_arch_check) { + accumulate_callchain_ = accumulate_callchain; + build_callchain_ = build_callchain; + use_caller_as_callchain_root_ = use_caller_as_callchain_root; + strict_unwind_arch_check_ = strict_unwind_arch_check; } - // The data member 'callchain' can only move, not copy. - SampleEntry(SampleEntry&&) = default; - SampleEntry(SampleEntry&) = delete; -}; + void ProcessSampleRecord(const SampleRecord& r) { + if (use_branch_address_ && (r.sample_type & PERF_SAMPLE_BRANCH_STACK)) { + for (auto& item : r.branch_stack_data.stack) { + if (item.from != 0 && item.to != 0) { + CreateBranchSample(r, item); + } + } + return; + } + bool in_kernel = r.InKernel(); + AccumulateInfoT acc_info; + EntryT* sample = CreateSample(r, in_kernel, &acc_info); + if (sample == nullptr) { + return; + } + if (accumulate_callchain_) { + std::vector<uint64_t> ips; + if (r.sample_type & PERF_SAMPLE_CALLCHAIN) { + ips.insert(ips.end(), r.callchain_data.ips.begin(), + r.callchain_data.ips.end()); + } + const ThreadEntry* thread = GetThreadOfSample(sample); + // Use stack_user_data.data.size() instead of stack_user_data.dyn_size, to + // make up for the missing kernel patch in N9. See b/22612370. + if (thread != nullptr && (r.sample_type & PERF_SAMPLE_REGS_USER) && + (r.regs_user_data.reg_mask != 0) && + (r.sample_type & PERF_SAMPLE_STACK_USER) && + (!r.stack_user_data.data.empty())) { + RegSet regs = + CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); + std::vector<char> stack( + r.stack_user_data.data.begin(), + r.stack_user_data.data.begin() + r.stack_user_data.data.size()); + ArchType arch = GetArchForAbi(ScopedCurrentArch::GetCurrentArch(), + r.regs_user_data.abi); + std::vector<uint64_t> unwind_ips = UnwindCallChain( + arch, *thread, regs, stack, strict_unwind_arch_check_); + if (!unwind_ips.empty()) { + ips.push_back(PERF_CONTEXT_USER); + ips.insert(ips.end(), unwind_ips.begin(), unwind_ips.end()); + } + } -typedef std::function<int(const SampleEntry&, const SampleEntry&)> compare_sample_func_t; + std::vector<EntryT*> callchain; + callchain.push_back(sample); -class SampleTree { - public: - SampleTree(ThreadTree* thread_tree, compare_sample_func_t sample_compare_function) - : thread_tree_(thread_tree), - sample_comparator_(sample_compare_function), - sample_tree_(sample_comparator_), - callchain_sample_tree_(sample_comparator_), - sorted_sample_comparator_(sample_compare_function), - sorted_sample_tree_(sorted_sample_comparator_), - total_samples_(0), - total_period_(0) { - } + bool first_ip = true; + for (auto& ip : ips) { + if (ip >= PERF_CONTEXT_MAX) { + switch (ip) { + case PERF_CONTEXT_KERNEL: + in_kernel = true; + break; + case PERF_CONTEXT_USER: + in_kernel = false; + break; + default: + LOG(DEBUG) << "Unexpected perf_context in callchain: " << ip; + } + } else { + if (first_ip) { + first_ip = false; + // Remove duplication with sampled ip. + if (ip == r.ip_data.ip) { + continue; + } + } + EntryT* callchain_sample = + CreateCallChainSample(sample, ip, in_kernel, callchain, acc_info); + if (callchain_sample == nullptr) { + break; + } + callchain.push_back(callchain_sample); + } + } - void SetFilters(const std::unordered_set<int>& pid_filter, - const std::unordered_set<int>& tid_filter, - const std::unordered_set<std::string>& comm_filter, - const std::unordered_set<std::string>& dso_filter); - - SampleEntry* AddSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period, - bool in_kernel); - void AddBranchSample(int pid, int tid, uint64_t from_ip, uint64_t to_ip, uint64_t branch_flags, - uint64_t time, uint64_t period); - SampleEntry* AddCallChainSample(int pid, int tid, uint64_t ip, uint64_t time, uint64_t period, - bool in_kernel, const std::vector<SampleEntry*>& callchain); - void InsertCallChainForSample(SampleEntry* sample, const std::vector<SampleEntry*>& callchain, - uint64_t period); - void VisitAllSamples(std::function<void(const SampleEntry&)> callback); - - uint64_t TotalSamples() const { - return total_samples_; + if (build_callchain_) { + std::set<EntryT*> added_set; + if (use_caller_as_callchain_root_) { + std::reverse(callchain.begin(), callchain.end()); + } + while (callchain.size() >= 2) { + EntryT* sample = callchain[0]; + callchain.erase(callchain.begin()); + // Add only once for recursive calls on callchain. + if (added_set.find(sample) != added_set.end()) { + continue; + } + added_set.insert(sample); + InsertCallChainForSample(sample, callchain, acc_info); + } + } + } } - uint64_t TotalPeriod() const { - return total_period_; + std::vector<EntryT*> GetSamples() const { + std::vector<EntryT*> result; + for (auto& entry : sample_set_) { + result.push_back(entry); + } + return result; } - private: - bool IsFilteredOut(const SampleEntry& value); - SampleEntry* InsertSample(SampleEntry& value); - SampleEntry* AllocateSample(SampleEntry& value); + protected: + virtual EntryT* CreateSample(const SampleRecord& r, bool in_kernel, + AccumulateInfoT* acc_info) = 0; + virtual EntryT* CreateBranchSample(const SampleRecord& r, + const BranchStackItemType& item) = 0; + virtual EntryT* CreateCallChainSample(const EntryT* sample, uint64_t ip, + bool in_kernel, + const std::vector<EntryT*>& callchain, + const AccumulateInfoT& acc_info) = 0; + virtual const ThreadEntry* GetThreadOfSample(EntryT*) = 0; + virtual void InsertCallChainForSample(EntryT* sample, + const std::vector<EntryT*>& callchain, + const AccumulateInfoT& acc_info) = 0; + virtual bool FilterSample(const EntryT*) { return true; } - struct SampleComparator { - bool operator()(SampleEntry* sample1, SampleEntry* sample2) const { - return compare_function(*sample1, *sample2) < 0; + virtual void UpdateSummary(const EntryT*) {} + + virtual void MergeSample(EntryT* sample1, EntryT* sample2) = 0; + + EntryT* InsertSample(std::unique_ptr<EntryT> sample) { + if (sample == nullptr || !FilterSample(sample.get())) { + return nullptr; } - SampleComparator(compare_sample_func_t compare_function) : compare_function(compare_function) { + UpdateSummary(sample.get()); + EntryT* result; + auto it = sample_set_.find(sample.get()); + if (it == sample_set_.end()) { + result = sample.get(); + sample_set_.insert(sample.get()); + sample_storage_.push_back(std::move(sample)); + } else { + result = *it; + MergeSample(*it, sample.get()); } + return result; + } - compare_sample_func_t compare_function; - }; + EntryT* InsertCallChainSample(std::unique_ptr<EntryT> sample, + const std::vector<EntryT*>& callchain) { + if (sample == nullptr) { + return nullptr; + } + if (!FilterSample(sample.get())) { + // Store in callchain_sample_set_ for use in other EntryT's callchain. + auto it = callchain_sample_set_.find(sample.get()); + if (it != callchain_sample_set_.end()) { + return *it; + } + EntryT* result = sample.get(); + callchain_sample_set_.insert(sample.get()); + sample_storage_.push_back(std::move(sample)); + return result; + } + + auto it = sample_set_.find(sample.get()); + if (it != sample_set_.end()) { + EntryT* sample = *it; + // Process only once for recursive function call. + if (std::find(callchain.begin(), callchain.end(), sample) != + callchain.end()) { + return sample; + } + } + return InsertSample(std::move(sample)); + } + + std::set<EntryT*, SampleComparator<EntryT>> sample_set_; + + private: + // If a CallChainSample is filtered out, it is stored in callchain_sample_set_ + // and only used in other EntryT's callchain. + std::set<EntryT*, SampleComparator<EntryT>> callchain_sample_set_; + std::vector<std::unique_ptr<EntryT>> sample_storage_; + + bool use_branch_address_; + bool accumulate_callchain_; + bool build_callchain_; + bool use_caller_as_callchain_root_; + bool strict_unwind_arch_check_; +}; + +template <typename EntryT> +class SampleTreeSorter { + public: + SampleTreeSorter(SampleComparator<EntryT> comparator) + : comparator_(comparator) {} + + virtual ~SampleTreeSorter() {} - struct SortedSampleComparator { - bool operator()(SampleEntry* sample1, SampleEntry* sample2) const { - uint64_t period1 = sample1->period + sample1->accumulated_period; - uint64_t period2 = sample2->period + sample2->accumulated_period; - if (period1 != period2) { - return period1 > period2; + void Sort(std::vector<EntryT*>& v, bool sort_callchain) { + if (sort_callchain) { + for (auto& sample : v) { + SortCallChain(sample); } - return compare_function(*sample1, *sample2) < 0; } - SortedSampleComparator(compare_sample_func_t compare_function) - : compare_function(compare_function) { + if (!comparator_.empty()) { + std::sort(v.begin(), v.end(), [this](const EntryT* s1, const EntryT* s2) { + return comparator_(s1, s2); + }); } + } - compare_sample_func_t compare_function; - }; + protected: + void SortCallChain(EntryT* sample) { sample->callchain.SortByPeriod(); } - ThreadTree* thread_tree_; - SampleComparator sample_comparator_; - std::set<SampleEntry*, SampleComparator> sample_tree_; - // If a CallChainSample is filtered out, it is stored in callchain_sample_tree_ and only used - // in other SampleEntry's callchain. - std::set<SampleEntry*, SampleComparator> callchain_sample_tree_; + private: + SampleComparator<EntryT> comparator_; +}; - SortedSampleComparator sorted_sample_comparator_; - std::set<SampleEntry*, SortedSampleComparator> sorted_sample_tree_; - std::vector<std::unique_ptr<SampleEntry>> sample_storage_; +template <typename EntryT, typename InfoT> +class SampleTreeDisplayer { + public: + SampleTreeDisplayer(SampleDisplayer<EntryT, InfoT> displayer) + : displayer_(displayer) {} - std::unordered_set<int> pid_filter_; - std::unordered_set<int> tid_filter_; - std::unordered_set<std::string> comm_filter_; - std::unordered_set<std::string> dso_filter_; + virtual ~SampleTreeDisplayer() {} - uint64_t total_samples_; - uint64_t total_period_; + void DisplaySamples(FILE* fp, const std::vector<EntryT*>& samples, + const InfoT* info) { + displayer_.SetInfo(info); + for (const auto& sample : samples) { + displayer_.AdjustWidth(sample); + } + displayer_.PrintNames(fp); + for (const auto& sample : samples) { + displayer_.PrintSample(fp, sample); + } + } + + private: + SampleDisplayer<EntryT, InfoT> displayer_; }; #endif // SIMPLE_PERF_SAMPLE_TREE_H_ diff --git a/simpleperf/sample_tree_test.cpp b/simpleperf/sample_tree_test.cpp index 434ee713..a88ecf98 100644 --- a/simpleperf/sample_tree_test.cpp +++ b/simpleperf/sample_tree_test.cpp @@ -17,64 +17,105 @@ #include <gtest/gtest.h> #include "sample_tree.h" +#include "thread_tree.h" -struct ExpectedSampleInMap { +namespace { + +struct SampleEntry { int pid; int tid; - const char* comm; + const char* thread_comm; std::string dso_name; uint64_t map_start_addr; size_t sample_count; + + SampleEntry(int pid, int tid, const char* thread_comm, + const std::string& dso_name, uint64_t map_start_addr, + size_t sample_count = 1u) + : pid(pid), + tid(tid), + thread_comm(thread_comm), + dso_name(dso_name), + map_start_addr(map_start_addr), + sample_count(sample_count) {} }; -static void SampleMatchExpectation(const SampleEntry& sample, const ExpectedSampleInMap& expected, - bool* has_error) { - *has_error = true; - ASSERT_TRUE(sample.thread != nullptr); - ASSERT_EQ(expected.pid, sample.thread->pid); - ASSERT_EQ(expected.tid, sample.thread->tid); - ASSERT_STREQ(expected.comm, sample.thread_comm); - ASSERT_TRUE(sample.map != nullptr); - ASSERT_EQ(expected.dso_name, sample.map->dso->Path()); - ASSERT_EQ(expected.map_start_addr, sample.map->start_addr); - ASSERT_EQ(expected.sample_count, sample.sample_count); - *has_error = false; -} +BUILD_COMPARE_VALUE_FUNCTION(TestComparePid, pid); +BUILD_COMPARE_VALUE_FUNCTION(TestCompareTid, tid); +BUILD_COMPARE_STRING_FUNCTION(TestCompareDsoName, dso_name.c_str()); +BUILD_COMPARE_VALUE_FUNCTION(TestCompareMapStartAddr, map_start_addr); + +class TestSampleComparator : public SampleComparator<SampleEntry> { + public: + TestSampleComparator() { + AddCompareFunction(TestComparePid); + AddCompareFunction(TestCompareTid); + AddCompareFunction(CompareComm); + AddCompareFunction(TestCompareDsoName); + AddCompareFunction(TestCompareMapStartAddr); + } +}; -static void CheckSampleCallback(const SampleEntry& sample, - std::vector<ExpectedSampleInMap>& expected_samples, size_t* pos) { - ASSERT_LT(*pos, expected_samples.size()); - bool has_error; - SampleMatchExpectation(sample, expected_samples[*pos], &has_error); - ASSERT_FALSE(has_error) << "Error matching sample at pos " << *pos; - ++*pos; -} +class TestSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, int> { + public: + TestSampleTreeBuilder(ThreadTree* thread_tree) + : SampleTreeBuilder(TestSampleComparator()), thread_tree_(thread_tree) {} -static int CompareSampleFunction(const SampleEntry& sample1, const SampleEntry& sample2) { - if (sample1.thread->pid != sample2.thread->pid) { - return sample1.thread->pid - sample2.thread->pid; + void AddSample(int pid, int tid, uint64_t ip, bool in_kernel) { + const ThreadEntry* thread = thread_tree_->FindThreadOrNew(pid, tid); + const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel); + InsertSample(std::unique_ptr<SampleEntry>(new SampleEntry( + pid, tid, thread->comm, map->dso->Path(), map->start_addr))); } - if (sample1.thread->tid != sample2.thread->tid) { - return sample1.thread->tid - sample2.thread->tid; + + protected: + SampleEntry* CreateSample(const SampleRecord&, bool, int*) override { + return nullptr; } - if (strcmp(sample1.thread_comm, sample2.thread_comm) != 0) { - return strcmp(sample1.thread_comm, sample2.thread_comm); + SampleEntry* CreateBranchSample(const SampleRecord&, + const BranchStackItemType&) override { + return nullptr; + }; + SampleEntry* CreateCallChainSample(const SampleEntry*, uint64_t, bool, + const std::vector<SampleEntry*>&, + const int&) override { + return nullptr; } - if (sample1.map->dso->Path() != sample2.map->dso->Path()) { - return sample1.map->dso->Path() > sample2.map->dso->Path() ? 1 : -1; + const ThreadEntry* GetThreadOfSample(SampleEntry*) override { + return nullptr; } - if (sample1.map->start_addr != sample2.map->start_addr) { - return sample1.map->start_addr - sample2.map->start_addr; + void InsertCallChainForSample(SampleEntry*, const std::vector<SampleEntry*>&, + const int&) override {} + void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override { + sample1->sample_count += sample2->sample_count; } - return 0; + + private: + ThreadTree* thread_tree_; +}; + +static void SampleMatchExpectation(const SampleEntry& sample, + const SampleEntry& expected, + bool* has_error) { + *has_error = true; + ASSERT_EQ(expected.pid, sample.pid); + ASSERT_EQ(expected.tid, sample.tid); + ASSERT_STREQ(expected.thread_comm, sample.thread_comm); + ASSERT_EQ(expected.dso_name, sample.dso_name); + ASSERT_EQ(expected.map_start_addr, sample.map_start_addr); + ASSERT_EQ(expected.sample_count, sample.sample_count); + *has_error = false; } -void VisitSampleTree(SampleTree* sample_tree, - const std::vector<ExpectedSampleInMap>& expected_samples) { - size_t pos = 0; - sample_tree->VisitAllSamples( - std::bind(&CheckSampleCallback, std::placeholders::_1, expected_samples, &pos)); - ASSERT_EQ(expected_samples.size(), pos); +static void CheckSamples(const std::vector<SampleEntry*>& samples, + const std::vector<SampleEntry>& expected_samples) { + ASSERT_EQ(samples.size(), expected_samples.size()); + for (size_t i = 0; i < samples.size(); ++i) { + bool has_error; + SampleMatchExpectation(*samples[i], expected_samples[i], &has_error); + ASSERT_FALSE(has_error) << "Error matching sample at pos " << i; + } +} } class SampleTreeTest : public testing::Test { @@ -88,102 +129,107 @@ class SampleTreeTest : public testing::Test { thread_tree.AddThreadMap(1, 11, 1, 10, 0, 0, "process1_thread11"); thread_tree.AddThreadMap(2, 2, 1, 20, 0, 0, "process2_thread2"); thread_tree.AddKernelMap(10, 20, 0, 0, "kernel"); - sample_tree = std::unique_ptr<SampleTree>(new SampleTree(&thread_tree, CompareSampleFunction)); + sample_tree_builder.reset(new TestSampleTreeBuilder(&thread_tree)); } - void VisitSampleTree(const std::vector<ExpectedSampleInMap>& expected_samples) { - ::VisitSampleTree(sample_tree.get(), expected_samples); + void CheckSamples(const std::vector<SampleEntry>& expected_samples) { + ::CheckSamples(sample_tree_builder->GetSamples(), expected_samples); } ThreadTree thread_tree; - std::unique_ptr<SampleTree> sample_tree; + std::unique_ptr<TestSampleTreeBuilder> sample_tree_builder; }; TEST_F(SampleTreeTest, ip_in_map) { - sample_tree->AddSample(1, 1, 1, 0, 0, false); - sample_tree->AddSample(1, 1, 2, 0, 0, false); - sample_tree->AddSample(1, 1, 5, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "process1_thread1", 1, 3}, + sample_tree_builder->AddSample(1, 1, 1, false); + sample_tree_builder->AddSample(1, 1, 2, false); + sample_tree_builder->AddSample(1, 1, 5, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 3), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, different_pid) { - sample_tree->AddSample(1, 1, 1, 0, 0, false); - sample_tree->AddSample(2, 2, 1, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "process1_thread1", 1, 1}, {2, 2, "p2t2", "process2_thread2", 1, 1}, + sample_tree_builder->AddSample(1, 1, 1, false); + sample_tree_builder->AddSample(2, 2, 1, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1), + SampleEntry(2, 2, "p2t2", "process2_thread2", 1, 1), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, different_tid) { - sample_tree->AddSample(1, 1, 1, 0, 0, false); - sample_tree->AddSample(1, 11, 1, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 11, "p1t11", "process1_thread11", 1, 1}, + sample_tree_builder->AddSample(1, 1, 1, false); + sample_tree_builder->AddSample(1, 11, 1, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1), + SampleEntry(1, 11, "p1t11", "process1_thread11", 1, 1), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, different_comm) { - sample_tree->AddSample(1, 1, 1, 0, 0, false); + sample_tree_builder->AddSample(1, 1, 1, false); thread_tree.AddThread(1, 1, "p1t1_comm2"); - sample_tree->AddSample(1, 1, 1, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1_comm2", "process1_thread1", 1, 1}, + sample_tree_builder->AddSample(1, 1, 1, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1), + SampleEntry(1, 1, "p1t1_comm2", "process1_thread1", 1, 1), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, different_map) { - sample_tree->AddSample(1, 1, 1, 0, 0, false); - sample_tree->AddSample(1, 1, 6, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "process1_thread1", 1, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1}, + sample_tree_builder->AddSample(1, 1, 1, false); + sample_tree_builder->AddSample(1, 1, 6, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "process1_thread1", 1, 1), + SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, unmapped_sample) { - sample_tree->AddSample(1, 1, 0, 0, 0, false); - sample_tree->AddSample(1, 1, 31, 0, 0, false); - sample_tree->AddSample(1, 1, 70, 0, 0, false); + sample_tree_builder->AddSample(1, 1, 0, false); + sample_tree_builder->AddSample(1, 1, 31, false); + sample_tree_builder->AddSample(1, 1, 70, false); // Match the unknown map. - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "unknown", 0, 3}, + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "unknown", 0, 3), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST_F(SampleTreeTest, map_kernel) { - sample_tree->AddSample(1, 1, 10, 0, 0, true); - sample_tree->AddSample(1, 1, 10, 0, 0, false); - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "p1t1", "kernel", 10, 1}, {1, 1, "p1t1", "process1_thread1_map2", 6, 1}, + sample_tree_builder->AddSample(1, 1, 10, true); + sample_tree_builder->AddSample(1, 1, 10, false); + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "p1t1", "kernel", 10, 1), + SampleEntry(1, 1, "p1t1", "process1_thread1_map2", 6, 1), }; - VisitSampleTree(expected_samples); + CheckSamples(expected_samples); } TEST(sample_tree, overlapped_map) { ThreadTree thread_tree; - SampleTree sample_tree(&thread_tree, CompareSampleFunction); + TestSampleTreeBuilder sample_tree_builder(&thread_tree); thread_tree.AddThread(1, 1, "thread1"); thread_tree.AddThreadMap(1, 1, 1, 10, 0, 0, "map1"); // Add map 1. - sample_tree.AddSample(1, 1, 5, 0, 0, false); // Hit map 1. + sample_tree_builder.AddSample(1, 1, 5, false); // Hit map 1. thread_tree.AddThreadMap(1, 1, 5, 20, 0, 0, "map2"); // Add map 2. - sample_tree.AddSample(1, 1, 6, 0, 0, false); // Hit map 2. - sample_tree.AddSample(1, 1, 4, 0, 0, false); // Hit map 1. + sample_tree_builder.AddSample(1, 1, 6, false); // Hit map 2. + sample_tree_builder.AddSample(1, 1, 4, false); // Hit map 1. thread_tree.AddThreadMap(1, 1, 2, 7, 0, 0, "map3"); // Add map 3. - sample_tree.AddSample(1, 1, 7, 0, 0, false); // Hit map 3. - sample_tree.AddSample(1, 1, 10, 0, 0, false); // Hit map 2. - - std::vector<ExpectedSampleInMap> expected_samples = { - {1, 1, "thread1", "map1", 1, 2}, - {1, 1, "thread1", "map2", 5, 1}, - {1, 1, "thread1", "map2", 9, 1}, - {1, 1, "thread1", "map3", 2, 1}, + sample_tree_builder.AddSample(1, 1, 7, false); // Hit map 3. + sample_tree_builder.AddSample(1, 1, 10, false); // Hit map 2. + + std::vector<SampleEntry> expected_samples = { + SampleEntry(1, 1, "thread1", "map1", 1, 2), + SampleEntry(1, 1, "thread1", "map2", 5, 1), + SampleEntry(1, 1, "thread1", "map2", 9, 1), + SampleEntry(1, 1, "thread1", "map3", 2, 1), }; - VisitSampleTree(&sample_tree, expected_samples); + CheckSamples(sample_tree_builder.GetSamples(), expected_samples); } diff --git a/simpleperf/thread_tree.cpp b/simpleperf/thread_tree.cpp index daf3ff52..d9714042 100644 --- a/simpleperf/thread_tree.cpp +++ b/simpleperf/thread_tree.cpp @@ -185,6 +185,15 @@ const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip, bool return result != nullptr ? result : &unknown_map_; } +const MapEntry* ThreadTree::FindMap(const ThreadEntry* thread, uint64_t ip) { + MapEntry* result = FindMapByAddr(thread->maps, ip); + if (result != nullptr) { + return result; + } + result = FindMapByAddr(kernel_map_tree_, ip); + return result != nullptr ? result : &unknown_map_; +} + const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip) { uint64_t vaddr_in_file; if (map->dso == kernel_dso_.get()) { diff --git a/simpleperf/thread_tree.h b/simpleperf/thread_tree.h index de10138a..6dc7dd7e 100644 --- a/simpleperf/thread_tree.h +++ b/simpleperf/thread_tree.h @@ -72,6 +72,8 @@ class ThreadTree { void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time, const std::string& filename); const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip, bool in_kernel); + // Find map for an ip address when we don't know whether it is in kernel. + const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip); const Symbol* FindSymbol(const MapEntry* map, uint64_t ip); const MapEntry* UnknownMap() const { return &unknown_map_; |