Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0bddb611 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add Binder Observer and use it to collect data" into main

parents 253826b0 9c4ce148
Loading
Loading
Loading
Loading
+22 −25
Original line number Diff line number Diff line
@@ -230,6 +230,17 @@ filegroup {
    ],
}

filegroup {
    name: "libbinder_observer_sources",
    srcs: [
        "BinderObserver.cpp",
        "BinderStatsPusher.cpp",
    ],
    visibility: [
        ":__subpackages__",
    ],
}

cc_defaults {
    name: "libbinder_common_defaults",
    host_supported: true,
@@ -277,7 +288,11 @@ cc_defaults {
        // the code knows to export them.
        "-fvisibility=hidden",
        "-DBUILDING_LIBBINDER",
    ],
    ] + select(release_flag("RELEASE_LIBBINDER_BINDER_OBSERVER"), {
        true: ["-DLIBBINDER_BINDER_OBSERVER"],
        false: ["-DNO_LIBBINDER_BINDER_OBSERVER"],
        default: ["-DNO_LIBBINDER_BINDER_OBSERVER"],
    }),
}

cc_defaults {
@@ -464,28 +479,6 @@ cc_library_shared {
    ],
}

soong_config_module_type {
    name: "release_libbinder_binder_observer_config",
    module_type: "cc_defaults",
    config_namespace: "libbinder",
    bool_variables: ["release_libbinder_binder_observer"],
    properties: [
        "cflags",
    ],
}

release_libbinder_binder_observer_config {
    name: "release_libbinder_enable_binder_observer_flag",
    soong_config_variables: {
        release_libbinder_binder_observer: {
            cflags: ["-DLIBBINDER_BINDER_OBSERVER"],
            conditions_default: {
                cflags: ["-DNO_LIBBINDER_BINDER_OBSERVER"],
            },
        },
    },
}

