blob: 53a1ff2e339158a7d34a8cca58f1bae19c802a2d [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.
*/
#define LOG_TAG "libbinder.BinderStatsPusher"
#include "BinderStatsPusher.h"
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android/os/binder/BinderCallsStats.h>
#include <android/os/binder/BinderSpamStats.h>
#include <android/os/binder/IBinderStatsConsumerService.h>
#include <android_os_binder_flags.h>
#include <binder/Functional.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <utils/SystemClock.h>
#include <algorithm>
#include <charconv>
#include "../BuildFlags.h"
#include "../JvmUtils.h"
#include "BinderStatsUtils.h"
#include "HistogramScale.h"
namespace android {
[[clang::no_destroy]] static const StaticString16 kBinderStatsServiceName(u"binder_stats_consumer");
sp<os::binder::IBinderStatsConsumerService> BinderStatsPusher::getBinderStatsServiceLocked(
const int64_t nowSec) {
// When this is removed, the device does not get past the boot animation
// TODO(b/299356196): This might result in dropped stats for high usage apps like
// servicemanager.
if (!mLastServiceCheckSucceeded && (mServiceCheckTimeSec + kCheckServiceTimeoutSec > nowSec)) {
return nullptr;
};
auto sm = defaultServiceManager();
if (!sm) {
LOG_ALWAYS_FATAL("defaultServiceManager() returned nullptr.");
}
auto service = interface_cast<os::binder::IBinderStatsConsumerService>(
defaultServiceManager()->checkService(kBinderStatsServiceName));
mServiceCheckTimeSec = nowSec;
if (service == nullptr) {
mLastServiceCheckSucceeded = false;
} else {
mLastServiceCheckSucceeded = true;
}
return service;
}
#if defined(LIBBINDER_BINDER_OBSERVER_V2)
constexpr int64_t kNanosPerSecond = 1000'000'000;
constexpr bool kBinderStatsLatencyHistogram = android::os::binder::flags::binder_stats_v3();
struct BinderCallDataByEndTimeLess {
bool operator()(const BinderCallData& a, const BinderCallData& b) const {
auto aEndTimeSec = a.endTimeNanos / kNanosPerSecond;
auto bEndTimeSec = b.endTimeNanos / kNanosPerSecond;
return std::tie(aEndTimeSec, a.interfaceDescriptor, a.transactionCode, a.senderUid) <
std::tie(bEndTimeSec, b.interfaceDescriptor, b.transactionCode, b.senderUid);
}
};
void aggregateSingleCallDataLocked(const BinderCallData& datum,
os::binder::SingleSecondBinderStats& stat) {
stat.callCount++;
if (datum.hasLatencyData()) {
stat.durationCount++;
int64_t durationNanos;
if (__builtin_sub_overflow(datum.endTimeNanos, datum.startTimeNanos, &durationNanos)) {
ALOGW("Duration nanos calculation overflow");
durationNanos = 0;
}
int64_t durationMicros = durationNanos / 1000;
if (__builtin_add_overflow(durationMicros, stat.durationMicrosSum,
&stat.durationMicrosSum)) {
ALOGW("Duration Micros Sum calculation overflow");
stat.durationMicrosSum = std::numeric_limits<int32_t>::max();
}
int64_t square;
if (__builtin_mul_overflow(durationMicros, durationMicros, &square)) {
ALOGW("Duration Micros calculation overflow");
stat.durationMicrosSquaredSum = std::numeric_limits<int64_t>::max();
} else {
if (__builtin_add_overflow(stat.durationMicrosSquaredSum, square,
&stat.durationMicrosSquaredSum)) {
ALOGW("Duration Micros Squared Sum calculation overflow");
stat.durationMicrosSquaredSum = std::numeric_limits<int64_t>::max();
}
}
if (kBinderStatsLatencyHistogram) {
stat.durationBinIndices.push_back(HistogramScale::getBinIndex(durationNanos));
}
}
if (datum.cpuTimeNanos > 0) {
stat.cpuTimeCount++;
int64_t cpuTimeMicros = (datum.cpuTimeNanos) / 1000;
if (__builtin_add_overflow(cpuTimeMicros, stat.cpuTimeMicrosSum, &stat.cpuTimeMicrosSum)) {
stat.cpuTimeMicrosSum = std::numeric_limits<int32_t>::max();
ALOGW("CPU Time Micros Sum calculation overflow");
}
int64_t square;
if (__builtin_mul_overflow(cpuTimeMicros, cpuTimeMicros, &square)) {
ALOGW("CPU Time Micros Squared calculation overflow");
stat.cpuTimeMicrosSquaredSum = std::numeric_limits<uint64_t>::max();
} else {
if (__builtin_add_overflow(stat.cpuTimeMicrosSquaredSum, square,
&stat.cpuTimeMicrosSquaredSum)) {
ALOGW("CPU Time Micros Squared Sum calculation overflow");
stat.cpuTimeMicrosSquaredSum = std::numeric_limits<uint64_t>::max();
}
}
}
}
void BinderStatsPusher::sortAndAggregateStatsLocked(const int64_t nowSec,
std::vector<BinderCallData>& data) {
if (data.empty()) {
return;
}
sp<os::binder::IBinderStatsConsumerService> service = getBinderStatsServiceLocked(nowSec);
// Ensure that if this is a local binder and this thread isn't attached
// to the VM then skip pushing. This is required since StatsBootstrap is
// a Java service and needs a JNI interface to be called from native code.
bool isProcessSystemServer = false;
if (service != nullptr) {
isProcessSystemServer = IInterface::asBinder(service)->localBinder() != nullptr;
}
if (isProcessSystemServer) {
if (!isThreadAttachedToJVM()) {
// TODO(b/458340205): Attach thread to JVM instead of setting service to nullptr.
// Setting it to nullptr will drop stats.
service = nullptr;
}
}
if (service == nullptr) {
return;
}
int64_t callingIdentity;
if (isProcessSystemServer) {
callingIdentity = IPCThreadState::self()->clearCallingIdentity();
}
auto guard = binder::impl::make_scope_guard([&] {
if (isProcessSystemServer) {
IPCThreadState::self()->restoreCallingIdentity(callingIdentity);
}
});
auto reportStatsAndClearBuffer = [&]() {
if (service != nullptr) {
auto status = service->reportSecondGranularityStats(mSingleSecondStats);
if (!status.isOk()) {
ALOGW("reportSecondGranularityStats failed: %s", status.toString8().c_str());
}
mSingleSecondStats.clear();
}
};
// Sort data to group calls by interface, transaction code, and sender UID within the same
// second. This allows for efficient chunking and aggregation of related binder calls.
std::sort(data.begin(), data.end(), BinderCallDataByEndTimeLess());
auto chunkStart = data.begin();
os::binder::SingleSecondBinderStats chunkStat{};
for (auto it = data.begin(); it != data.end(); ++it) {
aggregateSingleCallDataLocked(*it, chunkStat);
auto nextIt = it + 1;
bool endOfChunk = (nextIt == data.end());
// A chunk is a set of binderCallData which have the same endTimeSecond, senderUid,
// interfaceDescriptor and transactionCode. After sorting they will be contiguous.
if (!endOfChunk) {
bool isSameChunk = chunkStart->endTimeNanos / kNanosPerSecond ==
nextIt->endTimeNanos / kNanosPerSecond &&
chunkStart->senderUid == nextIt->senderUid &&
chunkStart->transactionCode == nextIt->transactionCode &&
chunkStart->interfaceDescriptor == nextIt->interfaceDescriptor;
endOfChunk = !isSameChunk;
}
if (endOfChunk) {
if (mSingleSecondStats.size() >= kMaxStatsCount) {
reportStatsAndClearBuffer();
}
if (chunkStat.callCount > 0) {
chunkStat.interfaceDescriptor = chunkStart->interfaceDescriptor;
chunkStat.aidlMethod = chunkStart->aidlMethodName;
chunkStat.clientUid = chunkStart->senderUid;
mSingleSecondStats.push_back(std::move(chunkStat));
}
chunkStart = nextIt;
if (chunkStart != data.end()) {
chunkStat = os::binder::SingleSecondBinderStats{};
}
}
}
if (!mSingleSecondStats.empty()) {
reportStatsAndClearBuffer();
}
}
// TODO(b/458340205): Remove the function.
void BinderStatsPusher::pushLocked(const int64_t /*nowSec*/) {
// With LIBBINDER_BINDER_OBSERVER_V2 true, aggregation and reporting are handled by
// BinderCallsVectorAggregation::addCallStatsLocked, so this function is empty.
}
#else // !defined(LIBBINDER_BINDER_OBSERVER_V2)
void BinderStatsPusher::appendCallStatsToReportLocked(const BinderCallData& chunk,
const CallAggregation& agg) {
if (agg.callsWithLatency > 0 || agg.cpuTimeCount > 0) {
mCallStats.emplace_back(os::binder::BinderCallsStats());
auto& callsStats = mCallStats.back();
callsStats.clientUid = static_cast<int32_t>(chunk.senderUid);
callsStats.interfaceDescriptor = chunk.interfaceDescriptor;
callsStats.aidlMethod = chunk.aidlMethodName;
callsStats.callCount = static_cast<int32_t>(agg.callsWithLatency);
callsStats.durationSumMicros = agg.durationSumMicros;
callsStats.secondsWithAtLeast10Calls = agg.secondsWithAtLeast10Calls;
callsStats.secondsWithAtLeast50Calls = agg.secondsWithAtLeast50Calls;
callsStats.cpuTimeCount = static_cast<int32_t>(agg.cpuTimeCount);
callsStats.cpuTimeSumMicros = agg.cpuTimeSumMicros;
callsStats.cpuTimeSumSquaredMicros = agg.cpuTimeSumSquaredMicros;
}
}
__attribute__((no_sanitize("signed-integer-overflow"))) void
BinderStatsPusher::aggregateStatsLocked(const int64_t nowSec) {
sp<os::binder::IBinderStatsConsumerService> service = getBinderStatsServiceLocked(nowSec);
// Ensure that if this is a local binder and this thread isn't attached
// to the VM then skip pushing. This is required since StatsBootstrap is
// a Java service and needs a JNI interface to be called from native code.
bool isProcessSystemServer = false;
if (service != nullptr) {
isProcessSystemServer = IInterface::asBinder(service)->localBinder() != nullptr;
}
if (isProcessSystemServer) {
if (!isThreadAttachedToJVM()) {
// TODO(b/458340205): Attach thread to JVM instead of setting service to nullptr.
// Setting it to nullptr will drop stats.
service = nullptr;
}
}
if (service == nullptr) {
return;
}
int64_t callingIdentity;
if (isProcessSystemServer) {
callingIdentity = IPCThreadState::self()->clearCallingIdentity();
}
auto guard = binder::impl::make_scope_guard([&] {
if (isProcessSystemServer) {
IPCThreadState::self()->restoreCallingIdentity(callingIdentity);
}
});
auto& callsBuffer = mCallsAggregation.getBufferLocked();
for (auto outerIt = callsBuffer.begin(); outerIt != callsBuffer.end();
/* no increment */) {
int32_t secondsWithAtLeast125Calls = 0;
int32_t secondsWithAtLeast250Calls = 0;
uint32_t peakCallCountPerSecond = 0;
CallAggregation agg;
for (auto innerIt = outerIt->second.begin(); innerIt != outerIt->second.end();
/* no increment */) {
// Check if the buffer period has passed.
int64_t startTimeSec = innerIt->first;
if (nowSec - startTimeSec >= kAggregationWindowSec) {
uint32_t totalCalls = innerIt->second.totalCalls;
if (totalCalls > kSpamFirstWatermark) {
secondsWithAtLeast125Calls++;
if (totalCalls > kSpamSecondWatermark) {
secondsWithAtLeast250Calls++;
}
peakCallCountPerSecond = std::max(peakCallCountPerSecond, totalCalls);
}
if (innerIt->second.callsWithLatency > 0) {
agg.callsWithLatency += innerIt->second.callsWithLatency;
agg.durationSumMicros += innerIt->second.durationSumMicros;
agg.callDurationSumSquaredMicros +=
innerIt->second.callDurationSumSquaredMicros;
if (innerIt->second.callsWithLatency >= kLatencyCountFirstWatermark) {
agg.secondsWithAtLeast10Calls++;
if (innerIt->second.callsWithLatency >= kLatencyCountSecondWatermark) {
agg.secondsWithAtLeast50Calls++;
}
}
}
if (innerIt->second.cpuTimeCount > 0) {
agg.cpuTimeCount += innerIt->second.cpuTimeCount;
agg.cpuTimeSumMicros += innerIt->second.cpuTimeSumMicros;
agg.cpuTimeSumSquaredMicros += innerIt->second.cpuTimeSumSquaredMicros;
}
// Erase the datum from the buffer so we don't aggregate it again
innerIt = outerIt->second.erase(innerIt);
} else {
++innerIt;
}
}
if (mCallStats.size() >= kMaxStatsCount) {
service->reportCallStats(mCallStats);
mCallStats.clear();
}
appendCallStatsToReportLocked(outerIt->first, agg);
agg.reset();
if (mSpamStats.size() >= kMaxStatsCount) {
service->reportSpamStats(mSpamStats);
mSpamStats.clear();
}
if (secondsWithAtLeast125Calls > 0) {
auto datum = outerIt->first;
mSpamStats.emplace_back(os::binder::BinderSpamStats());
auto& spamStats = mSpamStats.back();
spamStats.clientUid = static_cast<int32_t>(datum.senderUid);
spamStats.interfaceDescriptor = datum.interfaceDescriptor;
spamStats.aidlMethod = datum.aidlMethodName;
spamStats.secondsWithAtLeast125Calls = secondsWithAtLeast125Calls;
spamStats.secondsWithAtLeast250Calls = secondsWithAtLeast250Calls;
spamStats.peakCallCountPerSecond = peakCallCountPerSecond;
}
if (outerIt->second.empty()) {
outerIt = callsBuffer.erase(outerIt);
} else {
++outerIt;
}
}
if (!mCallStats.empty()) {
service->reportCallStats(mCallStats);
mCallStats.clear();
}
if (!mSpamStats.empty()) {
service->reportSpamStats(mSpamStats);
mSpamStats.clear();
}
}
void BinderStatsPusher::pushLocked(const int64_t nowSec) {
aggregateStatsLocked(nowSec);
}
#endif // defined(LIBBINDER_BINDER_OBSERVER_V2)
BinderStatsPusher::AddCallDataFunctor BinderStatsPusher::getAddCallDataToBufferLockedFunction() {
return AddCallDataFunctor(this);
}
} // namespace android