Skip to content

Commit

Permalink
add map functions: sum(), min(), max(), avg(), stats()
Browse files Browse the repository at this point in the history
  • Loading branch information
brendangregg committed Sep 2, 2018
1 parent c1e7b05 commit 0746ff9
Show file tree
Hide file tree
Showing 11 changed files with 661 additions and 6 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ Variables:
Functions:
- `quantize(int n)` - Produce a log2 histogram of values of `n`
- `count()` - Count the number of times this function is called
- `sum(int n)` - Sum this value
- `min(int n)` - Record the minimum value seen
- `max(int n)` - Record the maximum value seen
- `avg(int n)` - Average this value
- `stats(int n)` - Return the count, average, and total for this value
- `delete(@x)` - Delete the map element passed in as an argument
- `str(char *s)` - Returns the string pointed to by `s`
- `printf(char *fmt, ...)` - Print formatted to stdout
Expand Down
91 changes: 91 additions & 0 deletions src/ast/codegen_llvm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,96 @@ void CodegenLLVM::visit(Call &call)
b_.CreateLifetimeEnd(newval);
expr_ = nullptr;
}
else if (call.func == "sum")
{
Map &map = *call.map;
AllocaInst *key = getMapKey(map);
Value *oldval = b_.CreateMapLookupElem(map, key);
AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val");

call.vargs->front()->accept(*this);
b_.CreateStore(b_.CreateAdd(expr_, oldval), newval);
b_.CreateMapUpdateElem(map, key, newval);

// oldval can only be an integer so won't be in memory and doesn't need lifetime end
b_.CreateLifetimeEnd(key);
b_.CreateLifetimeEnd(newval);
expr_ = nullptr;
}
else if (call.func == "min")
{
Map &map = *call.map;
AllocaInst *key = getMapKey(map);
Value *oldval = b_.CreateMapLookupElem(map, key);
AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val");

// Store the max of (0xffffffff - val), so that our SGE comparison with uninitialized
// elements will always store on the first occurrance. Revent this later when printing.
Function *parent = b_.GetInsertBlock()->getParent();
call.vargs->front()->accept(*this);
Value *inverted = b_.CreateSub(b_.getInt64(0xffffffff), expr_);
BasicBlock *lt = BasicBlock::Create(module_->getContext(), "min.lt", parent);
BasicBlock *ge = BasicBlock::Create(module_->getContext(), "min.ge", parent);
b_.CreateCondBr(b_.CreateICmpSGE(inverted, oldval), ge, lt);

b_.SetInsertPoint(ge);
b_.CreateStore(inverted, newval);
b_.CreateMapUpdateElem(map, key, newval);
b_.CreateBr(lt);

b_.SetInsertPoint(lt);
b_.CreateLifetimeEnd(key);
b_.CreateLifetimeEnd(newval);
expr_ = nullptr;
}
else if (call.func == "max")
{
Map &map = *call.map;
AllocaInst *key = getMapKey(map);
Value *oldval = b_.CreateMapLookupElem(map, key);
AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val");

Function *parent = b_.GetInsertBlock()->getParent();
call.vargs->front()->accept(*this);
BasicBlock *lt = BasicBlock::Create(module_->getContext(), "min.lt", parent);
BasicBlock *ge = BasicBlock::Create(module_->getContext(), "min.ge", parent);
b_.CreateCondBr(b_.CreateICmpSGE(expr_, oldval), ge, lt);

b_.SetInsertPoint(ge);
b_.CreateStore(expr_, newval);
b_.CreateMapUpdateElem(map, key, newval);
b_.CreateBr(lt);

b_.SetInsertPoint(lt);
b_.CreateLifetimeEnd(key);
b_.CreateLifetimeEnd(newval);
expr_ = nullptr;
}
else if (call.func == "avg" || call.func == "stats")
{
// avg stores the count and total in a quantize map using indexes 0 and 1
// respectively, and the calculation is made when printing.
Map &map = *call.map;

AllocaInst *count_key = getQuantizeMapKey(map, b_.getInt64(0));
Value *count_old = b_.CreateMapLookupElem(map, count_key);
AllocaInst *count_new = b_.CreateAllocaBPF(map.type, map.ident + "_num");
b_.CreateStore(b_.CreateAdd(count_old, b_.getInt64(1)), count_new);
b_.CreateMapUpdateElem(map, count_key, count_new);
b_.CreateLifetimeEnd(count_key);
b_.CreateLifetimeEnd(count_new);

AllocaInst *total_key = getQuantizeMapKey(map, b_.getInt64(1));
Value *total_old = b_.CreateMapLookupElem(map, total_key);
AllocaInst *total_new = b_.CreateAllocaBPF(map.type, map.ident + "_val");
call.vargs->front()->accept(*this);
b_.CreateStore(b_.CreateAdd(expr_, total_old), total_new);
b_.CreateMapUpdateElem(map, total_key, total_new);
b_.CreateLifetimeEnd(total_key);
b_.CreateLifetimeEnd(total_new);

expr_ = nullptr;
}
else if (call.func == "quantize")
{
Map &map = *call.map;
Expand Down Expand Up @@ -315,6 +405,7 @@ void CodegenLLVM::visit(Call &call)

else
{
std::cerr << "Error: missing codegen for function \"" << call.func << "\"" << std::endl;
abort();
}
}
Expand Down
30 changes: 30 additions & 0 deletions src/ast/semantic_analyser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,36 @@ void SemanticAnalyser::visit(Call &call)

