/*
* 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.
*/
#include "tracing.h"
#include <string.h>
#include <map>
#include <string>
#include <vector>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "perf_event.h"
#include "utils.h"
const char TRACING_INFO_MAGIC[10] = {23, 8, 68, 't', 'r',
'a', 'c', 'i', 'n', 'g'};
template <class T>
void AppendData(std::vector<char>& data, const T& s) {
const char* p = reinterpret_cast<const char*>(&s);
data.insert(data.end(), p, p + sizeof(T));
}
static void AppendData(std::vector<char>& data, const char* s) {
data.insert(data.end(), s, s + strlen(s) + 1);
}
template <>
void AppendData(std::vector<char>& data, const std::string& s) {
data.insert(data.end(), s.c_str(), s.c_str() + s.size() + 1);
}
template <>
void MoveFromBinaryFormat(std::string& data, const char*& p) {
data.clear();
while (*p != '\0') {
data.push_back(*p++);
}
p++;
}
static void AppendFile(std::vector<char>& data, const std::string& file,
uint32_t file_size_bytes = 8) {
if (file_size_bytes == 8) {
uint64_t file_size = file.size();
AppendData(data, file_size);
} else if (file_size_bytes == 4) {
uint32_t file_size = file.size();
AppendData(data, file_size);
}
data.insert(data.end(), file.begin(), file.end());
}
static void DetachFile(const char*& p, std::string& file,
uint32_t file_size_bytes = 8) {
uint64_t file_size = ConvertBytesToValue(p, file_size_bytes);
p += file_size_bytes;
file.clear();
file.insert(file.end(), p, p + file_size);
p += file_size;
}
struct TraceType {
std::string system;
std::string name;
};
class TracingFile {
public:
TracingFile();
bool RecordHeaderFiles();
void RecordFtraceFiles(const std::vector<TraceType>& trace_types);
bool RecordEventFiles(const std::vector<TraceType>& trace_types);
bool RecordKallsymsFile();
bool RecordPrintkFormatsFile();
std::vector<char> BinaryFormat() const;
void LoadFromBinary(const std::vector<char>& data);
void Dump(size_t indent) const;
std::vector<TracingFormat> LoadTracingFormatsFromEventFiles() const;
const std::string& GetKallsymsFile() const { return kallsyms_file; }
uint32_t GetPageSize() const { return page_size; }
private:
char magic[10];
std::string version;
char endian;
uint8_t size_of_long;
uint32_t page_size;
std::string header_page_file;
std::string header_event_file;
std::vector<std::string> ftrace_format_files;
// pair of system, format_file_data.
std::vector<std::pair<std::string, std::string>> event_format_files;
std::string kallsyms_file;
std::string printk_formats_file;
};
TracingFile::TracingFile() {
memcpy(magic, TRACING_INFO_MAGIC, sizeof(TRACING_INFO_MAGIC));
version = "0.5";
endian = 0;
size_of_long = static_cast<int>(sizeof(long));
page_size = static_cast<uint32_t>(::GetPageSize());
}
bool TracingFile::RecordHeaderFiles() {
if (!android::base::ReadFileToString(
"/sys/kernel/debug/tracing/events/header_page", &header_page_file)) {
PLOG(ERROR)
<< "failed to read /sys/kernel/debug/tracing/events/header_page";
return false;
}
if (!android::base::ReadFileToString(
"/sys/kernel/debug/tracing/events/header_event",
&header_event_file)) {
PLOG(ERROR)
<< "failed to read /sys/kernel/debug/tracing/events/header_event";
return false;
}
return true;
}
void TracingFile::RecordFtraceFiles(const std::vector<TraceType>& trace_types) {
for (const auto& type : trace_types) {
std::string format_path = android::base::StringPrintf(
"/sys/kernel/debug/tracing/events/ftrace/%s/format", type.name.c_str());
std::string format_data;
if (android::base::ReadFileToString(format_path, &format_data)) {
ftrace_format_files.push_back(std::move(format_data));
}
}
}
bool TracingFile::RecordEventFiles(const std::vector<TraceType>& trace_types) {
for (const auto& type : trace_types) {
std::string format_path = android::base::StringPrintf(
"/sys/kernel/debug/tracing/events/%s/%s/format", type.system.c_str(),
type.name.c_str());
std::string format_data;
if (!android::base::ReadFileToString(format_path, &format_data)) {
PLOG(ERROR) << "failed to read " << format_path;
return false;
}
event_format_files.push_back(
std::make_pair(type.system, std::move(format_data)));
}
return true;
}
bool TracingFile::RecordPrintkFormatsFile() {
if (!android::base::ReadFileToString(
"/sys/kernel/debug/tracing/printk_formats", &printk_formats_file)) {
PLOG(ERROR) << "failed to read /sys/kernel/debug/tracing/printk_formats";
return false;
}
return true;
}
std::vector<char> TracingFile::BinaryFormat() const {
std::vector<char> ret;
ret.insert(ret.end(), magic, magic + sizeof(magic));
AppendData(ret, version);
ret.push_back(endian);
AppendData(ret, size_of_long);
AppendData(ret, page_size);
AppendData(ret, "header_page");
AppendFile(ret, header_page_file);
AppendData(ret, "header_event");
AppendFile(ret, header_event_file);
int count = static_cast<int>(ftrace_format_files.size());
AppendData(ret, count);
for (const auto& format : ftrace_format_files) {
AppendFile(ret, format);
}
count = static_cast<int>(event_format_files.size());
AppendData(ret, count);
for (const auto& pair : event_format_files) {
AppendData(ret, pair.first);
AppendData(ret, 1);
AppendFile(ret, pair.second);
}
AppendFile(ret, kallsyms_file, 4);
AppendFile(ret, printk_formats_file, 4);
return ret;
}
void TracingFile::LoadFromBinary(const std::vector<char>& data) {
const char* p = data.data();
const char* end = data.data() + data.size();
CHECK(memcmp(p, magic, sizeof(magic)) == 0);
p += sizeof(magic);
MoveFromBinaryFormat(version, p);
MoveFromBinaryFormat(endian, p);
MoveFromBinaryFormat(size_of_long, p);
MoveFromBinaryFormat(page_size, p);
std::string filename;
MoveFromBinaryFormat(filename, p);
CHECK_EQ(filename, "header_page");
DetachFile(p, header_page_file);
MoveFromBinaryFormat(filename, p);
CHECK_EQ(filename, "header_event");
DetachFile(p, header_event_file);
uint32_t count;
MoveFromBinaryFormat(count, p);
ftrace_format_files.resize(count);
for (uint32_t i = 0; i < count; ++i) {
DetachFile(p, ftrace_format_files[i]);
}
MoveFromBinaryFormat(count, p);
event_format_files.clear();
for (uint32_t i = 0; i < count; ++i) {
std::string system;
MoveFromBinaryFormat(system, p);
uint32_t count_in_system;
MoveFromBinaryFormat(count_in_system, p);
for (uint32_t i = 0; i < count_in_system; ++i) {
std::string format;
DetachFile(p, format);
event_format_files.push_back(std::make_pair(system, std::move(format)));
}
}
DetachFile(p, kallsyms_file, 4);
DetachFile(p, printk_formats_file, 4);
CHECK_EQ(p, end);
}
void TracingFile::Dump(size_t indent) const {
PrintIndented(indent, "tracing data:\n");
PrintIndented(indent + 1, "magic: ");
for (size_t i = 0; i < 3u; ++i) {
printf("0x%x ", magic[i]);
}
for (size_t i = 3; i < sizeof(magic); ++i) {
printf("%c", magic[i]);
}
printf("\n");
PrintIndented(indent + 1, "version: %s\n", version.c_str());
PrintIndented(indent + 1, "endian: %d\n", endian);
PrintIndented(indent + 1, "header_page:\n%s\n\n", header_page_file.c_str());
PrintIndented(indent + 1, "header_event:\n%s\n\n", header_event_file.c_str());
for (size_t i = 0; i < ftrace_format_files.size(); ++i) {
PrintIndented(indent + 1, "ftrace format file %zu/%zu:\n%s\n\n", i + 1,
ftrace_format_files.size(), ftrace_format_files[i].c_str());
}
for (size_t i = 0; i < event_format_files.size(); ++i) {
PrintIndented(indent + 1, "event format file %zu/%zu %s:\n%s\n\n", i + 1,
event_format_files.size(),
event_format_files[i].first.c_str(),
event_format_files[i].second.c_str());
}
PrintIndented(indent + 1, "kallsyms:\n%s\n\n", kallsyms_file.c_str());
PrintIndented(indent + 1, "printk_formats:\n%s\n\n",
printk_formats_file.c_str());
}
enum class FormatParsingState {
READ_NAME,
READ_ID,
READ_FIELDS,
READ_PRINTFMT,
};
// Parse lines like: field:char comm[16]; offset:8; size:16; signed:1;
static TracingField ParseTracingField(const std::string& s) {
TracingField field;
size_t start = 0;
std::string name;
std::string value;
for (size_t i = 0; i < s.size(); ++i) {
if (!isspace(s[i]) && (i == 0 || isspace(s[i - 1]))) {
start = i;
} else if (s[i] == ':') {
name = s.substr(start, i - start);
start = i + 1;
} else if (s[i] == ';') {
value = s.substr(start, i - start);
if (name == "field") {
size_t pos = value.find_first_of('[');
if (pos == std::string::npos) {
field.name = value;
field.elem_count = 1;
} else {
field.name = value.substr(0, pos);
field.elem_count =
static_cast<size_t>(strtoull(&value[pos + 1], nullptr, 10));
}
} else if (name == "offset") {
field.offset =
static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
} else if (name == "size") {
size_t size = static_cast<size_t>(strtoull(value.c_str(), nullptr, 10));
CHECK_EQ(size % field.elem_count, 0u);
field.elem_size = size / field.elem_count;
} else if (name == "signed") {
int is_signed = static_cast<int>(strtoull(value.c_str(), nullptr, 10));
field.is_signed = (is_signed == 1);
}
}
}
return field;
}
std::vector<TracingFormat> TracingFile::LoadTracingFormatsFromEventFiles()
const {
std::vector<TracingFormat> formats;
for (const auto& pair : event_format_files) {
TracingFormat format;
format.system_name = pair.first;
std::vector<std::string> strs = android::base::Split(pair.second, "\n");
FormatParsingState state = FormatParsingState::READ_NAME;
for (const auto& s : strs) {
if (state == FormatParsingState::READ_NAME) {
size_t pos = s.find_first_of("name:");
if (pos != std::string::npos) {
format.name = android::base::Trim(s.substr(pos + strlen("name:")));
state = FormatParsingState::READ_ID;
}
} else if (state == FormatParsingState::READ_ID) {
size_t pos = s.find_first_of("ID:");
if (pos != std::string::npos) {
format.id =
strtoull(s.substr(pos + strlen("ID:")).c_str(), nullptr, 10);
state = FormatParsingState::READ_FIELDS;
}
} else if (state == FormatParsingState::READ_FIELDS) {
size_t pos = s.find_first_of("field:");
if (pos != std::string::npos) {
TracingField field = ParseTracingField(s);
format.fields.push_back(field);
}
}
}
formats.push_back(format);
}
return formats;
}
Tracing::Tracing(const std::vector<char>& data) {
tracing_file_ = new TracingFile;
tracing_file_->LoadFromBinary(data);
}
Tracing::~Tracing() { delete tracing_file_; }
void Tracing::Dump(size_t indent) { tracing_file_->Dump(indent); }
TracingFormat Tracing::GetTracingFormatHavingId(uint64_t trace_event_id) {
if (tracing_formats_.empty()) {
tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
}
for (const auto& format : tracing_formats_) {
if (format.id == trace_event_id) {
return format;
}
}
LOG(FATAL) << "no tracing format for id " << trace_event_id;
return TracingFormat();
}
std::string Tracing::GetTracingEventNameHavingId(uint64_t trace_event_id) {
if (tracing_formats_.empty()) {
tracing_formats_ = tracing_file_->LoadTracingFormatsFromEventFiles();
}
for (const auto& format : tracing_formats_) {
if (format.id == trace_event_id) {
return android::base::StringPrintf("%s:%s", format.system_name.c_str(),
format.name.c_str());
}
}
return "";
}
const std::string& Tracing::GetKallsyms() const {
return tracing_file_->GetKallsymsFile();
}
uint32_t Tracing::GetPageSize() const { return tracing_file_->GetPageSize(); }
bool GetTracingData(const std::vector<const EventType*>& event_types,
std::vector<char>* data) {
data->clear();
std::vector<TraceType> trace_types;
for (const auto& type : event_types) {
CHECK_EQ(PERF_TYPE_TRACEPOINT, type->type);
size_t pos = type->name.find(':');
TraceType trace_type;
trace_type.system = type->name.substr(0, pos);
trace_type.name = type->name.substr(pos + 1);
trace_types.push_back(trace_type);
}
TracingFile tracing_file;
if (!tracing_file.RecordHeaderFiles()) {
return false;
}
tracing_file.RecordFtraceFiles(trace_types);
if (!tracing_file.RecordEventFiles(trace_types)) {
return false;
}
// Don't record /proc/kallsyms here, as it will be contained in
// KernelSymbolRecord.
if (!tracing_file.RecordPrintkFormatsFile()) {
return false;
}
*data = tracing_file.BinaryFormat();
return true;
}