blob: 842b5eb4174f948128f3b044accba6acdddfb44f [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.
*/
#include "cpu_reader.h"
#include <utility>
#include "perfetto/base/logging.h"
#include "proto_translation_table.h"
#include "protos/ftrace/ftrace_event.pbzero.h"
#include "protos/ftrace/print.pbzero.h"
#include "protos/ftrace/sched_switch.pbzero.h"
#include "protos/ftrace/ftrace_event_bundle.pbzero.h"
namespace perfetto {
namespace {
static bool ReadIntoString(const uint8_t* start,
const uint8_t* end,
size_t field_id,
protozero::ProtoZeroMessage* out) {
for (const uint8_t* c = start; c < end; c++) {
if (*c != '\0')
continue;
out->AppendBytes(field_id, reinterpret_cast<const char*>(start), c - start);
return true;
}
return false;
}
using BundleHandle =
protozero::ProtoZeroMessageHandle<protos::pbzero::FtraceEventBundle>;
const std::vector<bool> BuildEnabledVector(const ProtoTranslationTable& table,
const std::set<std::string>& names) {
std::vector<bool> enabled(table.largest_id() + 1);
for (const std::string& name : names) {
const Event* event = table.GetEventByName(name);
if (!event)
continue;
enabled[event->ftrace_event_id] = true;
}
return enabled;
}
// For further documentation of these constants see the kernel source:
// linux/include/linux/ring_buffer.h
// Some information about the values of these constants are exposed to user
// space at: /sys/kernel/debug/tracing/events/header_event
const uint32_t kTypeDataTypeLengthMax = 28;
const uint32_t kTypePadding = 29;
const uint32_t kTypeTimeExtend = 30;
const uint32_t kTypeTimeStamp = 31;
const size_t kPageSize = 4096;
struct PageHeader {
uint64_t timestamp;
uint32_t size;
uint32_t : 24;
uint32_t overwrite : 8;
};
struct EventHeader {
uint32_t type_or_length : 5;
uint32_t time_delta : 27;
};
struct TimeStamp {
uint64_t tv_nsec;
uint64_t tv_sec;
};
} // namespace
EventFilter::EventFilter(const ProtoTranslationTable& table,
std::set<std::string> names)
: enabled_ids_(BuildEnabledVector(table, names)),
enabled_names_(std::move(names)) {}
EventFilter::~EventFilter() = default;
CpuReader::CpuReader(const ProtoTranslationTable* table,
size_t cpu,
base::ScopedFile fd)
: table_(table), cpu_(cpu), fd_(std::move(fd)) {}
int CpuReader::GetFileDescriptor() {
return fd_.get();
}
bool CpuReader::Drain(const std::array<const EventFilter*, kMaxSinks>& filters,
const std::array<BundleHandle, kMaxSinks>& bundles) {
if (!fd_)
return false;
uint8_t* buffer = GetBuffer();
// TOOD(hjd): One read() per page may be too many.
long bytes = PERFETTO_EINTR(read(fd_.get(), buffer, kPageSize));
if (bytes != kPageSize)
return false;
for (size_t i = 0; i < kMaxSinks; i++) {
if (!filters[i])
break;
bool result = ParsePage(cpu_, buffer, filters[i], &*bundles[i], table_);
PERFETTO_DCHECK(result);
}
return true;
}
CpuReader::~CpuReader() = default;
uint8_t* CpuReader::GetBuffer() {
// TODO(primiano): Guard against overflows, like BufferedFrameDeserializer.
if (!buffer_)
buffer_ = std::unique_ptr<uint8_t[]>(new uint8_t[kPageSize]);
return buffer_.get();
}
// The structure of a raw trace buffer page is as follows:
// First a page header:
// 8 bytes of timestamp
// 8 bytes of page length TODO(hjd): other fields also defined here?
// // TODO(hjd): Document rest of format.
// Some information about the layout of the page header is available in user
// space at: /sys/kernel/debug/tracing/events/header_event
// This method is deliberately static so it can be tested independently.
bool CpuReader::ParsePage(size_t cpu,
const uint8_t* ptr,
const EventFilter* filter,
protos::pbzero::FtraceEventBundle* bundle,
const ProtoTranslationTable* table) {
const uint8_t* const start_of_page = ptr;
const uint8_t* const end_of_page = ptr + kPageSize;
bundle->set_cpu(cpu);
(void)start_of_page;
// TODO(hjd): Read this format dynamically?
PageHeader page_header;
if (!ReadAndAdvance(&ptr, end_of_page, &page_header))
return false;
// TODO(hjd): There is something wrong with the page header struct.
page_header.size = page_header.size & 0xfffful;
const uint8_t* const end = ptr + page_header.size;
if (end > end_of_page)
return false;
uint64_t timestamp = page_header.timestamp;
while (ptr < end) {
EventHeader event_header;
if (!ReadAndAdvance(&ptr, end, &event_header))
return false;
timestamp += event_header.time_delta;
switch (event_header.type_or_length) {
case kTypePadding: {
// Left over page padding or discarded event.
if (event_header.time_delta == 0) {
// TODO(hjd): Look at the next few bytes for read size;
PERFETTO_CHECK(false); // TODO(hjd): Handle
}
uint32_t length;
if (!ReadAndAdvance<uint32_t>(&ptr, end, &length))
return false;
ptr += length;
break;
}
case kTypeTimeExtend: {
// Extend the time delta.
uint32_t time_delta_ext;
if (!ReadAndAdvance<uint32_t>(&ptr, end, &time_delta_ext))
return false;
// See https://goo.gl/CFBu5x
timestamp += ((uint64_t)time_delta_ext) << 27;
break;
}
case kTypeTimeStamp: {
// Sync time stamp with external clock.
TimeStamp time_stamp;
if (!ReadAndAdvance<TimeStamp>(&ptr, end, &time_stamp))
return false;
// TODO(hjd): Handle.
break;
}
// Data record:
default: {
PERFETTO_CHECK(event_header.type_or_length <= kTypeDataTypeLengthMax);
// type_or_length is <=28 so it represents the length of a data record.
if (event_header.type_or_length == 0) {
// TODO(hjd): Look at the next few bytes for real size.
PERFETTO_CHECK(false);
}
const uint8_t* start = ptr;
const uint8_t* next = ptr + 4 * event_header.type_or_length;
uint16_t ftrace_event_id;
if (!ReadAndAdvance<uint16_t>(&ptr, end, &ftrace_event_id))
return false;
if (filter->IsEventEnabled(ftrace_event_id)) {
protos::pbzero::FtraceEvent* event = bundle->add_event();
event->set_timestamp(timestamp);
if (!ParseEvent(ftrace_event_id, start, next, table, event))
return false;
}
// Jump to next event.
ptr = next;
}
}
}
return true;
}
// |start| is the start of the current event.
// |end| is the end of the buffer.
bool CpuReader::ParseEvent(uint16_t ftrace_event_id,
const uint8_t* start,
const uint8_t* end,
const ProtoTranslationTable* table,
protozero::ProtoZeroMessage* message) {
PERFETTO_DCHECK(start < end);
const size_t length = end - start;
// TODO(hjd): Rework to work even if the event is unknown.
const Event& info = *table->GetEventById(ftrace_event_id);
// TODO(hjd): Test truncated events.
// If the end of the buffer is before the end of the event give up.
if (info.size > length) {
PERFETTO_DCHECK(false);
return false;
}
for (const Field& field : table->common_fields())
ParseField(field, start, end, message);
protozero::ProtoZeroMessage* nested =
message->BeginNestedMessage<protozero::ProtoZeroMessage>(
info.proto_field_id);
for (const Field& field : info.fields)
ParseField(field, start, end, nested);
// This finalizes |nested| automatically.
message->Finalize();
return true;
}
// Caller must guarantee that the field fits in the range,
// explicitly: start + field.ftrace_offset + field.ftrace_size <= end
// The only exception is fields with strategy = kCStringToString
// where the total size isn't known up front. In this case ParseField
// will check the string terminates in the bounds and won't read past |end|.
bool CpuReader::ParseField(const Field& field,
const uint8_t* start,
const uint8_t* end,
protozero::ProtoZeroMessage* message) {
PERFETTO_DCHECK(start + field.ftrace_offset + field.ftrace_size <= end);
const uint8_t* field_start = start + field.ftrace_offset;
uint32_t field_id = field.proto_field_id;
switch (field.strategy) {
case kUint32ToUint32:
case kUint32ToUint64:
ReadIntoVarInt<uint32_t>(field_start, field_id, message);
return true;
case kUint64ToUint64:
ReadIntoVarInt<uint64_t>(field_start, field_id, message);
return true;
case kInt32ToInt32:
case kInt32ToInt64:
ReadIntoVarInt<int32_t>(field_start, field_id, message);
return true;
case kInt64ToInt64:
ReadIntoVarInt<int64_t>(field_start, field_id, message);
return true;
case kFixedCStringToString:
// TODO(hjd): Add AppendMaxLength string to protozero.
return ReadIntoString(field_start, field_start + field.ftrace_size,
field_id, message);
case kCStringToString:
// TODO(hjd): Kernel-dive to check this how size:0 char fields work.
return ReadIntoString(field_start, end, field.proto_field_id, message);
}
// Not reached, for gcc.
PERFETTO_CHECK(false);
return false;
}
} // namespace perfetto