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

Commit 852db897 authored by Prabir Pradhan's avatar Prabir Pradhan
Browse files

Log InputDeviceUsageReported atom from the new metrics collector

Bug: 275726706
Test: atest inputflinger_tests
Test: statsd_testdrive
Change-Id: I3da4beefe161891ca9188e70ff58114d46801902
parent 3a549bba
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -68,6 +68,9 @@ struct InputDeviceIdentifier {
     * while conforming to the filename limitations.
     */
    std::string getCanonicalName() const;

    bool operator==(const InputDeviceIdentifier&) const = default;
    bool operator!=(const InputDeviceIdentifier&) const = default;
};

/* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
+14 −0
Original line number Diff line number Diff line
@@ -87,6 +87,20 @@ std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const
    return out;
}

/**
 * Convert map keys to string. The keys of the map should be integral type.
 */
template <typename K, typename V>
std::string dumpMapKeys(const std::map<K, V>& map,
                        std::string (*keyToString)(const K&) = constToString) {
    std::string out;
    for (const auto& [k, _] : map) {
        out += out.empty() ? "{" : ", ";
        out += keyToString(k);
    }
    return out.empty() ? "{}" : (out + "}");
}

/**
 * Convert a vector to a string. The values of the vector should be of a type supported by
 * constToString.
+151 −1
Original line number Diff line number Diff line
@@ -17,52 +17,202 @@
#define LOG_TAG "InputDeviceMetricsCollector"
#include "InputDeviceMetricsCollector.h"

#include <android-base/stringprintf.h>
#include <input/PrintTools.h>
#include <linux/input.h>
#include <statslog.h>

namespace android {

using android::base::StringPrintf;
using std::chrono::nanoseconds;

namespace {

constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5);

/**
 * Log debug messages about metrics events logged to statsd.
 * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart)
 */
const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);

int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
    switch (linuxBus) {
        case BUS_USB:
            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
        case BUS_BLUETOOTH:
            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
        default:
            return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
    }
}

class : public InputDeviceMetricsLogger {
    nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }

    void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
                                     nanoseconds sessionDuration) override {
        const int32_t durationMillis =
                std::chrono::duration_cast<std::chrono::milliseconds>(sessionDuration).count();
        const static std::vector<int32_t> empty;

        ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
        ALOGD_IF(DEBUG, "    Total duration: %dms", durationMillis);

        util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
                          identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
                          durationMillis, /*usage_sources=*/empty,
                          /*usage_durations_per_source=*/empty, /*uids=*/empty,
                          /*usage_durations_per_uid=*/empty);
    }
} sStatsdLogger;

bool isIgnoredInputDeviceId(int32_t deviceId) {
    switch (deviceId) {
        case INVALID_INPUT_DEVICE_ID:
        case VIRTUAL_KEYBOARD_ID:
            return true;
        default:
            return false;
    }
}

} // namespace

InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
      : mNextListener(listener){};
      : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}

InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener,
                                                         InputDeviceMetricsLogger& logger,
                                                         nanoseconds usageSessionTimeout)
      : mNextListener(listener), mLogger(logger), mUsageSessionTimeout(usageSessionTimeout) {}

void InputDeviceMetricsCollector::notifyInputDevicesChanged(
        const NotifyInputDevicesChangedArgs& args) {
    processUsages();
    onInputDevicesChanged(args.inputDeviceInfos);
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyConfigurationChanged(
        const NotifyConfigurationChangedArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
    processUsages();
    onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));

    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
    processUsages();
    onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));

    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
        const NotifyPointerCaptureChangedArgs& args) {
    processUsages();
    mNextListener.notify(args);
}

void InputDeviceMetricsCollector::dump(std::string& dump) {
    dump += "InputDeviceMetricsCollector:\n";

    dump += "  Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
    dump += "  Devices with active usage sessions: " +
            dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
}

void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
    std::map<DeviceId, InputDeviceIdentifier> newDeviceIds;

    for (const InputDeviceInfo& info : infos) {
        if (isIgnoredInputDeviceId(info.getId())) {
            continue;
        }
        newDeviceIds.emplace(info.getId(), info.getIdentifier());
    }

    for (auto [deviceId, identifier] : mLoggedDeviceInfos) {
        if (newDeviceIds.count(deviceId) != 0) {
            continue;
        }
        onInputDeviceRemoved(deviceId, identifier);
    }

    std::swap(newDeviceIds, mLoggedDeviceInfos);
}