soong_config_module_type {
    name: "libbinder_remove_cache_static_list_config",
    module_type: "cc_defaults",
@@ -558,7 +551,6 @@ cc_defaults {
        "libbinder_client_cache_flag",
        "libbinder_addservice_cache_flag",
        "libbinder_remove_cache_static_list_flag",
        "release_libbinder_enable_binder_observer_flag",
    ],
    srcs: [
        "BufferedTextOutput.cpp",
@@ -572,6 +564,7 @@ cc_defaults {
        ":libbinder_accessor_aidl",
        ":libbinder_device_interface_sources",
        ":android-os-statsbootstrap-aidl",
        ":libbinder_observer_sources",
    ],
    target: {
        vendor: {
@@ -605,7 +598,11 @@ cc_defaults {
    },
    cflags: [
        "-DBINDER_WITH_KERNEL_IPC",
    ],
    ] + select(release_flag("RELEASE_LIBBINDER_BINDER_OBSERVER"), {
        true: ["-DLIBBINDER_BINDER_OBSERVER"],
        false: ["-DNO_LIBBINDER_BINDER_OBSERVER"],
        default: ["-DNO_LIBBINDER_BINDER_OBSERVER"],
    }),
}

cc_library {
+57 −0
Original line number Diff line number Diff line
/*
 * 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 "BinderObserver.h"
#include <mutex>

#include <binder/IServiceManager.h>
#include <utils/SystemClock.h>
#include "BinderStatsUtils.h"

namespace android {
constexpr int kSendIntervalSec = 5;
bool BinderObserver::isFlushRequired(int64_t nowSec) {
    int64_t previousFlushTimeSec = mLastFlushTimeSec.load();
    return nowSec - previousFlushTimeSec >= kSendIntervalSec;
}

void BinderObserver::addStatMaybeFlush(const std::shared_ptr<BinderStatsSpscQueue>& queue,
                                       const BinderCallData& stat) {
    // If write fails, then buffer is full.
    int64_t nowSec = stat.endTimeNanos / 1000'000'000;
    if (!queue->push(stat)) {
        flushStats(nowSec);
        // If write fails again, we drop the stat.
        // TODO(b/299356196): Track dropped stats separately.
        queue->push(stat);
        return;
    }
    if (isFlushRequired(nowSec)) {
        flushStats(nowSec);
    }
}

void BinderObserver::flushStats(int64_t nowSec) {
    std::unique_lock<std::mutex> lock(mFlushLock, std::defer_lock);
    // skip flushing if flushing is already in progress
    if (!lock.try_lock()) {
        return;
    }
    // flush
    mLastFlushTimeSec = nowSec;
    std::vector<BinderCallData> data = mBinderStatsCollector.consumeData();
    mPusher.pushLocked(data, nowSec);
}
} // namespace android
 No newline at end of file
+70 −0
Original line number Diff line number Diff line
/*
 * 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 <mutex>
#include "BinderStatsPusher.h"
#include "BinderStatsSpscQueue.h"
#include "BinderStatsUtils.h"

namespace android {

/**
 * Collects and manages binder transaction statistics from IPC threads.
 *
 * Gathers BinderCallData from BinderStatsSpscQueue instances associated
 * with each IPCThreadState. It periodically flushes these queues, aggregates
 * data using BinderStatsCollector, and sends statistics via BinderStatsPusher.
 *
 * How it is used:
 * - An instance is typically owned by ProcessState.
 * - IPCThreadState instances register their BinderStatsSpscQueue with the
 *   BinderObserver.
 * - IPCThreadState pushes BinderCallData to its local queue and calls
 *   addStatMaybeFlush on the BinderObserver.
 * - flushStats (or flushIfRequired) periodically collects data from all
 *   registered queues and passes it to BinderStatsPusher.
 *
 * Threading Requirements:
 * - addStatMaybeFlush: thread-safe
 * - registerQueue, deregisterQueue: thread-safe
 */
class BinderObserver {
public:
    // Add stats to the local queue, flush if queue is full.
    void addStatMaybeFlush(const std::shared_ptr<BinderStatsSpscQueue>& queue,
                           const BinderCallData& stat);
    void registerQueue(std::shared_ptr<BinderStatsSpscQueue>& queue) {
        mBinderStatsCollector.registerQueue(queue);
    }
    void deregisterQueue(std::shared_ptr<BinderStatsSpscQueue>& queue) {
        mBinderStatsCollector.deregisterQueue(queue);
    }

private:
    void flushStats(int64_t nowMillis);
    bool isFlushRequired(int64_t nowMillis);
    // Time since last flush time. Used to trigger a flush if more than kSendTimeoutSec
    // has elapsed since last flush.
    std::atomic<int64_t> mLastFlushTimeSec;
    // BinderStatsCollector stores shared ptrs to queues in IPCThreadState and pulls data from them.
    BinderStatsCollector mBinderStatsCollector;
    // BinderStatsPusher aggregates Stats and pushes them to data store.
    BinderStatsPusher mPusher;
    // Lock used to ensure that only one thread is running a flush and push at a time.
    std::mutex mFlushLock;
};
} // namespace android
+131 −0
Original line number Diff line number Diff line
/*
 * 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 "BinderStatsPusher.h"
#include <android-base/properties.h>
#include <android/os/IStatsBootstrapAtomService.h>
#include <binder/IServiceManager.h>
#include <utils/SystemClock.h>
#include "BinderStatsUtils.h"
#include "JvmUtils.h"

namespace android {
// defined in stats/atoms/framework/framework_extension_atoms.proto
constexpr int32_t kBinderSpamAtomId = 1064;
[[clang::no_destroy]] static const StaticString16 kStatsBootstrapServiceName(u"statsbootstrap");

sp<os::IStatsBootstrapAtomService> BinderStatsPusher::getBootstrapAtomServiceLocked(
        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;
    };
    if (!mBootComplete) {
        // TODO(b/299356196): use gSystemBootCompleted instead
        if (android::base::GetIntProperty("sys.boot_completed", 0) == 0) {
            return nullptr;
        }
    }
    // store a boolean to reduce GetProperty calls
    mBootComplete = true;
    auto sm = defaultServiceManager();
    if (!sm) {
        LOG_ALWAYS_FATAL("defaultServiceManager() returned nullptr.");
    }
    auto service = interface_cast<os::IStatsBootstrapAtomService>(
            defaultServiceManager()->checkService(kStatsBootstrapServiceName));
    mServiceCheckTimeSec = nowSec;
    if (service == nullptr) {
        mLastServiceCheckSucceeded = false;
    } else {
        mLastServiceCheckSucceeded = true;
    }
    return service;
}

void BinderStatsPusher::aggregateBinderSpamLocked(const std::vector<BinderCallData>& data,
                                                  const sp<os::IStatsBootstrapAtomService>& service,
                                                  const int64_t nowSec) {
    for (const auto& datum : data) {
        int64_t timeSec = static_cast<int64_t>(datum.startTimeNanos) / 1000'000'000;
        mSpamStatsBuffer[datum][timeSec]++;
    }
    // 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.
    // TODO(b/299356196): Ensure that mSpamStatsBuffer doesn't grow indefinitely.
    if (!service || (IInterface::asBinder(service)->localBinder() && getJavaVM() == nullptr)) {
        return;
    }

    for (auto outerIt = mSpamStatsBuffer.begin(); outerIt != mSpamStatsBuffer.end();
         /* no increment */) {
        bool hasSpam = false;
        int32_t secondsWithAtLeast125Calls = 0;
        int32_t secondsWithAtLeast250Calls = 0;
        const BinderCallData& datum = outerIt->first;
        std::unordered_map<int64_t, uint32_t>& innerMap = outerIt->second;
        for (auto innerIt = innerMap.begin(); innerIt != innerMap.end(); /* no increment */) {
            int64_t startTimeSec = innerIt->first;
            uint32_t count = innerIt->second;

            // Check if the delay period has passed.
            if (nowSec - startTimeSec >= kSpamAggregationWindowSec) {
                if (count >= kSpamFirstWatermark) {
                    hasSpam = true;
                    secondsWithAtLeast125Calls++;
                    if (count >= kSpamSecondWatermark) {
                        secondsWithAtLeast250Calls++;
                    }
                }
                innerIt = innerMap.erase(innerIt);
            } else {
                ++innerIt;
            }
        }
        if (hasSpam) {
            auto atom = os::StatsBootstrapAtom();
            atom.atomId = kBinderSpamAtomId;
            std::vector<os::StatsBootstrapAtomValue> values;
            atom.values.push_back(createPrimitiveValue(static_cast<int64_t>(datum.senderUid)));
            atom.values.push_back(createPrimitiveValue(static_cast<int64_t>(getuid()))); // host uid
            atom.values.push_back(createPrimitiveValue(datum.interfaceDescriptor));
            // TODO (b/299356196): get actual method name.
            atom.values.push_back(
                    createPrimitiveValue(std::to_string(datum.transactionCode))); // aidl method
            atom.values.push_back(
                    createPrimitiveValue(static_cast<int32_t>(secondsWithAtLeast125Calls)));
            atom.values.push_back(
                    createPrimitiveValue(static_cast<int32_t>(secondsWithAtLeast250Calls)));
            // TODO(b/414551350): combine multiple binder calls into one.
            service->reportBootstrapAtom(atom);
        }
        // If the inner map is now empty after removing expired entries, remove the outer entry.
        if (innerMap.empty()) {
            outerIt = mSpamStatsBuffer.erase(outerIt);
        } else {
            ++outerIt;
        }
    }
}

