blob: b0b5fc36bf40dcd80205b549b570451469d11057 [file] [log] [blame]
/*
* Copyright (C) 2018 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_PAGE_POOL_H_
#define SRC_TRACED_PROBES_FTRACE_PAGE_POOL_H_
#include <stdint.h>
#include <mutex>
#include <vector>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/optional.h"
#include "perfetto/ext/base/paged_memory.h"
#include "perfetto/ext/base/thread_checker.h"
#include "perfetto/ext/base/utils.h"
namespace perfetto {
// This class is a page pool tailored around the needs of the ftrace CpuReader.
// It has two responsibilities:
// 1) A cheap bump-pointer page allocator for the writing side of CpuReader.
// 2) A thread-safe producer/consumer queue to synchronize the read/write
// threads of CpuReader.
// For context, CpuReader (and hence this class) is used on two threads:
// (1) A worker thread that writes into the buffer and (2) the main thread which
// reads all the content in big batches and turn them into protos.
// There is at most one thread writing and at most one thread reading. In rare
// circumstances they can be active At the same time.
// This class is optimized for the following use case:
// - Most of the times CpuReader wants to write 4096 bytes. In some rare cases
// (read() during flush) it wants to write < 4096 bytes.
// - Even when it writes < 4096 bytes, CpuReader can figure out the size of the
// payload from the ftrace header. We don't need extra tracking to tell how
// much of each page is used.
// - Doing a syscall for each page write is overkill. In most occasions
// CpuReader writes bursts of several pages in one go.
// - We can't really predict upfront how big the write bursts will be, hence we
// cannot predict the size of the pool, unless we accept a very high bound.
// In extreme, yet rare, conditions, CpuReader will read the whole per-cpu
// ftrace buffer, while the reader is still reading the previous batch.
// - Write burst should not be too frequent, so once they are over it's worth
// spending some extra cycles to release the memory.
// - The reader side always wants to read *all* the written pages in one batch.
// While this happens though, the write might want to write more.
//
// The architecture of this class is as follows. Pages are organized in
// PageBlock(s). A PageBlock is simply an array of pages and is the elementary
// unit of memory allocation and frees. Pages within one block are cheaply
// allocated with a simple bump-pointer allocator.
//
// [ Writer (thread worker) ] | [ Reader (main thread) ]
// ~~~~~~~~~~~~~~~~~~~~~
// +---> write queue ------------> ready queue --+
// | |
// +------------------------------- freelist <---+
// ~~~~~~~~~~~~~~~~~~~~~
// ~ mutex protected ~
// ~~~~~~~~~~~~~~~~~~~~~
class PagePool {
public:
class PageBlock {
public:
static constexpr size_t kPagesPerBlock = 32; // 32 * 4KB = 128 KB.
static constexpr size_t kBlockSize = kPagesPerBlock * base::kPageSize;
// This factory method is just that we accidentally create extra blocks
// without realizing by triggering the default constructor in containers.
static PageBlock Create() { return PageBlock(); }
PageBlock(PageBlock&&) noexcept = default;
PageBlock& operator=(PageBlock&&) = default;
size_t size() const { return size_; }
bool IsFull() const { return size_ >= kPagesPerBlock; }
// Returns the pointer to the contents of the i-th page in the block.
uint8_t* At(size_t i) const {
PERFETTO_DCHECK(i < kPagesPerBlock);
return reinterpret_cast<uint8_t*>(mem_.Get()) + i * base::kPageSize;
}
uint8_t* CurPage() const { return At(size_); }
void NextPage() {
PERFETTO_DCHECK(!IsFull());
size_++;
}
// Releases memory of the block and marks it available for reuse.
void Clear() {
size_ = 0;
mem_.AdviseDontNeed(mem_.Get(), kBlockSize);
}
private:
PageBlock(const PageBlock&) = delete;
PageBlock& operator=(const PageBlock&) = delete;
PageBlock() { mem_ = base::PagedMemory::Allocate(kBlockSize); }
base::PagedMemory mem_;
size_t size_ = 0;
};
PagePool() {
PERFETTO_DETACH_FROM_THREAD(writer_thread_);
PERFETTO_DETACH_FROM_THREAD(reader_thread_);
}
// Grabs a new page, eventually allocating a whole new PageBlock.
// If contents are written to the page, the caller must call EndWrite().
// If no data is written, it is okay to leave the BeginWrite() unpaired
// (e.g., in case of a non-blocking read returning no data) and call again
// BeginWrite() in the future.
uint8_t* BeginWrite() {
PERFETTO_DCHECK_THREAD(writer_thread_);
if (write_queue_.empty() || write_queue_.back().IsFull())
NewPageBlock(); // Slowpath. Tries the freelist first, then allocates.
return write_queue_.back().CurPage();
}
// Marks the last page as written and bumps the write pointer.
void EndWrite() {
PERFETTO_DCHECK_THREAD(writer_thread_);
PERFETTO_DCHECK(!write_queue_.empty() && !write_queue_.back().IsFull());
write_queue_.back().NextPage();
}
// Makes all written pages available to the reader.
void CommitWrittenPages() {
PERFETTO_DCHECK_THREAD(writer_thread_);
std::lock_guard<std::mutex> lock(mutex_);
read_queue_.insert(read_queue_.end(),
std::make_move_iterator(write_queue_.begin()),
std::make_move_iterator(write_queue_.end()));
write_queue_.clear();
}
// Moves ownership of all the page blocks in the read queue to the caller.
// The caller is expected to move them back after reading through EndRead().
// PageBlocks will be freed if the caller doesn't call EndRead().
std::vector<PageBlock> BeginRead() {
PERFETTO_DCHECK_THREAD(reader_thread_);
std::lock_guard<std::mutex> lock(mutex_);
auto res = std::move(read_queue_);
read_queue_.clear();
return res;
}
// Returns the page blocks borrowed for read and makes them available for
// reuse. This allows the writer to avoid doing syscalls after the initial
// writes.
void EndRead(std::vector<PageBlock> page_blocks);
size_t freelist_size_for_testing() const { return freelist_.size(); }
private:
PagePool(const PagePool&) = delete;
PagePool& operator=(const PagePool&) = delete;
void NewPageBlock();
PERFETTO_THREAD_CHECKER(writer_thread_)
std::vector<PageBlock> write_queue_; // Accessed exclusively by the writer.
std::mutex mutex_; // Protects both the read queue and the freelist.
PERFETTO_THREAD_CHECKER(reader_thread_)
std::vector<PageBlock> read_queue_; // Accessed by both threads.
std::vector<PageBlock> freelist_; // Accessed by both threads.
};
} // namespace perfetto
#endif // SRC_TRACED_PROBES_FTRACE_PAGE_POOL_H_