blob: 61f0fb5a6886756b0c78c964d042ba01bd1a309d [file] [log] [blame]
/*
* Copyright (C) 2017 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.
*/
#ifndef SRC_TRACED_PROBES_FTRACE_CPU_READER_H_
#define SRC_TRACED_PROBES_FTRACE_CPU_READER_H_
#include <stdint.h>
#include <string.h>
#include <array>
#include <atomic>
#include <memory>
#include <set>
#include <thread>
#include "gtest/gtest_prod.h"
#include "perfetto/base/page_allocator.h"
#include "perfetto/base/scoped_file.h"
#include "perfetto/base/thread_checker.h"
#include "perfetto/protozero/message.h"
#include "perfetto/protozero/message_handle.h"
#include "perfetto/traced/data_source_types.h"
#include "src/traced/probes/ftrace/ftrace_config.h"
#include "src/traced/probes/ftrace/ftrace_metadata.h"
#include "src/traced/probes/ftrace/proto_translation_table.h"
namespace perfetto {
class FtraceDataSource;
class ProtoTranslationTable;
namespace protos {
namespace pbzero {
class FtraceEventBundle;
} // namespace pbzero
} // namespace protos
class EventFilter; // Declared down below.
// Reads raw ftrace data for a cpu and writes that into the perfetto userspace
// buffer.
class CpuReader {
public:
using FtraceEventBundle = protos::pbzero::FtraceEventBundle;
// |on_data_available| will be called on an arbitrary thread when at least one
// page of ftrace data is available for draining on this CPU.
CpuReader(const ProtoTranslationTable*,
size_t cpu,
base::ScopedFile fd,
std::function<void()> on_data_available);
~CpuReader();
// Drains all available data from the staging pipe into the buffer of the
// passed data sources.
// Should be called in response to the |on_data_available| callback.
bool Drain(const std::set<FtraceDataSource*>&);
template <typename T>
static bool ReadAndAdvance(const uint8_t** ptr, const uint8_t* end, T* out) {
if (*ptr > end - sizeof(T))
return false;
memcpy(reinterpret_cast<void*>(out), reinterpret_cast<const void*>(*ptr),
sizeof(T));
*ptr += sizeof(T);
return true;
}
// Caller must do the bounds check:
// [start + offset, start + offset + sizeof(T))
// Returns the raw value not the varint.
template <typename T>
static T ReadIntoVarInt(const uint8_t* start,
uint32_t field_id,
protozero::Message* out) {
T t;
memcpy(&t, reinterpret_cast<const void*>(start), sizeof(T));
out->AppendVarInt<T>(field_id, t);
return t;
}
template <typename T>
static void ReadInode(const uint8_t* start,
uint32_t field_id,
protozero::Message* out,
FtraceMetadata* metadata) {
T t = ReadIntoVarInt<T>(start, field_id, out);
metadata->AddInode(static_cast<Inode>(t));
}
template <typename T>
static void ReadDevId(const uint8_t* start,
uint32_t field_id,
protozero::Message* out,
FtraceMetadata* metadata) {
T t;
memcpy(&t, reinterpret_cast<const void*>(start), sizeof(T));
BlockDeviceID dev_id = TranslateBlockDeviceIDToUserspace<T>(t);
out->AppendVarInt<BlockDeviceID>(field_id, dev_id);
metadata->AddDevice(dev_id);
}
static void ReadPid(const uint8_t* start,
uint32_t field_id,
protozero::Message* out,
FtraceMetadata* metadata) {
int32_t pid = ReadIntoVarInt<int32_t>(start, field_id, out);
metadata->AddPid(pid);
}
static void ReadCommonPid(const uint8_t* start,
uint32_t field_id,
protozero::Message* out,
FtraceMetadata* metadata) {
int32_t pid = ReadIntoVarInt<int32_t>(start, field_id, out);
metadata->AddCommonPid(pid);
}
// Internally the kernel stores device ids in a different layout to that
// exposed to userspace via stat etc. There's no userspace function to convert
// between the formats so we have to do it ourselves.
template <typename T>
static BlockDeviceID TranslateBlockDeviceIDToUserspace(T kernel_dev) {
// Provided search index s_dev from
// https://github.com/torvalds/linux/blob/v4.12/include/linux/fs.h#L404
// Convert to user space id using
// https://github.com/torvalds/linux/blob/v4.12/include/linux/kdev_t.h#L10
// TODO(azappone): see if this is the same on all platforms
uint64_t maj = static_cast<uint64_t>(kernel_dev) >> 20;
uint64_t min = static_cast<uint64_t>(kernel_dev) & ((1U << 20) - 1);
return static_cast<BlockDeviceID>( // From makedev()
((maj & 0xfffff000ULL) << 32) | ((maj & 0xfffULL) << 8) |
((min & 0xffffff00ULL) << 12) | ((min & 0xffULL)));
}
// Parse a raw ftrace page beginning at ptr and write the events a protos
// into the provided bundle respecting the given event filter.
// |table| contains the mix of compile time (e.g. proto field ids) and
// run time (e.g. field offset and size) information necessary to do this.
// The table is initialized once at start time by the ftrace controller
// which passes it to the CpuReader which passes it here.
static size_t ParsePage(const uint8_t* ptr,
const EventFilter*,
protos::pbzero::FtraceEventBundle*,
const ProtoTranslationTable* table,
FtraceMetadata*);
// Parse a single raw ftrace event beginning at |start| and ending at |end|
// and write it into the provided bundle as a proto.
// |table| contains the mix of compile time (e.g. proto field ids) and
// run time (e.g. field offset and size) information necessary to do this.
// The table is initialized once at start time by the ftrace controller
// which passes it to the CpuReader which passes it to ParsePage which
// passes it here.
static bool ParseEvent(uint16_t ftrace_event_id,
const uint8_t* start,
const uint8_t* end,
const ProtoTranslationTable* table,
protozero::Message* message,
FtraceMetadata* metadata);
static bool ParseField(const Field& field,
const uint8_t* start,
const uint8_t* end,
protozero::Message* message,
FtraceMetadata* metadata);
private:
enum ThreadCtl : uint32_t { kRun = 0, kExit };
static void RunWorkerThread(size_t cpu,
int trace_fd,
int staging_write_fd,
const std::function<void()>& on_data_available,
std::atomic<ThreadCtl>* cmd_atomic);
uint8_t* GetBuffer();
CpuReader(const CpuReader&) = delete;
CpuReader& operator=(const CpuReader&) = delete;
const ProtoTranslationTable* const table_;
const size_t cpu_;
base::ScopedFile trace_fd_;
base::ScopedFile staging_read_fd_;
base::ScopedFile staging_write_fd_;
base::PageAllocator::UniquePtr buffer_;
std::thread worker_thread_;
std::atomic<ThreadCtl> cmd_{kRun};
PERFETTO_THREAD_CHECKER(thread_checker_)
};
// Class for efficient 'is event with id x enabled?' tests.
// Mirrors the data in a FtraceConfig but in a format better suited
// to be consumed by CpuReader.
class EventFilter {
public:
EventFilter(const ProtoTranslationTable&, std::set<std::string>);
~EventFilter();
bool IsEventEnabled(size_t ftrace_event_id) const {
if (ftrace_event_id == 0 || ftrace_event_id > enabled_ids_.size()) {
return false;
}
return enabled_ids_[ftrace_event_id];
}
const std::set<std::string>& enabled_names() const { return enabled_names_; }
private:
EventFilter(const EventFilter&) = delete;
EventFilter& operator=(const EventFilter&) = delete;
const std::vector<bool> enabled_ids_;
std::set<std::string> enabled_names_;
};
} // namespace perfetto
#endif // SRC_TRACED_PROBES_FTRACE_CPU_READER_H_