void BinderStatsPusher::pushLocked(const std::vector<BinderCallData>& data, const int64_t nowSec) {
    auto service = getBootstrapAtomServiceLocked(nowSec);
    aggregateBinderSpamLocked(data, service, nowSec);
}

} // namespace android
+88 −0
Original line number Diff line number Diff line
/*
 * 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 <vector>
#include "BinderStatsUtils.h"

class BinderStatsPusherTest_GetBootstrapService_Test;

namespace android {
/**
 * Processes and pushes binder transaction statistics to the StatsBootstrapAtomService.
 *
 * This class is responsible for aggregating collected BinderCallData
 * such as binder spam which are then reported as atoms.
 * It manages the interaction with the StatsBootstrapAtomService, including
 * handling boot completion and service availability checks.
 *
 * This class is not Thread-safe.
 */
class BinderStatsPusher {
public:
    // Pushes binder transaction data to the StatsBootstrapAtomService.
    void pushLocked(const std::vector<BinderCallData>& data, const int64_t nowSec);

private:
    friend ::BinderStatsPusherTest_GetBootstrapService_Test;
    sp<os::IStatsBootstrapAtomService> getBootstrapAtomServiceLocked(const int64_t nowSec);

    // timeout for checking the service.
    static const int32_t kCheckServiceTimeoutSec = 5;
    static const int32_t kSpamFirstWatermark = 125;
    static const int32_t kSpamSecondWatermark = 250;
    // Time window to aggregate the data in. Once the data point's startTime
    // is older than the Aggregation window, we will allow the data to be sent.
    static const int64_t kSpamAggregationWindowSec = 5;

    // KeyEqual function for Binder Spam aggregation of BinderCallData
    struct SpamStatsKeyEqual {
        bool operator()(const BinderCallData& lhs, const BinderCallData& rhs) const {
            return lhs.transactionCode == rhs.transactionCode && lhs.senderUid == rhs.senderUid &&
                    lhs.interfaceDescriptor == rhs.interfaceDescriptor;
        }
    };
    // Custom Hash for Binder Spam aggregation of BinderCallData in std::unordered_map.
    struct SpamStatsKeyHash {
        size_t operator()(const BinderCallData& bcd) const {
            size_t h = std::hash<uid_t>{}(bcd.senderUid); // uid_t is usually uint32_t or uint64_t
            h = std::__hash_combine(h, std::hash<std::u16string_view>{}(bcd.interfaceDescriptor));
            h = std::__hash_combine(h, std::hash<uint32_t>{}(bcd.transactionCode));
            return h;
        }
    };

    using SpamStatsMap =
            std::unordered_map<BinderCallData,
                               std::unordered_map<int64_t /*startTimeSec*/, uint32_t /*count*/>,
                               SpamStatsKeyHash,   // Hash
                               SpamStatsKeyEqual>; // KeyEqual

    // TODO(b/299356196): replace with gSystemBootCompleted
    bool mBootComplete = false;
    // If last service check was a success.
    bool mLastServiceCheckSucceeded = true;
    // time of last check
    int64_t mServiceCheckTimeSec = -kCheckServiceTimeoutSec - 1;
    // Aggregates binder transaction data into BinderSpamReport objects.
    void aggregateBinderSpamLocked(const std::vector<BinderCallData>& data,
                                   const sp<os::IStatsBootstrapAtomService>& service,
                                   const int64_t nowSec);
    // The stats which are not sent to StatsBootStrap
    SpamStatsMap mSpamStatsBuffer;
};

} // namespace android
Loading