call.type = SizedType(Type::count, 8);
}
else if (call.func == "sum") {
check_assignment(call, true, false);
check_nargs(call, 1);

call.type = SizedType(Type::sum, 8);
}
else if (call.func == "min") {
check_assignment(call, true, false);
check_nargs(call, 1);

call.type = SizedType(Type::min, 8);
}
else if (call.func == "max") {
check_assignment(call, true, false);
check_nargs(call, 1);

call.type = SizedType(Type::max, 8);
}
else if (call.func == "avg") {
check_assignment(call, true, false);
check_nargs(call, 1);

call.type = SizedType(Type::avg, 8);
}
else if (call.func == "stats") {
check_assignment(call, true, false);
check_nargs(call, 1);

call.type = SizedType(Type::stats, 8);
}
else if (call.func == "delete") {
check_assignment(call, false, false);
if (check_nargs(call, 1)) {
Expand Down
138 changes: 134 additions & 4 deletions src/bpftrace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ int BPFtrace::print_maps()
int err;
if (map.type_.type == Type::quantize)
err = print_map_quantize(map, 0, 0);
else if (map.type_.type == Type::avg || map.type_.type == Type::stats)
err = print_map_stats(map);
else
err = print_map(map, 0, 0);

Expand Down Expand Up @@ -530,7 +532,8 @@ int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0)
{
int value_size = map.type_.size;
if (map.type_.type == Type::count)
if (map.type_.type == Type::count ||
map.type_.type == Type::sum || map.type_.type == Type::min || map.type_.type == Type::max)
value_size *= ncpus_;
auto value = std::vector<uint8_t>(value_size);
int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data());
Expand All @@ -545,13 +548,27 @@ int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
old_key = key;
}

if (map.type_.type == Type::count)
if (map.type_.type == Type::count || map.type_.type == Type::sum)
{
std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b)
{
return reduce_value(a.second, ncpus_) < reduce_value(b.second, ncpus_);
});
}
else if (map.type_.type == Type::min)
{
std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b)
{
return min_value(a.second, ncpus_) < min_value(b.second, ncpus_);
});
}
else if (map.type_.type == Type::max)
{
std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b)
{
return max_value(a.second, ncpus_) < max_value(b.second, ncpus_);
});
}
else
{
sort_by_key(map.key_.args_, values_by_key);
Expand Down Expand Up @@ -583,8 +600,12 @@ int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
std::cout << resolve_usym(*(uintptr_t*)value.data());
else if (map.type_.type == Type::string)
std::cout << value.data() << std::endl;
else if (map.type_.type == Type::count)
else if (map.type_.type == Type::count || map.type_.type == Type::sum)
std::cout << reduce_value(value, ncpus_) / div << std::endl;
else if (map.type_.type == Type::min)
std::cout << min_value(value, ncpus_) / div << std::endl;
else if (map.type_.type == Type::max)
std::cout << max_value(value, ncpus_) / div << std::endl;
else
std::cout << *(int64_t*)value.data() / div << std::endl;
}
Expand Down Expand Up @@ -683,6 +704,88 @@ int BPFtrace::print_map_quantize(IMap &map, uint32_t top, uint32_t div)
return 0;
}

int BPFtrace::print_map_stats(IMap &map)
{
// A quantize-map adds an extra 8 bytes onto the end of its key for storing
// the bucket number.

std::vector<uint8_t> old_key;
try
{
old_key = find_empty_key(map, map.key_.size() + 8);
}
catch (std::runtime_error &e)
{
std::cerr << "Error getting key for map '" << map.name_ << "': "
<< e.what() << std::endl;
return -2;
}
auto key(old_key);

std::map<std::vector<uint8_t>, std::vector<uint64_t>> values_by_key;

while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0)
{
auto key_prefix = std::vector<uint8_t>(map.key_.size());
int bucket = key.at(map.key_.size());

for (size_t i=0; i<map.key_.size(); i++)
key_prefix.at(i) = key.at(i);

int value_size = map.type_.size * ncpus_;
auto value = std::vector<uint8_t>(value_size);
int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data());
if (err)
{
std::cerr << "Error looking up elem: " << err << std::endl;
return -1;
}

if (values_by_key.find(key_prefix) == values_by_key.end())
{
// New key - create a list of buckets for it
values_by_key[key_prefix] = std::vector<uint64_t>(2);
}
values_by_key[key_prefix].at(bucket) = reduce_value(value, ncpus_);

