1 /*
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"
19 #include "../InputDeviceMetricsSource.h"
20
21 #include <inttypes.h>
22
23 #include <android-base/logging.h>
24 #include <android-base/properties.h>
25 #include <android-base/stringprintf.h>
26 #include <android/os/IInputConstants.h>
27 #include <input/Input.h>
28 #include <input/InputDevice.h>
29 #include <log/log.h>
30
31 using android::base::HwTimeoutMultiplier;
32 using android::base::StringPrintf;
33
34 namespace android::inputdispatcher {
35
36 namespace {
37
38 /**
39 * Events that are older than this time will be considered mature, at which point we will stop
40 * waiting for the apps to provide further information about them.
41 * It's likely that the apps will ANR if the events are not received by this deadline, and we
42 * already track ANR metrics separately.
43 */
44 const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
45 android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
46 HwTimeoutMultiplier());
47
isMatureEvent(nsecs_t eventTime,nsecs_t now)48 static bool isMatureEvent(nsecs_t eventTime, nsecs_t now) {
49 std::chrono::duration age = std::chrono::nanoseconds(now) - std::chrono::nanoseconds(eventTime);
50 return age > ANR_TIMEOUT;
51 }
52
53 /**
54 * A multimap allows to have several entries with the same key. This function just erases a specific
55 * key-value pair. Equivalent to the imaginary std api std::multimap::erase(key, value).
56 */
57 template <typename K, typename V>
eraseByValue(std::multimap<K,V> & map,const V & value)58 static void eraseByValue(std::multimap<K, V>& map, const V& value) {
59 for (auto it = map.begin(); it != map.end();) {
60 if (it->second == value) {
61 it = map.erase(it);
62 } else {
63 it++;
64 }
65 }
66 }
67
68 } // namespace
69
LatencyTracker(InputEventTimelineProcessor & processor,std::vector<InputDeviceInfo> & inputDevices)70 LatencyTracker::LatencyTracker(InputEventTimelineProcessor& processor,
71 std::vector<InputDeviceInfo>& inputDevices)
72 : mTimelineProcessor(&processor), mInputDevices(inputDevices) {}
73
trackListener(const NotifyArgs & args)74 void LatencyTracker::trackListener(const NotifyArgs& args) {
75 if (const NotifyKeyArgs* keyArgs = std::get_if<NotifyKeyArgs>(&args)) {
76 std::set<InputDeviceUsageSource> sources =
77 getUsageSourcesForKeyArgs(*keyArgs, mInputDevices);
78 trackListener(keyArgs->id, keyArgs->eventTime, keyArgs->readTime, keyArgs->deviceId,
79 sources, keyArgs->action, InputEventType::KEY);
80
81 } else if (const NotifyMotionArgs* motionArgs = std::get_if<NotifyMotionArgs>(&args)) {
82 std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(*motionArgs);
83 trackListener(motionArgs->id, motionArgs->eventTime, motionArgs->readTime,
84 motionArgs->deviceId, sources, motionArgs->action, InputEventType::MOTION);
85 } else {
86 LOG(FATAL) << "Unexpected NotifyArgs type: " << args.index();
87 }
88 }
89
trackListener(int32_t inputEventId,nsecs_t eventTime,nsecs_t readTime,DeviceId deviceId,const std::set<InputDeviceUsageSource> & sources,int32_t inputEventAction,InputEventType inputEventType)90 void LatencyTracker::trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime,
91 DeviceId deviceId,
92 const std::set<InputDeviceUsageSource>& sources,
93 int32_t inputEventAction, InputEventType inputEventType) {
94 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);
102 eraseByValue(mEventTimes, inputEventId);
103 return;
104 }
105
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
125 const InputEventActionType inputEventActionType = [&]() {
126 switch (inputEventType) {
127 case InputEventType::MOTION: {
128 switch (MotionEvent::getActionMasked(inputEventAction)) {
129 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
157 mTimelines.emplace(inputEventId,
158 InputEventTimeline(eventTime, readTime, identifier->vendor,
159 identifier->product, sources, inputEventActionType));
160 mEventTimes.emplace(eventTime, inputEventId);
161 }
162
trackFinishedEvent(int32_t inputEventId,const sp<IBinder> & connectionToken,nsecs_t deliveryTime,nsecs_t consumeTime,nsecs_t finishTime)163 void 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()) {
168 // 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
170 // 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
trackGraphicsLatency(int32_t inputEventId,const sp<IBinder> & connectionToken,std::array<nsecs_t,GraphicsTimeline::SIZE> graphicsTimeline)194 void 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()) {
199 // 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
201 // 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 */
reportAndPruneMatureRecords(nsecs_t newEventTime)226 void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
227 while (!mEventTimes.empty()) {
228 const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
229 if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) {
230 // 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
dump(const char * prefix) const246 std::string LatencyTracker::dump(const char* prefix) const {
247 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
252 } // namespace android::inputdispatcher
253