blob: 39524cc0adcca9271f31851271e9292a8e5c5dd4 [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.
*/
#include <android/os/binder/BinderCallsStats.h>
#include <android/os/binder/BinderSpamStats.h>
#include <android/os/binder/BnBinderStatsConsumerService.h>
#include <dlfcn.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/SystemClock.h>
#include <../BuildFlags.h>
#include <../JvmUtils.h>
#include <../observer/BinderStatsPusher.h>
#include <../observer/BinderStatsUtils.h>
#include <android_os_binder_flags.h>
#include <jni.h>
#include "fakeservicemanager/FakeServiceManager.h"
using android::FakeServiceManager;
using namespace android;
using namespace testing;
using os::binder::BinderCallsStats;
using os::binder::BinderSpamStats;
constexpr bool kBinderStatsLatencyHistogram = android::os::binder::flags::binder_stats_v3();
#ifdef LIBBINDER_BINDER_OBSERVER_V2
constexpr int64_t kSpamAggregationWindowSec = 1;
constexpr int64_t kLatencyAggregationWindowSec = 1; // Same as spam for now
#else
constexpr int64_t kSpamAggregationWindowSec = 5;
constexpr int64_t kLatencyAggregationWindowSec = 5; // Same as spam for now
#endif
// --- Mocks ---
/**
* Mock for IBinderStatsConsumerService to intercept calls for reporting stats.
*/
class MockBinderStatsConsumerService : public os::binder::BnBinderStatsConsumerService {
public:
MOCK_METHOD(binder::Status, reportSpamStats, (const std::vector<BinderSpamStats>&), (override));
MOCK_METHOD(binder::Status, reportCallStats, (const std::vector<BinderCallsStats>&),
(override));
MOCK_METHOD(binder::Status, reportSecondGranularityStats,
(const std::vector<os::binder::SingleSecondBinderStats>&), (override));
MOCK_METHOD(BBinder*, localBinder, (), (override));
};
/**
* Mock for IServiceManager to control service lookup.
*/
class MockServiceManager : public FakeServiceManager {
public:
MOCK_METHOD(sp<IBinder>, checkService, (const String16& name), (const, override));
};
// --- Test Fixture ---
/**
* Initializes the default service manager with a mock implementation once per test run.
*/
void initServiceManagerOnce() {
static std::once_flag gSmOnce;
std::call_once(gSmOnce, [] {
sp<NiceMock<MockServiceManager>> mockServiceManager =
sp<NiceMock<MockServiceManager>>::make();
setDefaultServiceManager(mockServiceManager);
});
}
/**
* Struct for expected BinderSpamStats to support designated initializers.
*/
struct ExpectedSpamStats {
uint32_t clientUid = 0;
String16 interfaceDescriptor;
String16 aidlMethod;
int32_t secondsWithAtLeast125Calls = 0;
int32_t secondsWithAtLeast250Calls = 0;
int32_t peakCallCountPerSecond = 0;
};
/**
* Helper function to create a BinderSpamStats for spam comparison.
*/
BinderSpamStats createExpectedSpamStats(const ExpectedSpamStats& p) {
auto stats = BinderSpamStats();
stats.clientUid = static_cast<int32_t>(p.clientUid);
stats.interfaceDescriptor = p.interfaceDescriptor;
stats.aidlMethod = p.aidlMethod;
stats.secondsWithAtLeast125Calls = p.secondsWithAtLeast125Calls;
stats.secondsWithAtLeast250Calls = p.secondsWithAtLeast250Calls;
stats.peakCallCountPerSecond = p.peakCallCountPerSecond;
return stats;
}
/**
* Struct for expected BinderCallsStats to support designated initializers.
*/
struct ExpectedCallStats {
uint32_t clientUid = 0;
String16 interfaceDescriptor;
String16 aidlMethod;
int64_t callCount = 0;
int64_t durationSumMicros = 0;
int64_t callDurationSumSquaredMicros = 0;
int32_t secondsWithAtLeast10Calls = 0;
int32_t secondsWithAtLeast50Calls = 0;
int64_t cpuTimeCount = 0;
int64_t cpuTimeSumMicros = 0;
int64_t cpuTimeSumSquaredMicros = 0;
};
/**
* Helper function to create a BinderCallsStats for latency comparison.
*/
BinderCallsStats createExpectedCallStats(const ExpectedCallStats& p) {
auto stats = BinderCallsStats();
stats.clientUid = static_cast<int32_t>(p.clientUid);
stats.interfaceDescriptor = p.interfaceDescriptor;
stats.aidlMethod = p.aidlMethod;
stats.callCount = p.callCount;
stats.durationSumMicros = p.durationSumMicros;
stats.callDurationSumSquaredMicros = p.callDurationSumSquaredMicros;
stats.secondsWithAtLeast10Calls = p.secondsWithAtLeast10Calls;
stats.secondsWithAtLeast50Calls = p.secondsWithAtLeast50Calls;
stats.cpuTimeCount = p.cpuTimeCount;
stats.cpuTimeSumMicros = p.cpuTimeSumMicros;
stats.cpuTimeSumSquaredMicros = p.cpuTimeSumSquaredMicros;
return stats;
}
struct ExpectedSingleSecondStats {
uint32_t clientUid;
String16 interfaceDescriptor;
String16 aidlMethod;
int64_t callCount = 0;
int64_t durationSumMicros = 0;
int64_t callDurationSumSquaredMicros = 0;
int64_t cpuTimeCount = 0;
int64_t cpuTimeSumMicros = 0;
int64_t cpuTimeSumSquaredMicros = 0;
std::vector<int64_t> rawLatencies = {};
};
os::binder::SingleSecondBinderStats createExpectedSingleSecondBinderStats(
const ExpectedSingleSecondStats& p) {
auto stats = os::binder::SingleSecondBinderStats();
stats.clientUid = static_cast<int32_t>(p.clientUid);
stats.interfaceDescriptor = p.interfaceDescriptor;
stats.aidlMethod = p.aidlMethod.empty() ? String16(u"") : p.aidlMethod;
stats.durationCount = p.callCount;
stats.callCount = p.callCount;
stats.durationMicrosSum = p.durationSumMicros;
stats.durationMicrosSquaredSum = p.callDurationSumSquaredMicros;
stats.cpuTimeCount = p.cpuTimeCount;
stats.cpuTimeMicrosSum = p.cpuTimeSumMicros;
stats.cpuTimeMicrosSquaredSum = p.cpuTimeSumSquaredMicros;
return stats;
}
// Helper to calculate the compact histogram data from raw latency values for testing
std::vector<uint8_t> calculateCompactHistogramData(const std::vector<int64_t>& latencyValues) {
std::vector<uint8_t> result;
if (!kBinderStatsLatencyHistogram) {
return result;
}
for (int64_t value : latencyValues) {
result.push_back(android::HistogramScale::getBinIndex(value));
}
std::sort(result.begin(), result.end());
return result;
}
os::binder::SingleSecondBinderStats createExpectedSingleSecondStatsWithHistogram(
const ExpectedSingleSecondStats& p) {
auto stats = createExpectedSingleSecondBinderStats(p);
stats.durationBinIndices = calculateCompactHistogramData(p.rawLatencies);
return stats;
}
MATCHER_P(SpamStatsEq, expectedStats, "") {
return arg.clientUid == expectedStats.clientUid &&
arg.interfaceDescriptor == expectedStats.interfaceDescriptor &&
arg.aidlMethod == expectedStats.aidlMethod &&
arg.secondsWithAtLeast125Calls == expectedStats.secondsWithAtLeast125Calls &&
arg.secondsWithAtLeast250Calls == expectedStats.secondsWithAtLeast250Calls &&
arg.peakCallCountPerSecond == expectedStats.peakCallCountPerSecond;
}
MATCHER_P(CallStatsEq, expectedStats, "") {
return arg.clientUid == expectedStats.clientUid &&
arg.interfaceDescriptor == expectedStats.interfaceDescriptor &&
arg.aidlMethod == expectedStats.aidlMethod &&
arg.callCount == expectedStats.callCount &&
arg.durationSumMicros == expectedStats.durationSumMicros &&
arg.secondsWithAtLeast10Calls == expectedStats.secondsWithAtLeast10Calls &&
arg.secondsWithAtLeast50Calls == expectedStats.secondsWithAtLeast50Calls;
}
MATCHER_P(CallStatsWithCpuEq, expectedStats, "") {
return arg.clientUid == expectedStats.clientUid &&
arg.interfaceDescriptor == expectedStats.interfaceDescriptor &&
arg.aidlMethod == expectedStats.aidlMethod &&
arg.callCount == expectedStats.callCount &&
arg.durationSumMicros == expectedStats.durationSumMicros &&
arg.secondsWithAtLeast10Calls == expectedStats.secondsWithAtLeast10Calls &&
arg.secondsWithAtLeast50Calls == expectedStats.secondsWithAtLeast50Calls &&
arg.cpuTimeCount == expectedStats.cpuTimeCount &&
arg.cpuTimeSumMicros == expectedStats.cpuTimeSumMicros &&
arg.cpuTimeSumSquaredMicros == expectedStats.cpuTimeSumSquaredMicros;
}
MATCHER_P(SingleSecondBinderStatsEq, expectedStats, "") {
auto actualHistogram = arg.durationBinIndices;
auto expectedHistogram = expectedStats.durationBinIndices;
std::sort(actualHistogram.begin(), actualHistogram.end());
std::sort(expectedHistogram.begin(), expectedHistogram.end());
return arg.clientUid == expectedStats.clientUid &&
arg.interfaceDescriptor == expectedStats.interfaceDescriptor &&
arg.aidlMethod == expectedStats.aidlMethod &&
arg.durationCount == expectedStats.durationCount &&
arg.callCount == expectedStats.callCount &&
arg.durationMicrosSum == expectedStats.durationMicrosSum &&
arg.durationMicrosSquaredSum == expectedStats.durationMicrosSquaredSum &&
arg.cpuTimeCount == expectedStats.cpuTimeCount &&
arg.cpuTimeMicrosSum == expectedStats.cpuTimeMicrosSum &&
arg.cpuTimeMicrosSquaredSum == expectedStats.cpuTimeMicrosSquaredSum &&
actualHistogram == expectedHistogram;
}
class BinderStatsPusherHelper {
public:
static void doPush(BinderStatsPusher& pusher, std::vector<BinderCallData> data,
const int64_t nowSec) {
auto addCallDataFn = pusher.getAddCallDataToBufferLockedFunction();
for (auto& datum : data) {
addCallDataFn(std::move(datum));
}
#if defined(LIBBINDER_BINDER_OBSERVER_V2)
// For V2, aggregation is triggered by the data stream.
// To trigger processing of the pushed data, we need to add data points
// for subsequent seconds. We add two points to be sure to flush the
// previous second buffer.
int64_t maxEndTimeSec = 0;
if (!data.empty()) {
for (const auto& d : data) {
if (d.endTimeNanos > 0) {
maxEndTimeSec =
std::max((long long)maxEndTimeSec, d.endTimeNanos / 1000'000'000LL);
}
}
}
if (maxEndTimeSec == 0) {
// Fallback for tests that don't set endTimeNanos
maxEndTimeSec = nowSec + 1;
}
BinderCallData flush_datum1 = {.endTimeNanos = (maxEndTimeSec + 1) * 1000'000'000LL};
addCallDataFn(std::move(flush_datum1));
BinderCallData flush_datum2 = {.endTimeNanos = (maxEndTimeSec + 2) * 1000'000'000LL};
addCallDataFn(std::move(flush_datum2));
#else
pusher.pushLocked(nowSec);
#endif
}
};
class BinderStatsPusherTest : public Test {
protected:
sp<NiceMock<MockBinderStatsConsumerService>> mockStatsService;
sp<NiceMock<MockServiceManager>> mockServiceManager;
BinderStatsPusher pusher;
void SetUp() override {
mockStatsService = sp<NiceMock<MockBinderStatsConsumerService>>::make();
initServiceManagerOnce();
mockServiceManager = sp<NiceMock<MockServiceManager>>::cast(defaultServiceManager());
ASSERT_NE(mockServiceManager, nullptr)
<< "Default service manager is not the expected mock type";
// Default behavior: Service Manager returns the mock Stats Service
ON_CALL(*mockServiceManager.get(), checkService(String16("binder_stats_consumer")))
.WillByDefault(Return(IInterface::asBinder(mockStatsService)));
ON_CALL(*mockStatsService.get(), localBinder()).WillByDefault(Return(nullptr));
}
void TearDown() override { testing::Mock::VerifyAndClear(defaultServiceManager().get()); }
};
// --- Test Cases ---
/**
* Unit Test
*/
TEST_F(BinderStatsPusherTest, GetBinderStatsService) {
EXPECT_CALL(*mockServiceManager, checkService(String16("binder_stats_consumer")))
.Times(1)
.WillOnce(Return(IInterface::asBinder(mockStatsService)));
auto service = pusher.getBinderStatsServiceLocked(15);
ASSERT_EQ(service, mockStatsService);
}
TEST_F(BinderStatsPusherTest, AggregateSpamNoSpamBelowThreshold) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Spam stats are not supported with V2 observer";
}
std::vector<BinderCallData> data;
int64_t currentTimeSec = 14;
// Create data within the delay window (kDelaySeconds = 2)
for (int i = 0; i < 50; ++i) { // Less than kMinSpamCount (125)
data.push_back(BinderCallData{
.startTimeNanos = (currentTimeSec - 5) * 1000'000'000LL,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IFoo"),
.aidlMethodName = String16("MethodName1"),
.transactionCode = 1,
.senderUid = 1001,
});
}
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateSpamOneSecondSpam) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Spam stats are not supported with V2 observer";
}
std::vector<BinderCallData> data;
int64_t currentTimeNanos = 9'100'000'000;
int32_t totalCalls = 150;
// Create enough data in the *same second* to trigger spam, far enough in the past
for (int i = 0; i < totalCalls; ++i) { // More than kMinSpamCount (125)
data.push_back(BinderCallData{
.startTimeNanos = currentTimeNanos - 8000'000'000,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IFoo"),
.aidlMethodName = String16("MethodName2"),
.transactionCode = 1,
.senderUid = 1001,
});
}
auto expectedStats =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls});
EXPECT_CALL(*mockStatsService, reportSpamStats(ElementsAre(SpamStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeNanos / 1000'000'000);
}
TEST_F(BinderStatsPusherTest, AggregateSpamDelayedSpam) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Spam stats are not supported with V2 observer";
}
std::vector<BinderCallData> data;
int64_t currentTimeNanos = 9'100'000'000;
// Create spam data within the delay window (kDelaySeconds = 2)
for (int i = 0; i < 150; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = currentTimeNanos - 1000'000'000,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IBar"),
.aidlMethodName = String16("MethodName2"),
.transactionCode = 2,
.senderUid = 1002,
});
}
// Expect no calls, data is delayed
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeNanos / 1000'000'000);
}
TEST_F(BinderStatsPusherTest, AggregateSpamMixedOlderAndDelayed) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Binder Observer V2 is enabled";
}
std::vector<BinderCallData> data;
int64_t currentTimeNanos = 9'100'000'000;
int32_t totalCalls = 130;
for (int i = 0; i < totalCalls; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = currentTimeNanos - 8000'000'000,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IBaz"),
.aidlMethodName = String16("MethodName3"),
.transactionCode = 3,
.senderUid = 1003,
});
}
// Delayed spam data (within kDelaySeconds)
for (int i = 0; i < 140; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = currentTimeNanos - 1000'000'000,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IQux"),
.aidlMethodName = String16("MethodName4"),
.transactionCode = 4,
.senderUid = 1004,
});
}
// Expect immediate spam to be reported now
auto expectedImmediateStats =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls});
EXPECT_CALL(*mockStatsService,
reportSpamStats(ElementsAre(SpamStatsEq(expectedImmediateStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeNanos / 1000'000'000);
}
TEST_F(BinderStatsPusherTest, AggregateSpamSecondWatermark) {
std::vector<BinderCallData> data;
int64_t spamTimeNanos = 2'000'000'000LL;
int64_t currentTimeSec = (spamTimeNanos / 1000'000'000LL) + kSpamAggregationWindowSec + 1;
int32_t totalCalls = 300;
// Create data exceeding the second watermark (250 calls/sec)
std::vector<int64_t> rawLatencies;
for (int i = 0; i < totalCalls; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = spamTimeNanos,
.endTimeNanos = spamTimeNanos + 1,
.interfaceDescriptor = String16("IHighVolume"),
.aidlMethodName = String16("MethodName5"),
.transactionCode = 5,
.senderUid = 1005,
});
rawLatencies.push_back(1);
}
if (kBinderObserverV2Enabled) {
auto expectedStats = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls,
.rawLatencies = rawLatencies});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
} else {
auto expectedStats =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.secondsWithAtLeast250Calls = 1,
.peakCallCountPerSecond = totalCalls});
EXPECT_CALL(*mockStatsService, reportSpamStats(ElementsAre(SpamStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
}
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateSpamAcrossMultipleSeconds) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Spam stats across multiple seconds aren't supported with V2 observer";
}
std::vector<BinderCallData> data;
int64_t firstSpamSecondNanos = 2'000'000'000LL; // 2s
int64_t secondSpamSecondNanos = 3'000'000'000LL; // 3s
int64_t currentTimeSec = (secondSpamSecondNanos / 1000'000'000LL) + kSpamAggregationWindowSec +
1; // 3 + 5 + 1 = 9s
// Spam for the first second
int32_t totalCalls1 = 150;
for (int i = 0; i < totalCalls1; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = firstSpamSecondNanos,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IMultiSecond"),
.aidlMethodName = String16("MethodName6"),
.transactionCode = 6,
.senderUid = 1006,
});
}
// Spam for the second second
int32_t totalCalls2 = 160;
for (int i = 0; i < totalCalls2; ++i) {
// Use the same UID, code, desc for aggregation
data.push_back(BinderCallData{
.startTimeNanos = secondSpamSecondNanos,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IMultiSecond"),
.aidlMethodName = String16("MethodName6"),
.transactionCode = 6,
.senderUid = 1006,
});
}
if (kBinderObserverV2Enabled) {
auto expectedStats1 =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls1});
auto expectedStats2 =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls2});
EXPECT_CALL(*mockStatsService,
reportSpamStats(UnorderedElementsAre(SpamStatsEq(expectedStats1),
SpamStatsEq(expectedStats2))))
.Times(1);
} else {
// Expect one atom representing spam across 2 seconds
auto expectedStats = createExpectedSpamStats(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 2,
.peakCallCountPerSecond = std::max(totalCalls1, totalCalls2)});
EXPECT_CALL(*mockStatsService, reportSpamStats(ElementsAre(SpamStatsEq(expectedStats))))
.Times(1);
}
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateSpamProcessesDelayedDataOnSubsequentCall) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Binder Observer V2 is enabled";
}
std::vector<BinderCallData> callData1;
int64_t callTimeSec1 = 10;
int64_t spamDataTimeNanos = callTimeSec1 * 1000'000'000LL;
int32_t totalCalls = 150;
for (int i = 0; i < totalCalls; ++i) {
callData1.push_back(BinderCallData{
.startTimeNanos = spamDataTimeNanos,
.endTimeNanos = 0,
.interfaceDescriptor = String16("IDelayed"),
.aidlMethodName = String16("MethodName7"),
.transactionCode = 7,
.senderUid = 1007,
});
}
auto expectedStats =
createExpectedSpamStats({.clientUid = callData1[0].senderUid,
.interfaceDescriptor = callData1[0].interfaceDescriptor,
.aidlMethod = callData1[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls});
// First push: data should be buffered as it's too recent
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1); // For the first push
BinderStatsPusherHelper::doPush(pusher, callData1, callTimeSec1);
// Second push: advance time so the previous data is now outside the aggregation window
std::vector<BinderCallData> callData2; // Can be empty or contain new data
int64_t call2_time_sec = callTimeSec1 + kSpamAggregationWindowSec + 1; // 10 + 5 + 1 = 16s
EXPECT_CALL(*mockStatsService, reportSpamStats(ElementsAre(SpamStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1); // For the second push
BinderStatsPusherHelper::doPush(pusher, callData2, call2_time_sec);
}
TEST_F(BinderStatsPusherTest, AggregateSpamForDifferentMethodsSimultaneously) {
std::vector<BinderCallData> data;
int64_t spamTimeNanos = 4'000'000'000LL; // 4s
int64_t currentTimeSec =
(spamTimeNanos / 1000'000'000LL) + kSpamAggregationWindowSec + 1; // 4 + 5 + 1 = 10s
// Spam for method 1
int32_t totalCalls1 = 200;
std::vector<int64_t> rawLatencies1;
int64_t totalDurationMicros1 = 0;
int64_t durationSumSquaredMicros1 = 0;
for (int i = 0; i < totalCalls1; ++i) {
int64_t durNanos = i * 1000;
int64_t durMicros = durNanos / 1000;
data.push_back({.startTimeNanos = spamTimeNanos,
.endTimeNanos = spamTimeNanos + durNanos,
.interfaceDescriptor = String16("IMultiMethod"),
.aidlMethodName = String16("MethodName9"),
.transactionCode = 9,
.senderUid = 1008});
rawLatencies1.push_back(durNanos);
totalDurationMicros1 += durMicros;
durationSumSquaredMicros1 += durMicros * durMicros;
}
// Spam for method 2
int32_t totalCalls2 = 220;
std::vector<int64_t> rawLatencies2;
int64_t totalDurationMicros2 = 0;
int64_t durationSumSquaredMicros2 = 0;
for (int i = 0; i < totalCalls2; ++i) {
int64_t durNanos = i * 1000 + 3 * i;
int64_t durMicros = durNanos / 1000;
data.push_back({.startTimeNanos = spamTimeNanos,
.endTimeNanos = spamTimeNanos + durNanos,
.interfaceDescriptor = String16("IMultiMethod"),
.aidlMethodName = String16("MethodName8"),
.transactionCode = 8,
.senderUid = 1008});
rawLatencies2.push_back(durNanos);
totalDurationMicros2 += durMicros;
durationSumSquaredMicros2 += durMicros * durMicros;
}
if (kBinderObserverV2Enabled) {
auto expectedStats1 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = 1008,
.interfaceDescriptor = String16("IMultiMethod"),
.aidlMethod = String16("MethodName9"),
.callCount = totalCalls1,
.durationSumMicros = totalDurationMicros1,
.callDurationSumSquaredMicros = durationSumSquaredMicros1,
.rawLatencies = rawLatencies1});
auto expectedStats2 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = 1008,
.interfaceDescriptor = String16("IMultiMethod"),
.aidlMethod = String16("MethodName8"),
.callCount = totalCalls2,
.durationSumMicros = totalDurationMicros2,
.callDurationSumSquaredMicros = durationSumSquaredMicros2,
.rawLatencies = rawLatencies2});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats1),
SingleSecondBinderStatsEq(expectedStats2))))
.Times(1);
} else {
auto expectedStats1 =
createExpectedSpamStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls1});
auto expectedStats2 = createExpectedSpamStats(
{.clientUid = data[totalCalls1].senderUid,
.interfaceDescriptor = data[totalCalls1].interfaceDescriptor,
.aidlMethod = data[totalCalls1].aidlMethodName,
.secondsWithAtLeast125Calls = 1,
.peakCallCountPerSecond = totalCalls2});
EXPECT_CALL(*mockStatsService,
reportSpamStats(UnorderedElementsAre(SpamStatsEq(expectedStats1),
SpamStatsEq(expectedStats2))))
.Times(1);
}
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, SkipPushForLocalBinderWithoutJvm) {
// Simulate a local binder service
sp<BBinder> localBinderInstance = sp<BBinder>::make();
ON_CALL(*mockStatsService.get(), localBinder())
.WillByDefault(Return(localBinderInstance.get()));
// getJavaVM() is expected to return nullptr in the test environment (JvmUtils.h)
std::vector<BinderCallData> data;
int64_t spamTimeNanos = 2'000'000'000LL; // 2s
int64_t currentTimeSec = (spamTimeNanos / 1000'000'000LL) + kSpamAggregationWindowSec + 1; // 8s
for (int i = 0; i < 150; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = spamTimeNanos,
.endTimeNanos = 0,
.interfaceDescriptor = String16("ILocalSkipped"),
.aidlMethodName = String16("MethodName10"),
.transactionCode = 10,
.senderUid = 1009,
});
}
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, DataNotDroppedWhenPushIsSkippedThenSucceeds) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Not supported for Binder Observer V2";
}
std::vector<BinderCallData> latencyCallData1;
int64_t timeSec1 = 20;
// Data old enough to be processed immediately
int64_t latencyDataTimeNanos = (timeSec1 - kLatencyAggregationWindowSec) * 1000'000'000LL;
int64_t totalDurationMicros = 0;
int64_t durationSumSquaredMicros = 0;
int32_t totalCalls = 15; // More than kLatencyCountFirstWatermark
for (int i = 0; i < totalCalls; ++i) {
int64_t durationMicros = (i + 1) * 100;
latencyCallData1.push_back(BinderCallData{
.startTimeNanos = latencyDataTimeNanos,
.endTimeNanos = latencyDataTimeNanos + durationMicros * 1000,
.interfaceDescriptor = String16("IServiceSkipped"),
.aidlMethodName = String16("MethodName11"),
.transactionCode = 11,
.senderUid = 1010,
});
totalDurationMicros += durationMicros;
durationSumSquaredMicros += durationMicros * durationMicros;
}
auto expectedStats =
createExpectedCallStats({.clientUid = latencyCallData1[0].senderUid,
.interfaceDescriptor = latencyCallData1[0].interfaceDescriptor,
.aidlMethod = latencyCallData1[0].aidlMethodName,
.callCount = totalCalls,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros,
.secondsWithAtLeast10Calls = 1});
// First push: Service is unavailable
EXPECT_CALL(*mockServiceManager, checkService(String16("binder_stats_consumer")))
.WillOnce(Return(nullptr));
// localBinder() shouldn't be called if service is null
EXPECT_CALL(*mockStatsService, localBinder()).Times(0);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportSecondGranularityStats(_)).Times(0);
BinderStatsPusherHelper::doPush(pusher, latencyCallData1, timeSec1);
Mock::VerifyAndClearExpectations(mockServiceManager.get());
Mock::VerifyAndClearExpectations(mockStatsService.get());
// Second push: Service becomes available. Advance time beyond service check timeout.
if (kBinderObserverV2Enabled) {
int64_t timeSec2 = timeSec1 + 1;
auto expectedV2Stats = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = latencyCallData1[0].senderUid,
.interfaceDescriptor = latencyCallData1[0].interfaceDescriptor,
.aidlMethod = String16(""),
.callCount = 15,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros});
EXPECT_CALL(*mockServiceManager, checkService(String16("binder_stats_consumer")))
.WillOnce(Return(IInterface::asBinder(mockStatsService)));
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedV2Stats))))
.Times(1);
BinderStatsPusherHelper::doPush(pusher, {}, timeSec2);
} else {
int64_t timeSec2 = timeSec1 + 6;
EXPECT_CALL(*mockServiceManager, checkService(String16("binder_stats_consumer")))
.WillOnce(Return(IInterface::asBinder(mockStatsService)));
EXPECT_CALL(*mockStatsService, localBinder()).Times(1); // Called when service is not null
EXPECT_CALL(*mockStatsService,
reportCallStats(ElementsAre(CallStatsWithCpuEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
pusher.pushLocked(timeSec2);
}
}
TEST_F(BinderStatsPusherTest, sizeOfStruct) {
#ifdef __LP64__
EXPECT_EQ(sizeof(android::BinderCallData), 48);
#else
EXPECT_EQ(sizeof(android::BinderCallData), 36);
#endif
}
// --- Latency Tests ---
TEST_F(BinderStatsPusherTest, AggregateLatencyNoLatencyBelowThreshold) {
std::vector<BinderCallData> data;
int64_t currentTimeSec = 14;
// Create data within the delay window
for (int i = 0; i < 5; ++i) { // Less than kLatencyCountFirstWatermark (10)
data.push_back(BinderCallData{
.startTimeNanos =
(currentTimeSec - kLatencyAggregationWindowSec + 1) * 1000'000'000LL,
.endTimeNanos =
(currentTimeSec - kLatencyAggregationWindowSec + 1) * 1000'000'000LL +
1000000 /* 1ms */,
.interfaceDescriptor = String16("ILatencyFoo"),
.aidlMethodName = String16("MethodName1"),
.transactionCode = 1,
.senderUid = 2001,
});
}
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyOneSecondLatency) {
std::vector<BinderCallData> data;
int64_t callTimeNanos = 2'000'000'000LL; // 2s
int64_t currentTimeSec =
(callTimeNanos / 1000'000'000LL) + kLatencyAggregationWindowSec + 1; // 2 + 5 + 1 = 8s
int64_t totalDurationMicros = 0;
int64_t durationSumSquaredMicros = 0;
// Create enough data in the *same second* to trigger latency reporting
for (int i = 0; i < 15; ++i) { // More than kLatencyCountFirstWatermark (10)
int64_t durationMicros = (i + 1) * 100; // e.g. 100us, 200us ..
data.push_back(BinderCallData{
.startTimeNanos = callTimeNanos,
.endTimeNanos = callTimeNanos + durationMicros * 1000,
.interfaceDescriptor = String16("ILatencyBar"),
.aidlMethodName = String16("MethodName2"),
.transactionCode = 2,
.senderUid = 2002,
});
totalDurationMicros += durationMicros;
durationSumSquaredMicros += durationMicros * durationMicros;
}
if (kBinderObserverV2Enabled) {
std::vector<int64_t> rawLatencies;
for (int i = 0; i < 15; ++i) {
rawLatencies.push_back((i + 1) * 100 * 1000); // Nanos
}
auto expectedStats = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = 15,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros,
.rawLatencies = rawLatencies});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats))))
.Times(1);
} else {
auto expectedStats =
createExpectedCallStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = 15,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros,
.secondsWithAtLeast10Calls = 1});
EXPECT_CALL(*mockStatsService,
reportCallStats(ElementsAre(CallStatsWithCpuEq(expectedStats))))
.Times(1);
}
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyDelayedLatency) {
std::vector<BinderCallData> data;
int64_t currentTimeNanos = 9'100'000'000;
// Create latency data within the aggregation window
for (int i = 0; i < 15; ++i) {
data.push_back(BinderCallData{
.startTimeNanos = currentTimeNanos - 1000'000'000,
.endTimeNanos = currentTimeNanos - 1000'000'000 + 500000 /* 0.5ms */,
.interfaceDescriptor = String16("ILatencyBaz"),
.aidlMethodName = String16("MethodName3"),
.transactionCode = 3,
.senderUid = 2003,
});
}
// Expect no calls, data is delayed
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeNanos / 1000'000'000);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyProcessesDelayedDataOnSubsequentCall) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "Binder Observer V2 is enabled";
}
std::vector<BinderCallData> callData1;
int64_t callTimeSec1 = 10;
int64_t latencyDataTimeNanos = callTimeSec1 * 1000'000'000LL; // 10s, will be delayed
int64_t totalDurationMicros1 = 0;
int64_t durationSumSquaredMicros1 = 0;
for (int i = 0; i < 15; ++i) {
int64_t durationMicros = (i + 1) * 150;
callData1.push_back(BinderCallData{
.startTimeNanos = latencyDataTimeNanos,
.endTimeNanos = latencyDataTimeNanos + durationMicros * 1000,
.interfaceDescriptor = String16("ILatencyDelayed"),
.aidlMethodName = String16("MethodName4"),
.transactionCode = 4,
.senderUid = 2004,
});
totalDurationMicros1 += durationMicros;
durationSumSquaredMicros1 += durationMicros * durationMicros;
}
// First push: data should be buffered as it's too recent
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, callData1, callTimeSec1);
// Second push: advance time so the previous data is now outside the aggregation window
std::vector<BinderCallData> callData2; // Can be empty
int64_t call2_time_sec = callTimeSec1 + kLatencyAggregationWindowSec + 1; // 10 + 5 + 1 = 16s
auto expectedStats =
createExpectedCallStats({.clientUid = callData1[0].senderUid,
.interfaceDescriptor = callData1[0].interfaceDescriptor,
.aidlMethod = callData1[0].aidlMethodName,
.callCount = 15,
.durationSumMicros = totalDurationMicros1,
.callDurationSumSquaredMicros = durationSumSquaredMicros1,
.secondsWithAtLeast10Calls = 1});
EXPECT_CALL(*mockStatsService, reportCallStats(ElementsAre(CallStatsWithCpuEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, callData2, call2_time_sec);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyWithCpu) {
std::vector<BinderCallData> data;
int64_t callTimeNanos = 2'000'000'000LL; // 2s
int64_t currentTimeSec =
(callTimeNanos / 1000'000'000LL) + kLatencyAggregationWindowSec + 1; // 2 + 5 + 1 = 8s
int64_t totalDurationMicros = 0;
int64_t durationSumSquaredMicros = 0;
int64_t totalCpuTimeMicros = 0;
int64_t totalcpuTimeSumSquaredMicros = 0;
// Create enough data in the *same second* to trigger latency reporting
for (int i = 0; i < 15; ++i) { // More than kLatencyCountFirstWatermark (10)
int64_t durationMicros = (i + 1) * 100; // e.g. 100us, 200us ..
int64_t cpuTimeMicros = (i + 1) * 50;
data.push_back(BinderCallData{
.startTimeNanos = callTimeNanos,
.endTimeNanos = callTimeNanos + durationMicros * 1000,
.cpuTimeNanos = cpuTimeMicros * 1000,
.interfaceDescriptor = String16("ILatencyCpu"),
.aidlMethodName = String16("MethodName2"),
.transactionCode = 2,
.senderUid = 2002,
});
totalDurationMicros += durationMicros;
durationSumSquaredMicros += durationMicros * durationMicros;
totalCpuTimeMicros += cpuTimeMicros;
totalcpuTimeSumSquaredMicros += cpuTimeMicros * cpuTimeMicros;
}
if (kBinderObserverV2Enabled) {
std::vector<int64_t> rawLatencies;
for (int i = 0; i < 15; ++i) {
rawLatencies.push_back((i + 1) * 100 * 1000); // Nanos
}
auto expectedStats = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = 15,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros,
.cpuTimeCount = 15,
.cpuTimeSumMicros = totalCpuTimeMicros,
.cpuTimeSumSquaredMicros = totalcpuTimeSumSquaredMicros,
.rawLatencies = rawLatencies});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats))))
.Times(1);
} else {
auto expectedStats =
createExpectedCallStats({.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = 15,
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = durationSumSquaredMicros,
.secondsWithAtLeast10Calls = 1,
.cpuTimeCount = 15,
.cpuTimeSumMicros = totalCpuTimeMicros,
.cpuTimeSumSquaredMicros = totalcpuTimeSumSquaredMicros});
EXPECT_CALL(*mockStatsService,
reportCallStats(ElementsAre(CallStatsWithCpuEq(expectedStats))))
.Times(1);
}
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyMultipleSeconds) {
if (kBinderObserverV2Enabled) {
GTEST_SKIP() << "This test is for Binder Observer V1 only.";
}
std::vector<BinderCallData> data;
int64_t firstCallSecondNanos = 2'000'000'000LL; // 2s
int64_t secondCallSecondNanos = 3'000'000'000LL; // 3s
int64_t currentTimeSec = (secondCallSecondNanos / 1000'000'000LL) +
kLatencyAggregationWindowSec + 1; // 3 + 5 + 1 = 9s
int64_t totalDurationMicros1 = 0;
int64_t durationSumSquaredMicros1 = 0;
const int totalCalls1 = 12;
// Data for the first second
for (int i = 0; i < totalCalls1; ++i) { // 12 calls
int64_t durationMicros = (i + 1) * 100;
data.push_back(BinderCallData{
.startTimeNanos = firstCallSecondNanos,
.endTimeNanos = firstCallSecondNanos + durationMicros * 1000,
.interfaceDescriptor = String16("ILatencyMultiSec"),
.aidlMethodName = String16("MethodName5"),
.transactionCode = 5,
.senderUid = 2005,
});
totalDurationMicros1 += durationMicros;
durationSumSquaredMicros1 += durationMicros * durationMicros;
}
int64_t totalDurationMicros2 = 0;
int64_t durationSumSquaredMicros2 = 0;
const int totalCalls2 = 8;
// Data for the second second
for (int i = 0; i < totalCalls2; ++i) { // 8 calls
int64_t durationMicros = (i + 1) * 120;
// Use the same UID, code, desc for aggregation
data.push_back(BinderCallData{
.startTimeNanos = secondCallSecondNanos,
.endTimeNanos = secondCallSecondNanos + durationMicros * 1000,
.interfaceDescriptor = String16("ILatencyMultiSec"),
.aidlMethodName = String16("MethodName5"),
.transactionCode = 5,
.senderUid = 2005,
});
totalDurationMicros2 += durationMicros;
durationSumSquaredMicros2 += durationMicros * durationMicros;
}
if (kBinderObserverV2Enabled) {
auto expectedStats1 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls1,
.durationSumMicros = totalDurationMicros1,
.callDurationSumSquaredMicros = durationSumSquaredMicros1});
auto expectedStats2 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls2,
.durationSumMicros = totalDurationMicros2,
.callDurationSumSquaredMicros = durationSumSquaredMicros2});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats1),
SingleSecondBinderStatsEq(expectedStats2))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(2);
} else {
// Expect one atom representing aggregated data across 2 seconds
// Total calls = 12 + 8 = 20
// secondsWithAtLeast10Calls = 1 (only the first second has >= 10 calls)
// secondsWithAtLeast50Calls = 0
auto expectedStats = createExpectedCallStats(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls1 + totalCalls2,
.durationSumMicros = totalDurationMicros1 + totalDurationMicros2,
.callDurationSumSquaredMicros =
durationSumSquaredMicros1 + durationSumSquaredMicros2,
.secondsWithAtLeast10Calls = 1});
EXPECT_CALL(*mockStatsService, reportCallStats(ElementsAre(CallStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
}
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyMultipleSecondsV2) {
if (!kBinderObserverV2Enabled) {
GTEST_SKIP() << "This test is for Binder Observer V2 only.";
}
std::vector<BinderCallData> data;
int64_t firstCallSecondNanos = 2'100'000'000LL; // 2.1s
int64_t secondCallSecondNanos = 3'100'000'000LL; // 3.1s
int64_t totalDurationMicros1 = 0;
int64_t durationSumSquaredMicros1 = 0;
const int totalCalls1 = 12;
// Data for the first second
std::vector<int64_t> rawLatencies1;
for (int i = 0; i < totalCalls1; ++i) { // 12 calls
int64_t durationNanos = (i + 1) * 100'000;
data.push_back(BinderCallData{
.startTimeNanos = firstCallSecondNanos,
.endTimeNanos = firstCallSecondNanos + durationNanos,
.interfaceDescriptor = String16("ILatencyMultiSec"),
.aidlMethodName = String16("MethodName5"),
.transactionCode = 5,
.senderUid = 2005,
});
int64_t durationMicros = durationNanos / 1000;
totalDurationMicros1 += durationMicros;
durationSumSquaredMicros1 += durationMicros * durationMicros;
rawLatencies1.push_back(durationNanos);
}
int64_t totalDurationMicros2 = 0;
int64_t durationSumSquaredMicros2 = 0;
const int totalCalls2 = 8;
// Data for the second second
std::vector<int64_t> rawLatencies2;
for (int i = 0; i < totalCalls2; ++i) { // 8 calls
int64_t durationNanos = (i + 1) * 120'000;
data.push_back(BinderCallData{
.startTimeNanos = secondCallSecondNanos,
.endTimeNanos = secondCallSecondNanos + durationNanos,
.interfaceDescriptor = String16("ILatencyMultiSec"),
.aidlMethodName = String16("MethodName5"),
.transactionCode = 5,
.senderUid = 2005,
});
int64_t durationMicros = durationNanos / 1000;
rawLatencies2.push_back(durationNanos);
totalDurationMicros2 += durationMicros;
durationSumSquaredMicros2 += durationMicros * durationMicros;
}
// Expect two separate reports for each second.
auto expectedStats1 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls1,
.durationSumMicros = totalDurationMicros1,
.callDurationSumSquaredMicros = durationSumSquaredMicros1,
.rawLatencies = rawLatencies1});
auto expectedStats2 = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = totalCalls2,
.durationSumMicros = totalDurationMicros2,
.callDurationSumSquaredMicros = durationSumSquaredMicros2,
.rawLatencies = rawLatencies2});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats1))));
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats2))));
EXPECT_CALL(*mockStatsService, reportCallStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(2);
BinderStatsPusherHelper::doPush(pusher, data, 0 /* nowSec not used */);
}
TEST_F(BinderStatsPusherTest, AggregateLatencyWithHistogramV2) {
std::vector<BinderCallData> data;
int64_t callTimeNanos = 2'000'000'000LL; // 2s
// Current time far enough in the future to trigger aggregation
int64_t currentTimeSec = (callTimeNanos / 1000'000'000LL) + kLatencyAggregationWindowSec + 1;
std::vector<int64_t> rawLatencies = {10'000, 20'000, 100'000, 1500'000,
2000'000, 2500'000, 5000'000}; // Latencies in nanos
int64_t totalDurationMicros = 0;
int64_t callDurationSumSquaredMicros = 0;
for (int64_t durationNanos : rawLatencies) {
int64_t durationMicros = durationNanos / 1000;
data.push_back(BinderCallData{
.startTimeNanos = callTimeNanos,
.endTimeNanos = callTimeNanos + durationMicros * 1000,
.interfaceDescriptor = String16("ILatencyHistogram"),
.aidlMethodName = String16("MethodNameHistogram"),
.transactionCode = 100,
.senderUid = 3000,
});
totalDurationMicros += durationMicros;
callDurationSumSquaredMicros += durationMicros * durationMicros;
}
// Add enough calls to trigger secondsWithAtLeast10Calls, but not 50
for (int i = 0; i < 5; ++i) { // Total 7 (from rawLatencies) + 5 = 12 calls
int64_t durationNanos = 500'000 + i * 1000;
data.push_back(BinderCallData{
.startTimeNanos = callTimeNanos,
.endTimeNanos = callTimeNanos + durationNanos,
.interfaceDescriptor = String16("ILatencyHistogram"),
.aidlMethodName = String16("MethodNameHistogram"),
.transactionCode = 100,
.senderUid = 3000,
});
int64_t durationMicros = durationNanos / 1000;
totalDurationMicros += durationMicros;
callDurationSumSquaredMicros += durationMicros * durationMicros;
rawLatencies.push_back(durationNanos);
}
auto expectedStats = createExpectedSingleSecondStatsWithHistogram(
{.clientUid = data[0].senderUid,
.interfaceDescriptor = data[0].interfaceDescriptor,
.aidlMethod = data[0].aidlMethodName,
.callCount = static_cast<int64_t>(data.size()),
.durationSumMicros = totalDurationMicros,
.callDurationSumSquaredMicros = callDurationSumSquaredMicros,
.rawLatencies = rawLatencies});
EXPECT_CALL(*mockStatsService,
reportSecondGranularityStats(
UnorderedElementsAre(SingleSecondBinderStatsEq(expectedStats))))
.Times(1);
EXPECT_CALL(*mockStatsService, reportSpamStats(_)).Times(0);
EXPECT_CALL(*mockStatsService, localBinder()).Times(1);
BinderStatsPusherHelper::doPush(pusher, data, currentTimeSec);
}