Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ function (build_gcov)
quipper_perf)
add_test(NAME addr2line_test COMMAND addr2line_test)

add_executable(summary_calculation_test
summary_calculation_test.cc
gcov.cc
profile_reader.cc
profile_writer.cc
symbol_map.cc)
target_link_libraries(summary_calculation_test
gtest
gtest_main
absl::flags
addr2line_lib
glog)
add_test(NAME summary_calculation_test COMMAND summary_calculation_test)

add_custom_command(PRE_BUILD
OUTPUT prepare_cmds
COMMAND ln -s -f ${CMAKE_HOME_DIRECTORY}/testdata)
Expand Down
1 change: 1 addition & 0 deletions gcov.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ABSL_FLAG(uint64_t, gcov_version, 0x3430372a,
"Gcov version number.");

const uint32 GCOV_TAG_AFDO_SUMMARY = 0xa8000000;
const uint32 GCOV_TAG_AFDO_FILE_NAMES = 0xaa000000;
const uint32 GCOV_TAG_AFDO_FUNCTION = 0xac000000;
const uint32 GCOV_TAG_MODULE_GROUPING = 0xae000000;
Expand Down
1 change: 1 addition & 0 deletions gcov.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "base/common.h"
#include "third_party/abseil/absl/flags/declare.h"

extern const uint32 GCOV_TAG_AFDO_SUMMARY;
extern const uint32 GCOV_TAG_AFDO_FILE_NAMES;
extern const uint32 GCOV_TAG_AFDO_FUNCTION;
extern const uint32 GCOV_TAG_MODULE_GROUPING;
Expand Down
32 changes: 32 additions & 0 deletions profile_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
#include "base/logging.h"
#include "addr2line.h"
#include "gcov.h"
#include "profile_writer.h"
#include "symbol_map.h"
#include "third_party/abseil/absl/flags/flag.h"