void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
                                                       const InputDeviceIdentifier& identifier) {
    // Report usage for that device if there is an active session.
    auto it = mActiveUsageSessions.find(deviceId);
    if (it != mActiveUsageSessions.end()) {
        mLogger.logInputDeviceUsageReported(identifier, it->second.end - it->second.start);
        mActiveUsageSessions.erase(it);
    }
    // We don't remove this from mLoggedDeviceInfos because it will be updated in
    // onInputDevicesChanged().
}

void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime) {
    if (mLoggedDeviceInfos.count(deviceId) == 0) {
        // Do not track usage for devices that are not logged.
        return;
    }

    auto [it, inserted] = mActiveUsageSessions.try_emplace(deviceId, eventTime, eventTime);
    if (!inserted) {
        it->second.end = eventTime;
    }
}

void InputDeviceMetricsCollector::processUsages() {
    const auto usageSessionExpiryTime = mLogger.getCurrentTime() - mUsageSessionTimeout;

    std::vector<DeviceId> completedUsageSessions;

    for (const auto& [deviceId, usageSession] : mActiveUsageSessions) {
        if (usageSession.end <= usageSessionExpiryTime) {
            completedUsageSessions.emplace_back(deviceId);
        }
    }

    for (DeviceId deviceId : completedUsageSessions) {
        const auto it = mLoggedDeviceInfos.find(deviceId);
        LOG_ALWAYS_FATAL_IF(it == mLoggedDeviceInfos.end());

        const auto& session = mActiveUsageSessions[deviceId];
        mLogger.logInputDeviceUsageReported(it->second, session.end - session.start);

        mActiveUsageSessions.erase(deviceId);
    }
}

} // namespace android
+45 −0
Original line number Diff line number Diff line
@@ -18,6 +18,12 @@

#include "InputListener.h"

#include <ftl/mixins.h>
#include <input/InputDevice.h>
#include <chrono>
#include <map>
#include <vector>

namespace android {

/**
@@ -34,11 +40,24 @@ public:
    virtual void dump(std::string& dump) = 0;
};

/** The logging interface for the metrics collector, injected for testing. */
class InputDeviceMetricsLogger {
public:
    virtual std::chrono::nanoseconds getCurrentTime() = 0;
    virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
                                             std::chrono::nanoseconds duration) = 0;
    virtual ~InputDeviceMetricsLogger() = default;
};

class InputDeviceMetricsCollector : public InputDeviceMetricsCollectorInterface {
public:
    explicit InputDeviceMetricsCollector(InputListenerInterface& listener);
    ~InputDeviceMetricsCollector() override = default;

    // Test constructor
    InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger,
                                std::chrono::nanoseconds usageSessionTimeout);

    void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
    void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
    void notifyKey(const NotifyKeyArgs& args) override;
@@ -53,6 +72,32 @@ public:

private:
    InputListenerInterface& mNextListener;
    InputDeviceMetricsLogger& mLogger;
    const std::chrono::nanoseconds mUsageSessionTimeout;

    // Type-safe wrapper for input device id.
    struct DeviceId : ftl::Constructible<DeviceId, std::int32_t>,
                      ftl::Equatable<DeviceId>,
                      ftl::Orderable<DeviceId> {
        using Constructible::Constructible;
    };
    static std::string toString(const DeviceId& id) {
        return std::to_string(ftl::to_underlying(id));
    }

    std::map<DeviceId, InputDeviceIdentifier> mLoggedDeviceInfos;

    struct UsageSession {
        std::chrono::nanoseconds start;
        std::chrono::nanoseconds end;
    };
    // The input devices that currently have active usage sessions.
    std::map<DeviceId, UsageSession> mActiveUsageSessions;

    void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos);
    void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier);
    void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime);
    void processUsages();
};

} // namespace android
+1 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ cc_test {
        "FocusResolver_test.cpp",
        "GestureConverter_test.cpp",
        "HardwareStateConverter_test.cpp",
        "InputDeviceMetricsCollector_test.cpp",
        "InputMapperTest.cpp",
        "InputProcessor_test.cpp",
        "InputProcessorConverter_test.cpp",
Loading