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

Commit 0da7ad19 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Snap for 13806861 from e8a9c735 to 25Q4-release

Change-Id: I7b2d7982f74edd95970f91f697fde7d731ebe02d
parents e8ae11a2 e8a9c735
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -234,6 +234,7 @@ filegroup {
    name: "libbinder_observer_sources",
    srcs: [
        "BinderObserver.cpp",
        "BinderObserverConfig.cpp",
        "BinderStatsPusher.cpp",
    ],
    visibility: [
+41 −0
Original line number Diff line number Diff line
@@ -24,6 +24,47 @@

namespace android {
constexpr int kSendIntervalSec = 5;

BinderObserver::CallInfo BinderObserver::onBeginTransaction(BBinder* binder, uint32_t code,
                                                            uid_t callingUid) {
    if (!mConfig->isEnabled()) {
        return {};
    }

    // Sharding based on the pointer would be faster but would also increase the cardinality of
    // the different AIDLs that we report. Ideally, we want something stable within the
    // current boot session, so we use the interface descriptor.
    [[clang::no_destroy]] static const StaticString16 kDeletedBinder(u"<deleted_binder>");
    String16 interfaceDescriptor =
            binder == nullptr ? kDeletedBinder : binder->getInterfaceDescriptor();

    BinderObserverConfig::TrackingInfo trackingInfo =
            mConfig->getTrackingInfo(interfaceDescriptor, code);

    return {
            .interfaceDescriptor = interfaceDescriptor,
            .code = code,
            .callingUid = callingUid,
            .trackingInfo = trackingInfo,
            .startTimeNanos = trackingInfo.isTracked() ? uptimeNanos() : 0,
    };
}

void BinderObserver::onEndTransaction(const std::shared_ptr<BinderStatsSpscQueue>& queue,
                                      const CallInfo& callInfo) {
    if (!mConfig->isEnabled() || !callInfo.trackingInfo.isTracked()) {
        return;
    }
    BinderCallData observerData = {
            .interfaceDescriptor = callInfo.interfaceDescriptor,
            .transactionCode = callInfo.code,
            .startTimeNanos = callInfo.startTimeNanos,
            .endTimeNanos = callInfo.trackingInfo.trackLatency ? uptimeNanos() : 0,
            .senderUid = callInfo.callingUid,
    };
    addStatMaybeFlush(queue, observerData);
}

bool BinderObserver::isFlushRequired(int64_t nowSec) {
    int64_t previousFlushTimeSec = mLastFlushTimeSec.load();
    return nowSec - previousFlushTimeSec >= kSendIntervalSec;
+38 −11
Original line number Diff line number Diff line
@@ -15,10 +15,14 @@
 */
#pragma once

#include <binder/Binder.h>
#include <mutex>

#include "BinderObserverConfig.h"
#include "BinderStatsPusher.h"
#include "BinderStatsSpscQueue.h"
#include "BinderStatsUtils.h"
#include "binder_module.h"

namespace android {

@@ -29,34 +33,57 @@ namespace android {
 * with each IPCThreadState. It periodically flushes these queues, aggregates
 * data using BinderStatsCollector, and sends statistics via BinderStatsPusher.
 *
 * How it is used:
 * How it is works:
 * - 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.
 * - IPCThreadState instances register themselves with the BinderObserver.
 * - IPCThreadState reports binder calls using onBeginTransaction/onEndTransaction
 * - this pushes BinderCallData to the stats queue of the thread via addStatMaybeFlush.
 * - flushStats (or flushIfRequired) periodically collects data from all
 *   registered queues and passes it to BinderStatsPusher.
 *
 * Threading Requirements:
 * - onBeginTransaction, onEndTransaction, registerThread, deregisterThread: thread-safe
 * - 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) {
    // Initial data for tracking a binder call
    struct CallInfo {
        String16 interfaceDescriptor;
        uint32_t code;
        uid_t callingUid;
        BinderObserverConfig::TrackingInfo trackingInfo;
        int64_t startTimeNanos;
    };

    std::shared_ptr<BinderStatsSpscQueue> registerThread() {
        if (!mConfig->isEnabled()) {
            return nullptr;
        }
        std::shared_ptr<BinderStatsSpscQueue> queue = std::make_shared<BinderStatsSpscQueue>();
        mBinderStatsCollector.registerQueue(queue);
        return queue;
    }
    void deregisterThread(std::shared_ptr<BinderStatsSpscQueue>& queue) {
        if (!mConfig->isEnabled()) {
            LOG_ALWAYS_FATAL_IF(queue != nullptr,
                                "Non-null queue when BinderObserver is disabled.");
            return;
        }
    void deregisterQueue(std::shared_ptr<BinderStatsSpscQueue>& queue) {
        mBinderStatsCollector.deregisterQueue(queue);
    }
    CallInfo onBeginTransaction(BBinder* binder, uint32_t code, uid_t callingUid);
    void onEndTransaction(const std::shared_ptr<BinderStatsSpscQueue>& queue,
                          const CallInfo& callInfo);

private:
    // Add stats to the local queue, flush if queue is full.
    void addStatMaybeFlush(const std::shared_ptr<BinderStatsSpscQueue>& queue,
                           const BinderCallData& stat);
    void flushStats(int64_t nowMillis);
    bool isFlushRequired(int64_t nowMillis);

    std::unique_ptr<BinderObserverConfig> mConfig = BinderObserverConfig::createConfig();
    // Time since last flush time. Used to trigger a flush if more than kSendTimeoutSec
    // has elapsed since last flush.
    std::atomic<int64_t> mLastFlushTimeSec;
+102 −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.
 */
#define LOG_TAG "libbinder.BinderObserverConfig"

#include <cutils/android_filesystem_config.h> // for AID_SYSTEM
#include <stdlib.h>                           // for getprogname
#include <unistd.h>                           // for getuid()

#include "BinderObserverConfig.h"

namespace android {

// __BIONIC__ has getprogname() but __GLIBC__ and others have program_invocation_short_name
#if !defined(__BIONIC__)
const char* getprogname() {
    return program_invocation_short_name;
}
#endif

std::string BinderObserverConfig::Environment::readFileLine(const std::string& path) {
    std::string content;
    std::ifstream file(path);
    if (file.is_open()) {
        std::getline(file, content);
    }
    return content;
}

uid_t BinderObserverConfig::Environment::getUid() {
    return getuid();
}

std::string BinderObserverConfig::Environment::getProcessName() {
    return getprogname();
}

std::pair<size_t, size_t> BinderObserverConfig::getBootStableTokens(Environment& environment) {
    std::string bootToken = environment.readFileLine(kBootIdPath);

    // Boot id looks like this: "16e12b27-2a84-4355-84cd-948348d6c998"
    LOG_ALWAYS_FATAL_IF(bootToken.size() != kBootIdSize, "Bad boot_id: '%s'", bootToken.c_str());

    // Use the first half for process sharding and the second half for AIDL sharding.
    return std::make_pair(environment.hashString8(bootToken.substr(0, kBootIdSize / 2)),
                          environment.hashString8(bootToken.substr(kBootIdSize / 2)));
}

std::unique_ptr<BinderObserverConfig> BinderObserverConfig::createConfig(
        std::unique_ptr<BinderObserverConfig::Environment>&& environment) {
    // Determine if we are in the system_server process and get the appropriate sharding config.
    uid_t uid = environment->getUid();
    std::string processName = environment->getProcessName();

    ShardingConfig sharding = (uid == AID_SYSTEM && processName == "system_server")
            ? environment->getSystemServerSharding()
            : environment->getOtherProcessesSharding();

    if (sharding.processMod == 0) {
        // Sharding of 0 means disabled. No need to read further configuration.
        ALOGI("Binder stats disabled for %s uid=%d by configuration.", processName.c_str(), uid);
        return std::unique_ptr<BinderObserverConfig>(
                new BinderObserverConfig(std::move(environment), false, sharding, 0));
    }

    // Note: we want sharding to be stable for each session. Otherwise, for short-lived
    // proecsses, we will track different AIDLs each time the process runs, which will
    // increase the cardinality of the metrics we track.

    // We also want process sharding and AIDL sharding to be independent, as otherwise
    // certain process+AIDL combinations may be reported more frequently than others.
    // That's why we use two independent tokens.
    auto [processOffset, aidlOffset] = getBootStableTokens(*environment);

    // We use simple modulo arithmetc to keep sharding easier to understand and test. Everything
    // is mod-ed before addition too to ensure we don't overflow.
    size_t modulo = sharding.processMod;

    size_t token = processOffset % modulo;
    token += uid % modulo;
    token += environment->hashString8(processName) % modulo;
    bool enabled = token % modulo == 0;

    ALOGI("Binder stats for %s uid=%d enabled=%d sharding={%zu, %zu, %zu}", processName.c_str(),
          uid, enabled, sharding.processMod, sharding.spamMod, sharding.callMod);
    return std::unique_ptr<BinderObserverConfig>(
            new BinderObserverConfig(std::move(environment), enabled, sharding, aidlOffset));
}

} // namespace android
+153 −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 <log/log.h>

#include <fstream>
#include <limits>
#include <string>

namespace android {

class BinderObserverConfigTest;

/**
 * Manages the configuration of the binder observer.
 *
 * Applies sharding to determine whether the observer should be enabled for the current process
 * and which AIDL methods should be tracked.
 *
 * The configuration does not change within the lifecycle of the process.
 */
class BinderObserverConfig {
public:
    struct TrackingInfo {
        bool trackSpam;
        bool trackLatency;

        bool operator==(const TrackingInfo&) const = default;
        bool isTracked() const { return trackSpam || trackLatency; }
    };

    static std::unique_ptr<BinderObserverConfig> createConfig() {
        return createConfig(std::make_unique<Environment>());
    }

    // Returns whether binder stats are enabled for the current process. Should not change
    // within the lifecycle of a process.
    bool isEnabled() { return mEnabled; }

    TrackingInfo getTrackingInfo(const std::u16string_view& interfaceDescriptor, uint32_t txnCode) {
        if (!mEnabled) {
            return {
                    .trackSpam = false,
                    .trackLatency = false,
            };
        }

        // If present callMod must be a multiple of spamMod, so use it as a modulo.
        size_t modulo = mSharding.callMod != 0 ? mSharding.callMod : mSharding.spamMod;
        // As above, mod before addition too to ensure we don't overflow.
        size_t token = mAidlOffset % modulo;
        token += txnCode % modulo;
        token += mEnvironment->hashString16(interfaceDescriptor) % modulo;
        return {
                .trackSpam = mSharding.spamMod > 0 && token % mSharding.spamMod == 0,
                .trackLatency = mSharding.callMod > 0 && token % mSharding.callMod == 0,
        };
    }

private:
    friend class BinderObserverConfigTest;

    struct ShardingConfig {
        size_t processMod;
        size_t spamMod;
        size_t callMod;

        bool operator==(const ShardingConfig&) const = default;
    };

    // Contains a 128-bit random number stable for the current session
    // Looks like this: "16e12b27-2a84-4355-84cd-948348d6c998"
    static constexpr const char* kBootIdPath = "/proc/sys/kernel/random/boot_id";

    // The expected length of /proc/sys/kernel/random/boot_id
    static constexpr size_t kBootIdSize = 36;

    // Interacting with the environment abstracted out for testability.
    class Environment {
    public:
        virtual ~Environment() = default;
        virtual std::string readFileLine(const std::string& path);
        virtual uid_t getUid();
        virtual std::string getProcessName();

        virtual ShardingConfig getSystemServerSharding() {
            // TODO(b/299356196): Read sharding config from system properties (and sanitize it!)
            return {
                    .processMod = 10,
                    .spamMod = 50,
                    .callMod = 100,
            };
        }

        virtual ShardingConfig getOtherProcessesSharding() {
            // TODO(b/299356196): Read sharding config from system properties (and sanitize it!)
            return {
                    .processMod = 50,
                    .spamMod = 10,
                    .callMod = 20,
            };
        }

        virtual size_t hashString8(const std::string& content) {
            return std::hash<std::string>{}(content);
        }

        virtual size_t hashString16(const std::u16string_view& content) {
            return std::hash<std::u16string_view>{}(content);
        }
    };

    BinderObserverConfig(std::unique_ptr<BinderObserverConfig::Environment>&& environment,
                         bool enabled, BinderObserverConfig::ShardingConfig sharding,
                         size_t aidlOffset)
          : mEnvironment(std::move(environment)),
            mEnabled(enabled),
            mSharding(sharding),
            mAidlOffset(aidlOffset) {
        if (mEnabled) {
            LOG_ALWAYS_FATAL_IF(mSharding.spamMod == 0, "Enabled but nothing to monitor.");

            // Call sharding must always be a multiple of spam sharding (or 0, which is also OK)
            LOG_ALWAYS_FATAL_IF(mSharding.callMod % mSharding.spamMod != 0,
                                "Call reporting must be a subset of spam reporting.");
        }
    }

    static std::pair<size_t, size_t> getBootStableTokens(Environment& environment);

    static std::unique_ptr<BinderObserverConfig> createConfig(
            std::unique_ptr<BinderObserverConfig::Environment>&& environment);

    const std::unique_ptr<Environment> mEnvironment;
    const bool mEnabled;
    const ShardingConfig mSharding;
    const size_t mAidlOffset;
};
} // namespace android
Loading