blob: c8af86944de9d991a4d5206c769c9fab14776127 [file] [log] [blame]
Siarhei Vishniakouf2652122021-03-05 21:39:46 +00001/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "LatencyTracker"
18#include "LatencyTracker.h"
Asmita Poddardd9a6cd2023-09-26 15:35:12 +000019#include "../InputDeviceMetricsSource.h"
Siarhei Vishniakouf2652122021-03-05 21:39:46 +000020
21#include <inttypes.h>
22
23#include <android-base/properties.h>
24#include <android-base/stringprintf.h>
25#include <android/os/IInputConstants.h>
26#include <input/Input.h>
Asmita Poddardd9a6cd2023-09-26 15:35:12 +000027#include <input/InputDevice.h>
Siarhei Vishniakouf2652122021-03-05 21:39:46 +000028#include <log/log.h>
29
30using android::base::HwTimeoutMultiplier;
31using android::base::StringPrintf;
32
33namespace android::inputdispatcher {
34
35/**
36 * Events that are older than this time will be considered mature, at which point we will stop
37 * waiting for the apps to provide further information about them.
38 * It's likely that the apps will ANR if the events are not received by this deadline, and we
39 * already track ANR metrics separately.
40 */
41const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
42 android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
43 HwTimeoutMultiplier());
44
45static bool isMatureEvent(nsecs_t eventTime, nsecs_t now) {
46 std::chrono::duration age = std::chrono::nanoseconds(now) - std::chrono::nanoseconds(eventTime);
47 return age > ANR_TIMEOUT;
48}
49
50/**
51 * A multimap allows to have several entries with the same key. This function just erases a specific
52 * key-value pair. Equivalent to the imaginary std api std::multimap::erase(key, value).
53 */
54template <typename K, typename V>
Siarhei Vishniakou363e7292021-07-09 03:22:42 +000055static void eraseByValue(std::multimap<K, V>& map, const V& value) {
56 for (auto it = map.begin(); it != map.end();) {
Siarhei Vishniakouf2652122021-03-05 21:39:46 +000057 if (it->second == value) {
Siarhei Vishniakou363e7292021-07-09 03:22:42 +000058 it = map.erase(it);
59 } else {
60 it++;
Siarhei Vishniakouf2652122021-03-05 21:39:46 +000061 }
62 }
63}
64
65LatencyTracker::LatencyTracker(InputEventTimelineProcessor* processor)
66 : mTimelineProcessor(processor) {
67 LOG_ALWAYS_FATAL_IF(processor == nullptr);
68}
69
jioana099e19a2024-09-19 15:37:34 +000070void LatencyTracker::trackNotifyMotion(const NotifyMotionArgs& args) {
71 std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
72 trackListener(args.id, args.eventTime, args.readTime, args.deviceId, sources, args.action,
73 InputEventType::MOTION);
74}
75
76void LatencyTracker::trackNotifyKey(const NotifyKeyArgs& args) {
77 int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NONE;
78 for (auto& inputDevice : mInputDevices) {
79 if (args.deviceId == inputDevice.getId()) {
80 keyboardType = inputDevice.getKeyboardType();
81 break;
82 }
83 }
84 std::set<InputDeviceUsageSource> sources =
85 std::set{getUsageSourceForKeyArgs(keyboardType, args)};
86 trackListener(args.id, args.eventTime, args.readTime, args.deviceId, sources, args.action,
87 InputEventType::KEY);
88}
89
jioana0bdbea12024-08-10 19:26:04 +000090void LatencyTracker::trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime,
91 DeviceId deviceId,
92 const std::set<InputDeviceUsageSource>& sources,
jioana97cc8ac2024-09-09 15:01:43 +000093 int32_t inputEventAction, InputEventType inputEventType) {
Siarhei Vishniakouf2652122021-03-05 21:39:46 +000094 reportAndPruneMatureRecords(eventTime);
95 const auto it = mTimelines.find(inputEventId);
96 if (it != mTimelines.end()) {
97 // Input event ids are randomly generated, so it's possible that two events have the same
98 // event id. Drop this event, and also drop the existing event because the apps would
99 // confuse us by reporting the rest of the timeline for one of them. This should happen
100 // rarely, so we won't lose much data
101 mTimelines.erase(it);
Siarhei Vishniakou363e7292021-07-09 03:22:42 +0000102 eraseByValue(mEventTimes, inputEventId);
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000103 return;
104 }
Asmita Poddardd9a6cd2023-09-26 15:35:12 +0000105
106 // Create an InputEventTimeline for the device ID. The vendorId and productId
107 // can be obtained from the InputDeviceIdentifier of the particular device.
108 const InputDeviceIdentifier* identifier = nullptr;
109 for (auto& inputDevice : mInputDevices) {
110 if (deviceId == inputDevice.getId()) {
111 identifier = &inputDevice.getIdentifier();
112 break;
113 }
114 }
115
116 // If no matching ids can be found for the device from among the input devices connected,
117 // the call to trackListener will be dropped.
118 // Note: there generally isn't expected to be a situation where we can't find an InputDeviceInfo
119 // but a possibility of it is handled in case of race conditions
120 if (identifier == nullptr) {
121 ALOGE("Could not find input device identifier. Dropping call to LatencyTracker.");
122 return;
123 }
124
jioana0bdbea12024-08-10 19:26:04 +0000125 const InputEventActionType inputEventActionType = [&]() {
126 switch (inputEventType) {
127 case InputEventType::MOTION: {
jioana97cc8ac2024-09-09 15:01:43 +0000128 switch (MotionEvent::getActionMasked(inputEventAction)) {
jioana0bdbea12024-08-10 19:26:04 +0000129 case AMOTION_EVENT_ACTION_DOWN:
130 return InputEventActionType::MOTION_ACTION_DOWN;
131 case AMOTION_EVENT_ACTION_MOVE:
132 return InputEventActionType::MOTION_ACTION_MOVE;
133 case AMOTION_EVENT_ACTION_UP:
134 return InputEventActionType::MOTION_ACTION_UP;
135 case AMOTION_EVENT_ACTION_HOVER_MOVE:
136 return InputEventActionType::MOTION_ACTION_HOVER_MOVE;
137 case AMOTION_EVENT_ACTION_SCROLL:
138 return InputEventActionType::MOTION_ACTION_SCROLL;
139 default:
140 return InputEventActionType::UNKNOWN_INPUT_EVENT;
141 }
142 }
143 case InputEventType::KEY: {
144 switch (inputEventAction) {
145 case AKEY_EVENT_ACTION_DOWN:
146 case AKEY_EVENT_ACTION_UP:
147 return InputEventActionType::KEY;
148 default:
149 return InputEventActionType::UNKNOWN_INPUT_EVENT;
150 }
151 }
152 default:
153 return InputEventActionType::UNKNOWN_INPUT_EVENT;
154 }
155 }();
156
Asmita Poddardd9a6cd2023-09-26 15:35:12 +0000157 mTimelines.emplace(inputEventId,
jioana97cc8ac2024-09-09 15:01:43 +0000158 InputEventTimeline(eventTime, readTime, identifier->vendor,
jioana0bdbea12024-08-10 19:26:04 +0000159 identifier->product, sources, inputEventActionType));
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000160 mEventTimes.emplace(eventTime, inputEventId);
161}
162
163void LatencyTracker::trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
164 nsecs_t deliveryTime, nsecs_t consumeTime,
165 nsecs_t finishTime) {
166 const auto it = mTimelines.find(inputEventId);
167 if (it == mTimelines.end()) {
Siarhei Vishniakou363e7292021-07-09 03:22:42 +0000168 // This could happen if we erased this event when duplicate events were detected. It's
169 // also possible that an app sent a bad (or late) 'Finish' signal, since it's free to do
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000170 // anything in its process. Just drop the report and move on.
171 return;
172 }
173
174 InputEventTimeline& timeline = it->second;
175 const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
176 if (connectionIt == timeline.connectionTimelines.end()) {
177 // Most likely case: app calls 'finishInputEvent' before it reports the graphics timeline
178 timeline.connectionTimelines.emplace(connectionToken,
179 ConnectionTimeline{deliveryTime, consumeTime,
180 finishTime});
181 } else {
182 // Already have a record for this connectionToken
183 ConnectionTimeline& connectionTimeline = connectionIt->second;
184 const bool success =
185 connectionTimeline.setDispatchTimeline(deliveryTime, consumeTime, finishTime);
186 if (!success) {
187 // We are receiving unreliable data from the app. Just delete the entire connection
188 // timeline for this event
189 timeline.connectionTimelines.erase(connectionIt);
190 }
191 }
192}
193
194void LatencyTracker::trackGraphicsLatency(
195 int32_t inputEventId, const sp<IBinder>& connectionToken,
196 std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
197 const auto it = mTimelines.find(inputEventId);
198 if (it == mTimelines.end()) {
Siarhei Vishniakou363e7292021-07-09 03:22:42 +0000199 // This could happen if we erased this event when duplicate events were detected. It's
200 // also possible that an app sent a bad (or late) 'Timeline' signal, since it's free to do
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000201 // anything in its process. Just drop the report and move on.
202 return;
203 }
204
205 InputEventTimeline& timeline = it->second;
206 const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
207 if (connectionIt == timeline.connectionTimelines.end()) {
208 timeline.connectionTimelines.emplace(connectionToken, std::move(graphicsTimeline));
209 } else {
210 // Most likely case
211 ConnectionTimeline& connectionTimeline = connectionIt->second;
212 const bool success = connectionTimeline.setGraphicsTimeline(std::move(graphicsTimeline));
213 if (!success) {
214 // We are receiving unreliable data from the app. Just delete the entire connection
215 // timeline for this event
216 timeline.connectionTimelines.erase(connectionIt);
217 }
218 }
219}
220
221/**
222 * We should use the current time 'now()' here to determine the age of the event, but instead we
223 * are using the latest 'eventTime' for efficiency since this time is already acquired, and
224 * 'trackListener' should happen soon after the event occurs.
225 */
226void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
227 while (!mEventTimes.empty()) {
228 const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
Harry Cutts33476232023-01-30 19:57:29 +0000229 if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) {
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000230 // Report and drop this event
231 const auto it = mTimelines.find(oldestInputEventId);
232 LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
233 "Event %" PRId32 " is in mEventTimes, but not in mTimelines",
234 oldestInputEventId);
235 const InputEventTimeline& timeline = it->second;
236 mTimelineProcessor->processTimeline(timeline);
237 mTimelines.erase(it);
238 mEventTimes.erase(mEventTimes.begin());
239 } else {
240 // If the oldest event does not need to be pruned, no events should be pruned.
241 return;
242 }
243 }
244}
245
Siarhei Vishniakou4c9d6ff2023-04-18 11:23:20 -0700246std::string LatencyTracker::dump(const char* prefix) const {
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000247 return StringPrintf("%sLatencyTracker:\n", prefix) +
248 StringPrintf("%s mTimelines.size() = %zu\n", prefix, mTimelines.size()) +
249 StringPrintf("%s mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
250}
251
Asmita Poddardd9a6cd2023-09-26 15:35:12 +0000252void LatencyTracker::setInputDevices(const std::vector<InputDeviceInfo>& inputDevices) {
253 mInputDevices = inputDevices;
254}
255
Siarhei Vishniakouf2652122021-03-05 21:39:46 +0000256} // namespace android::inputdispatcher