blob: c9c00ff04b802608e1ad6b983022887d19b7c02b [file] [log] [blame]
/*
* Copyright (c) 2016 Catalysts GmbH
*
* 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 <fstream>
#include <sstream>
#include "common.h"
#include "bcc_libbpf_inc.h"
#include "vendor/optional.hpp"
#include "vendor/tinyformat.hpp"
namespace ebpf {
using std::experimental::optional;
// Get enum value from BTF, since the enum may be anonymous, like:
// [608] ENUM '(anon)' size=4 vlen=1
// 'TASK_COMM_LEN' val=16
// we have to traverse the whole BTF.
// Though there is a BTF_KIND_ENUM64, but it is unlikely that it will
// be used as array size, we don't handle it here.
static optional<int32_t> get_enum_val_from_btf(const char *name) {
optional<int32_t> val;
auto btf = btf__load_vmlinux_btf();
if (libbpf_get_error(btf))
return {};
for (size_t i = 1; i < btf__type_cnt(btf); i++) {
auto t = btf__type_by_id(btf, i);
if (btf_kind(t) != BTF_KIND_ENUM)
continue;
auto m = btf_enum(t);
for (int j = 0, n = btf_vlen(t); j < n; j++, m++) {
if (!strcmp(btf__name_by_offset(btf, m->name_off), name)) {
val = m->val;
break;
}
}
if (val)
break;
}
btf__free(btf);
return val;
}
std::vector<int> read_cpu_range(std::string path) {
std::ifstream cpus_range_stream { path };
std::vector<int> cpus;
std::string cpu_range;
while (std::getline(cpus_range_stream, cpu_range, ',')) {
std::size_t rangeop = cpu_range.find('-');
if (rangeop == std::string::npos) {
cpus.push_back(std::stoi(cpu_range));
}
else {
int start = std::stoi(cpu_range.substr(0, rangeop));
int end = std::stoi(cpu_range.substr(rangeop + 1));
for (int i = start; i <= end; i++)
cpus.push_back(i);
}
}
return cpus;
}
std::vector<int> get_online_cpus() {
return read_cpu_range("/sys/devices/system/cpu/online");
}
std::vector<int> get_possible_cpus() {
return read_cpu_range("/sys/devices/system/cpu/possible");
}
std::string get_pid_exe(pid_t pid) {
char exe_path[4096];
int res;
std::string exe_link = tfm::format("/proc/%d/exe", pid);
res = readlink(exe_link.c_str(), exe_path, sizeof(exe_path));
if (res == -1)
return "";
if (res >= static_cast<int>(sizeof(exe_path)))
res = sizeof(exe_path) - 1;
exe_path[res] = '\0';
return std::string(exe_path);
}
enum class field_kind_t {
common,
data_loc,
regular,
invalid,
pad,
};
static inline field_kind_t _get_field_kind(std::string const& line,
std::string& field_type,
std::string& field_name,
int *last_offset) {
auto field_pos = line.find("field:");
if (field_pos == std::string::npos)
return field_kind_t::invalid;
auto field_semi_pos = line.find(';', field_pos);
if (field_semi_pos == std::string::npos)
return field_kind_t::invalid;
auto offset_pos = line.find("offset:", field_semi_pos);
if (offset_pos == std::string::npos)
return field_kind_t::invalid;
auto semi_pos = line.find(';', offset_pos);
if (semi_pos == std::string::npos)
return field_kind_t::invalid;
auto offset_str = line.substr(offset_pos + 7,
semi_pos - offset_pos - 7);
int offset = std::stoi(offset_str, nullptr);
auto size_pos = line.find("size:", semi_pos);
if (size_pos == std::string::npos)
return field_kind_t::invalid;
semi_pos = line.find(';', size_pos);
if (semi_pos == std::string::npos)
return field_kind_t::invalid;
auto size_str = line.substr(size_pos + 5,
semi_pos - size_pos - 5);
int size = std::stoi(size_str, nullptr);
if (*last_offset < offset) {
*last_offset += 1;
return field_kind_t::pad;
}
*last_offset = offset + size;
auto field = line.substr(field_pos + 6/*"field:"*/,
field_semi_pos - field_pos - 6);
auto pos = field.find_last_of("\t ");
if (pos == std::string::npos)
return field_kind_t::invalid;
field_type = field.substr(0, pos);
field_name = field.substr(pos + 1);
if (field_type.find("__data_loc") != std::string::npos)
return field_kind_t::data_loc;
if (field_name.find("common_") == 0)
return field_kind_t::common;
// We may have `char comm[TASK_COMM_LEN];` on kernel v5.18+
// Let's replace `TASK_COMM_LEN` with value extracted from BTF
if (field_name.find("[") != std::string::npos) {
auto pos1 = field_name.find("[");
auto pos2 = field_name.find("]");
auto dim = field_name.substr(pos1 + 1, pos2 - pos1 - 1);
if (!dim.empty() && !isdigit(dim[0])) {
auto v = get_enum_val_from_btf(dim.c_str());
if (v)
dim = std::to_string(*v);
field_name.replace(pos1 + 1, pos2 - pos1 - 1, dim, 0);
}
return field_kind_t::regular;
}
// adjust the field_type based on the size of field
// otherwise, incorrect value may be retrieved for big endian
// and the field may have incorrect structure offset.
if (size == 2) {
if (field_type == "char" || field_type == "int8_t")
field_type = "s16";
if (field_type == "unsigned char" || field_type == "uint8_t")
field_type = "u16";
} else if (size == 4) {
if (field_type == "char" || field_type == "short" ||
field_type == "int8_t" || field_type == "int16_t")
field_type = "s32";
if (field_type == "unsigned char" || field_type == "unsigned short" ||
field_type == "uint8_t" || field_type == "uint16_t")
field_type = "u32";
} else if (size == 8) {
if (field_type == "char" || field_type == "short" || field_type == "int" ||
field_type == "int8_t" || field_type == "int16_t" ||
field_type == "int32_t" || field_type == "pid_t")
field_type = "s64";
if (field_type == "unsigned char" || field_type == "unsigned short" ||
field_type == "unsigned int" || field_type == "uint8_t" ||
field_type == "uint16_t" || field_type == "uint32_t" ||
field_type == "unsigned" || field_type == "u32" ||
field_type == "uid_t" || field_type == "gid_t")
field_type = "u64";
}
return field_kind_t::regular;
}
#define DEBUGFS_TRACEFS "/sys/kernel/debug/tracing"
#define TRACEFS "/sys/kernel/tracing"
std::string tracefs_path() {
static bool use_debugfs = access(DEBUGFS_TRACEFS, F_OK) == 0;
return use_debugfs ? DEBUGFS_TRACEFS : TRACEFS;
}
std::string tracepoint_format_file(std::string const& category,
std::string const& event) {
return tracefs_path() + "/events/" + category + "/" + event + "/format";
}
std::string parse_tracepoint(std::istream &input, std::string const& category,
std::string const& event) {
std::string tp_struct = "struct tracepoint__" + category + "__" + event + " {\n";
tp_struct += "\tu64 __do_not_use__;\n";
int last_offset = 0, common_offset = 8;
for (std::string line; getline(input, line); ) {
std::string field_type, field_name;
field_kind_t kind;
do {
kind = _get_field_kind(line, field_type, field_name, &last_offset);
switch (kind) {
case field_kind_t::invalid:
continue;
case field_kind_t::common:
for (;common_offset < last_offset; common_offset++)
{
tp_struct += "\tchar __do_not_use__" + std::to_string(common_offset) + ";\n";
}
continue;
case field_kind_t::data_loc:
tp_struct += "\tint data_loc_" + field_name + ";\n";
break;
case field_kind_t::regular:
tp_struct += "\t" + field_type + " " + field_name + ";\n";
break;
case field_kind_t::pad:
tp_struct += "\tchar __pad_" + std::to_string(last_offset - 1) + ";\n";
break;
}
} while (kind == field_kind_t::pad);
}
tp_struct += "};\n";
return tp_struct;
}
} // namespace ebpf