blob: ab8e928c71b45c646633fdba1f2b54963b0864d8 [file]
/*
* Copyright (C) 2025 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.
*/
#pragma once
#include <array>
#include <atomic>
#include "BinderStatsUtils.h"
namespace android {
/**
* Single producer Single consumer concurrent queue.
* This is made to collect and store binder transaction data for Binder metrics.
*
* Each IPCThreadState has one of these and will push all collected data to this queue.
*
* Each IPCThreadState, in its constructor, should register this queue with the
* BinderStatsCollector.
*
* The push and tryPop can be called concurrently from two threads.
* The push should be called from a single thread at all times or be behind a lock.
* The tryPop should be called from a single thread at all times or be behind a lock.
*
* Length is best effort and can be called from any thread.
*/
class BinderStatsSpscQueue {
public:
static constexpr size_t kQueueSize = 128;
private:
static_assert((kQueueSize & (kQueueSize - 1)) == 0, "Size must be a power of 2");
static constexpr size_t CAPACITY = kQueueSize;
// Cache-line aligned array of items
alignas(64) std::array<BinderCallData, CAPACITY> mBuffer{};
// Cache-line aligned counters to prevent false sharing
alignas(64) std::atomic<uint64_t> mHead{0};
alignas(64) std::atomic<uint64_t> mTail{0};
public:
/**
* @brief Attempts to push an item into the queue
*
* @param item The item to push (will be moved into the queue)
* @return true if the item was successfully pushed
* @return false if the queue was full
*/
__attribute__((no_sanitize("unsigned-integer-overflow"))) bool push(
const BinderCallData& item) {
const uint64_t head = mHead.load(std::memory_order_acquire);
const uint64_t tail = mTail.load(std::memory_order_relaxed);
// Check if full using distance
if (tail - head == CAPACITY) {
return false;
}
mBuffer.at(tail % CAPACITY) = item;
mTail.store(tail + 1, std::memory_order_release);
return true;
}
/**
* @brief Best effort length of the queue. Best used for knowing if a lazy
* consumer should start consuming.
* @return approximate size of queue
*/
__attribute__((no_sanitize("unsigned-integer-overflow"))) size_t length() {
const uint64_t head = mHead.load(std::memory_order_acquire);
const uint64_t tail = mTail.load(std::memory_order_acquire);
return tail - head;
}
/**
* @brief Pops an item from the queue if available
* @return std::optional<BinderCallData> The popped item
*/
__attribute__((no_sanitize("unsigned-integer-overflow"))) std::optional<BinderCallData>
tryPop() {
const uint64_t tail = mTail.load(std::memory_order_acquire);
const uint64_t head = mHead.load(std::memory_order_relaxed);
if (head != tail) {
BinderCallData item = std::move(mBuffer.at(head % CAPACITY));
mHead.store(head + 1, std::memory_order_release);
return item;
}
return std::nullopt;
}
};
/**
* Keeps track of SpscQueues corresponding to threads producing binder stats
* and allows the data from them to be consumed.
* Each binder thread must register its queue at creation time and deregister it
* before it is being destroyed
*/
class BinderStatsCollector {
public:
/**
* @brief Consumes all data in the registered queues
*
* This is a thread safe operation
*/
template <typename Fn>
void consumeData(Fn consumerFn) {
std::vector<std::shared_ptr<BinderStatsSpscQueue>> queuesCopy;
std::vector<std::shared_ptr<BinderStatsSpscQueue>> deregisteredQueuesCopy;
{
std::lock_guard<std::mutex> lock(mMutex);
queuesCopy = mQueues;
deregisteredQueuesCopy.swap(mDeregisteredQueues);
}
for (auto& queue : queuesCopy) {
LOG_ALWAYS_FATAL_IF(queue == nullptr, "queue pointer null");
while (auto item = queue->tryPop()) {
consumerFn(std::move(*item));
}
}
for (auto& queue : deregisteredQueuesCopy) {
LOG_ALWAYS_FATAL_IF(queue == nullptr, "queue pointer null");
while (auto item = queue->tryPop()) {
consumerFn(std::move(*item));
}
queue.reset();
}
}
/**
* @brief Register queue
*
* This is a thread safe operation
*/
void registerQueue(std::shared_ptr<BinderStatsSpscQueue> spscQueue) {
LOG_ALWAYS_FATAL_IF(spscQueue == nullptr, "queue pointer null");
std::lock_guard<std::mutex> lock(mMutex);
mQueues.push_back(spscQueue);
}
/**
* @brief Deregister queue
*
* This is a thread safe operation
*/
void deregisterQueue(std::shared_ptr<BinderStatsSpscQueue> spscQueue) {
LOG_ALWAYS_FATAL_IF(spscQueue == nullptr, "queue pointer null");
std::lock_guard<std::mutex> lock(mMutex);
mDeregisteredQueues.push_back(spscQueue);
auto it = std::find(mQueues.begin(), mQueues.end(), spscQueue);
if (it != mQueues.end()) {
mQueues.erase(it);
}
}
private:
std::vector<std::shared_ptr<BinderStatsSpscQueue>> mQueues;
std::vector<std::shared_ptr<BinderStatsSpscQueue>> mDeregisteredQueues;
std::mutex mMutex;
};
} // namespace android