Skip to content

Commit

Permalink
Output: Stream output when printing maps
Browse files Browse the repository at this point in the history
Symbolicating user stack traces can be slow for large binaries and when
symbol caching is not effective. When printing a large map containing
stack traces, bpftrace can appear to "hang" while it's stuck
symbolicating all of them, before it prints them in a single batch.

This commit changes the behaviour so that we print each stack trace as
soon as it's ready, to show some progress to the user.
  • Loading branch information
ajor committed Jun 25, 2024
1 parent 65a08f4 commit e942bf2
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 62 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to

#### Added
#### Changed
- Stream output when printing maps
- [#3264](https://github.com/bpftrace/bpftrace/pull/3264)
#### Deprecated
#### Removed
#### Fixed
Expand Down
86 changes: 46 additions & 40 deletions src/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -382,18 +382,19 @@ std::string Output::struct_to_str(const std::vector<std::string> &elems) const
return "{ " + str_join(elems, ", ") + " }";
}

std::string Output::map_to_str(
void Output::map_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
uint32_t div,
const std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
&values_by_key) const
{
std::vector<std::string> elems;
uint32_t i = 0;
size_t total = values_by_key.size();
const auto &map_type = bpftrace.resources.maps_info.at(map.name()).value_type;

bool first = true;
for (auto &pair : values_by_key) {
auto key = pair.first;
auto value = pair.second;
Expand All @@ -403,16 +404,19 @@ std::string Output::map_to_str(
continue;
}

if (first)
first = false;
else
map_elem_delim(map_type);

auto key_str = map_key_to_str(bpftrace, map, key);
auto value_str = value_to_str(
bpftrace, map_type, value, map.is_per_cpu_type(), div);
elems.push_back(map_keyval_to_str(map_type, key_str, value_str));
map_key_val(map_type, key_str, value_str);
}

return str_join(elems, map_elem_delim_to_str(map_type));
}