namespace devtools_crosstool_autofdo {
AutoFDOProfileReader::~AutoFDOProfileReader() {
if (summary_info_) {
delete summary_info_;
}
}

void AutoFDOProfileReader::ReadModuleGroup() {
CHECK_EQ(gcov_read_unsigned(), GCOV_TAG_MODULE_GROUPING);
Expand Down Expand Up @@ -95,6 +101,26 @@ void AutoFDOProfileReader::ReadSymbolProfile(const SourceStack &stack,
}
}

void AutoFDOProfileReader::ReadSummary() {
if (absl::GetFlag(FLAGS_gcov_version) >= 3) {
ProfileSummaryInformation info;
CHECK_EQ(gcov_read_unsigned(), GCOV_TAG_AFDO_SUMMARY);
info.total_count_ = gcov_read_counter(); // Total count
info.max_count_ = gcov_read_counter(); // Max count
info.max_function_count_ = gcov_read_counter(); // Max function count
info.num_counts_ = gcov_read_counter(); // Num counts
info.num_functions_ = gcov_read_counter(); // Num functions
unsigned num = gcov_read_counter();
info.detailed_summaries_.resize(num);
for (unsigned i = 0; i < num; i++) {
info.detailed_summaries_[i].cutoff_ = gcov_read_unsigned(); // Cutoff
info.detailed_summaries_[i].min_count_ = gcov_read_counter(); // Min count
info.detailed_summaries_[i].num_counts_ = gcov_read_counter(); // Num counts >= min count
}
summary_info_ = new ProfileSummaryInformation(info);
}
}

void AutoFDOProfileReader::ReadNameTable() {
CHECK_EQ(gcov_read_unsigned(), GCOV_TAG_AFDO_FILE_NAMES);
gcov_read_unsigned();
Expand Down Expand Up @@ -132,6 +158,7 @@ bool AutoFDOProfileReader::ReadFromFile(const std::string &output_file) {
absl::SetFlag(&FLAGS_gcov_version, gcov_read_unsigned());
gcov_read_unsigned();

ReadSummary();
ReadNameTable();
ReadFunctionProfile();
ReadModuleGroup();
Expand All @@ -141,4 +168,9 @@ bool AutoFDOProfileReader::ReadFromFile(const std::string &output_file) {

return true;
}

ProfileSummaryInformation *AutoFDOProfileReader::GetSummaryInformation() const {
return summary_info_;
}

} // namespace devtools_crosstool_autofdo
13 changes: 11 additions & 2 deletions profile_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,25 @@

namespace devtools_crosstool_autofdo {

struct ProfileSummaryInformation;
class SymbolMap;

class AutoFDOProfileReader : public ProfileReader {
public:
// None of the args are owned by this class.
explicit AutoFDOProfileReader(SymbolMap *symbol_map, bool force_update)
: symbol_map_(symbol_map), force_update_(force_update) {}
: symbol_map_(symbol_map), force_update_(force_update), summary_info_(nullptr) {}

explicit AutoFDOProfileReader()
: symbol_map_(nullptr), force_update_(false) {}
: symbol_map_(nullptr), force_update_(false), summary_info_(nullptr) {}

~AutoFDOProfileReader();

bool ReadFromFile(const std::string &output_file) override;

// Get the summary from the profile, if it was read in.
ProfileSummaryInformation *GetSummaryInformation() const;

private:
void ReadWorkingSet();
// Reads the module grouping info into the gcda file.
Expand Down Expand Up @@ -54,10 +60,13 @@ class AutoFDOProfileReader : public ProfileReader {
// where symbol_map was built purely from profile thus alias symbol info
// is not available. In that case, we should always update the symbol.
void ReadSymbolProfile(const SourceStack &stack, bool update);
// Read in the summary information. This requires at least GCOV version 3.
void ReadSummary();
void ReadNameTable();

SymbolMap *symbol_map_;
bool force_update_;
ProfileSummaryInformation *summary_info_;
std::vector<std::pair<std::string, int>> names_;
std::vector<std::string> file_names_;
};
Expand Down
103 changes: 103 additions & 0 deletions profile_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,26 @@ void AutoFDOProfileWriter::WriteFunctionProfile() {
FileIndexMap file_map;
StringTableUpdater::Update(*symbol_map_, &string_index_map, &file_map);

// Write out the GCOV_TAG_AFDO_SUMMARY section.
if (absl::GetFlag(FLAGS_gcov_version) >= 3) {
assert(summary != nullptr);
Copy link
Copy Markdown
Contributor Author

@dc03-work dc03-work Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I accidentally left an assert in here from the first commit that doesn't work anymore. I think it didn't fail to compile because the assert get preprocessed out - the summary variable doesn't exist anywhere anymore.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Send a PR to cleanup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR put up as #261.

ProfileSummaryInformation info = ProfileSummaryComputer::Compute(
*symbol_map_, {std::begin(ProfileSummaryInformation::default_cutoffs),
std::end(ProfileSummaryInformation::default_cutoffs)});
gcov_write_unsigned(GCOV_TAG_AFDO_SUMMARY);
gcov_write_counter(info.total_count_);
gcov_write_counter(info.max_count_);
gcov_write_counter(info.max_function_count_);
gcov_write_counter(info.num_counts_);
gcov_write_counter(info.num_functions_);
gcov_write_counter(info.detailed_summaries_.size());
for (const auto &detailed_summary : info.detailed_summaries_) {
gcov_write_unsigned(detailed_summary.cutoff_);
gcov_write_counter(detailed_summary.min_count_);
gcov_write_counter(detailed_summary.num_counts_);
}
}

for (auto &name_index : string_index_map) {
name_index.second = current_name_index++;
length_4bytes += (name_index.first.size()
Expand Down Expand Up @@ -243,6 +263,89 @@ bool AutoFDOProfileWriter::WriteToFile(const std::string &output_filename) {
return true;
}

bool ProfileSummaryInformation::operator==(
const ProfileSummaryInformation &other) const {
return total_count_ == other.total_count_
&& max_count_ == other.max_count_
&& max_function_count_ == other.max_function_count_
&& num_counts_ == other.num_counts_
&& num_functions_ == other.num_functions_
&& std::equal(detailed_summaries_.begin(), detailed_summaries_.end(),
other.detailed_summaries_.begin());
}

bool ProfileSummaryInformation::DetailedSummary::operator==(
const ProfileSummaryInformation::DetailedSummary &other) const {
return cutoff_ == other.cutoff_
&& min_count_ == other.min_count_
&& num_counts_ == other.num_counts_;
}

ProfileSummaryComputer::ProfileSummaryComputer()
: cutoffs_{std::begin(ProfileSummaryInformation::default_cutoffs),
std::end(ProfileSummaryInformation::default_cutoffs)} {}

ProfileSummaryComputer::ProfileSummaryComputer(std::vector<uint32_t> cutoffs)
: cutoffs_{std::move(cutoffs)} {}

void ProfileSummaryComputer::VisitTopSymbol(const std::string &name,
const Symbol *node) {
info_.num_functions_++;
info_.max_function_count_ =
std::max(info_.max_function_count_, node->head_count);
}

void ProfileSummaryComputer::Visit(const Symbol *node) {
// There is a slight difference against the values computed by
// SampleProfileSummaryBuilder/LLVMProfileBuilder as it represents
// lineno:discriminator pairs as 16:32 bits. This causes line numbers >=
// UINT16_MAX to be counted incorrectly (see GetLineNumberFromOffset in
// source_info.h) as they collide with line numbers < UINT16_MAX. This issue
// is completely avoided here by just not using the offset info at all.
for (auto &pos_count : node->pos_counts) {
uint64_t count = pos_count.second.count;
info_.total_count_ += count;
info_.max_count_ = std::max(info_.max_count_, count);
info_.num_counts_++;
count_frequencies_[count]++;
}
}

void ProfileSummaryComputer::ComputeDetailedSummary() {
auto iter = count_frequencies_.begin();
auto end = count_frequencies_.end();

uint32_t counts_seen = 0;
uint64_t curr_sum = 0;
uint64_t count = 0;

for (const uint32_t cutoff : cutoffs_) {
assert(cutoff <= 999'999);
constexpr int scale = 1'000'000;
using uint128_t = unsigned __int128;
uint128_t desired_count = info_.total_count_;
desired_count = desired_count * uint128_t(cutoff);
desired_count = desired_count / uint128_t(scale);
// This should never fail as cutoff is always <= scale, so
// (info_.total_count_ * (cutoff / scale)) is always <= info_.total_count_.
assert(desired_count <= info_.total_count_);
while (curr_sum < desired_count && iter != end) {
count = iter->first;
uint32_t freq = iter->second;
curr_sum += (count * freq);
counts_seen += freq;
iter++;
}
// curr_sum is the cumulative sum of frequencies, of which
// info_.total_count_ is the maximum value (as computed in
// ProfileSummaryComputer::Visit). Thus, this assertion will only fail if
// desired_count > info_.total_count_ and the maximum value that curr_sum
// can sum to is lesser than it.
assert(curr_sum >= desired_count);
info_.detailed_summaries_.push_back({cutoff, count, counts_seen});
}
}

// Debugging support. ProfileDumper emits a detailed dump of the contents
// of the input profile.
class ProfileDumper : public SymbolTraverser {
Expand Down
72 changes: 72 additions & 0 deletions profile_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,78 @@ class StringTableUpdater: public SymbolTraverser {
FileIndexMap *file_map_;
};

struct ProfileSummaryInformation {
static constexpr std::array<uint32_t, 16> default_cutoffs = {
10000, 100000, 200000, 300000, 400000, 500000, 600000, 700000,
800000, 900000, 950000, 990000, 999000, 999900, 999990, 999999};

// The detailed summary is a histogram-based calculation of the minimum
// execution count required to belong to a certain set of percentile of
// counts.
struct DetailedSummary {
// The percentile that this represents (multiplied by 1,000,000).
uint32_t cutoff_{};
// The minimum execution count required to belong to this percentile.
uint64_t min_count_{};
// The number of samples which belong to this percentile.
uint64_t num_counts_{};

bool operator==(const DetailedSummary &other) const;
};

// The sum of execution counts of all samples.
uint64_t total_count_{};
// The maximum individual count.
uint64_t max_count_{};
// The maximum head count across all functions.
uint64_t max_function_count_{};
// The number of lines that have samples.
uint64_t num_counts_{};
// The number of functions that have samples.
uint64_t num_functions_{};
// The percentile threshold information.
std::vector<DetailedSummary> detailed_summaries_{};

bool operator==(const ProfileSummaryInformation &other) const;
};

class ProfileSummaryComputer : public SymbolTraverser {
public:
// This type is neither copyable nor movable.
ProfileSummaryComputer(const ProfileSummaryComputer &) = delete;
ProfileSummaryComputer &operator=(const ProfileSummaryComputer &) = delete;

ProfileSummaryComputer();
ProfileSummaryComputer(std::vector<uint32_t> cutoffs);

static ProfileSummaryInformation Compute(const SymbolMap &symbol_map,
std::vector<uint32_t> cutoffs) {
ProfileSummaryComputer computer(std::move(cutoffs));
computer.Start(symbol_map);
computer.ComputeDetailedSummary();
return std::move(computer.info_);
}

protected:
void VisitTopSymbol(const std::string &name, const Symbol *node) override;
void Visit(const Symbol *node) override;

private:
ProfileSummaryInformation info_{};
// Sorted map of frequencies - used to compute the histogram.
std::map<uint64_t, uint32_t, std::greater<uint64_t>> count_frequencies_;
std::vector<uint32_t> cutoffs_;

// Compute the percentile information. This is done using a histogram based on
// (execution count, number of such counts) pairs, where a percentile
// represents the minimum execution count required to belong to that
// percentile.
//
// This is adapted from the LLVM implementation in
// ProfileSummaryBuilder::computeDetailedSummary (ProfileSummaryBuilder.cpp).
void ComputeDetailedSummary();
};

} // namespace devtools_crosstool_autofdo

#endif // AUTOFDO_PROFILE_WRITER_H_
Loading
Loading