Break down input device usage by uid
When InputDispatcher notifies that there is an interaction between a
device and UIDs, track the usage of the input device by UID.
Since Dispatcher calls the policy from its own thread, we use an
AtomicQueue to hold the requests until we process the usages again from
the Reader thread.
Bug: 275726706
Test: atest inputflinger_tests
Test: statsd_testdrive
Change-Id: Ic4aae4bce7d3779adf23309973b964461bd92e7e
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index 397b1cd..f92c4f4 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -27,6 +27,7 @@
using android::base::StringPrintf;
using std::chrono::nanoseconds;
+using std::chrono_literals::operator""ns;
namespace {
@@ -72,10 +73,19 @@
ftl::enum_string(src).c_str(), durMillis);
}
+ ALOGD_IF(DEBUG, " Uid breakdown:");
+
+ std::vector<int32_t> uids;
+ std::vector<int32_t> durationsPerUid;
+ for (auto& [uid, dur] : report.uidBreakdown) {
+ uids.push_back(uid);
+ int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+ durationsPerUid.push_back(durMillis);
+ ALOGD_IF(DEBUG, " - uid: %d\t duration: %dms", uid, durMillis);
+ }
util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
- durationMillis, sources, durationsPerSource, /*uids=*/empty,
- /*usage_durations_per_uid=*/empty);
+ durationMillis, sources, durationsPerSource, uids, durationsPerUid);
}
} sStatsdLogger;
@@ -248,7 +258,11 @@
void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
const std::set<int32_t>& uids) {
- // TODO: Implement.
+ std::set<Uid> typeSafeUids;
+ for (auto uid : uids) {
+ typeSafeUids.emplace(uid);
+ }
+ mInteractionsQueue.push(DeviceId{deviceId}, timestamp, typeSafeUids);
}
void InputDeviceMetricsCollector::dump(std::string& dump) {
@@ -309,17 +323,33 @@
}
}
-void InputDeviceMetricsCollector::reportCompletedSessions() {
- const auto currentTime = mLogger.getCurrentTime();
+void InputDeviceMetricsCollector::onInputDeviceInteraction(const Interaction& interaction) {
+ auto activeSessionIt = mActiveUsageSessions.find(std::get<DeviceId>(interaction));
+ if (activeSessionIt == mActiveUsageSessions.end()) {
+ return;
+ }
+ activeSessionIt->second.recordInteraction(interaction);
+}
+
+void InputDeviceMetricsCollector::reportCompletedSessions() {
+ // Process all pending interactions.
+ for (auto interaction = mInteractionsQueue.pop(); interaction;
+ interaction = mInteractionsQueue.pop()) {
+ onInputDeviceInteraction(*interaction);
+ }
+
+ const auto currentTime = mLogger.getCurrentTime();
std::vector<DeviceId> completedUsageSessions;
+ // Process usages for all active session to determine if any sessions have expired.
for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
if (activeSession.checkIfCompletedAt(currentTime)) {
completedUsageSessions.emplace_back(deviceId);
}
}
+ // Close out and log all expired usage sessions.
for (DeviceId deviceId : completedUsageSessions) {
const auto infoIt = mLoggedDeviceInfos.find(deviceId);
LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
@@ -351,6 +381,23 @@
mDeviceSession.end = eventTime;
}
+void InputDeviceMetricsCollector::ActiveSession::recordInteraction(const Interaction& interaction) {
+ const auto sessionExpiryTime = mDeviceSession.end + mUsageSessionTimeout;
+ const auto timestamp = std::get<nanoseconds>(interaction);
+ if (timestamp >= sessionExpiryTime) {
+ // This interaction occurred after the device's current active session is set to expire.
+ // Ignore it.
+ return;
+ }
+
+ for (Uid uid : std::get<std::set<Uid>>(interaction)) {
+ auto [activeUidIt, inserted] = mActiveSessionsByUid.try_emplace(uid, timestamp, timestamp);
+ if (!inserted) {
+ activeUidIt->second.end = timestamp;
+ }
+ }
+}
+
bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) {
const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
@@ -365,6 +412,21 @@
mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
mActiveSessionsBySource.erase(it);
}
+
+ std::vector<Uid> completedUidSessionsForDevice;
+ for (auto& [uid, session] : mActiveSessionsByUid) {
+ if (session.end <= sessionExpiryTime) {
+ completedUidSessionsForDevice.emplace_back(uid);
+ }
+ }
+ for (Uid uid : completedUidSessionsForDevice) {
+ auto it = mActiveSessionsByUid.find(uid);
+ const auto& [_, session] = *it;
+ mUidUsageBreakdown.emplace_back(uid, session.end - session.start);
+ mActiveSessionsByUid.erase(it);
+ }
+
+ // This active session has expired if there are no more active source sessions tracked.
return mActiveSessionsBySource.empty();
}
@@ -377,7 +439,12 @@
}
mActiveSessionsBySource.clear();
- return {deviceUsageDuration, mSourceUsageBreakdown};
+ for (const auto& [uid, uidSession] : mActiveSessionsByUid) {
+ mUidUsageBreakdown.emplace_back(uid, uidSession.end - uidSession.start);
+ }
+ mActiveSessionsByUid.clear();
+
+ return {deviceUsageDuration, mSourceUsageBreakdown, mUidUsageBreakdown};
}
} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 5587698..387786f 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -18,6 +18,7 @@
#include "InputListener.h"
#include "NotifyArgs.h"
+#include "SyncQueue.h"
#include <ftl/mixins.h>
#include <input/InputDevice.h>
@@ -98,9 +99,14 @@
using SourceUsageBreakdown =
std::vector<std::pair<InputDeviceUsageSource, std::chrono::nanoseconds /*duration*/>>;
+ // Describes the breakdown of an input device usage session by the UIDs that it interacted with.
+ using UidUsageBreakdown =
+ std::vector<std::pair<int32_t /*uid*/, std::chrono::nanoseconds /*duration*/>>;
+
struct DeviceUsageReport {
std::chrono::nanoseconds usageDuration;
SourceUsageBreakdown sourceBreakdown;
+ UidUsageBreakdown uidBreakdown;
};
virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&,
@@ -146,13 +152,25 @@
return std::to_string(ftl::to_underlying(id));
}
+ // Type-safe wrapper for a UID.
+ struct Uid : ftl::Constructible<Uid, std::int32_t>, ftl::Equatable<Uid>, ftl::Orderable<Uid> {
+ using Constructible::Constructible;
+ };
+ static inline std::string toString(const Uid& src) {
+ return std::to_string(ftl::to_underlying(src));
+ }
+
std::map<DeviceId, InputDeviceInfo> mLoggedDeviceInfos;
+ using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
+ SyncQueue<Interaction> mInteractionsQueue;
+
class ActiveSession {
public:
explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout,
std::chrono::nanoseconds startTime);
void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source);
+ void recordInteraction(const Interaction&);
bool checkIfCompletedAt(std::chrono::nanoseconds timestamp);
InputDeviceMetricsLogger::DeviceUsageReport finishSession();
@@ -167,6 +185,9 @@
std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{};
InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{};
+
+ std::map<Uid, UsageSession> mActiveSessionsByUid{};
+ InputDeviceMetricsLogger::UidUsageBreakdown mUidUsageBreakdown{};
};
// The input devices that currently have active usage sessions.
@@ -177,6 +198,7 @@
using SourceProvider = std::function<std::set<InputDeviceUsageSource>(const InputDeviceInfo&)>;
void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
const SourceProvider& getSources);
+ void onInputDeviceInteraction(const Interaction&);
void reportCompletedSessions();
};
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index e38f88c..c555d95 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -341,8 +341,9 @@
TestInputListener mTestListener;
InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
- void assertUsageLogged(const InputDeviceIdentifier& identifier, nanoseconds duration,
- std::optional<SourceUsageBreakdown> sourceBreakdown = {}) {
+ void assertUsageLogged(InputDeviceIdentifier identifier, nanoseconds duration,
+ std::optional<SourceUsageBreakdown> sourceBreakdown = {},
+ std::optional<UidUsageBreakdown> uidBreakdown = {}) {
ASSERT_GE(mLoggedUsageSessions.size(), 1u);
const auto& [loggedIdentifier, report] = *mLoggedUsageSessions.begin();
ASSERT_EQ(identifier, loggedIdentifier);
@@ -350,6 +351,9 @@
if (sourceBreakdown) {
ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
}
+ if (uidBreakdown) {
+ ASSERT_EQ(uidBreakdown, report.uidBreakdown);
+ }
mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
}
@@ -357,6 +361,8 @@
void setCurrentTime(nanoseconds time) { mCurrentTime = time; }
+ nsecs_t currentTime() const { return mCurrentTime.count(); }
+
NotifyMotionArgs generateMotionArgs(int32_t deviceId,
uint32_t source = AINPUT_SOURCE_TOUCHSCREEN,
std::vector<ToolType> toolTypes = {ToolType::FINGER}) {
@@ -622,4 +628,146 @@
ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
}
+TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+
+ // Notify interaction with UIDs before the device is used.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ // Use the device.
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // Notify interaction for the wrong device.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{42});
+
+ // Notify interaction after usage session would have expired.
+ // This interaction should not be tracked.
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{2, 3});
+
+ // Use the device again, by starting a new usage session.
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // The first usage session is logged.
+ static const UidUsageBreakdown emptyBreakdown;
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 100ns, /*sourceBreakdown=*/{},
+ /*uidBreakdown=*/emptyBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2, 3});
+
+ expectedUidBreakdown.emplace_back(1, 200ns);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ expectedUidBreakdown.emplace_back(3, 0ns);
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 200ns, /*sourceBreakdown=*/{},
+ expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {generateTestDeviceInfo()}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1});
+
+ setCurrentTime(TIME + 300ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 3});
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{4});
+
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 4});
+
+ setCurrentTime(TIME + 400ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{2, 3});
+
+ setCurrentTime(TIME + 500ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{3});
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedUidBreakdown.emplace_back(1, 300ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 0ns);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ expectedUidBreakdown.emplace_back(4, 100ns);
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(), 500ns + USAGE_TIMEOUT,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID), generateTestDeviceInfo(DEVICE_ID_2)}});
+ UidUsageBreakdown expectedUidBreakdown1;
+ UidUsageBreakdown expectedUidBreakdown2;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), /*uids=*/{1, 2});
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), /*uids=*/{1, 3});
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown1.emplace_back(1, 200ns);
+ expectedUidBreakdown1.emplace_back(2, 200ns);
+ expectedUidBreakdown2.emplace_back(1, 100ns);
+ expectedUidBreakdown2.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID), 200ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown1));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(getIdentifier(DEVICE_ID_2), 100ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown2));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
} // namespace android