std::string Output::map_hist_to_str(
void Output::map_hist_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
Expand All @@ -421,17 +425,22 @@ std::string Output::map_hist_to_str(
const std::vector<std::pair<std::vector<uint8_t>, uint64_t>>
&total_counts_by_key) const
{
std::vector<std::string> elems;
uint32_t i = 0;
const auto &map_info = bpftrace.resources.maps_info.at(map.name());
const auto &map_type = map_info.value_type;
bool first = true;
for (auto &key_count : total_counts_by_key) {
auto &key = key_count.first;
auto &value = values_by_key.at(key);

if (top && values_by_key.size() > top && i++ < (values_by_key.size() - top))
continue;

if (first)
first = false;
else
map_elem_delim(map_type);

auto key_str = map_key_to_str(bpftrace, map, key);
std::string val_str;
if (map_type.IsHistTy()) {
Expand All @@ -440,13 +449,11 @@ std::string Output::map_hist_to_str(
auto &args = map_info.lhist_args;
val_str = lhist_to_str(value, args->min, args->max, args->step);
}

elems.push_back(map_keyval_to_str(map_type, key_str, val_str));
map_key_val(map_type, key_str, val_str);
}
return str_join(elems, map_elem_delim_to_str(map_type));
}

std::string Output::map_stats_to_str(
void Output::map_stats_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
Expand All @@ -455,9 +462,9 @@ std::string Output::map_stats_to_str(
const std::vector<std::pair<std::vector<uint8_t>, int64_t>>
&total_counts_by_key) const
{
std::vector<std::string> elems;
const auto &map_type = bpftrace.resources.maps_info.at(map.name()).value_type;
uint32_t i = 0;
bool first = true;
for (auto &key_count : total_counts_by_key) {
auto &key = key_count.first;
auto &value = values_by_key.at(key);
Expand All @@ -466,6 +473,11 @@ std::string Output::map_stats_to_str(
i++ < (values_by_key.size() - top))
continue;

if (first)
first = false;
else
map_elem_delim(map_type);

auto key_str = map_key_to_str(bpftrace, map, key);

int64_t count = (int64_t)value.at(0);
Expand All @@ -484,10 +496,8 @@ std::string Output::map_stats_to_str(
} else
value_str = std::to_string(average / div);

elems.push_back(map_keyval_to_str(map_type, key_str, value_str));
map_key_val(map_type, key_str, value_str);
}

return str_join(elems, map_elem_delim_to_str(map_type));
}

void TextOutput::map(
Expand All @@ -498,7 +508,7 @@ void TextOutput::map(
const std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
&values_by_key) const
{
out_ << map_to_str(bpftrace, map, top, div, values_by_key);
map_contents(bpftrace, map, top, div, values_by_key);
out_ << std::endl;
}

Expand Down Expand Up @@ -599,7 +609,7 @@ void TextOutput::map_hist(
const std::vector<std::pair<std::vector<uint8_t>, uint64_t>>
&total_counts_by_key) const
{
out_ << map_hist_to_str(
map_hist_contents(
bpftrace, map, top, div, values_by_key, total_counts_by_key);
out_ << std::endl;
}
Expand All @@ -613,7 +623,7 @@ void TextOutput::map_stats(
const std::vector<std::pair<std::vector<uint8_t>, int64_t>>
&total_counts_by_key) const
{
out_ << map_stats_to_str(
map_stats_contents(
bpftrace, map, top, div, values_by_key, total_counts_by_key);
out_ << std::endl << std::endl;
}
Expand Down Expand Up @@ -677,24 +687,21 @@ std::string TextOutput::map_key_to_str(BPFtrace &bpftrace,
return map.name() + map_key.argument_value_list_str(bpftrace, key);
}

std::string TextOutput::map_keyval_to_str(const SizedType &map_type,
const std::string &key,
const std::string &val) const
void TextOutput::map_key_val(const SizedType &map_type,
const std::string &key,
const std::string &val) const
{
std::string res = key + ": ";
out_ << key + ": ";
if (map_type.IsHistTy() || map_type.IsLhistTy())
res += "\n";
res += val;
return res;
out_ << "\n";
out_ << val;
}

std::string TextOutput::map_elem_delim_to_str(const SizedType &map_type) const
void TextOutput::map_elem_delim(const SizedType &map_type) const
{
if (!map_type.IsKstackTy() && !map_type.IsUstackTy() &&
!map_type.IsKsymTy() && !map_type.IsUsymTy() && !map_type.IsInetTy())
return "\n";

return "";
out_ << "\n";
}

std::string TextOutput::key_value_pairs_to_str(
Expand Down Expand Up @@ -762,7 +769,7 @@ void JsonOutput::map(
if (map_key.size() > 0) // check if this map has keys
out_ << "{";

out_ << map_to_str(bpftrace, map, top, div, values_by_key);
map_contents(bpftrace, map, top, div, values_by_key);

if (map_key.size() > 0)
out_ << "}";
Expand Down Expand Up @@ -872,7 +879,7 @@ void JsonOutput::map_hist(
if (map_key.size() > 0) // check if this map has keys
out_ << "{";

out_ << map_hist_to_str(
map_hist_contents(
bpftrace, map, top, div, values_by_key, total_counts_by_key);

if (map_key.size() > 0)
Expand All @@ -899,7 +906,7 @@ void JsonOutput::map_stats(
if (map_key.size() > 0) // check if this map has keys
out_ << "{";

out_ << map_stats_to_str(
map_stats_contents(
bpftrace, map, top, div, values_by_key, total_counts_by_key);

if (map_key.size() > 0)
Expand Down Expand Up @@ -991,18 +998,17 @@ std::string JsonOutput::map_key_to_str(BPFtrace &bpftrace,
return "";
}

std::string JsonOutput::map_keyval_to_str(const SizedType &map_type
__attribute__((unused)),
const std::string &key,
const std::string &val) const
void JsonOutput::map_key_val(const SizedType &map_type __attribute__((unused)),
const std::string &key,
const std::string &val) const
{
return key.empty() ? val : key + ": " + val;
out_ << (key.empty() ? val : key + ": " + val);
}

std::string JsonOutput::map_elem_delim_to_str(const SizedType &map
__attribute__((unused))) const
void JsonOutput::map_elem_delim(const SizedType &map
__attribute__((unused))) const
{
return ", ";
out_ << ", ";
}

std::string JsonOutput::key_value_pairs_to_str(
Expand Down
37 changes: 15 additions & 22 deletions src/output.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ class Output {
};

// Write map to output
// Ideally, the implementation should use map_to_str to convert a map into
// a string, format it properly, and print it to out_.
virtual void map(
BPFtrace &bpftrace,
const BpfMap &map,
Expand All @@ -63,8 +61,6 @@ class Output {
const std::vector<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>>
&values_by_key) const = 0;
// Write map histogram to output
// Ideally, the implementation should use map_hist_to_str to convert a map
// histogram into a string, format it properly, and print it to out_.
virtual void map_hist(
BPFtrace &bpftrace,
const BpfMap &map,
Expand All @@ -75,8 +71,6 @@ class Output {
const std::vector<std::pair<std::vector<uint8_t>, uint64_t>>
&total_counts_by_key) const = 0;
// Write map statistics to output
// Ideally, the implementation should use map_stats_to_str to convert map
// statistics into a string, format it properly, and print it to out_.
virtual void map_stats(
BPFtrace &bpftrace,
const BpfMap &map,
Expand Down Expand Up @@ -131,7 +125,7 @@ class Output {
// Convert map into string
// Default behaviour: format each (key, value) pair using output-specific
// methods and join them into a single string
virtual std::string map_to_str(
virtual void map_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
Expand All @@ -141,7 +135,7 @@ class Output {
// Convert map histogram into string
// Default behaviour: format each (key, hist) pair using output-specific
// methods and join them into a single string
virtual std::string map_hist_to_str(
virtual void map_hist_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
Expand All @@ -153,7 +147,7 @@ class Output {
// Convert map statistics into string
// Default behaviour: format each (key, stats) pair using output-specific
// methods and join them into a single string
virtual std::string map_stats_to_str(
virtual void map_stats_contents(
BPFtrace &bpftrace,
const BpfMap &map,
uint32_t top,
Expand All @@ -166,12 +160,11 @@ class Output {
const BpfMap &map,
const std::vector<uint8_t> &key) const = 0;
// Properly join map key and value strings
virtual std::string map_keyval_to_str(const SizedType &map_type,
const std::string &key,
const std::string &val) const = 0;
virtual void map_key_val(const SizedType &map_type,
const std::string &key,
const std::string &val) const = 0;
// Delimiter to join (properly formatted) map elements into a single string
virtual std::string map_elem_delim_to_str(
const SizedType &map_type) const = 0;
virtual void map_elem_delim(const SizedType &map_type) const = 0;
// Convert non-map value into string
// This method should properly handle all non-map value types.
// Aggregate types (array, struct, tuple) are formatted using output-specific
Expand Down Expand Up @@ -261,10 +254,10 @@ class TextOutput : public Output {
std::string map_key_to_str(BPFtrace &bpftrace,
const BpfMap &map,
const std::vector<uint8_t> &key) const override;
std::string map_keyval_to_str(const SizedType &map_type,
const std::string &key,
const std::string &val) const override;
std::string map_elem_delim_to_str(const SizedType &map_type) const override;
void map_key_val(const SizedType &map_type,
const std::string &key,
const std::string &val) const override;
void map_elem_delim(const SizedType &map_type) const override;
std::string field_to_str(const std::string &name,
const std::string &value) const override;
std::string tuple_to_str(
Expand Down Expand Up @@ -340,10 +333,10 @@ class JsonOutput : public Output {
std::string map_key_to_str(BPFtrace &bpftrace,
const BpfMap &map,
const std::vector<uint8_t> &key) const override;
std::string map_keyval_to_str(const SizedType &map_type,
const std::string &key,
const std::string &val) const override;
std::string map_elem_delim_to_str(const SizedType &map_type) const override;
void map_key_val(const SizedType &map_type,
const std::string &key,
const std::string &val) const override;
void map_elem_delim(const SizedType &map_type) const override;
std::string field_to_str(const std::string &name,
const std::string &value) const override;
std::string tuple_to_str(
Expand Down

0 comments on commit e942bf2

Please sign in to comment.