old_key = key;
}

// Sort based on sum of counts in all buckets
std::vector<std::pair<std::vector<uint8_t>, uint64_t>> total_counts_by_key;
for (auto &map_elem : values_by_key)
{
assert(map_elem.second.size() == 2);
uint64_t count = map_elem.second.at(0);
uint64_t total = map_elem.second.at(1);
assert(count != 0);
total_counts_by_key.push_back({map_elem.first, total / count});
}
std::sort(total_counts_by_key.begin(), total_counts_by_key.end(), [&](auto &a, auto &b)
{
return a.second < b.second;
});

for (auto &key_count : total_counts_by_key)
{
auto &key = key_count.first;
auto &value = values_by_key[key];
std::cout << map.name_ << map.key_.argument_value_list(*this, key) << ": ";

uint64_t count = value.at(0);
uint64_t total = value.at(1);

if (map.type_.type == Type::stats)
std::cout << "count " << count << ", average " << total / count << ", total " << total << std::endl;
else
std::cout << total / count << std::endl;
}

std::cout << std::endl;

return 0;
}

int BPFtrace::print_quantize(const std::vector<uint64_t> &values, uint32_t div) const
{
int max_index = -1;
Expand Down Expand Up @@ -767,12 +870,39 @@ uint64_t BPFtrace::reduce_value(const std::vector<uint8_t> &value, int ncpus)
return sum;
}

uint64_t BPFtrace::max_value(const std::vector<uint8_t> &value, int ncpus)
{
uint64_t val, max = 0;
for (int i=0; i<ncpus; i++)
{
val = *(uint64_t*)(value.data() + i*sizeof(uint64_t*));
if (val > max)
max = val;
}
return max;
}

uint64_t BPFtrace::min_value(const std::vector<uint8_t> &value, int ncpus)
{
uint64_t val, max = 0;
for (int i=0; i<ncpus; i++)
{
val = *(uint64_t*)(value.data() + i*sizeof(uint64_t*));
if (val > max)
max = val;
}
return (0xffffffff - max);
}

std::vector<uint8_t> BPFtrace::find_empty_key(IMap &map, size_t size) const
{
if (size == 0) size = 8;
auto key = std::vector<uint8_t>(size);
int value_size = map.type_.size;
if (map.type_.type == Type::count || map.type_.type == Type::quantize)
if (map.type_.type == Type::count || map.type_.type == Type::quantize ||
map.type_.type == Type::sum || map.type_.type == Type::min ||
map.type_.type == Type::max || map.type_.type == Type::avg ||
map.type_.type == Type::stats)
value_size *= ncpus_;
auto value = std::vector<uint8_t>(value_size);

Expand Down
3 changes: 3 additions & 0 deletions src/bpftrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,11 @@ class BPFtrace
int zero_map(IMap &map);
int print_map(IMap &map, uint32_t top, uint32_t div);
int print_map_quantize(IMap &map, uint32_t top, uint32_t div);
int print_map_stats(IMap &map);
int print_quantize(const std::vector<uint64_t> &values, uint32_t div) const;
static uint64_t reduce_value(const std::vector<uint8_t> &value, int ncpus);
static uint64_t min_value(const std::vector<uint8_t> &value, int ncpus);
static uint64_t max_value(const std::vector<uint8_t> &value, int ncpus);
static std::string quantize_index_label(int power);
std::vector<uint8_t> find_empty_key(IMap &map, size_t size) const;
};
Expand Down
6 changes: 4 additions & 2 deletions src/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ Map::Map(const std::string &name, const SizedType &type, const MapKey &key)
key_ = key;

int key_size = key.size();
if (type.type == Type::quantize)
if (type.type == Type::quantize ||
type.type == Type::avg || type.type == Type::stats)
key_size += 8;
if (key_size == 0)
key_size = 8;

enum bpf_map_type map_type;
if ((type.type == Type::quantize || type.type == Type::count) &&
if ((type.type == Type::quantize || type.type == Type::count ||
type.type == Type::sum || type.type == Type::min || type.type == Type::max || type.type == Type::avg || type.type == Type::stats) &&
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)))
{
map_type = BPF_MAP_TYPE_PERCPU_HASH;
Expand Down
5 changes: 5 additions & 0 deletions src/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ std::string typestr(Type t)
case Type::integer: return "integer"; break;
case Type::quantize: return "quantize"; break;
case Type::count: return "count"; break;
case Type::sum: return "sum"; break;
case Type::min: return "min"; break;
case Type::max: return "max"; break;
case Type::avg: return "avg"; break;
case Type::stats: return "stats"; break;
case Type::stack: return "stack"; break;
case Type::ustack: return "ustack"; break;
case Type::string: return "string"; break;
Expand Down
5 changes: 5 additions & 0 deletions src/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ enum class Type
integer,
quantize,
count,
sum,
min,
max,
avg,
stats,
stack,
ustack,
string,
Expand Down

0 comments on commit 0746ff9

Please sign in to comment.