Merge "traced_perf: basic producer scaffolding with logging of samples on Flush."
diff --git a/BUILD.gn b/BUILD.gn
index 8f2df22..6e2753d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -51,6 +51,10 @@
}
}
+if (enable_perfetto_traced_perf) {
+ all_targets += [ "src/profiling/perf:traced_perf" ]
+}
+
if (perfetto_build_with_android) {
all_targets += [ "src/android_internal/:libperfetto_android_internal" ]
}
diff --git a/gn/perfetto.gni b/gn/perfetto.gni
index 4dbcd42..42f0cc5 100644
--- a/gn/perfetto.gni
+++ b/gn/perfetto.gni
@@ -147,6 +147,11 @@
(perfetto_build_standalone && is_clang &&
(is_linux || (is_android && android_api_level >= 26)))
+ # Build the perf event profiler (traced_perf).
+ # TODO(b/144281346): under development.
+ enable_perfetto_traced_perf =
+ perfetto_build_standalone && (is_linux || is_android)
+
# The Trace Processor: offline analytical engine to process traces and compute
# metrics using a SQL engine.
enable_perfetto_trace_processor =
diff --git a/gn/perfetto_unittests.gni b/gn/perfetto_unittests.gni
index b8e033f..167bb94 100644
--- a/gn/perfetto_unittests.gni
+++ b/gn/perfetto_unittests.gni
@@ -56,6 +56,10 @@
]
}
+if (enable_perfetto_traced_perf) {
+ perfetto_unittests_targets += [ "src/profiling/perf:producer_unittests" ]
+}
+
if (enable_perfetto_trace_processor) {
perfetto_unittests_targets += [ "src/trace_processor:unittests" ]
diff --git a/src/profiling/perf/BUILD.gn b/src/profiling/perf/BUILD.gn
new file mode 100644
index 0000000..e24d619
--- /dev/null
+++ b/src/profiling/perf/BUILD.gn
@@ -0,0 +1,79 @@
+# Copyright (C) 2019 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.
+
+import("../../../gn/fuzzer.gni")
+import("../../../gn/perfetto.gni")
+import("../../../gn/test.gni")
+
+assert(enable_perfetto_traced_perf)
+
+executable("traced_perf") {
+ deps = [
+ ":traced_perf_main",
+ "../../../gn:default_deps",
+ ]
+ sources = [
+ "main.cc",
+ ]
+}
+
+source_set("traced_perf_main") {
+ deps = [
+ ":producer",
+ "../../../gn:default_deps",
+ "../../../src/base",
+ "../../../src/tracing:ipc",
+ ]
+ sources = [
+ "traced_perf.cc",
+ "traced_perf.h",
+ ]
+}
+
+source_set("producer") {
+ deps = [
+ "../../../gn:default_deps",
+ "../../../protos/perfetto/config:cpp",
+ "../../../protos/perfetto/config/profiling:zero",
+ "../../../protos/perfetto/trace:zero",
+ "../../../src/base",
+ "../../../src/base:unix_socket",
+ "../../../src/tracing:ipc",
+ ]
+ sources = [
+ "event_config.h",
+ "event_reader.cc",
+ "event_reader.h",
+ "perf_producer.cc",
+ "perf_producer.h",
+ ]
+}
+
+source_set("producer_unittests") {
+ testonly = true
+ deps = [
+ ":producer",
+ "../../../gn:default_deps",
+ "../../../gn:gtest_and_gmock",
+ "../../../protos/perfetto/config:cpp",
+ "../../../protos/perfetto/config:zero",
+ "../../../protos/perfetto/config/profiling:zero",
+ "../../../protos/perfetto/trace:zero",
+ "../../../src/protozero",
+ "../../base",
+ ]
+ sources = [
+ "event_config_unittest.cc",
+ ]
+}
diff --git a/src/profiling/perf/event_config.h b/src/profiling/perf/event_config.h
new file mode 100644
index 0000000..3b17ef5
--- /dev/null
+++ b/src/profiling/perf/event_config.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 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_PROFILING_PERF_EVENT_CONFIG_H_
+#define SRC_PROFILING_PERF_EVENT_CONFIG_H_
+
+#include <linux/perf_event.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "perfetto/ext/base/optional.h"
+#include "protos/perfetto/config/data_source_config.gen.h"
+#include "protos/perfetto/config/profiling/perf_event_config.pbzero.h"
+
+namespace perfetto {
+namespace profiling {
+
+// Describes a single profiling configuration. Bridges the gap between the data
+// source config proto, and the raw "perf_event_attr" structs to pass to the
+// perf_event_open syscall.
+// TODO(rsavitski): make sampling conditional? Or should we always go through
+// the sampling interface for simplicity? Reads can be done on-demand even if
+// sampling is on. So the question becomes whether we need *only* on-demand
+// reads.
+class EventConfig {
+ public:
+ static base::Optional<EventConfig> Create(const DataSourceConfig& ds_config) {
+ protos::pbzero::PerfEventConfig::Decoder pb_config(
+ ds_config.perf_event_config_raw());
+
+ if (!pb_config.has_tid())
+ return base::nullopt;
+
+ return EventConfig(pb_config);
+ }
+
+ int32_t target_tid() const { return target_tid_; }
+
+ perf_event_attr* perf_attr() const {
+ return const_cast<perf_event_attr*>(&perf_event_attr_);
+ }
+
+ private:
+ EventConfig(const protos::pbzero::PerfEventConfig::Decoder& pb_config)
+ : target_tid_(pb_config.tid()) {
+ auto& pe = perf_event_attr_;
+ memset(&pe, 0, sizeof(perf_event_attr));
+ pe.size = sizeof(perf_event_attr);
+
+ pe.exclude_kernel = true;
+ pe.disabled = false;
+
+ // Ask the kernel to tune sampling period to get ~100 Hz.
+ pe.type = PERF_TYPE_SOFTWARE;
+ pe.config = PERF_COUNT_SW_CPU_CLOCK;
+ pe.sample_freq = 100;
+ pe.freq = true;
+
+ pe.sample_type = PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER;
+ // Needs to be < ((u16)(~0u)), and have bottom 8 bits clear.
+ pe.sample_stack_user = (1u << 15);
+
+ // Note: can't use inherit with task-scoped event mmap
+ pe.inherit = false;
+ }
+
+ // TODO(rsavitski): this will have to represent entire event groups, thus this
+ // class will represent N events. So we'll need N cpus/tids, but likely still
+ // a single perf_event_attr.
+ int32_t target_tid_ = 0;
+ perf_event_attr perf_event_attr_;
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_EVENT_CONFIG_H_
diff --git a/src/profiling/perf/event_config_unittest.cc b/src/profiling/perf/event_config_unittest.cc
new file mode 100644
index 0000000..1e32374
--- /dev/null
+++ b/src/profiling/perf/event_config_unittest.cc
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 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 "src/profiling/perf/event_config.h"
+
+#include <linux/perf_event.h>
+#include <stdint.h>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "protos/perfetto/config/data_source_config.gen.h"
+#include "protos/perfetto/config/data_source_config.pbzero.h"
+#include "protos/perfetto/config/profiling/perf_event_config.pbzero.h"
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+static DataSourceConfig ConfigForTid(int32_t tid) {
+ protozero::HeapBuffered<protos::pbzero::PerfEventConfig> pb_config;
+ pb_config->set_tid(tid);
+ protozero::HeapBuffered<protos::pbzero::DataSourceConfig> ds_config;
+ ds_config->set_perf_event_config_raw(pb_config.SerializeAsString());
+ DataSourceConfig cfg;
+ PERFETTO_CHECK(cfg.ParseRawProto(ds_config.SerializeAsString()));
+ return cfg;
+}
+
+TEST(EventConfigTest, TidRequired) {
+ // Doesn't pass validation without a TID
+ DataSourceConfig cfg;
+ ASSERT_TRUE(cfg.ParseRawProto(std::string{}));
+
+ base::Optional<EventConfig> event_config = EventConfig::Create(cfg);
+ ASSERT_FALSE(event_config.has_value());
+}
+
+TEST(EventConfigTest, AttrStructConstructed) {
+ auto cfg = ConfigForTid(42);
+ base::Optional<EventConfig> event_config = EventConfig::Create(cfg);
+
+ ASSERT_TRUE(event_config.has_value());
+ ASSERT_TRUE(event_config->perf_attr() != nullptr);
+}
+
+} // namespace
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/event_reader.cc b/src/profiling/perf/event_reader.cc
new file mode 100644
index 0000000..7bc5ccf
--- /dev/null
+++ b/src/profiling/perf/event_reader.cc
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2019 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 "src/profiling/perf/event_reader.h"
+
+#include <linux/perf_event.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "perfetto/ext/base/utils.h"
+
+namespace perfetto {
+namespace profiling {
+
+namespace {
+
+template <typename T>
+const char* ReadValue(T* value_out, const char* ptr) {
+ memcpy(value_out, reinterpret_cast<const void*>(ptr), sizeof(T));
+ return ptr + sizeof(T);
+}
+
+bool IsPowerOfTwo(size_t v) {
+ return (v != 0 && ((v & (v - 1)) == 0));
+}
+
+static int perf_event_open(perf_event_attr* attr,
+ pid_t pid,
+ int cpu,
+ int group_fd,
+ unsigned long flags) {
+ return static_cast<int>(
+ syscall(__NR_perf_event_open, attr, pid, cpu, group_fd, flags));
+}
+
+base::ScopedFile PerfEventOpen(const EventConfig& event_cfg) {
+ base::ScopedFile perf_fd{
+ perf_event_open(event_cfg.perf_attr(), event_cfg.target_tid(),
+ /*cpu=*/-1, /*group_fd=*/-1, PERF_FLAG_FD_CLOEXEC)};
+ return perf_fd;
+}
+
+} // namespace
+
+PerfRingBuffer::PerfRingBuffer(PerfRingBuffer&& other) noexcept
+ : metadata_page_(other.metadata_page_),
+ mmap_sz_(other.mmap_sz_),
+ data_buf_(other.data_buf_),
+ data_buf_sz_(other.data_buf_sz_) {
+ other.metadata_page_ = nullptr;
+ other.mmap_sz_ = 0;
+ other.data_buf_ = nullptr;
+ other.data_buf_sz_ = 0;
+}
+
+PerfRingBuffer& PerfRingBuffer::operator=(PerfRingBuffer&& other) noexcept {
+ if (this == &other)
+ return *this;
+
+ this->~PerfRingBuffer();
+ new (this) PerfRingBuffer(std::move(other));
+ return *this;
+}
+
+PerfRingBuffer::~PerfRingBuffer() {
+ if (!valid())
+ return;
+
+ if (munmap(reinterpret_cast<void*>(metadata_page_), mmap_sz_) != 0)
+ PERFETTO_PLOG("failed munmap");
+}
+
+base::Optional<PerfRingBuffer> PerfRingBuffer::Allocate(
+ int perf_fd,
+ size_t data_page_count) {
+ // perf_event_open requires the ring buffer to be a power of two in size.
+ PERFETTO_CHECK(IsPowerOfTwo(data_page_count));
+
+ PerfRingBuffer ret;
+
+ // mmap request is one page larger than the buffer size (for the metadata).
+ ret.data_buf_sz_ = data_page_count * base::kPageSize;
+ ret.mmap_sz_ = ret.data_buf_sz_ + base::kPageSize;
+
+ // If PROT_WRITE, kernel won't overwrite unread samples.
+ void* mmap_addr = mmap(nullptr, ret.mmap_sz_, PROT_READ | PROT_WRITE,
+ MAP_SHARED, perf_fd, 0);
+ if (mmap_addr == MAP_FAILED) {
+ PERFETTO_PLOG("failed mmap (check perf_event_mlock_kb in procfs)");
+ return base::nullopt;
+ }
+
+ // Expected layout is [ metadata page ] [ data pages ... ]
+ ret.metadata_page_ = reinterpret_cast<perf_event_mmap_page*>(mmap_addr);
+ ret.data_buf_ = reinterpret_cast<char*>(mmap_addr) + base::kPageSize;
+ PERFETTO_CHECK(ret.metadata_page_->data_offset == base::kPageSize);
+ PERFETTO_CHECK(ret.metadata_page_->data_size = ret.data_buf_sz_);
+
+ return base::make_optional(std::move(ret));
+}
+
+// TODO(rsavitski): look into more specific barrier builtins. Copying simpleperf
+// for now. See |perf_output_put_handle| in the kernel for the barrier
+// requirements.
+#pragma GCC diagnostic push
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Watomic-implicit-seq-cst"
+#endif
+std::vector<char> PerfRingBuffer::ReadAvailable() {
+ if (!valid())
+ return {};
+
+ uint64_t write_offset = metadata_page_->data_head;
+ uint64_t read_offset = metadata_page_->data_tail;
+ __sync_synchronize(); // needs to be rmb()
+
+ size_t read_pos = static_cast<size_t>(read_offset & (data_buf_sz_ - 1));
+ size_t data_sz = static_cast<size_t>(write_offset - read_offset);
+
+ if (data_sz == 0) {
+ return {};
+ }
+
+ // memcpy accounting for wrapping
+ std::vector<char> data(data_sz);
+ size_t copy_sz = std::min(data_sz, data_buf_sz_ - read_pos);
+ memcpy(data.data(), data_buf_ + read_pos, copy_sz);
+ if (copy_sz < data_sz) {
+ memcpy(data.data() + copy_sz, data_buf_, data_sz - copy_sz);
+ }
+
+ // consume the data
+ __sync_synchronize(); // needs to be mb()
+ metadata_page_->data_tail += data_sz;
+
+ PERFETTO_LOG("WIP: consumed [%zu] bytes from ring buffer", data_sz);
+ return data;
+}
+#pragma GCC diagnostic pop
+
+EventReader::EventReader(const EventConfig& event_cfg,
+ base::ScopedFile perf_fd,
+ PerfRingBuffer ring_buffer)
+ : event_cfg_(event_cfg),
+ perf_fd_(std::move(perf_fd)),
+ ring_buffer_(std::move(ring_buffer)) {}
+
+EventReader::EventReader(EventReader&& other) noexcept
+ : event_cfg_(other.event_cfg_),
+ perf_fd_(std::move(other.perf_fd_)),
+ ring_buffer_(std::move(other.ring_buffer_)) {}
+
+EventReader& EventReader::operator=(EventReader&& other) noexcept {
+ if (this == &other)
+ return *this;
+
+ this->~EventReader();
+ new (this) EventReader(std::move(other));
+ return *this;
+}
+
+base::Optional<EventReader> EventReader::ConfigureEvents(
+ const EventConfig& event_cfg) {
+ auto perf_fd = PerfEventOpen(event_cfg);
+ if (!perf_fd) {
+ PERFETTO_PLOG("failed perf_event_open");
+ return base::nullopt;
+ }
+
+ auto ring_buffer =
+ PerfRingBuffer::Allocate(perf_fd.get(), /*data_page_count=*/128);
+ if (!ring_buffer.has_value()) {
+ return base::nullopt;
+ }
+
+ return base::make_optional<EventReader>(event_cfg, std::move(perf_fd),
+ std::move(ring_buffer.value()));
+}
+
+void EventReader::ParseNextSampleBatch() {
+ std::vector<char> data = ring_buffer_.ReadAvailable();
+ if (data.size() == 0) {
+ PERFETTO_LOG("WIP: no samples");
+ return;
+ }
+
+ for (const char* ptr = data.data(); ptr < data.data() + data.size();) {
+ if (!ParseSampleAndAdvance(&ptr))
+ break;
+ }
+}
+
+bool EventReader::ParseSampleAndAdvance(const char** ptr) {
+ const char* sample_start = *ptr;
+ auto* event_hdr = reinterpret_cast<const perf_event_header*>(sample_start);
+
+ PERFETTO_LOG("WIP: event_header[%zu][%zu][%zu]",
+ static_cast<size_t>(event_hdr->type),
+ static_cast<size_t>(event_hdr->misc),
+ static_cast<size_t>(event_hdr->size));
+
+ if (event_hdr->type == PERF_RECORD_SAMPLE) {
+ ParsePerfRecordSample(sample_start, event_hdr->size);
+ } else {
+ PERFETTO_ELOG("WIP: unsupported event type");
+ }
+
+ *ptr = sample_start + event_hdr->size;
+ return true;
+}
+
+// TODO(rsavitski): actually handle the samples instead of logging.
+void EventReader::ParsePerfRecordSample(const char* sample_start,
+ size_t sample_size) {
+ const perf_event_attr* cfg = event_cfg_.perf_attr();
+
+ if (cfg->sample_type &
+ (~uint64_t(PERF_SAMPLE_TID | PERF_SAMPLE_STACK_USER))) {
+ PERFETTO_ELOG("WIP: unsupported sampling option.");
+ return;
+ }
+
+ // Parse the payload, which consists of concatenated data for each
+ // |attr.sample_type| flag.
+ const char* parse_pos = sample_start + sizeof(perf_event_header);
+
+ if (cfg->sample_type & PERF_SAMPLE_TID) {
+ uint32_t pid;
+ parse_pos = ReadValue(&pid, parse_pos);
+ PERFETTO_LOG("pid: %" PRIu32 "", pid);
+
+ uint32_t tid;
+ parse_pos = ReadValue(&tid, parse_pos);
+ PERFETTO_LOG("tid: %" PRIu32 "", tid);
+ }
+
+ if (cfg->sample_type & PERF_SAMPLE_STACK_USER) {
+ uint64_t max_stack_size; // the requested size
+ parse_pos = ReadValue(&max_stack_size, parse_pos);
+ PERFETTO_LOG("max_stack_size: %" PRIu64 "", max_stack_size);
+
+ parse_pos += max_stack_size; // skip raw data
+
+ // not written if requested stack sampling size is zero
+ if (max_stack_size > 0) {
+ uint64_t filled_stack_size;
+ parse_pos = ReadValue(&filled_stack_size, parse_pos);
+ PERFETTO_LOG("filled_stack_size: %" PRIu64 "", filled_stack_size);
+ }
+ }
+
+ PERFETTO_CHECK(parse_pos == sample_start + sample_size);
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/event_reader.h b/src/profiling/perf/event_reader.h
new file mode 100644
index 0000000..537f489
--- /dev/null
+++ b/src/profiling/perf/event_reader.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 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_PROFILING_PERF_EVENT_READER_H_
+#define SRC_PROFILING_PERF_EVENT_READER_H_
+
+#include <linux/perf_event.h>
+#include <stdint.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+
+#include "perfetto/ext/base/optional.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "src/profiling/perf/event_config.h"
+
+namespace perfetto {
+namespace profiling {
+
+// TODO(rsavitski): currently written for the non-overwriting ring buffer mode
+// (PROT_WRITE). Decide on whether there are use-cases for supporting the other.
+// TODO(rsavitski): given perf_event_mlock_kb limit, can we afford a ring buffer
+// per data source, or will we be forced to multiplex everything onto a single
+// ring buffer in the worst case? Alternatively, obtain CAP_IPC_LOCK (and do own
+// limiting)? Or get an adjusted RLIMIT_MEMLOCK?
+// TODO(rsavitski): polling for now, look into supporting the notification
+// mechanisms (such as epoll) later.
+class PerfRingBuffer {
+ public:
+ static base::Optional<PerfRingBuffer> Allocate(int perf_fd,
+ size_t data_page_count);
+
+ ~PerfRingBuffer();
+
+ // move-only
+ PerfRingBuffer(const PerfRingBuffer&) = delete;
+ PerfRingBuffer& operator=(const PerfRingBuffer&) = delete;
+ PerfRingBuffer(PerfRingBuffer&& other) noexcept;
+ PerfRingBuffer& operator=(PerfRingBuffer&& other) noexcept;
+
+ std::vector<char> ReadAvailable();
+
+ private:
+ PerfRingBuffer() = default;
+
+ bool valid() const { return metadata_page_ != nullptr; }
+
+ // TODO(rsavitski): volatile?
+ // Is exactly the start of the mmap'd region.
+ perf_event_mmap_page* metadata_page_ = nullptr;
+
+ // size of the mmap'd region (1 metadata page + data_buf_sz_)
+ size_t mmap_sz_ = 0;
+
+ // mmap'd ring buffer
+ char* data_buf_ = nullptr;
+ size_t data_buf_sz_ = 0;
+};
+
+class EventReader {
+ public:
+ // Allow base::Optional<EventReader> without making the constructor public.
+ template <typename EventReader, bool>
+ friend struct base::internal::OptionalStorageBase;
+
+ static base::Optional<EventReader> ConfigureEvents(
+ const EventConfig& event_cfg);
+
+ ~EventReader() = default;
+
+ // move-only
+ EventReader(const EventReader&) = delete;
+ EventReader& operator=(const EventReader) = delete;
+ EventReader(EventReader&&) noexcept;
+ EventReader& operator=(EventReader&&) noexcept;
+
+ // TODO(rsavitski): temporary.
+ void ParseNextSampleBatch();
+
+ private:
+ EventReader(const EventConfig& event_cfg,
+ base::ScopedFile perf_fd,
+ PerfRingBuffer ring_buffer);
+
+ bool ParseSampleAndAdvance(const char** ptr);
+ void ParsePerfRecordSample(const char* sample_payload, size_t sample_size);
+
+ const EventConfig event_cfg_;
+ base::ScopedFile perf_fd_;
+ PerfRingBuffer ring_buffer_;
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_EVENT_READER_H_
diff --git a/src/profiling/perf/main.cc b/src/profiling/perf/main.cc
new file mode 100644
index 0000000..7ca3729
--- /dev/null
+++ b/src/profiling/perf/main.cc
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2019 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 "src/profiling/perf/traced_perf.h"
+
+int main(int argc, char** argv) {
+ return perfetto::TracedPerfMain(argc, argv);
+}
diff --git a/src/profiling/perf/perf_producer.cc b/src/profiling/perf/perf_producer.cc
new file mode 100644
index 0000000..dd5d642
--- /dev/null
+++ b/src/profiling/perf/perf_producer.cc
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2019 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 "src/profiling/perf/perf_producer.h"
+
+#include <unistd.h>
+#include <utility>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
+#include "perfetto/ext/tracing/core/producer.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
+#include "perfetto/ext/tracing/ipc/producer_ipc_client.h"
+#include "perfetto/tracing/core/data_source_config.h"
+#include "perfetto/tracing/core/data_source_descriptor.h"
+#include "protos/perfetto/config/profiling/perf_event_config.pbzero.h"
+#include "src/profiling/perf/event_reader.h"
+
+namespace perfetto {
+namespace profiling {
+namespace {
+
+constexpr uint32_t kInitialConnectionBackoffMs = 100;
+constexpr uint32_t kMaxConnectionBackoffMs = 30 * 1000;
+
+constexpr char kProducerName[] = "perfetto.traced_perf";
+constexpr char kDataSourceName[] = "linux.perf";
+
+} // namespace
+
+PerfProducer::PerfProducer(base::TaskRunner* task_runner)
+ : task_runner_(task_runner), weak_factory_(this) {}
+
+// TODO(rsavitski): configure at setup + enable at start, or do everything on
+// start? Also, do we try to work around the old(?) cpu hotplug bugs as
+// simpleperf does?
+void PerfProducer::SetupDataSource(DataSourceInstanceID,
+ const DataSourceConfig&) {}
+
+void PerfProducer::StartDataSource(DataSourceInstanceID instance_id,
+ const DataSourceConfig& config) {
+ PERFETTO_LOG("StartDataSource(id=%" PRIu64 ", name=%s)", instance_id,
+ config.name().c_str());
+
+ if (config.name() != kDataSourceName)
+ return;
+
+ base::Optional<EventConfig> event_config = EventConfig::Create(config);
+ if (!event_config.has_value()) {
+ PERFETTO_LOG("PerfEventConfig rejected.");
+ return;
+ }
+
+ std::string maps_path = std::string("/proc/") +
+ std::to_string(event_config->target_tid()) +
+ std::string("/maps");
+ auto maps_fd = base::OpenFile(maps_path, O_RDONLY);
+ if (!maps_fd)
+ PERFETTO_PLOG("failed /proc/pid/maps open (proceeding)");
+
+ std::string mem_path = std::string("/proc/") +
+ std::to_string(event_config->target_tid()) +
+ std::string("/mem");
+ auto mem_fd = base::OpenFile(mem_path, O_RDONLY);
+ if (!mem_fd)
+ PERFETTO_PLOG("failed /proc/pid/mem open (proceeding)");
+
+ base::Optional<EventReader> event_reader =
+ EventReader::ConfigureEvents(event_config.value());
+ if (!event_reader.has_value()) {
+ PERFETTO_LOG("Failed to set up perf events.");
+ return;
+ }
+
+ // Build the DataSource instance.
+ auto it_inserted = data_sources_.emplace(
+ std::piecewise_construct, std::forward_as_tuple(instance_id),
+ std::forward_as_tuple(std::move(event_reader.value()), std::move(maps_fd),
+ std::move(mem_fd)));
+
+ PERFETTO_DCHECK(it_inserted.second);
+}
+
+void PerfProducer::StopDataSource(DataSourceInstanceID instance_id) {
+ PERFETTO_LOG("StopDataSource(id=%" PRIu64 ")", instance_id);
+
+ data_sources_.erase(instance_id);
+}
+
+void PerfProducer::Flush(FlushRequestID,
+ const DataSourceInstanceID* data_source_ids,
+ size_t num_data_sources) {
+ for (size_t i = 0; i < num_data_sources; i++) {
+ PERFETTO_LOG("Flush(id=%" PRIu64 ")", data_source_ids[i]);
+
+ auto ds_it = data_sources_.find(data_source_ids[i]);
+ if (ds_it != data_sources_.end()) {
+ auto& ds = ds_it->second;
+
+ // For now, parse whatever's been accumulated in the ring buffer.
+ ds.event_reader.ParseNextSampleBatch();
+ }
+ }
+}
+
+void PerfProducer::ConnectWithRetries(const char* socket_name) {
+ PERFETTO_DCHECK(state_ == kNotStarted);
+ state_ = kNotConnected;
+
+ ResetConnectionBackoff();
+ producer_socket_name_ = socket_name;
+ ConnectService();
+}
+
+void PerfProducer::ConnectService() {
+ PERFETTO_DCHECK(state_ == kNotConnected);
+ state_ = kConnecting;
+ endpoint_ = ProducerIPCClient::Connect(producer_socket_name_, this,
+ kProducerName, task_runner_);
+}
+
+void PerfProducer::IncreaseConnectionBackoff() {
+ connection_backoff_ms_ *= 2;
+ if (connection_backoff_ms_ > kMaxConnectionBackoffMs)
+ connection_backoff_ms_ = kMaxConnectionBackoffMs;
+}
+
+void PerfProducer::ResetConnectionBackoff() {
+ connection_backoff_ms_ = kInitialConnectionBackoffMs;
+}
+
+void PerfProducer::OnConnect() {
+ PERFETTO_DCHECK(state_ == kConnecting);
+ state_ = kConnected;
+ ResetConnectionBackoff();
+ PERFETTO_LOG("Connected to the service");
+
+ DataSourceDescriptor desc;
+ desc.set_name(kDataSourceName);
+ endpoint_->RegisterDataSource(desc);
+}
+
+void PerfProducer::OnDisconnect() {
+ PERFETTO_DCHECK(state_ == kConnected || state_ == kConnecting);
+ PERFETTO_LOG("Disconnected from tracing service");
+
+ auto weak_producer = weak_factory_.GetWeakPtr();
+ if (state_ == kConnected)
+ return task_runner_->PostTask([weak_producer] {
+ if (weak_producer)
+ weak_producer->Restart();
+ });
+
+ state_ = kNotConnected;
+ IncreaseConnectionBackoff();
+ task_runner_->PostDelayedTask(
+ [weak_producer] {
+ if (weak_producer)
+ weak_producer->ConnectService();
+ },
+ connection_backoff_ms_);
+}
+
+void PerfProducer::Restart() {
+ // We lost the connection with the tracing service. At this point we need
+ // to reset all the data sources. Trying to handle that manually is going to
+ // be error prone. What we do here is simply destroy the instance and
+ // recreate it again.
+ base::TaskRunner* task_runner = task_runner_;
+ const char* socket_name = producer_socket_name_;
+
+ // Invoke destructor and then the constructor again.
+ this->~PerfProducer();
+ new (this) PerfProducer(task_runner);
+
+ ConnectWithRetries(socket_name);
+}
+
+} // namespace profiling
+} // namespace perfetto
diff --git a/src/profiling/perf/perf_producer.h b/src/profiling/perf/perf_producer.h
new file mode 100644
index 0000000..d028c75
--- /dev/null
+++ b/src/profiling/perf/perf_producer.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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_PROFILING_PERF_PERF_PRODUCER_H_
+#define SRC_PROFILING_PERF_PERF_PRODUCER_H_
+
+#include <map>
+
+#include "perfetto/base/task_runner.h"
+#include "perfetto/ext/base/scoped_file.h"
+#include "perfetto/ext/base/weak_ptr.h"
+#include "perfetto/ext/tracing/core/basic_types.h"
+#include "perfetto/ext/tracing/core/producer.h"
+#include "perfetto/ext/tracing/core/tracing_service.h"
+#include "src/profiling/perf/event_config.h"
+#include "src/profiling/perf/event_reader.h"
+
+namespace perfetto {
+namespace profiling {
+
+// TODO(b/144281346): work in progress. Do not use.
+class PerfProducer : public Producer {
+ public:
+ PerfProducer(base::TaskRunner* task_runner);
+ ~PerfProducer() override = default;
+
+ PerfProducer(const PerfProducer&) = delete;
+ PerfProducer& operator=(const PerfProducer&) = delete;
+ PerfProducer(PerfProducer&&) = delete;
+ PerfProducer& operator=(PerfProducer&&) = delete;
+
+ void ConnectWithRetries(const char* socket_name);
+
+ // Producer Impl:
+ void OnConnect() override;
+ void OnDisconnect() override;
+ void OnTracingSetup() override {}
+ void SetupDataSource(DataSourceInstanceID, const DataSourceConfig&) override;
+ void StartDataSource(DataSourceInstanceID, const DataSourceConfig&) override;
+ void StopDataSource(DataSourceInstanceID) override;
+ void Flush(FlushRequestID,
+ const DataSourceInstanceID* data_source_ids,
+ size_t num_data_sources) override;
+ void ClearIncrementalState(const DataSourceInstanceID* /*data_source_ids*/,
+ size_t /*num_data_sources*/) override {}
+
+ private:
+ // State of the connection to tracing service (traced).
+ enum State {
+ kNotStarted = 0,
+ kNotConnected,
+ kConnecting,
+ kConnected,
+ };
+
+ // TODO(rsavitski): proc-fds need to live elsewhere, as they can be shared
+ // across data sources. We might also have arbitrarily many tasks handled
+ // by one data source (if scoping events to a cpu).
+ struct DataSource {
+ DataSource(EventReader _event_reader,
+ base::ScopedFile _maps_fd,
+ base::ScopedFile _mem_fd)
+ : event_reader(std::move(_event_reader)),
+ maps_fd(std::move(_maps_fd)),
+ mem_fd(std::move(_mem_fd)) {}
+
+ EventReader event_reader;
+
+ // note: currently populated, but unused.
+ base::ScopedFile maps_fd;
+ base::ScopedFile mem_fd;
+ };
+
+ void ConnectService();
+ void Restart();
+ void ResetConnectionBackoff();
+ void IncreaseConnectionBackoff();
+
+ base::TaskRunner* const task_runner_;
+ State state_ = kNotStarted;
+ const char* producer_socket_name_ = nullptr;
+ uint32_t connection_backoff_ms_ = 0;
+
+ std::unique_ptr<TracingService::ProducerEndpoint> endpoint_;
+ std::map<DataSourceInstanceID, DataSource> data_sources_;
+
+ base::WeakPtrFactory<PerfProducer> weak_factory_; // keep last
+};
+
+} // namespace profiling
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_PERF_PRODUCER_H_
diff --git a/src/profiling/perf/traced_perf.cc b/src/profiling/perf/traced_perf.cc
new file mode 100644
index 0000000..c165b20
--- /dev/null
+++ b/src/profiling/perf/traced_perf.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 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 "src/profiling/perf/traced_perf.h"
+#include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/tracing/ipc/default_socket.h"
+#include "src/profiling/perf/perf_producer.h"
+
+namespace perfetto {
+
+// TODO(rsavitski): watchdog.
+int TracedPerfMain(int, char**) {
+ base::UnixTaskRunner task_runner;
+ profiling::PerfProducer producer(&task_runner);
+ producer.ConnectWithRetries(GetProducerSocket());
+ task_runner.Run();
+ return 0;
+}
+
+} // namespace perfetto
diff --git a/src/profiling/perf/traced_perf.h b/src/profiling/perf/traced_perf.h
new file mode 100644
index 0000000..71c85e2
--- /dev/null
+++ b/src/profiling/perf/traced_perf.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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_PROFILING_PERF_TRACED_PERF_H_
+#define SRC_PROFILING_PERF_TRACED_PERF_H_
+
+namespace perfetto {
+
+int TracedPerfMain(int argc, char** argv);
+
+} // namespace perfetto
+
+#endif // SRC_PROFILING_PERF_TRACED_PERF_H_