426 lines
14 KiB
C++
426 lines
14 KiB
C++
/*
|
|
* 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;
|
|
}
|